wenxiangchao
4 天以前 559890c482f5973f06aafdfec9550fee5ba3c7f2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
using System;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using CanSimulatorServer;
using Xunit;
 
namespace Aging.MvpTests
{
    /// <summary>
    /// 模拟器服务端核心行为测试:HELLO 状态机、NODE_BUSY 仲裁、
    /// 跨节点路由推送、自动响应回推、命令串执行(AUTO_RESPONSE/STATUS)。
    /// 测试进程内托管 TcpSimulatorHost(随机端口),走真实 TCP 回环。
    /// </summary>
    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<string> RecvAsync() => ReadFrameBlocking();
 
            public async Task<string> HelloAsync(string bus, int node, string tag = "t")
            {
                await SendAsync($"HELLO:{bus}:{node}:{tag}");
                return await ReadFrameBlocking();
            }
 
            private async Task<string> ReadFrameBlocking()
            {
                var s = await CanSimulator.SimulatorFraming.ReadFrameAsync(Stream, CancellationToken.None);
                return s ?? "<EOF>";
            }
 
            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());
        }
    }
}