From 194211b3707697ccbee28a32bf6e778b35b583c9 Mon Sep 17 00:00:00 2001 From: Frederik Carlier Date: Sat, 28 Apr 2018 05:40:47 +0200 Subject: [PATCH] Fix flakey tests by making them async (#144) * Fix flakey tests by converting them to async and synchronizing code blocks * Use the same timeout for all tests * Use signals to gracefully shut down the mock server --- tests/KubernetesClient.Tests/AuthTests.cs | 20 +- .../Kubernetes.Exec.Tests.cs | 13 +- .../KubernetesClient.Tests.csproj | 3 +- tests/KubernetesClient.Tests/TestBase.cs | 131 ------------- .../V1StatusObjectViewTests.cs | 12 +- tests/KubernetesClient.Tests/WatchTests.cs | 178 +++++++++++------- .../WebSocketTestBase.cs | 42 +++-- 7 files changed, 156 insertions(+), 243 deletions(-) delete mode 100644 tests/KubernetesClient.Tests/TestBase.cs diff --git a/tests/KubernetesClient.Tests/AuthTests.cs b/tests/KubernetesClient.Tests/AuthTests.cs index 18b4317..ee4b865 100644 --- a/tests/KubernetesClient.Tests/AuthTests.cs +++ b/tests/KubernetesClient.Tests/AuthTests.cs @@ -20,12 +20,14 @@ using Xunit.Abstractions; namespace k8s.Tests { - public class AuthTests - : TestBase + public class AuthTests { - public AuthTests(ITestOutputHelper testOutput) : base(testOutput) + private readonly ITestOutputHelper testOutput; + + public AuthTests(ITestOutputHelper testOutput) { - } + this.testOutput = testOutput; + } private static HttpOperationResponse ExecuteListPods(IKubernetes client) { @@ -35,7 +37,7 @@ namespace k8s.Tests [Fact] public void Anonymous() { - using (var server = new MockKubeApiServer(TestOutput)) + using (var server = new MockKubeApiServer(testOutput)) { var client = new Kubernetes(new KubernetesClientConfiguration { @@ -48,7 +50,7 @@ namespace k8s.Tests Assert.Equal(1, listTask.Body.Items.Count); } - using (var server = new MockKubeApiServer(TestOutput, cxt => + using (var server = new MockKubeApiServer(testOutput, cxt => { cxt.Response.StatusCode = (int)HttpStatusCode.Unauthorized; return Task.FromResult(false); @@ -71,7 +73,7 @@ namespace k8s.Tests const string testName = "test_name"; const string testPassword = "test_password"; - using (var server = new MockKubeApiServer(TestOutput, cxt => + using (var server = new MockKubeApiServer(testOutput, cxt => { var header = cxt.Request.Headers["Authorization"].FirstOrDefault(); @@ -182,7 +184,7 @@ namespace k8s.Tests var clientCertificateValidationCalled = false; - using (var server = new MockKubeApiServer(TestOutput, listenConfigure: options => + using (var server = new MockKubeApiServer(testOutput, listenConfigure: options => { options.UseHttps(new HttpsConnectionAdapterOptions { @@ -264,7 +266,7 @@ namespace k8s.Tests { const string token = "testingtoken"; - using (var server = new MockKubeApiServer(TestOutput, cxt => + using (var server = new MockKubeApiServer(testOutput, cxt => { var header = cxt.Request.Headers["Authorization"].FirstOrDefault(); diff --git a/tests/KubernetesClient.Tests/Kubernetes.Exec.Tests.cs b/tests/KubernetesClient.Tests/Kubernetes.Exec.Tests.cs index 46ad8c1..166061b 100644 --- a/tests/KubernetesClient.Tests/Kubernetes.Exec.Tests.cs +++ b/tests/KubernetesClient.Tests/Kubernetes.Exec.Tests.cs @@ -22,6 +22,8 @@ namespace k8s.Tests public class PodExecTests : WebSocketTestBase { + private readonly ITestOutputHelper testOutput; + /// /// Create a new exec-in-pod test suite. /// @@ -31,6 +33,7 @@ namespace k8s.Tests public PodExecTests(ITestOutputHelper testOutput) : base(testOutput) { + this.testOutput = testOutput; } /// @@ -49,7 +52,7 @@ namespace k8s.Tests using (Kubernetes client = CreateTestClient()) { - Log.LogInformation("Invoking exec operation..."); + testOutput.WriteLine("Invoking exec operation..."); WebSocket clientSocket = await client.WebSocketNamespacedPodExecAsync( name: "mypod", @@ -63,19 +66,19 @@ namespace k8s.Tests ); Assert.Equal(K8sProtocol.ChannelV1, clientSocket.SubProtocol); // For WebSockets, the Kubernetes API defaults to the binary channel (v1) protocol. - Log.LogInformation("Client socket connected (socket state is {ClientSocketState}). Waiting for server-side socket to become available...", clientSocket.State); + testOutput.WriteLine($"Client socket connected (socket state is {clientSocket.State}). Waiting for server-side socket to become available..."); WebSocket serverSocket = await WebSocketTestAdapter.AcceptedPodExecV1Connection; - Log.LogInformation("Server-side socket is now available (socket state is {ServerSocketState}). Sending data to server socket...", serverSocket.State); + testOutput.WriteLine($"Server-side socket is now available (socket state is {serverSocket.State}). Sending data to server socket..."); const int STDOUT = 1; const string expectedOutput = "This is text send to STDOUT."; int bytesSent = await SendMultiplexed(serverSocket, STDOUT, expectedOutput); - Log.LogInformation("Sent {ByteCount} bytes to server socket; receiving from client socket...", bytesSent); + testOutput.WriteLine($"Sent {bytesSent} bytes to server socket; receiving from client socket..."); (string receivedText, byte streamIndex, int bytesReceived) = await ReceiveTextMultiplexed(clientSocket); - Log.LogInformation("Received {ByteCount} bytes from client socket ('{ReceivedText}', stream {StreamIndex}).", bytesReceived, receivedText, streamIndex); + testOutput.WriteLine($"Received {bytesReceived} bytes from client socket ('{receivedText}', stream {streamIndex})."); Assert.Equal(STDOUT, streamIndex); Assert.Equal(expectedOutput, receivedText); diff --git a/tests/KubernetesClient.Tests/KubernetesClient.Tests.csproj b/tests/KubernetesClient.Tests/KubernetesClient.Tests.csproj index 91b4369..d8a1ad2 100755 --- a/tests/KubernetesClient.Tests/KubernetesClient.Tests.csproj +++ b/tests/KubernetesClient.Tests/KubernetesClient.Tests.csproj @@ -9,6 +9,7 @@ + @@ -19,7 +20,7 @@ - + diff --git a/tests/KubernetesClient.Tests/TestBase.cs b/tests/KubernetesClient.Tests/TestBase.cs deleted file mode 100644 index eb17c8a..0000000 --- a/tests/KubernetesClient.Tests/TestBase.cs +++ /dev/null @@ -1,131 +0,0 @@ -using k8s.Tests.Logging; -using System; -using System.Reactive.Disposables; -using System.Reflection; -using System.Threading; -using Microsoft.Extensions.Logging; -using Xunit; -using Xunit.Abstractions; - -namespace k8s.Tests -{ - /// - /// The base class for test suites. - /// - public abstract class TestBase - : IDisposable - { - /// - /// Create a new test-suite. - /// - /// - /// Output for the current test. - /// - protected TestBase(ITestOutputHelper testOutput) - { - if (testOutput == null) - throw new ArgumentNullException(nameof(testOutput)); - - // We *must* have a synchronisation context for the test, or we'll see random deadlocks. - if (SynchronizationContext.Current == null) - { - SynchronizationContext.SetSynchronizationContext( - new SynchronizationContext() - ); - } - - TestOutput = testOutput; - LoggerFactory = new LoggerFactory().AddTestOutput(TestOutput, MinLogLevel); - Log = LoggerFactory.CreateLogger("CurrentTest"); - - // Ugly hack to get access to metadata for the current test. - CurrentTest = (ITest) - TestOutput.GetType() - .GetField("test", BindingFlags.NonPublic | BindingFlags.Instance) - .GetValue(TestOutput); - - Assert.True(CurrentTest != null, "Cannot retrieve current test from ITestOutputHelper."); - - Disposal.Add( - Log.BeginScope("CurrentTest", CurrentTest.DisplayName) - ); - } - - /// - /// Finaliser for . - /// - ~TestBase() - { - Dispose(false); - } - - /// - /// Dispose of resources being used by the test suite. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Dispose of resources being used by the test suite. - /// - /// - /// Explicit disposal? - /// - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - try - { - Disposal.Dispose(); - } - finally - { - if (LoggerFactory is IDisposable loggerFactoryDisposal) - loggerFactoryDisposal.Dispose(); - - if (Log is IDisposable logDisposal) - logDisposal.Dispose(); - } - } - } - - /// - /// A representing resources used by the test. - /// - protected CompositeDisposable Disposal { get; } = new CompositeDisposable(); - - /// - /// Output for the current test. - /// - protected ITestOutputHelper TestOutput { get; } - - /// - /// A representing the current test. - /// - protected ITest CurrentTest { get; } - - /// - /// The logger for the current test. - /// - protected ILogger Log { get; } - - /// - /// The logger factory for the current test. - /// - protected ILoggerFactory LoggerFactory { get; } - - /// - /// The logging level for the current test. - /// - protected virtual LogLevel MinLogLevel => LogLevel.Information; - - /// - /// The test server logging level for the current test. - /// - protected virtual LogLevel MinServerLogLevel => LogLevel.Warning; - } -} diff --git a/tests/KubernetesClient.Tests/V1StatusObjectViewTests.cs b/tests/KubernetesClient.Tests/V1StatusObjectViewTests.cs index 4161739..4ee6bf7 100644 --- a/tests/KubernetesClient.Tests/V1StatusObjectViewTests.cs +++ b/tests/KubernetesClient.Tests/V1StatusObjectViewTests.cs @@ -6,11 +6,13 @@ using Xunit.Abstractions; namespace k8s.Tests { - public class V1StatusObjectViewTests - : TestBase + public class V1StatusObjectViewTests { - public V1StatusObjectViewTests(ITestOutputHelper testOutput) : base(testOutput) + private readonly ITestOutputHelper testOutput; + + public V1StatusObjectViewTests(ITestOutputHelper testOutput) { + this.testOutput = testOutput; } [Fact] @@ -22,7 +24,7 @@ namespace k8s.Tests Status = "test status" }; - using (var server = new MockKubeApiServer(TestOutput, resp: JsonConvert.SerializeObject(v1Status))) + using (var server = new MockKubeApiServer(testOutput, resp: JsonConvert.SerializeObject(v1Status))) { var client = new Kubernetes(new KubernetesClientConfiguration { @@ -52,7 +54,7 @@ namespace k8s.Tests } }; - using (var server = new MockKubeApiServer(TestOutput, resp: JsonConvert.SerializeObject(corev1Namespace))) + using (var server = new MockKubeApiServer(testOutput, resp: JsonConvert.SerializeObject(corev1Namespace))) { var client = new Kubernetes(new KubernetesClientConfiguration { diff --git a/tests/KubernetesClient.Tests/WatchTests.cs b/tests/KubernetesClient.Tests/WatchTests.cs index e514222..95c0e96 100644 --- a/tests/KubernetesClient.Tests/WatchTests.cs +++ b/tests/KubernetesClient.Tests/WatchTests.cs @@ -13,22 +13,26 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Converters; +using Nito.AsyncEx; using Xunit; using Xunit.Abstractions; namespace k8s.Tests { public class WatchTests - : TestBase { private static readonly string MockAddedEventStreamLine = BuildWatchEventStreamLine(WatchEventType.Added); private static readonly string MockDeletedStreamLine = BuildWatchEventStreamLine(WatchEventType.Deleted); private static readonly string MockModifiedStreamLine = BuildWatchEventStreamLine(WatchEventType.Modified); private static readonly string MockErrorStreamLine = BuildWatchEventStreamLine(WatchEventType.Error); private static readonly string MockBadStreamLine = "bad json"; + private static readonly TimeSpan TestTimeout = TimeSpan.FromSeconds(15); - public WatchTests(ITestOutputHelper testOutput) : base(testOutput) + private readonly ITestOutputHelper testOutput; + + public WatchTests(ITestOutputHelper testOutput) { + this.testOutput = testOutput; } private static string BuildWatchEventStreamLine(WatchEventType eventType) @@ -50,9 +54,9 @@ namespace k8s.Tests } [Fact] - public void CannotWatch() + public async Task CannotWatch() { - using (var server = new MockKubeApiServer(testOutput: TestOutput)) + using (var server = new MockKubeApiServer(testOutput: testOutput)) { var client = new Kubernetes(new KubernetesClientConfiguration { @@ -60,53 +64,54 @@ namespace k8s.Tests }); // did not pass watch param + var listTask = await client.ListNamespacedPodWithHttpMessagesAsync("default"); + Assert.ThrowsAny(() => { - var listTask = client.ListNamespacedPodWithHttpMessagesAsync("default").Result; - Assert.ThrowsAny(() => - { - listTask.Watch((type, item) => { }); - }); - } + listTask.Watch((type, item) => { }); + }); // server did not response line by line + await Assert.ThrowsAnyAsync(() => { - Assert.ThrowsAny(() => - { - var listTask = client.ListNamespacedPodWithHttpMessagesAsync("default", watch: true).Result; + return client.ListNamespacedPodWithHttpMessagesAsync("default", watch: true); - // this line did not throw - // listTask.Watch((type, item) => { }); - }); - } + // this line did not throw + // listTask.Watch((type, item) => { }); + }); } } [Fact] - public void SuriveBadLine() + public async Task SuriveBadLine() { - using (CountdownEvent eventsReceived = new CountdownEvent(4 /* first line of response is eaten by WatcherDelegatingHandler */)) - using (var server = new MockKubeApiServer(TestOutput, async httpContext => - { - httpContext.Response.StatusCode = (int) HttpStatusCode.OK; - httpContext.Response.ContentLength = null; + AsyncCountdownEvent eventsReceived = new AsyncCountdownEvent(4 /* first line of response is eaten by WatcherDelegatingHandler */); + AsyncManualResetEvent serverShutdown = new AsyncManualResetEvent(); - await WriteStreamLine(httpContext, MockKubeApiServer.MockPodResponse); - await WriteStreamLine(httpContext, MockBadStreamLine); - await WriteStreamLine(httpContext, MockAddedEventStreamLine); - await WriteStreamLine(httpContext, MockBadStreamLine); - await WriteStreamLine(httpContext, MockModifiedStreamLine); + using (var server = + new MockKubeApiServer( + testOutput, + async httpContext => + { + httpContext.Response.StatusCode = (int)HttpStatusCode.OK; + httpContext.Response.ContentLength = null; - // make server alive, cannot set to int.max as of it would block response - await Task.Delay(TimeSpan.FromDays(1)); - return false; - })) + await WriteStreamLine(httpContext, MockKubeApiServer.MockPodResponse); + await WriteStreamLine(httpContext, MockBadStreamLine); + await WriteStreamLine(httpContext, MockAddedEventStreamLine); + await WriteStreamLine(httpContext, MockBadStreamLine); + await WriteStreamLine(httpContext, MockModifiedStreamLine); + + // make server alive, cannot set to int.max as of it would block response + await serverShutdown.WaitAsync(); + return false; + })) { var client = new Kubernetes(new KubernetesClientConfiguration { Host = server.Uri.ToString() }); - var listTask = client.ListNamespacedPodWithHttpMessagesAsync("default", watch: true).Result; + var listTask = await client.ListNamespacedPodWithHttpMessagesAsync("default", watch: true); var events = new HashSet(); var errors = 0; @@ -114,14 +119,14 @@ namespace k8s.Tests var watcher = listTask.Watch( (type, item) => { - Log.LogInformation("Watcher received '{EventType}' event.", type); + testOutput.WriteLine($"Watcher received '{type}' event."); events.Add(type); eventsReceived.Signal(); }, error => { - Log.LogInformation("Watcher received '{ErrorType}' error.", error.GetType().FullName); + testOutput.WriteLine($"Watcher received '{error.GetType().FullName}' error."); errors += 1; eventsReceived.Signal(); @@ -129,8 +134,10 @@ namespace k8s.Tests ); // wait server yields all events + await Task.WhenAny(eventsReceived.WaitAsync(), Task.Delay(TestTimeout)); + Assert.True( - eventsReceived.Wait(TimeSpan.FromMilliseconds(3000)), + eventsReceived.CurrentCount == 0, "Timed out waiting for all events / errors to be received." ); @@ -141,23 +148,27 @@ namespace k8s.Tests Assert.True(watcher.Watching); - // prevent from server down exception trigger - Thread.Sleep(TimeSpan.FromMilliseconds(1000)); + // Let the server know it can initiate a shut down. + serverShutdown.Set(); } } [Fact] - public void DisposeWatch() + public async Task DisposeWatch() { - using (var eventsReceived = new CountdownEvent(1)) - using (var server = new MockKubeApiServer(TestOutput, async httpContext => + var eventsReceived = new AsyncCountdownEvent(1); + bool serverRunning = true; + + using (var server = new MockKubeApiServer(testOutput, async httpContext => { await WriteStreamLine(httpContext, MockKubeApiServer.MockPodResponse); - for (;;) + while (serverRunning) { await WriteStreamLine(httpContext, MockAddedEventStreamLine); } + + return true; })) { var client = new Kubernetes(new KubernetesClientConfiguration @@ -165,21 +176,22 @@ namespace k8s.Tests Host = server.Uri.ToString() }); - var listTask = client.ListNamespacedPodWithHttpMessagesAsync("default", watch: true).Result; - + var listTask = await client.ListNamespacedPodWithHttpMessagesAsync("default", watch: true); var events = new HashSet(); var watcher = listTask.Watch( - (type, item) => { + (type, item) => + { events.Add(type); eventsReceived.Signal(); } ); // wait at least an event + await Task.WhenAny(eventsReceived.WaitAsync(), Task.Delay(TestTimeout)); Assert.True( - eventsReceived.Wait(TimeSpan.FromSeconds(10)), + eventsReceived.CurrentCount == 0, "Timed out waiting for events." ); @@ -190,19 +202,28 @@ namespace k8s.Tests events.Clear(); - // make sure wait event called - Thread.Sleep(TimeSpan.FromMilliseconds(1000)); + // Let the server disconnect + serverRunning = false; + + var timeout = Task.Delay(TestTimeout); + + while(!timeout.IsCompleted && watcher.Watching) + { + await Task.Yield(); + } + Assert.Empty(events); Assert.False(watcher.Watching); - } } [Fact] - public void WatchAllEvents() + public async Task WatchAllEvents() { - using (CountdownEvent eventsReceived = new CountdownEvent(4 /* first line of response is eaten by WatcherDelegatingHandler */)) - using (var server = new MockKubeApiServer(TestOutput, async httpContext => + AsyncCountdownEvent eventsReceived = new AsyncCountdownEvent(4 /* first line of response is eaten by WatcherDelegatingHandler */); + AsyncManualResetEvent serverShutdown = new AsyncManualResetEvent(); + + using (var server = new MockKubeApiServer(testOutput, async httpContext => { await WriteStreamLine(httpContext, MockKubeApiServer.MockPodResponse); await WriteStreamLine(httpContext, MockAddedEventStreamLine); @@ -211,7 +232,7 @@ namespace k8s.Tests await WriteStreamLine(httpContext, MockErrorStreamLine); // make server alive, cannot set to int.max as of it would block response - await Task.Delay(TimeSpan.FromDays(1)); + await serverShutdown.WaitAsync(); return false; })) { @@ -229,14 +250,14 @@ namespace k8s.Tests var watcher = listTask.Watch( (type, item) => { - Log.LogInformation("Watcher received '{EventType}' event.", type); + testOutput.WriteLine($"Watcher received '{type}' event."); events.Add(type); eventsReceived.Signal(); }, error => { - Log.LogInformation("Watcher received '{ErrorType}' error.", error.GetType().FullName); + testOutput.WriteLine($"Watcher received '{error.GetType().FullName}' error."); errors += 1; eventsReceived.Signal(); @@ -244,8 +265,10 @@ namespace k8s.Tests ); // wait server yields all events + await Task.WhenAny(eventsReceived.WaitAsync(), Task.Delay(TestTimeout)); + Assert.True( - eventsReceived.Wait(TimeSpan.FromMilliseconds(3000)), + eventsReceived.CurrentCount == 0, "Timed out waiting for all events / errors to be received." ); @@ -254,23 +277,24 @@ namespace k8s.Tests Assert.Contains(WatchEventType.Modified, events); Assert.Contains(WatchEventType.Error, events); - Assert.Equal(0, errors); Assert.True(watcher.Watching); + + serverShutdown.Set(); } } [Fact] - public void WatchServerDisconnect() + public async Task WatchServerDisconnect() { Exception exceptionCatched = null; - using (var exceptionReceived = new AutoResetEvent(false)) - using (var waitForException = new AutoResetEvent(false)) - using (var server = new MockKubeApiServer(TestOutput, async httpContext => + var exceptionReceived = new AsyncManualResetEvent(false); + var waitForException = new AsyncManualResetEvent(false); + using (var server = new MockKubeApiServer(testOutput, async httpContext => { await WriteStreamLine(httpContext, MockKubeApiServer.MockPodResponse); - waitForException.WaitOne(); + await waitForException.WaitAsync(); throw new IOException("server down"); })) { @@ -279,20 +303,23 @@ namespace k8s.Tests Host = server.Uri.ToString() }); - var listTask = client.ListNamespacedPodWithHttpMessagesAsync("default", watch: true).Result; + var listTask = await client.ListNamespacedPodWithHttpMessagesAsync("default", watch: true); waitForException.Set(); Watcher watcher; watcher = listTask.Watch( (type, item) => { }, - e => { + e => + { exceptionCatched = e; exceptionReceived.Set(); }); // wait server down + await Task.WhenAny(exceptionReceived.WaitAsync(), Task.Delay(TestTimeout)); + Assert.True( - exceptionReceived.WaitOne(TimeSpan.FromSeconds(10)), + exceptionReceived.IsSet, "Timed out waiting for exception" ); @@ -314,16 +341,18 @@ namespace k8s.Tests } [Fact] - public void TestWatchWithHandlers() + public async Task TestWatchWithHandlers() { - using (CountdownEvent eventsReceived = new CountdownEvent(1)) - using (var server = new MockKubeApiServer(TestOutput, async httpContext => + AsyncCountdownEvent eventsReceived = new AsyncCountdownEvent(1); + AsyncManualResetEvent serverShutdown = new AsyncManualResetEvent(); + + using (var server = new MockKubeApiServer(testOutput, async httpContext => { await WriteStreamLine(httpContext, MockKubeApiServer.MockPodResponse); await WriteStreamLine(httpContext, MockAddedEventStreamLine); // make server alive, cannot set to int.max as of it would block response - await Task.Delay(TimeSpan.FromDays(1)); + await serverShutdown.WaitAsync(); return false; })) { @@ -338,20 +367,23 @@ namespace k8s.Tests Assert.False(handler1.Called); Assert.False(handler2.Called); - var listTask = client.ListNamespacedPodWithHttpMessagesAsync("default", watch: true).Result; + var listTask = await client.ListNamespacedPodWithHttpMessagesAsync("default", watch: true); var events = new HashSet(); var watcher = listTask.Watch( - (type, item) => { + (type, item) => + { events.Add(type); eventsReceived.Signal(); } ); // wait server yields all events + await Task.WhenAny(eventsReceived.WaitAsync(), Task.Delay(TestTimeout)); + Assert.True( - eventsReceived.Wait(TimeSpan.FromMilliseconds(10000)), + eventsReceived.CurrentCount == 0, "Timed out waiting for all events / errors to be received." ); @@ -359,6 +391,8 @@ namespace k8s.Tests Assert.True(handler1.Called); Assert.True(handler2.Called); + + serverShutdown.Set(); } } } diff --git a/tests/KubernetesClient.Tests/WebSocketTestBase.cs b/tests/KubernetesClient.Tests/WebSocketTestBase.cs index 3980c3a..03dde8e 100644 --- a/tests/KubernetesClient.Tests/WebSocketTestBase.cs +++ b/tests/KubernetesClient.Tests/WebSocketTestBase.cs @@ -19,14 +19,15 @@ namespace k8s.Tests /// /// The base class for Kubernetes WebSocket test suites. /// - public abstract class WebSocketTestBase - : TestBase + public abstract class WebSocketTestBase : IDisposable { /// /// The next server port to use. /// static int NextPort = 13255; + private readonly ITestOutputHelper testOutput; + /// /// Create a new . /// @@ -34,13 +35,14 @@ namespace k8s.Tests /// Output for the current test. /// protected WebSocketTestBase(ITestOutputHelper testOutput) - : base(testOutput) { + this.testOutput = testOutput; + int port = Interlocked.Increment(ref NextPort); // Useful to diagnose test timeouts. TestCancellation.Register( - () => Log.LogInformation("Test-level cancellation token has been canceled.") + () => testOutput.WriteLine("Test-level cancellation token has been canceled.") ); ServerBaseAddress = new Uri($"http://localhost:{port}"); @@ -52,9 +54,6 @@ namespace k8s.Tests .ConfigureLogging(ConfigureTestServerLogging) .UseUrls(ServerBaseAddress.AbsoluteUri) .Build(); - - Disposal.Add(CancellationSource); - Disposal.Add(Host); } /// @@ -115,7 +114,7 @@ namespace k8s.Tests throw new ArgumentNullException(nameof(logging)); logging.ClearProviders(); // Don't log to console. - logging.AddTestOutput(TestOutput, MinLogLevel); + logging.AddTestOutput(this.testOutput, LogLevel.Information); } /// @@ -165,7 +164,7 @@ namespace k8s.Tests if (serverSocket == null) throw new ArgumentNullException(nameof(serverSocket)); - Log.LogInformation("Disconnecting..."); + testOutput.WriteLine("Disconnecting..."); // Asynchronously perform the server's half of the handshake (the call to clientSocket.CloseAsync will block until it receives the server-side response). ArraySegment receiveBuffer = new byte[1024]; @@ -173,19 +172,16 @@ namespace k8s.Tests .ContinueWith(async received => { if (received.IsFaulted) - Log.LogError(new EventId(0), received.Exception.Flatten().InnerExceptions[0], "Server socket operation to receive Close message failed."); + testOutput.WriteLine("Server socket operation to receive Close message failed: {0}", received.Exception.Flatten().InnerExceptions[0]); else if (received.IsCanceled) - Log.LogWarning("Server socket operation to receive Close message was canceled."); + testOutput.WriteLine("Server socket operation to receive Close message was canceled."); else { - Log.LogInformation("Received {MessageType} message from server socket (expecting {ExpectedMessageType}).", - received.Result.MessageType, - WebSocketMessageType.Close - ); + testOutput.WriteLine($"Received {received.Result.MessageType} message from server socket (expecting {WebSocketMessageType.Close})."); if (received.Result.MessageType == WebSocketMessageType.Close) { - Log.LogInformation("Closing server socket (with status {CloseStatus})...", received.Result.CloseStatus); + testOutput.WriteLine($"Closing server socket (with status {received.Result.CloseStatus})..."); await serverSocket.CloseAsync( received.Result.CloseStatus.Value, @@ -193,22 +189,22 @@ namespace k8s.Tests TestCancellation ); - Log.LogInformation("Server socket closed."); + testOutput.WriteLine("Server socket closed."); } Assert.Equal(WebSocketMessageType.Close, received.Result.MessageType); } }); - Log.LogInformation("Closing client socket..."); + testOutput.WriteLine("Closing client socket..."); await clientSocket.CloseAsync(closeStatus, closeStatusDescription, TestCancellation).ConfigureAwait(false); - Log.LogInformation("Client socket closed."); + testOutput.WriteLine("Client socket closed."); await closeServerSocket.ConfigureAwait(false); - Log.LogInformation("Disconnected."); + testOutput.WriteLine("Disconnected."); Assert.Equal(closeStatus, clientSocket.CloseStatus); Assert.Equal(clientSocket.CloseStatus, serverSocket.CloseStatus); @@ -299,6 +295,12 @@ namespace k8s.Tests ); } + public void Dispose() + { + this.CancellationSource.Dispose(); + this.Host.Dispose(); + } + /// /// A implementation representing no credentials (i.e. anonymous). ///