using System.Diagnostics; using Xunit; using Xunit.Sdk; namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests; /// /// Shared fixture that starts libplctag's ab_server simulator in the background for /// the duration of an integration test collection. Binary is expected on PATH; the per-test /// JSON profile is passed via --config. /// /// /// ab_server is a C binary shipped in the same repo as libplctag (see /// test-data-sources.md §2 and plan decision #99). On a developer workstation it's /// built once from source and placed on PATH; in CI we intend to publish a prebuilt Windows /// x64 binary as a GitHub release asset in a follow-up PR so the fixture can download + /// extract it at setup time. Until then every test in this project is skipped when /// ab_server is not locatable. /// /// Per-family JSON profiles (ControlLogix / CompactLogix / Micro800 / GuardLogix) /// ship under Profiles/ and drive the simulator's tag shape — this is where the /// UDT + Program-scope coverage gap will be filled by the hand-rolled stub in PR 6. /// public sealed class AbServerFixture : IAsyncLifetime { private Process? _proc; public int Port { get; } = 44818; public bool IsAvailable { get; private set; } public ValueTask InitializeAsync() => InitializeAsync(default); public ValueTask DisposeAsync() => DisposeAsync(default); public async ValueTask InitializeAsync(CancellationToken cancellationToken) { if (LocateBinary() is not string binary) { IsAvailable = false; return; } IsAvailable = true; _proc = new Process { StartInfo = new ProcessStartInfo { FileName = binary, Arguments = $"--port {Port} --plc controllogix", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, }, }; _proc.Start(); // Give the server a moment to accept its listen socket before tests try to connect. await Task.Delay(500, cancellationToken).ConfigureAwait(false); } public ValueTask DisposeAsync(CancellationToken cancellationToken) { try { if (_proc is { HasExited: false }) { _proc.Kill(entireProcessTree: true); _proc.WaitForExit(5_000); } } catch { /* best-effort cleanup */ } _proc?.Dispose(); return ValueTask.CompletedTask; } /// /// Locate ab_server on PATH. Returns null when missing — tests that /// depend on it should use so CI runs without the binary /// simply skip rather than fail. /// public static string? LocateBinary() { var names = new[] { "ab_server.exe", "ab_server" }; var path = Environment.GetEnvironmentVariable("PATH") ?? ""; foreach (var dir in path.Split(Path.PathSeparator)) { foreach (var name in names) { var candidate = Path.Combine(dir, name); if (File.Exists(candidate)) return candidate; } } return null; } } /// /// [Fact]-equivalent that skips when ab_server is not available on PATH. /// Integration tests use this instead of [Fact] so a developer box without /// ab_server installed still gets a green run. /// public sealed class AbServerFactAttribute : FactAttribute { public AbServerFactAttribute() { if (AbServerFixture.LocateBinary() is null) Skip = "ab_server not on PATH; install libplctag test binaries to run."; } }