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