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