M1 drop-out is a no-row Unspecified+IsRestatement update (projector case); M2 ack judged over every declared limit; L1 ActiveAcknowledged for an already-acked new limit; L2 Bad Acked/severity carried/skipped; L3 host end-to-end test; L4 BOOL member-by-member tests + mixed ALMD/ALMA; L5 detection requires no plain Acked. ALMD active bit is InAlarm per Rockwell's ALMD reference; InFaulted is input health. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
13 KiB
ab_server test fixture
Coverage map + gap inventory for the AB CIP integration fixture backed by
libplctag's ab_server simulator.
TL;DR: ab_server is a connectivity + atomic-read smoke harness for the AB
CIP driver. It does not benchmark UDTs, alarms, or any family-specific
quirk. UDT / alarm / quirk behavior is verified only by unit tests with
FakeAbCipTagRuntime.
What the fixture is
- Binary:
ab_server— a C program from the upstream libplctag/libplctag repository (MIT license). It is not part of this repo's source tree;Docker/Dockerfileclones libplctag at a pinned tag and builds theab_serverCMake target in a multi-stage build. - Launcher: Docker (only supported path).
Docker/Dockerfilemulti-stage-buildsab_serverfrom source by cloning libplctag at a pinned tag + copies the binary into a slim runtime image.Docker/docker-compose.ymlhas per-family services (controllogix/compactlogix/micro800/guardlogix); all bind:44818. - Lifecycle:
AbServerFixtureTCP-probes10.100.0.35:44818(the shared Docker host) at collection init + records a skip reason when unreachable. Tests skip via[AbServerFact]/[AbServerTheory]which check the same probe. - Profiles:
KnownProfiles.{ControlLogix, CompactLogix, Micro800, GuardLogix}inAbServerProfile.cs— thin Family + ComposeProfile + Notes records; the compose file is the canonical source of truth for which tags get seeded + which--plcmode the simulator boots in. - Tests: one smoke,
AbCipReadSmokeTests.Driver_reads_seeded_DInt_from_ab_server, parametrized over all four profiles via[AbServerTheory]+[MemberData]. - Endpoint override:
AB_SERVER_ENDPOINT=host:portpoints the fixture at a real PLC instead of the local container.
Per-transport suites and capture recipe
Every integration test in AbCip.IntegrationTests now runs once per transport (TransportMatrix:
Libplctag and Native). Run one compose profile at a time and name it with AB_SERVER_COMPOSE_PROFILE
(all profiles bind :44818, so set it to the running profile; unset, only the default controllogix rows run — ab_server does not self-identify its --plc mode, so crossing every family at one container failed the non-matching rows instead of skipping them, #594). NativeLeaseReleaseSoakTests is the ab_server soak of the background lease release (AB_LEASE_SOAK_CYCLES, default 100; read-only, skips without the fixture, #593). On the 2026-10-07 run native passed everything
libplctag passed on all seven profiles; on micro800 libplctag failed on the empty-path defect described in
AbCip.md and native passed. That defect is now fixed: a re-run of the micro800
smoke test passes on both transports. Evidence:
native-ab-transport-gate-results.
- Write-capable tests (the Logix Emulate ALMD test) require
AB_SERVER_ALLOW_WRITES=1and refuse whileAB_DIFF_ENDPOINTis set. - Differential harness (
AbCip.IntegrationTests/Differential,LiveIntegration) compares libplctag and native on browse, template and read parity against one endpoint. It is read-only by construction, aborts on the first connection failure, never retries, and caps the native phase at one Forward Open. Env:AB_DIFF_ENDPOINT,AB_DIFF_PATH,AB_DIFF_FAMILY,AB_DIFF_TAGS,AB_DIFF_MAX_TAGS(at most 500),AB_DIFF_MAX_TEMPLATES,AB_DIFF_PACE_MS(default 20),AB_DIFF_COUNT_OPENS. Always run with--filter "FullyQualifiedName~Differential"andAB_SERVER_ENDPOINT=127.0.0.1:1. - ab_server serves no UDTs and refuses 1-byte writes, so template parity is vacuous there and BOOL/SINT parity is weak evidence. Native writes remain simulator-only.
- Golden-frame capture:
scripts/ab-capture/capture.sh(capture client inscripts/ab-capture/client); fixtures live intests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Captures/with a README.
What it actually covers
- Read path: driver → libplctag → CIP-over-EtherNet/IP → simulator → back.
- Atomic Logix types per seed:
DINT,REAL,BOOL,SINT,STRING. - One
DINT[16]array tag (ControlLogix profile only). --plc controllogixand--plc compactlogixmode dispatch.- The skip-on-missing-binary behavior (
AbServerFactAttribute) so a fresh clone without the simulator stays green.
What it does NOT cover
Each gap below is either stated explicitly in the profile's Notes field or
inferable from the seed-tag set + smoke-test surface.
1. UDTs / CIP Template Object (class 0x6C)
ControlLogix profile Notes: "ab_server lacks full UDT emulation."
Unverified against ab_server:
- PR 6 structured read/write (
AbCipStructureMemberfan-out) - #179 Template Object shape reader (
CipTemplateObjectDecoder+FetchUdtShapeAsync) - #194 whole-UDT read optimization (
AbCipUdtReadPlanner+AbCipUdtMemberLayout+ theReadGroupAsyncpath inAbCipDriver)
Unit coverage: AbCipFetchUdtShapeTests, CipTemplateObjectDecoderTests,
AbCipUdtMemberLayoutTests, AbCipUdtReadPlannerTests,
AbCipDriverWholeUdtReadTests — all with golden Template-Object byte buffers
- offset-keyed
FakeAbCipTagvalues.
2. ALMD / ALMA alarm projection (#177)
Depends on the ALMD UDT shape, which ab_server cannot emulate. The
OnAlarmEvent raise/clear path + ack-write semantics are not exercised
end-to-end.
Unit coverage: AbCipAlarmProjectionTests — fakes feed InAlarm /
Severity via ValuesByOffset + assert the emitted AlarmEventArgs.
3. Micro800 unconnected-only path
Micro800 profile Notes: "--plc=Micro800 mode (unconnected-only, empty path).
Driver-side enforcement verified in the unit suite."
The compose service boots ab_server --plc=Micro800 with an empty routing path.
The unconnected-session requirement (PR 11) is validated at the driver unit-test
level via FakeAbCipTagRuntime; the wire-level contract (what happens when
a connected-send arrives at a real Micro800 backplane) is not exercised by the
simulator. Real Micro800 (2080-series) on a lab rig would be the authoritative
benchmark.
4. GuardLogix safety subsystem
GuardLogix profile Notes: "ab_server doesn't emulate the safety
subsystem."
Only the _S-suffix naming classifier (PR 12, SecurityClassification.ViewOnly
forced on safety tags) runs. Actual safety-partition write rejection — what
happens when a non-safety write lands on a real 1756-L8xS — is not exercised.
5. CompactLogix connection size and the regular-open fallback
Since T21 the CompactLogix profile defaults to a Large Forward Open (4002), matching what libplctag
opens for plc=compactlogix. The old 504 default was cosmetic under libplctag, which never received it,
and cost the native transport ~3× the round trips per poll. A pre-v20 CompactLogix (1769-L3x) or a bridge that refuses
the Large open is handled by CipConnection's fallback (a regular open at min(size, 504) in the same
throttled attempt). That fallback is pinned by unit tests against a fake target only: ab_server accepts
any size, so a refusing controller is never exercised here.
6. BOOL-within-DINT read-modify-write (#181)
The AbCipDriver.WriteBitInDIntAsync RMW path + its per-parent SemaphoreSlim
serialization is unit-tested only (AbCipBoolInDIntRmwTests). ab_server
seeds a plain TestBOOL tag; the .N bit-within-DINT syntax that triggers
the RMW path is not exercised end-to-end.
7. Capability surfaces beyond read
No smoke test for:
IWritable.WriteAsyncITagDiscovery.DiscoverAsync(@tagswalker)ISubscribable.SubscribeAsync(poll-group engine)IHostConnectivityProbestate transitions under wire failureIPerCallHostResolvermulti-device routing
The driver implements all of these + they have unit coverage, but the only
end-to-end path ab_server validates today is atomic ReadAsync.
Logix Emulate golden-box tier
Rockwell Studio 5000 Logix Emulate sits above ab_server in fidelity + below real hardware. When an operator has Emulate running on a reachable Windows box + sets two env vars, the suite promotes several behaviours from unit-only to end-to-end wire-level coverage:
$env:AB_SERVER_PROFILE = 'emulate'
$env:AB_SERVER_ENDPOINT = '<emulate-pc-ip>:44818'
dotnet test tests\Drivers\ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests
With AB_SERVER_PROFILE unset or abserver, the Emulate-tier classes
skip cleanly + the ab_server Docker fixture runs as usual.
| Gap this fixture doc calls out | ab_server | Logix Emulate | Real hardware |
|---|---|---|---|
| UDT / CIP Template Object (task #194) | no | yes | yes |
| ALMD alarm projection (task #177) | no | yes | yes |
@tags Symbol Object walk with Program: scope |
partial | yes | yes |
| Add-On Instructions | no | yes | yes |
| GuardLogix safety-partition write rejection | no | yes (Emulate 5580) | yes |
| CompactLogix Large-open refusal → regular-open fallback | no | yes (5370 firmware) | yes |
| EtherNet/IP embedded-switch behaviour | no | no | yes |
| Redundant chassis failover (1756-RM) | no | no | yes |
| Motion control timing | no | no | yes |
Tests that promote to Emulate (gated on AB_SERVER_PROFILE=emulate
via AbServerProfileGate.SkipUnless):
AbCipEmulateUdtReadTests.WholeUdt_read_decodes_each_member_at_its_Template_Object_offset— #194 whole-UDT optimization, verified against real Template Object bytesAbCipEmulateAlmdTests.Real_ALMD_raise_fires_OnAlarmEvent_through_the_driver_projection— #177 ALMD projection, verified against the real ALMD instruction
Required Studio 5000 project state is documented in
tests/…/AbCip.IntegrationTests/LogixProject/README.md;
the .L5X export lands there once the Emulate PC is on-site + the
project is authored.
Costs to accept:
- Rockwell TechConnect or per-seat license — not redistributable; not CI-runnable. Each operator licenses their own Emulate install.
- Windows-only + Hyper-V conflict — Emulate can't coexist with
Docker Desktop's WSL 2 backend on the same OS, same way TwinCAT XAR
can't (see
docs/v2/dev-environment.md§Integration host). - Manual lifecycle — no
docker compose upequivalent; operator opens Emulate, loads the L5X, clicks Run. The L5X in the repo keeps project state reproducible, runtime-start is human.
When to trust ab_server, when to reach for a rig
| Question | ab_server | Unit tests | Logix Emulate | Lab rig |
|---|---|---|---|---|
| "Does the driver talk CIP at all?" | yes | - | yes | - |
| "Is my atomic read path wired correctly?" | yes | yes | yes | yes |
| "Does whole-UDT grouping work?" | no | yes | yes | yes |
| "Do ALMD alarms raise + clear?" | no | yes | yes | yes |
| "Is Micro800 unconnected-only enforced wire-side?" | partial (--plc=Micro800 boots, but wire rejection untested) | partial | yes | yes (required) |
| "Does GuardLogix reject non-safety writes on safety tags?" | no | no | yes (Emulate 5580) | yes |
| "Does CompactLogix refuse a Large Forward Open?" | no | partial | yes (5370 firmware) | yes |
| "Does BOOL-in-DINT RMW race against concurrent writers?" | no | yes | partial | yes (stress) |
| "Does EtherNet/IP embedded-switch behave correctly?" | no | no | no | yes (required) |
| "Does redundant-chassis failover work?" | no | no | no | yes (required) |
Follow-up candidates
If integration-level UDT / alarm / quirk proof becomes a shipping gate, the options are roughly:
- Logix Emulate golden-box tier (scaffolded; see the section above) — highest-fidelity path short of real hardware. Closes UDT / ALMD / AOI / optimized-DB gaps in one license + one Windows PC.
- Extend
ab_serverupstream — the project accepts PRs + already carries a CIP framing layer that UDT emulation could plug into. - Stand up a lab rig — physical
1756-L7x/5069-L3x/2080-LC30/1756-L8xScontrollers. The only path that covers safety partitions across nodes, redundant chassis, embedded-switch behaviour, and motion timing.
See also:
tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/AbServerFixture.cstests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/AbServerProfile.cstests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/AbServerProfileGate.cs—AB_SERVER_PROFILEtier gatetests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/AbCipReadSmokeTests.cstests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/Docker/— ab_server image + composetests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/Emulate/— Logix Emulate tier teststests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/LogixProject/README.md— L5X project state the Emulate tier expectsdocs/v2/test-data-sources.md§2 — the broader test-data-source picking rationale this fixture slots into