New project tests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests/ with four pieces. TwinCATXarFixture — TCP probe against the ADS-over-TCP port 48898 on the host from TWINCAT_TARGET_HOST env var, requires TWINCAT_TARGET_NETID for the target AmsNetId, optional TWINCAT_TARGET_PORT for runtime 2+ (default 851 = PLC runtime 1). Doesn't own a lifecycle — XAR can't run in Docker because it bypasses the Windows kernel scheduler to hit real-time cycles, so the VM stays operator-managed. Explicit skip reasons surface the setup steps (start VM, set env vars, reactivate trial license) instead of a confusing hang. TwinCATFactAttribute + TwinCATTheoryAttribute — xunit skip gate matching AbServerFactAttribute / OpcPlcCollection patterns. TwinCAT3SmokeTests — three smoke tests through the real AdsTwinCATClient + real ADS over TCP. Driver_reads_seeded_DINT_through_real_ADS reads GVL_Fixture.nCounter, asserts >= 1234 (MAIN increments every cycle so an exact match would race). Driver_write_then_read_round_trip_on_scratch_REAL writes 42.5 to GVL_Fixture.rSetpoint + reads back, catches the ADS write path regression that unit tests can't see. Driver_subscribe_receives_native_ADS_notifications_on_counter_changes validates the #189 native-notification path end-to-end — AddDeviceNotification fires OnDataChange at the PLC cycle boundary, the test observes one firing within 3 s. All three gated on TWINCAT_TARGET_HOST + NETID; skip via TwinCATFactAttribute when unset, verified in this commit with 3 clean [SKIP] results. TwinCatProject/README.md — the tsproj state the smoke tests depend on. GVL_Fixture with nCounter:DINT:=1234 + rSetpoint:REAL:=0.0 + bFlag:BOOL:=TRUE; MAIN program with the single-line ladder `GVL_Fixture.nCounter := GVL_Fixture.nCounter + 1;`; PlcTask cyclic @ 10 ms priority 20; PLC runtime 1 (AMS port 851). Explains why tsproj over the compiled bootproject (text-diffable, rebuildable, no per-install state). Full XAR VM setup walkthrough — Hyper-V Gen 2 VM, TC3 XAE+XAR install, noting the AmsNetId from the tray icon, bilateral route configuration (VM System Manager → Routes + dev box StaticRoutes.xml), project import, Activate Configuration + Run Mode. License-rotation section walks through two options — scheduled TcActivate.exe /reactivate via Task Scheduler (not officially Beckhoff-supported, reportedly works on current builds) or paid runtime license (~$1k one-time per runtime per CPU). Final section shows the exact env-var recipe + dotnet test command on the dev box. docs/drivers/TwinCAT-Test-Fixture.md — flipped TL;DR from "there is no integration fixture" to "scaffolding lives at tests/..., remaining operational work is VM + tsproj + license rotation". "What the fixture is" gains an Integration section describing the XAR VM target. "What it actually covers" gains an Integration subsection listing the three named smoke tests. Follow-up candidates rewritten — the #1 item used to be "TwinCAT 3 runtime on CI" as a speculative option; now it's concrete "XAR VM live-population" with a link to #221 + the project README for the operational walkthrough. License rotation becomes #2 with both automation paths. Key fixture / config files list adds the three new files + the project README. docs/drivers/README.md coverage-map row updated from "no integration fixture" to "XAR-VM integration scaffolding". Solution file picks up the new tests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests entry alongside the existing TwinCAT.Tests. xunit CollectionDefinition added to TwinCATXarFixture after the first build revealed the [Collection("TwinCATXar")] reference on TwinCAT3SmokeTests had no matching registration. Build 0 errors; 3 skip-clean test outcomes verified. #221 stays open as in_progress until the VM + tsproj land. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.1 KiB
TwinCAT test fixture
Coverage map + gap inventory for the Beckhoff TwinCAT ADS driver.
TL;DR: Integration-test scaffolding lives at
tests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests/ (task #221).
TwinCATXarFixture probes TCP 48898 on an operator-supplied VM; three
smoke tests (read / write / native notification) run end-to-end through
the real ADS stack when the VM is reachable, skip cleanly otherwise.
Remaining operational work: stand up a TwinCAT 3 XAR runtime in a
Hyper-V VM, author the .tsproj project documented at
TwinCatProject/README.md, rotate the 7-day trial license (or buy a
paid runtime). Unit tests via FakeTwinCATClient still carry the
exhaustive contract coverage.
TwinCAT is the only driver outside Galaxy that uses native
notifications (no polling) for ISubscribable, and the fake exposes a
fire-event harness so notification routing is contract-tested rigorously
at the unit layer.
What the fixture is
Integration layer (task #221, scaffolded):
tests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests/ —
TwinCATXarFixture TCP-probes ADS port 48898 on the host specified by
TWINCAT_TARGET_HOST + requires TWINCAT_TARGET_NETID (AmsNetId of the
VM). No fixture-owned lifecycle — XAR can't run in Docker because it
bypasses the Windows kernel scheduler, so the VM stays
operator-managed. TwinCatProject/README.md documents the required
.tsproj project state; the file itself lands once the XAR VM is up +
the project is authored. Three smoke tests:
Driver_reads_seeded_DINT_through_real_ADS,
Driver_write_then_read_round_trip_on_scratch_REAL, and
Driver_subscribe_receives_native_ADS_notifications_on_counter_changes
— all skip cleanly via [TwinCATFact] when the runtime isn't
reachable.
Unit layer: tests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Tests/ is
still the primary coverage. FakeTwinCATClient also fakes the
AddDeviceNotification flow so tests can trigger callbacks without a
running runtime.
What it actually covers
Integration (XAR VM, task #221 — code scaffolded, needs VM + project)
TwinCAT3SmokeTests.Driver_reads_seeded_DINT_through_real_ADS— real AMS handshake + ADS read ofGVL_Fixture.nCounter(seeded at 1234, MAIN increments each cycle)TwinCAT3SmokeTests.Driver_write_then_read_round_trip_on_scratch_REAL— real ADS write + read onGVL_Fixture.rSetpointTwinCAT3SmokeTests.Driver_subscribe_receives_native_ADS_notifications_on_counter_changes— realAddDeviceNotificationagainst the cycle-incrementing counter; observesOnDataChangefiring within 3 s of subscribe
All three gated on TWINCAT_TARGET_HOST + TWINCAT_TARGET_NETID env
vars; skip cleanly via [TwinCATFact] when the VM isn't reachable or
vars are unset.
Unit
TwinCATAmsAddressTests—ads://<netId>:<port>parsing + routingTwinCATCapabilityTests— data-type mapping (primitives + declared UDTs), read-only classificationTwinCATReadWriteTests— read + write through the fake, status mappingTwinCATSymbolPathTests— symbol-path routing for nested struct membersTwinCATSymbolBrowserTests—ITagDiscovery.DiscoverAsyncviaReadSymbolsAsync(#188) + system-symbol filteringTwinCATNativeNotificationTests—AddDeviceNotification(#189) registration, callback-delivery-to-OnDataChangewiring, unregister on unsubscribeTwinCATDriverTests—IDriverlifecycle
Capability surfaces whose contract is verified: IDriver, IReadable,
IWritable, ITagDiscovery, ISubscribable, IHostConnectivityProbe,
IPerCallHostResolver.
What it does NOT cover
1. AMS / ADS wire traffic
No real AMS router frame is exchanged. Beckhoff's TwinCAT.Ads NuGet (their
own .NET SDK, not libplctag-style OSS) has no in-process fake; tests stub
the ITwinCATClient abstraction above it.
2. Multi-route AMS
ADS supports chained routes (<localNetId> → <routerNetId> → <targetNetId>)
for PLCs behind an EC master / IPC gateway. Parse coverage exists; wire-path
coverage doesn't.
3. Notification reliability under jitter
AddDeviceNotification delivers at the runtime's cycle boundary; under high
CPU load or network jitter real notifications can coalesce. The fake fires
one callback per test invocation — real callback-coalescing behavior is
untested.
4. TC2 vs TC3 variant handling
TwinCAT 2 (ADS v1) and TwinCAT 3 (ADS v2) have subtly different
GetSymbolInfoByName semantics + symbol-table layouts. Driver targets TC3;
TC2 compatibility is not exercised.
5. Cycle-time alignment for ISubscribable
Native ADS notifications fire on the PLC cycle boundary. The fake test harness assumes notifications fire on a timer the test controls; cycle-aligned firing under real PLC control is not verified.
6. Alarms / history
Driver doesn't implement IAlarmSource or IHistoryProvider — not in
scope for this driver family. TwinCAT 3's TcEventLogger could theoretically
back an IAlarmSource, but shipping that is a separate feature.
When to trust TwinCAT tests, when to reach for a rig
| Question | Unit tests | Real TwinCAT runtime |
|---|---|---|
| "Does the AMS address parser accept X?" | yes | - |
"Does notification → OnDataChange wire correctly?" |
yes (contract) | yes |
| "Does symbol browsing filter TwinCAT internals?" | yes | yes |
| "Does a real ADS read return correct bytes?" | no | yes (required) |
| "Do notifications coalesce under load?" | no | yes (required) |
| "Does a TC2 PLC work the same as TC3?" | no | yes (required) |
Follow-up candidates
- XAR VM live-population — scaffolding is in place (this PR); the
remaining work is operational: stand up the Hyper-V VM, install XAR,
author the
.tsprojperTwinCatProject/README.md, configure the bilateral ADS route, setTWINCAT_TARGET_HOST+TWINCAT_TARGET_NETIDon the dev box. Then the three smoke tests transition skip → pass. Tracked as #221. - License-rotation automation — XAR's 7-day trial expires on
schedule. Either automate
TcActivate.exe /reactivatevia a scheduled task on the VM (not officially supported; reportedly works for some TC3 builds), or buy a paid runtime license (~$1k one-time per runtime per CPU) to kill the rotation. The doc atTwinCatProject/README.md§License rotation walks through both. - Lab rig — cheapest IPC (CX7000 / CX9020) on a dedicated network; the only route that covers TC2 + real EtherCAT I/O timing + cycle jitter under CPU load.
Key fixture / config files
tests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests/TwinCATXarFixture.cs— TCP probe + skip-attributes + env-var parsingtests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests/TwinCAT3SmokeTests.cs— three wire-level smoke teststests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests/TwinCatProject/README.md— project spec + VM setup + license-rotation notestests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Tests/FakeTwinCATClient.cs— in-process fake with the notification-fire harness used byTwinCATNativeNotificationTestssrc/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATDriver.cs— ctor takesITwinCATClientFactory