using System.Net; using System.Net.Sockets; using Cysharp.Threading.Tasks; using NUnit.Framework; using ShrinkNetwork; [NonParallelizable] public class NetworkTransportTests { [Test] public async Task TcpRpcRoundTripAndCanceledSendPreserveNextFrame() { var reservation = new TcpListener(IPAddress.Loopback, 0); reservation.Start(); var port = ((IPEndPoint)reservation.LocalEndpoint).Port; reservation.Stop(); var serverTransport = new ShrinkTcpServerTransport(IPAddress.Loopback, port); var clientTransport = new ShrinkTcpClientTransport("127.0.0.1", port); var server = Service(); var client = Service(); var connected = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); client.OnSessionConnected += session => connected.TrySetResult(session); server.RegisterRequestHandler((_, request) => UniTask.FromResult(new Response { Text = request.Text })); try { server.BindTransport(serverTransport); client.BindTransport(clientTransport); var session = await connected.Task.WaitAsync(TimeSpan.FromSeconds(5)); using var cancellation = new CancellationTokenSource(); cancellation.Cancel(); Assert.CatchAsync(async () => await clientTransport.SendAsync(1, new ReadOnlyMemory(new byte[100]), cancellation.Token)); var reply = await client.CallAsync(session, new Request { Text = new string('t', 32768) }).AsTask().WaitAsync(TimeSpan.FromSeconds(5)); Assert.That(reply.Text.Length, Is.EqualTo(32768)); Assert.That(client.GetDiagnosticsSnapshot().RpcCompleted, Is.EqualTo(1)); } finally { clientTransport.Stop(); serverTransport.Stop(); } } [Test] public async Task NestedRpcResponseBypassesSerialHandlerQueue() { var a = new ShrinkLoopbackTransport(); var b = new ShrinkLoopbackTransport(); a.LinkPeer(b); var client = Service(); var server = Service(); client.RegisterRequestHandler((_, req) => UniTask.FromResult(new Response { Text = "nested:" + req.Text })); server.RegisterRequestHandler(async (ctx, req) => await server.CallAsync(ctx.Session, req)); client.BindTransport(a); server.BindTransport(b); a.OpenSession(1); var response = await client.CallAsync(client.Sessions[1], new Request { Text = "ok" }).AsTask().WaitAsync(TimeSpan.FromSeconds(3)); Assert.That(response.Text, Is.EqualTo("nested:ok")); a.Stop(); b.Stop(); } [Test] public async Task CongestionDisconnectsReliablePeerAndDoesNotResurrectSession() { var a = new ShrinkLoopbackTransport(); var b = new ShrinkLoopbackTransport(); a.LinkPeer(b); using var queue = new ShrinkNetworkDispatchQueue(1, byteCapacity: 1024); var client = Service(); var server = Service(); server.DispatchScheduler = queue; client.BindTransport(a); server.BindTransport(b); a.OpenSession(1); var session = client.Sessions[1]; var rejected = 0; server.OnDispatchRejected += _ => rejected++; await client.SendAsync(session, new Request()); await client.SendAsync(session, new Request()); Assert.That(rejected, Is.EqualTo(1)); Assert.That(server.Sessions, Is.Empty); await queue.PumpAsync(1); Assert.That(server.Sessions, Is.Empty); a.Stop(); b.Stop(); } private static ShrinkNetworkService Service() { var service = new ShrinkNetworkService(); service.RegisterMessage(1); service.RegisterMessage(2); return service; } public sealed class Request : IShrinkNetworkRequest { public string Text { get; set; } = ""; } public sealed class Response : ShrinkRpcResponseBase { public string Text { get; set; } = ""; } }