using System; using System.IO; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using CanSimulatorServer; using Xunit; namespace Aging.MvpTests { /// /// 模拟器服务端核心行为测试:HELLO 状态机、NODE_BUSY 仲裁、 /// 跨节点路由推送、自动响应回推、命令串执行(AUTO_RESPONSE/STATUS)。 /// 测试进程内托管 TcpSimulatorHost(随机端口),走真实 TCP 回环。 /// public class SimulatorServerCoreTests : IDisposable { private readonly TcpSimulatorHost _host; private readonly int _port; public SimulatorServerCoreTests() { _host = new TcpSimulatorHost(); _host.StartAsync().GetAwaiter().GetResult(); _port = _host.Port; } public void Dispose() => _host.Dispose(); // ==================== 客户端辅助 ==================== private sealed class FakeClient : IDisposable { public TcpClient Socket = null!; public Stream Stream = null!; public async Task ConnectAsync(int port) { Socket = new TcpClient(); await Socket.ConnectAsync("127.0.0.1", port); Stream = Socket.GetStream(); } public Task SendAsync(string msg) { byte[] f = CanSimulator.SimulatorFraming.Encode(msg); return Stream.WriteAsync(f, 0, f.Length); } public Task RecvAsync() => ReadFrameBlocking(); public async Task HelloAsync(string bus, int node, string tag = "t") { await SendAsync($"HELLO:{bus}:{node}:{tag}"); return await ReadFrameBlocking(); } private async Task ReadFrameBlocking() { var s = await CanSimulator.SimulatorFraming.ReadFrameAsync(Stream, CancellationToken.None); return s ?? ""; } public void Dispose() { try { Stream?.Dispose(); Socket?.Close(); } catch { } } } // ==================== HELLO 状态机 ==================== [Fact] public void Hello_FirstClient_AcksOk() { using var c = new FakeClient(); c.ConnectAsync(_port).GetAwaiter().GetResult(); var ack = c.HelloAsync("CAN_BUS_1", 0).GetAwaiter().GetResult(); Assert.StartsWith("OK:HELLO", ack); } [Fact] public void Query_BeforeHello_ReturnsNotHandshaked() { using var c = new FakeClient(); c.ConnectAsync(_port).GetAwaiter().GetResult(); c.SendAsync("QUERY:STATUS").GetAwaiter().GetResult(); var resp = c.RecvAsync().GetAwaiter().GetResult(); Assert.Equal("ERR:NOT_HANDSHAKED", resp); } [Fact] public void UnknownCommand_ReturnsUnknownCmd_WithoutDroppingConnection() { using var c = new FakeClient(); c.ConnectAsync(_port).GetAwaiter().GetResult(); c.HelloAsync("CAN_BUS_1", 0).GetAwaiter().GetResult(); c.SendAsync("FOO:bar").GetAwaiter().GetResult(); Assert.Equal("ERR:UNKNOWN_CMD:FOO:bar", c.RecvAsync().GetAwaiter().GetResult()); // 连接保持可用:后续合法命令仍有响应 c.SendAsync("QUERY:STATUS").GetAwaiter().GetResult(); Assert.StartsWith("OK:", c.RecvAsync().GetAwaiter().GetResult()); } // ==================== NODE_BUSY 仲裁 ==================== [Fact] public void Hello_SameBusSameNode_SecondRejectedNodeBusy() { using var a = new FakeClient(); using var b = new FakeClient(); a.ConnectAsync(_port).GetAwaiter().GetResult(); b.ConnectAsync(_port).GetAwaiter().GetResult(); Assert.StartsWith("OK:HELLO", a.HelloAsync("CAN_BUS_1", 3).GetAwaiter().GetResult()); Assert.Equal("ERR:NODE_BUSY", b.HelloAsync("CAN_BUS_1", 3).GetAwaiter().GetResult()); // 不同总线同节点编号不冲突 Assert.StartsWith("OK:HELLO", b.HelloAsync("CAN_BUS_2", 3).GetAwaiter().GetResult()); } [Fact] public void Hello_AfterDisconnect_SameNodeCanReattach() { var a = new FakeClient(); a.ConnectAsync(_port).GetAwaiter().GetResult(); a.HelloAsync("CAN_BUS_1", 7).GetAwaiter().GetResult(); a.Dispose(); // 模拟连接断开 using var b = new FakeClient(); b.ConnectAsync(_port).GetAwaiter().GetResult(); // 服务端感知 EOF 需少许时间,轮询重试 string ack = ""; for (int i = 0; i < 50 && !ack.StartsWith("OK"); i++) { Thread.Sleep(20); b.SendAsync($"HELLO:CAN_BUS_1:7:retry{i}").GetAwaiter().GetResult(); ack = b.RecvAsync().GetAwaiter().GetResult(); } Assert.StartsWith("OK:HELLO", ack); } // ==================== 路由与推送 ==================== [Fact] public void Send_FromOneNode_OtherNodeReceivesEvPush() { using var a = new FakeClient(); using var b = new FakeClient(); a.ConnectAsync(_port).GetAwaiter().GetResult(); b.ConnectAsync(_port).GetAwaiter().GetResult(); a.HelloAsync("CAN_BUS_1", 0).GetAwaiter().GetResult(); b.HelloAsync("CAN_BUS_1", 1).GetAwaiter().GetResult(); a.SendAsync("SEND:0x123:AA BB CC").GetAwaiter().GetResult(); var ack = a.RecvAsync().GetAwaiter().GetResult(); Assert.StartsWith("OK:TX", ack); // 对端节点收到服务端主动推送 var push = b.RecvAsync().GetAwaiter().GetResult(); Assert.StartsWith("EV:RECV:0x123:AA BB CC", push); } [Fact] public void Send_AutoResponseEnabled_RespondsToPeerAndSelf() { using var a = new FakeClient(); using var b = new FakeClient(); a.ConnectAsync(_port).GetAwaiter().GetResult(); b.ConnectAsync(_port).GetAwaiter().GetResult(); a.HelloAsync("CAN_BUS_1", 10).GetAwaiter().GetResult(); b.HelloAsync("CAN_BUS_1", 11).GetAwaiter().GetResult(); // b 开启自动响应(QUERY 写命令,响应一行) b.SendAsync("QUERY:AUTO_RESPONSE:ON").GetAwaiter().GetResult(); Assert.StartsWith("OK:", b.RecvAsync().GetAwaiter().GetResult()); // a 发帧 → b 路由收帧(推送) + 自动响应帧回 a a.SendAsync("SEND:0x500:11 22").GetAwaiter().GetResult(); Assert.StartsWith("OK:TX", a.RecvAsync().GetAwaiter().GetResult()); // b 自己收到源路由推送 Assert.StartsWith("EV:RECV:0x500:11 22", b.RecvAsync().GetAwaiter().GetResult()); // a 收到自动响应帧(来源=b 节点 11) var respPush = a.RecvAsync().GetAwaiter().GetResult(); Assert.StartsWith("EV:RECV:0x500:11 22", respPush); Assert.EndsWith(":11", respPush); // srcNodeIndex=11 } // ==================== 命令执行 ==================== [Fact] public void Query_Status_AfterHello_ReturnsOkStatus() { using var a = new FakeClient(); a.ConnectAsync(_port).GetAwaiter().GetResult(); a.HelloAsync("CAN_BUS_1", 20).GetAwaiter().GetResult(); a.SendAsync("QUERY:STATUS").GetAwaiter().GetResult(); var resp = a.RecvAsync().GetAwaiter().GetResult(); Assert.StartsWith("OK:STATUS:", resp); Assert.Contains("Node=20", resp); // 节点以编号寻址 } [Fact] public void GarbageFrame_ClosesSession_ButHostKeepsListening() { // 非法长度前缀(协议失步):服务端断开该会话,但后续新连接仍能 HELLO int p = _port; using (var raw = new TcpClient("127.0.0.1", p)) { byte[] bad = { 0x00, 0x00, 0x00, 0x00 }; // 长度 0 raw.GetStream().Write(bad, 0, 4); } Thread.Sleep(100); using var c = new FakeClient(); c.ConnectAsync(_port).GetAwaiter().GetResult(); Assert.StartsWith("OK:HELLO", c.HelloAsync("CAN_BUS_9", 0).GetAwaiter().GetResult()); } // ==================== 总线级应答器 ==================== [Fact] public void BusEcho_Enabled_SenderReceivesOwnFrameAsResponder() { using var a = new FakeClient(); a.ConnectAsync(_port).GetAwaiter().GetResult(); a.HelloAsync("CAN_ECHO_ON", 0).GetAwaiter().GetResult(); a.SendAsync("QUERY:BUS_ECHO:ON").GetAwaiter().GetResult(); Assert.StartsWith("OK:", a.RecvAsync().GetAwaiter().GetResult()); // 单节点发帧(标准 ID):应答器应把同帧回显给发送者(无需第二节点) a.SendAsync("SEND:0x111:AA BB").GetAwaiter().GetResult(); Assert.StartsWith("OK:TX", a.RecvAsync().GetAwaiter().GetResult()); var echo = a.RecvAsync().GetAwaiter().GetResult(); Assert.StartsWith("EV:RECV:0x111:AA BB", echo); Assert.EndsWith(":RESPONDER", echo); } [Fact] public void BusEcho_Disabled_NoEchoDelivered() { using var a = new FakeClient(); a.ConnectAsync(_port).GetAwaiter().GetResult(); a.HelloAsync("CAN_ECHO_OFF", 0).GetAwaiter().GetResult(); // 默认关 a.SendAsync("SEND:0x912:11").GetAwaiter().GetResult(); Assert.StartsWith("OK:TX", a.RecvAsync().GetAwaiter().GetResult()); // 紧接着的响应应为查询本身,中间不应插入任何 EV:RECV 回显 a.SendAsync("QUERY:BUS_ECHO").GetAwaiter().GetResult(); Assert.Equal("OK:BUS_ECHO:OFF", a.RecvAsync().GetAwaiter().GetResult()); } } }