等锁超时、全路径签名、无锁窗口不下发、Fault 终态可等待、Pass+机构不再短路、Alarm 不杀单、catch-up 停在未完成站;VehicleCode=0/重复车号拒绝注册,Hub 已运行时不抢监听口;FileLogger 空目录可写。 Co-authored-by: Cursor <cursoragent@cursor.com>
135 lines
4.4 KiB
C#
135 lines
4.4 KiB
C#
using StandardScene.Magnetic.Protocol;
|
|
using StandardScene.Magnetic.Tests.TestHelpers;
|
|
using System;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Threading;
|
|
using Xunit;
|
|
|
|
namespace StandardScene.Magnetic.Tests.Protocol
|
|
{
|
|
public sealed class Fass2UdpHubMultiCarTests : IDisposable
|
|
{
|
|
private readonly int _hubPort;
|
|
private readonly TestUdpCar _car1;
|
|
private readonly TestUdpCar _car2;
|
|
|
|
public Fass2UdpHubMultiCarTests()
|
|
{
|
|
_hubPort = AllocateUdpPort();
|
|
_car1 = new TestUdpCar(1, AllocateUdpPort());
|
|
_car2 = new TestUdpCar(2, AllocateUdpPort());
|
|
|
|
Fass2UdpHub.Register(_car1);
|
|
Fass2UdpHub.Register(_car2);
|
|
Fass2UdpHub.EnsureStarted(_hubPort);
|
|
Thread.Sleep(100);
|
|
}
|
|
|
|
[Fact]
|
|
public void Hub_RoutesStateToRegisteredCar_AndRepliesWith0x10()
|
|
{
|
|
SendState(_car1.VehicleCode, 2, 10, 100);
|
|
var ack1 = _car1.WaitForAck();
|
|
Assert.NotNull(ack1);
|
|
Assert.Equal(Fass2Protocol.ControlFrameLength, ack1.Length);
|
|
Assert.Equal(Fass2Protocol.CmdStateResponse, ack1[1]);
|
|
Assert.Equal(0x01, ack1[2]);
|
|
Assert.Single(_car1.Reports);
|
|
Assert.Equal((ushort)10, _car1.Reports[0].Node.Node);
|
|
Assert.Empty(_car2.Reports);
|
|
}
|
|
|
|
[Fact]
|
|
public void Hub_DispatchesMultipleCarsIndependently()
|
|
{
|
|
SendState(1, 1, 11, 201);
|
|
SendState(2, 2, 22, 202);
|
|
|
|
Assert.NotNull(_car1.WaitForAck());
|
|
Assert.NotNull(_car2.WaitForAck());
|
|
|
|
Assert.Single(_car1.Reports);
|
|
Assert.Single(_car2.Reports);
|
|
Assert.Equal((ushort)11, _car1.Reports[0].Node.Node);
|
|
Assert.Equal((ushort)22, _car2.Reports[0].Node.Node);
|
|
Assert.Equal((ulong)201, _car1.Reports[0].Task);
|
|
Assert.Equal((ulong)202, _car2.Reports[0].Task);
|
|
}
|
|
|
|
[Fact]
|
|
public void EnsureStarted_NewPort_ReceivesOnNewPort()
|
|
{
|
|
var newPort = AllocateUdpPort();
|
|
Fass2UdpHub.EnsureStarted(newPort);
|
|
Thread.Sleep(150);
|
|
|
|
var frame = Fass2TestFrameBuilder.BuildStateFrame(1, 1, 88, 301);
|
|
using var sender = new UdpClient();
|
|
sender.Send(frame, frame.Length, new IPEndPoint(IPAddress.Loopback, newPort));
|
|
Assert.NotNull(_car1.WaitForAck());
|
|
Assert.Equal((ushort)88, _car1.Reports[_car1.Reports.Count - 1].Node.Node);
|
|
}
|
|
|
|
[Fact]
|
|
public void Hub_IgnoresUnregisteredCarCode()
|
|
{
|
|
SendState(99, 2, 30, 0);
|
|
Thread.Sleep(300);
|
|
Assert.Empty(_car1.Reports);
|
|
Assert.Empty(_car2.Reports);
|
|
}
|
|
|
|
[Fact]
|
|
public void Register_VehicleCodeZero_DoesNotReceiveState()
|
|
{
|
|
var zeroPort = AllocateUdpPort();
|
|
using var zero = new TestUdpCar(0, zeroPort);
|
|
Assert.False(Fass2UdpHub.Register(zero));
|
|
|
|
SendState(0, 2, 40, 0);
|
|
Thread.Sleep(200);
|
|
Assert.Empty(zero.Reports);
|
|
}
|
|
|
|
[Fact]
|
|
public void Register_DuplicateVehicleCode_KeepsFirstCar()
|
|
{
|
|
var dupPort = AllocateUdpPort();
|
|
using var dup = new TestUdpCar(1, dupPort);
|
|
Assert.False(Fass2UdpHub.Register(dup));
|
|
|
|
SendState(1, 2, 41, 401);
|
|
Assert.NotNull(_car1.WaitForAck());
|
|
Assert.Equal((ushort)41, _car1.Reports[_car1.Reports.Count - 1].Node.Node);
|
|
Assert.Empty(dup.Reports);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Fass2UdpHub.Unregister(_car1);
|
|
Fass2UdpHub.Unregister(_car2);
|
|
Fass2UdpHub.Stop();
|
|
_car1.Dispose();
|
|
_car2.Dispose();
|
|
}
|
|
|
|
private void SendState(ushort carCode, byte state, ushort nodeId, ulong taskId)
|
|
{
|
|
var frame = Fass2TestFrameBuilder.BuildStateFrame(carCode, state, nodeId, taskId);
|
|
using var sender = new UdpClient();
|
|
sender.Send(frame, frame.Length, new IPEndPoint(IPAddress.Loopback, _hubPort));
|
|
Thread.Sleep(80);
|
|
}
|
|
|
|
private static int AllocateUdpPort()
|
|
{
|
|
var listener = new TcpListener(IPAddress.Loopback, 0);
|
|
listener.Start();
|
|
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
|
|
listener.Stop();
|
|
return port;
|
|
}
|
|
}
|
|
}
|