test(ab): run the ab_server integration suites on both transports

This commit is contained in:
Joseph Doherty
2026-10-07 17:35:31 -04:00
parent 2171fc8ce3
commit 8c68694df1
8 changed files with 156 additions and 43 deletions
@@ -8,7 +8,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests;
/// <summary>
/// End-to-end smoke tests against the <c>ab_server</c> PCCC Docker container.
/// Promotes the AB Legacy driver from unit-only coverage (<c>FakeAbLegacyTag</c>)
/// to wire-level: real libplctag PCCC stack over real TCP against the ab_server
/// to wire-level: real PCCC stack (libplctag and native, via <see cref="TransportMatrix"/>) over real TCP against the ab_server
/// simulator. Parametrised over all three families (SLC 500 / MicroLogix / PLC-5)
/// via <c>[AbLegacyTheory]</c> + <c>[MemberData]</c>.
/// </summary>
@@ -17,29 +17,16 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests;
[Trait("Simulator", "ab_server-PCCC")]
public sealed class AbLegacyReadSmokeTests(AbLegacyServerFixture sim)
{
// Only one ab_server container binds :44818 at a time and `--plc=SLC500` only
// answers SLC-mode PCCC, etc. When `AB_LEGACY_COMPOSE_PROFILE` is set, the theory
// filters to that profile alone so the suite matches the running container. Unset
// (the default for real-hardware runs) parameterises across every family the driver
// supports.
/// <summary>Gets the available server profiles based on compose configuration.</summary>
public static IEnumerable<object[]> Profiles
{
get
{
var only = Environment.GetEnvironmentVariable("AB_LEGACY_COMPOSE_PROFILE");
var profiles = KnownProfiles.All.Where(p =>
string.IsNullOrEmpty(only) ||
string.Equals(p.ComposeProfile, only, StringComparison.OrdinalIgnoreCase));
return profiles.Select(p => new object[] { p });
}
}
// Rows come from TransportMatrix.Profiles, which keeps the AB_LEGACY_COMPOSE_PROFILE filter this
// class applied before the transport split: when set, only that profile is crossed with the
// transports, so the suite matches the running container.
/// <summary>Verifies that the driver reads seeded N file from the AB server via PCCC.</summary>
/// <param name="profile">The AB Legacy server profile describing the fixture endpoint.</param>
/// <param name="transport">The transport stack the driver runs on.</param>
[AbLegacyTheory]
[MemberData(nameof(Profiles))]
public async Task Driver_reads_seeded_N_file_from_ab_server_PCCC(AbLegacyServerProfile profile)
[MemberData(nameof(TransportMatrix.Profiles), MemberType = typeof(TransportMatrix))]
public async Task Driver_reads_seeded_N_file_from_ab_server_PCCC(AbLegacyServerProfile profile, AbLegacyTransport transport)
{
if (sim.SkipReasonFor(profile) is { } skip) Assert.Skip(skip);
@@ -59,7 +46,8 @@ public sealed class AbLegacyReadSmokeTests(AbLegacyServerFixture sim)
DataType: AbLegacyDataType.Int)),
Timeout = TimeSpan.FromSeconds(5),
Probe = new AbLegacyProbeOptions { Enabled = false },
}, driverInstanceId: $"ablegacy-smoke-{profile.Family}");
Transport = transport,
}, driverInstanceId: $"ablegacy-smoke-{profile.Family}-{transport}");
await drv.InitializeAsync("{}", TestContext.Current.CancellationToken);
@@ -72,8 +60,10 @@ public sealed class AbLegacyReadSmokeTests(AbLegacyServerFixture sim)
}
/// <summary>Verifies that SLC500 write-then-read round trip succeeds on N7 scratch register.</summary>
[AbLegacyFact]
public async Task Slc500_write_then_read_round_trip_on_N7_scratch_register()
/// <param name="transport">The transport stack the driver runs on.</param>
[AbLegacyTheory]
[MemberData(nameof(TransportMatrix.Transports), MemberType = typeof(TransportMatrix))]
public async Task Slc500_write_then_read_round_trip_on_N7_scratch_register(AbLegacyTransport transport)
{
// Addresses the SLC500 container by its OWN port, so it no longer matters which other
// families happen to be running. The old AB_LEGACY_COMPOSE_PROFILE guard existed because every
@@ -106,7 +96,8 @@ public sealed class AbLegacyReadSmokeTests(AbLegacyServerFixture sim)
Writable: true)),
Timeout = TimeSpan.FromSeconds(5),
Probe = new AbLegacyProbeOptions { Enabled = false },
}, driverInstanceId: "ablegacy-smoke-rw");
Transport = transport,
}, driverInstanceId: $"ablegacy-smoke-rw-{transport}");
await drv.InitializeAsync("{}", TestContext.Current.CancellationToken);
@@ -0,0 +1,35 @@
namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests;
/// <summary>
/// Crosses the PCCC compose profiles with both AbLegacy transports so each integration test runs
/// once on libplctag and once on the native managed stack.
/// </summary>
/// <remarks>
/// Preserves the <c>AB_LEGACY_COMPOSE_PROFILE</c> filter the smoke theory always applied: when set,
/// <see cref="KnownProfiles.All"/> is narrowed to that profile <b>before</b> it is crossed with the
/// transports, so the rows match the running container.
/// </remarks>
public static class TransportMatrix
{
/// <summary>Both transports, for tests that are not parameterised by profile.</summary>
public static IReadOnlyList<AbLegacyTransport> AllTransports { get; } =
[AbLegacyTransport.Libplctag, AbLegacyTransport.Native];
/// <summary>Gets <c>(profile, transport)</c> rows for every selected profile × both transports.</summary>
public static IEnumerable<object[]> Profiles
{
get
{
var only = Environment.GetEnvironmentVariable("AB_LEGACY_COMPOSE_PROFILE");
return from p in KnownProfiles.All
where string.IsNullOrEmpty(only) ||
string.Equals(p.ComposeProfile, only, StringComparison.OrdinalIgnoreCase)
from t in AllTransports
select new object[] { p, t };
}
}
/// <summary>Gets one row per transport.</summary>
public static IEnumerable<object[]> Transports =>
AllTransports.Select(t => new object[] { t });
}