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);
|
}
|
}
|
}
|