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
using System;
using System.Threading;
using System.Threading.Tasks;
using Aging.Infrastructure.Drivers;
using CanSimulatorServer;
using Xunit;
 
namespace Aging.MvpTests
{
    /// <summary>
    /// 统一 CAN 驱动(CanDeviceDriver, TCP NodeChannel 后端)行为测试:
    /// 在测试进程内托管模拟器服务,验证连接/发送/推送事件/本地缓冲查询/错误语义
    /// 与调试面板现有解析格式(RECV_ALL:n:…、OK:TX…)保持兼容。
    /// </summary>
    public class CanDeviceDriverTests : IDisposable
    {
        private readonly TcpSimulatorHost _host;
 
        public CanDeviceDriverTests()
        {
            _host = new TcpSimulatorHost();
            _host.StartAsync().GetAwaiter().GetResult();
        }
 
        public void Dispose() => _host.Dispose();
 
        private CanDeviceDriver NewDriver(string deviceId, string bus, int node) =>
            new CanDeviceDriver(deviceId, $"{bus}:{node}", hostOverride: "127.0.0.1", portOverride: _host.Port);
 
        [Fact]
        public void ConnectAsync_OnRunningServer_Succeeds()
        {
            using var drv = NewDriver("CAN-T1", "CAN_BUS_1", 0);
            drv.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
            Assert.True(drv.IsConnected);
        }
 
        [Fact]
        public void ConnectAsync_NoServer_ThrowsWithStartupHint()
        {
            // 指向必然空闲的端口:错误文案应引导用户启动模拟器服务
            using var drv = new CanDeviceDriver("CAN-T2", "CAN_BUS_1:0", hostOverride: "127.0.0.1", portOverride: 1);
            var ex = Assert.ThrowsAny<Exception>(() =>
                drv.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult());
            Assert.Contains("模拟器", ex.Message);
        }
 
        [Fact]
        public void SendCommandAsync_ReturnsOkTx_ReadableByPanel()
        {
            using var drv = NewDriver("CAN-T3", "CAN_BUS_1", 1);
            drv.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
 
            string resp = drv.SendCommandAsync("SEND:0x100:AA BB", CancellationToken.None)
                              .GetAwaiter().GetResult();
            Assert.StartsWith("OK:TX", resp);
        }
 
        [Fact]
        public void Push_FromPeer_TriggersEvent_AndLocalRecvAll()
        {
            using var a = NewDriver("CAN-T4A", "CAN_BUS_1", 2);
            using var b = NewDriver("CAN-T4B", "CAN_BUS_1", 3);
            a.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
            b.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
 
            CanSimulator.CanFrame received = null;
            a.DriverFrameReceived += (s, f) => received = f;
 
            b.SendCommandAsync("SEND:0x222:11 22 33", CancellationToken.None).GetAwaiter().GetResult();
 
            // 等待推送事件到达(读循环线程触发)
            SpinWait.SpinUntil(() => received != null, 3000);
            Assert.NotNull(received);
            Assert.Equal("0x222", received.FrameIdHex);
 
            // RECV_ALL 由驱动本地缓冲应答(不依赖服务端邮箱)
            string all = a.QueryAsync("RECV_ALL", CancellationToken.None).GetAwaiter().GetResult();
            Assert.StartsWith("RECV_ALL:1:", all);
            Assert.Contains("0x222:11 22 33", all);
        }
 
        [Fact]
        public void Query_AutoResponse_ProxiedToServer_ReturnsOk()
        {
            using var drv = NewDriver("CAN-T5", "CAN_BUS_1", 4);
            drv.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
 
            string resp = drv.QueryAsync("AUTO_RESPONSE:ON", CancellationToken.None).GetAwaiter().GetResult();
            Assert.StartsWith("OK:", resp);
            string back = drv.QueryAsync("AUTO_RESPONSE", CancellationToken.None).GetAwaiter().GetResult();
            Assert.Contains("ON", back);
        }
 
        [Fact]
        public void Query_Status_HasLegacyCompatibleFormat()
        {
            using var drv = NewDriver("CAN-T6", "CAN_BUS_1", 5);
            drv.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
 
            // 面板 GetStatValue 解析 "STATUS:Node=…, …, Rx=0, Tx=0, …"(逗号等号分隔)
            string resp = drv.QueryAsync("STATUS", CancellationToken.None).GetAwaiter().GetResult();
            Assert.StartsWith("STATUS:Node=", resp);
            Assert.Contains("Tx=", resp);
            Assert.Contains("Rx=", resp);
        }
 
        [Fact]
        public void Connect_NodeBusy_ThrowsWithNodeIndexHint()
        {
            using var first = NewDriver("CAN-T7A", "CAN_BUS_1", 6);
            first.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
 
            using var second = NewDriver("CAN-T7B", "CAN_BUS_1", 6);
            var ex = Assert.ThrowsAny<Exception>(() =>
                second.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult());
            Assert.Contains("节点编号", ex.Message);
        }
    }
}