public static async Task DataTestAsync <T>(IPipeServer <T> server, IPipeClient <T> client, List <T> values, Func <T, string>?hashFunc = null, CancellationToken cancellationToken = default) { Trace.WriteLine("Setting up test..."); var completionSource = new TaskCompletionSource <bool>(false); // ReSharper disable once AccessToModifiedClosure using var registration = cancellationToken.Register(() => completionSource.TrySetCanceled(cancellationToken)); var actualHash = (string?)null; var clientDisconnected = false; server.ClientConnected += (sender, args) => { Trace.WriteLine("Client connected"); }; server.ClientDisconnected += (sender, args) => { Trace.WriteLine("Client disconnected"); clientDisconnected = true; // ReSharper disable once AccessToModifiedClosure completionSource.TrySetResult(true); }; server.MessageReceived += (sender, args) => { Trace.WriteLine($"Server_OnMessageReceived: {args.Message}"); actualHash = hashFunc?.Invoke(args.Message); Trace.WriteLine($"ActualHash: {actualHash}"); // ReSharper disable once AccessToModifiedClosure completionSource.TrySetResult(true); }; server.ExceptionOccurred += (sender, args) => { Trace.WriteLine($"Server exception occurred: {args.Exception}"); // ReSharper disable once AccessToModifiedClosure completionSource.TrySetException(args.Exception); }; client.Connected += (sender, args) => Trace.WriteLine("Client_OnConnected"); client.Disconnected += (sender, args) => Trace.WriteLine("Client_OnDisconnected"); client.MessageReceived += (sender, args) => Trace.WriteLine($"Client_OnMessageReceived: {args.Message}"); client.ExceptionOccurred += (sender, args) => { Trace.WriteLine($"Client exception occurred: {args.Exception}"); // ReSharper disable once AccessToModifiedClosure completionSource.TrySetException(args.Exception); }; AppDomain.CurrentDomain.UnhandledException += (sender, args) => { if (args.ExceptionObject is Exception exception) { // ReSharper disable once AccessToModifiedClosure completionSource.TrySetException(exception); } }; server.ExceptionOccurred += (sender, args) => Trace.WriteLine(args.Exception.ToString()); client.ExceptionOccurred += (sender, args) => Trace.WriteLine(args.Exception.ToString()); await server.StartAsync(cancellationToken).ConfigureAwait(false); await client.ConnectAsync(cancellationToken).ConfigureAwait(false); Trace.WriteLine("Client and server started"); Trace.WriteLine("---"); var watcher = Stopwatch.StartNew(); foreach (var value in values) { var expectedHash = hashFunc?.Invoke(value); Trace.WriteLine($"ExpectedHash: {expectedHash}"); await client.WriteAsync(value, cancellationToken).ConfigureAwait(false); await completionSource.Task.ConfigureAwait(false); if (hashFunc != null) { Assert.IsNotNull(actualHash, "Server should have received a zero-byte message from the client"); } Assert.AreEqual(expectedHash, actualHash, "SHA-1 hashes for zero-byte message should match"); Assert.IsFalse(clientDisconnected, "Server should not disconnect the client for explicitly sending zero-length data"); Trace.WriteLine("---"); completionSource = new TaskCompletionSource <bool>(false); } Trace.WriteLine($"Test took {watcher.Elapsed}"); Trace.WriteLine("~~~~~~~~~~~~~~~~~~~~~~~~~~"); }