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. The fixture takes an /// (see ) so each AB family — ControlLogix, /// CompactLogix, Micro800, GuardLogix — starts the simulator with the right --plc /// mode + preseed tag set. Binary is expected on PATH; CI resolves that via a job step /// that downloads the pinned Windows build from libplctag GitHub Releases before /// dotnet test — see docs/v2/test-data-sources.md §2.CI for the exact step. /// /// /// ab_server is a C binary shipped in libplctag's repo (MIT). On developer /// workstations it's built once from source and placed on PATH; on CI the workflow file /// fetches a version-pinned prebuilt + stages it. Tests skip (via /// ) when the binary is not on PATH so a fresh clone /// without the simulator still gets a green unit-test run. /// /// Per-family profiles live in . When a test wants a /// specific family, instantiate the fixture with that profile — either via a /// derived type or by constructing directly in a /// parametric test (the latter is used below for the smoke suite). /// public sealed class AbServerFixture : IAsyncLifetime { private Process? _proc; /// The profile the simulator was started with. Same instance the driver-side options should use. public AbServerProfile Profile { get; } public int Port { get; } public bool IsAvailable { get; private set; } public AbServerFixture() : this(KnownProfiles.ControlLogix, AbServerProfile.DefaultPort) { } public AbServerFixture(AbServerProfile profile) : this(profile, AbServerProfile.DefaultPort) { } public AbServerFixture(AbServerProfile profile, int port) { Profile = profile ?? throw new ArgumentNullException(nameof(profile)); Port = port; } 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 = Profile.BuildCliArgs(Port), 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."; } } /// /// [Theory]-equivalent that skips when ab_server is not on PATH. Pair with /// [MemberData(nameof(KnownProfiles.All))]-style providers to run one theory row per /// profile so a single test covers all four families. /// public sealed class AbServerTheoryAttribute : TheoryAttribute { public AbServerTheoryAttribute() { if (AbServerFixture.LocateBinary() is null) Skip = "ab_server not on PATH; install libplctag test binaries to run."; } }