Rewrite src/ and tests/ project paths in docs, CLAUDE.md, README.md, and test-fixture READMEs to the new module-folder layout (Core/Server/Drivers/ Client/Tooling). References to retired v1 projects (Galaxy.Host/Proxy/Shared, the legacy monolithic test projects) are left untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5.6 KiB
Modbus test fixture
Coverage map + gap inventory for the Modbus TCP driver's integration-test
harness backed by pymodbus simulator profiles per PLC family.
TL;DR: Modbus is the best-covered driver — a real pymodbus server on
localhost with per-family seed-register profiles, plus a skip-gate when the
simulator port isn't reachable. Covers DL205 / Mitsubishi MELSEC / Siemens
S7-1500 family quirks end-to-end. Gaps are mostly error-path + alarm/history
shaped (neither is a Modbus-side concept).
What the fixture is
- Simulator:
pymodbus(Python, BSD) launched as a pinned Docker container attests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/Docker/. Docker is the only supported launch path. - Lifecycle:
ModbusSimulatorFixture(collection-scoped) TCP-probeslocalhost:5020on first use.MODBUS_SIM_ENDPOINTenv var overrides the endpoint so the same suite can target a real PLC. - Profiles:
DL205Profile,MitsubishiProfile,S7_1500Profile— each composes device-specific register-format + quirk-seed JSON for pymodbus. Profile JSONs live underDocker/profiles/and are baked into the image. - Compose services: one per profile (
standard/dl205/mitsubishi/s7_1500); only one binds:5020at a time. - Tests skip via
Assert.Skip(sim.SkipReason)when the probe fails; no custom FactAttribute needed becauseModbusSimulatorCollectioncarries the skip reason.
What it actually covers
DL205 (Automation Direct)
DL205SmokeTests— FC16 write → FC03 read round-trip on holding registerDL205CoilMappingTests— Y-output / C-relay / X-input address mapping (octal → Modbus offset)DL205ExceptionCodeTests— Modbus exception 0x02 → OPC UABadOutOfRangeagainst the dl205 profile (natural out-of-range path)ExceptionInjectionTests— every other exception code in the mapping table (0x01 / 0x03 / 0x04 / 0x05 / 0x06 / 0x0A / 0x0B) against theexception_injectionprofile on both read + write pathsDL205FloatCdabQuirkTests— CDAB word-swap float encodingDL205StringQuirkTests— packed-string V-memory layoutDL205VMemoryQuirkTests— V-memory octal addressingDL205XInputTests— X-register read-only enforcement
Mitsubishi MELSEC
MitsubishiSmokeTests— read + write round-tripMitsubishiQuirkTests— word-order, device-code mapping (D/M/X/Y ranges)
Siemens S7-1500 (Modbus gateway flavor)
S7_1500SmokeTests— read + write round-tripS7_ByteOrderTests— ABCD/DCBA/BADC/CDAB byte-order matrix
Capability surfaces hit
IReadable+IWritable— full round-tripISubscribable— via the sharedPollGroupEngine(polled subscription)IHostConnectivityProbe— TCP-reach transitions
What it does NOT cover
1. No ITagDiscovery
Modbus has no symbol table — the driver requires a static tag map from
DriverConfig. There is no discovery path to test + none in the fixture.
2. Error-path fuzzing
pymodbus serves the seeded values happily; the fixture can't easily inject
exception responses (code 0x01–0x0B) or malformed PDUs. The
AbCipStatusMapper-equivalent for exception codes is unit-tested via
DL205ExceptionCodeTests but the simulator itself never refuses a read.
3. Variant-specific quirks beyond the three profiles
- FX5U / QJ71MT91 Mitsubishi variants — profile scaffolds exist, no tests yet
- Non-S7-1500 Siemens (S7-1200 / ET200SP) — byte-order covered but connection-pool + fragmentation quirks untested
- DL205-family cousins (DL06, DL260) — no dedicated profile
4. Subscription stress
PollGroupEngine is unit-tested standalone but the simulator doesn't exercise
it under multi-register packing stress (FC03 with 125-register batches,
boundary splits, etc.).
5. Alarms / history
Not a Modbus concept. Driver doesn't implement IAlarmSource or
IHistoryProvider; no test coverage is the correct shape.
When to trust the Modbus fixture, when to reach for a rig
| Question | Fixture | Unit tests | Real PLC |
|---|---|---|---|
| "Does FC03/FC06/FC16 work end-to-end?" | yes | - | yes |
| "Does DL205 octal addressing map correctly?" | yes | yes | yes |
| "Does float CDAB word-swap round-trip?" | yes | yes | yes |
| "Does the driver handle exception responses?" | no | yes | yes (required) |
| "Does packing 125 regs into one FC03 work?" | no | no | yes (required) |
| "Does FX5U behave like Q-series?" | no | no | yes (required) |
Follow-up candidates
- Add
MODBUS_SIM_ENDPOINToverride documentation todocs/v2/test-data-sources.mdso operators can point the suite at a lab rig. Extend— shipped via thepymodbusprofiles to inject exception responsesexception_injectioncompose profile + standaloneexception_injector.pyserver. Rules inDocker/profiles/exception_injection.jsonmap(fc, address)to an exception code;ExceptionInjectionTestsexercises every code inMapModbusExceptionToStatus(0x01 / 0x02 / 0x03 / 0x04 / 0x05 / 0x06 / 0x0A / 0x0B) end-to-end on both read (FC03) and write (FC06) paths.- Add an FX5U profile once a lab rig is available; the scaffolding is in place.
Key fixture / config files
tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/ModbusSimulatorFixture.cstests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/DL205/DL205Profile.cstests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/Mitsubishi/MitsubishiProfile.cstests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/S7/S7_1500Profile.cstests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/Docker/— Dockerfile + compose + per-family JSON profiles