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