@@ -0,0 +1,68 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.PlcFamilies;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests;
|
||||
|
||||
/// <summary>
|
||||
/// PR 9 — per-device timeout integration scaffold. Build-only at PR 9 time: the ab_server
|
||||
/// PCCC simulator answers in <100 ms locally so a 500 ms per-device timeout doesn't
|
||||
/// normally trip. Either an <c>iptables --delay</c> sidecar or a <c>tc qdisc</c> netem
|
||||
/// filter must be wired up first; until then the test asserts that a <i>generous</i>
|
||||
/// per-device timeout still completes successfully (the precedence path itself is
|
||||
/// exercised), with the slow-path failure case expressed in unit tests via
|
||||
/// <see cref="FakeAbLegacyTag"/>.
|
||||
/// </summary>
|
||||
[Collection(AbLegacyServerCollection.Name)]
|
||||
[Trait("Category", "Integration")]
|
||||
[Trait("Simulator", "ab_server-PCCC")]
|
||||
public sealed class AbLegacyPerDeviceTimeoutTests(AbLegacyServerFixture sim)
|
||||
{
|
||||
[AbLegacyFact]
|
||||
public async Task Per_device_Timeout_override_flows_into_runtime_against_ab_server()
|
||||
{
|
||||
if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason);
|
||||
|
||||
var deviceUri = $"ab://{sim.Host}:{sim.Port}/{sim.CipPath}";
|
||||
await using var drv = new AbLegacyDriver(new AbLegacyDriverOptions
|
||||
{
|
||||
// Driver-wide tight 500 ms; per-device override gives the simulator 5 s headroom
|
||||
// to demonstrate the precedence rule in a wire-level setting.
|
||||
Timeout = TimeSpan.FromMilliseconds(500),
|
||||
Devices = [new AbLegacyDeviceOptions(
|
||||
deviceUri,
|
||||
AbLegacyPlcFamily.Slc500,
|
||||
Timeout: TimeSpan.FromSeconds(5))],
|
||||
Tags = [new AbLegacyTagDefinition(
|
||||
Name: "IntCounter",
|
||||
DeviceHostAddress: deviceUri,
|
||||
Address: "N7:0",
|
||||
DataType: AbLegacyDataType.Int)],
|
||||
Probe = new AbLegacyProbeOptions { Enabled = false },
|
||||
}, driverInstanceId: "ablegacy-pr9-timeout");
|
||||
|
||||
await drv.InitializeAsync("{}", TestContext.Current.CancellationToken);
|
||||
var snapshots = await drv.ReadAsync(["IntCounter"], TestContext.Current.CancellationToken);
|
||||
|
||||
// Per-device override picked up; the read against the simulator succeeds because the
|
||||
// 5 s per-device cap supersedes the otherwise-too-tight 500 ms driver-wide default.
|
||||
snapshots.Single().StatusCode.ShouldBe(AbLegacyStatusMapper.Good);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skeleton for the inverse — slow-link (<c>tc qdisc</c> / <c>iptables --delay</c>) +
|
||||
/// tight per-device timeout. Skipped pending the netem sidecar work tracked in
|
||||
/// <see href="../Docker/README.md#per-device-timeout-fixture">Docker/README.md</see>.
|
||||
/// </summary>
|
||||
[AbLegacyFact(Skip = "Pending netem / iptables-delay sidecar — see Docker/README.md")]
|
||||
public Task Per_device_Timeout_below_simulated_delay_surfaces_BadCommunicationError()
|
||||
{
|
||||
// Future shape:
|
||||
// docker compose --profile slc500-slow up -d (adds netem qdisc on the egress)
|
||||
// override Timeout: TimeSpan.FromMilliseconds(100)
|
||||
// ReadAsync ⇒ snapshots.Single().StatusCode == BadCommunicationError
|
||||
// while a sibling device (no override → 5 s) keeps reading Good.
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -139,6 +139,38 @@ bit writes to real hardware or RSEmulate 500 until upstream resolves.
|
||||
See [`docs/drivers/AbLegacy-Test-Fixture.md`](../../../docs/drivers/AbLegacy-Test-Fixture.md)
|
||||
for the full coverage map.
|
||||
|
||||
## Per-device timeout fixture (PR 9 / #252) — TODO
|
||||
|
||||
`AbLegacyPerDeviceTimeoutTests.Per_device_Timeout_below_simulated_delay_surfaces_BadCommunicationError`
|
||||
needs a slow-link sidecar before it can run for real. The simulator answers
|
||||
in <100 ms locally, so a 500 ms per-device timeout never trips against
|
||||
the unmodified container.
|
||||
|
||||
Two options, neither wired up at PR 9 time:
|
||||
|
||||
1. **`tc qdisc` netem inside the container** — add to `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
# services:
|
||||
# ablegacy-slc500-slow:
|
||||
# extends: ablegacy-slc500
|
||||
# cap_add: [NET_ADMIN]
|
||||
# command: >
|
||||
# sh -c "tc qdisc add dev eth0 root netem delay 800ms &&
|
||||
# ab_server --plc=SLC500 --port=44818 --path=1,0 --tag=N7[200]:INT16"
|
||||
```
|
||||
|
||||
`--cap-add=NET_ADMIN` is required because `tc qdisc` mutates the
|
||||
container's egress queue. Combine with `AB_LEGACY_COMPOSE_PROFILE=slc500-slow`
|
||||
to point the suite at the slow profile.
|
||||
|
||||
2. **`iptables --delay` shim** — sidecar container that NATs port 44818 and
|
||||
adds a fixed delay on the SYN/ACK + payload path. More portable than
|
||||
netem (no `NET_ADMIN` on the simulator itself) but adds a hop.
|
||||
|
||||
When either lands, drop the `Skip = …` on the integration test and assert
|
||||
the precedence rule end-to-end.
|
||||
|
||||
## References
|
||||
|
||||
- [libplctag on GitHub](https://github.com/libplctag/libplctag) — `ab_server`
|
||||
|
||||
Reference in New Issue
Block a user