using System.Net.Sockets;
namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests;
///
/// Reachability probe for a Modbus TCP simulator (ModbusPal or a real PLC). Parses
/// MODBUS_SIM_ENDPOINT (default localhost:502) and TCP-connects once at
/// fixture construction. Each test checks and calls
/// Assert.Skip when the endpoint was unreachable, so a dev box without a running
/// simulator still passes `dotnet test` cleanly — matches the Galaxy live-smoke pattern in
/// GalaxyRepositoryLiveSmokeTests.
///
///
///
/// Do NOT keep the probe socket open for the life of the fixture. The probe is a
/// one-shot liveness check; tests open their own transports (the real
/// ) against the same endpoint. Sharing a socket
/// across tests would serialize them on a single TCP stream.
///
///
/// The fixture is a collection fixture so the reachability probe runs once per test
/// session, not per test — checking every test would waste several seconds against a
/// firewalled endpoint that times out each attempt.
///
///
public sealed class ModbusSimulatorFixture : IAsyncDisposable
{
private const string DefaultEndpoint = "localhost:502";
private const string EndpointEnvVar = "MODBUS_SIM_ENDPOINT";
public string Host { get; }
public int Port { get; }
public string? SkipReason { get; }
public ModbusSimulatorFixture()
{
var raw = Environment.GetEnvironmentVariable(EndpointEnvVar) ?? DefaultEndpoint;
var parts = raw.Split(':', 2);
Host = parts[0];
Port = parts.Length == 2 && int.TryParse(parts[1], out var p) ? p : 502;
try
{
using var client = new TcpClient();
var task = client.ConnectAsync(Host, Port);
if (!task.Wait(TimeSpan.FromSeconds(2)) || !client.Connected)
{
SkipReason = $"Modbus simulator at {Host}:{Port} did not accept a TCP connection within 2s. " +
$"Start ModbusPal (or override {EndpointEnvVar}) and re-run.";
}
}
catch (Exception ex)
{
SkipReason = $"Modbus simulator at {Host}:{Port} unreachable: {ex.GetType().Name}: {ex.Message}. " +
$"Start ModbusPal (or override {EndpointEnvVar}) and re-run.";
}
}
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}
[Xunit.CollectionDefinition(Name)]
public sealed class ModbusSimulatorCollection : Xunit.ICollectionFixture
{
public const string Name = "ModbusSimulator";
}