Modbus — deletes tests/.../Modbus.IntegrationTests/Pymodbus/ (serve.ps1, standard.json, dl205.json, mitsubishi.json, s7_1500.json, README.md). Profile JSONs live only under Docker/profiles/ now. Docker/README.md loses its "Native-Python fallback" section; docs/drivers/Modbus-Test-Fixture.md "What the fixture is" bullet flipped from "primary launcher is Docker, native fallback under Pymodbus/" to "Docker is the only supported launch path". S7 — deletes tests/.../S7.IntegrationTests/PythonSnap7/ (server.py, s7_1500.json, serve.ps1, README.md). Docker/README.md loses "Native-Python fallback"; docs/drivers/S7-Test-Fixture.md updated to match. AB CIP — the biggest simplification because the native-binary spawn had the most code. AbServerFixture.cs rewrites: drops Process management (no more Process _proc + Kill/WaitForExit), drops LocateBinary() PATH lookup, drops the IAsyncLifetime initialize-spawns-server behavior. Fixture is now a thin TCP probe against localhost:44818 (or AB_SERVER_ENDPOINT override) — same shape as Snap7ServerFixture / ModbusSimulatorFixture / OpcPlcFixture. IsServerAvailable() simplifies to a single 500 ms probe. AbServerProfile.cs drops AbServerPlcArg + SeedTags + BuildCliArgs + ToCliSpec + the entire AbServerSeedTag record — the compose file is the canonical source of truth for which tags + which --plc mode each family gets; the profile record now carries just Family + ComposeProfile (matches the docker-compose service key) + Notes. KnownProfiles.ForFamily + .All stay for tests that iterate families. AbServerProfileTests.cs rewrites to match: drops BuildCliArgs_* + ToCliSpec_* + SeedTags_* tests; keeps the family-coverage contract tests + verifies the ComposeProfile strings match compose-file service names (a typo in either surfaces as a unit-test failure, not a silent "wrong family booted" at runtime). Docker/README.md loses "Native-binary fallback" section; docs/drivers/AbServer-Test-Fixture.md "What the fixture is" flipped to Docker-only with clearer skip rules. dev-environment.md §Docker fixtures — the "Native fallbacks" subsection goes away; replaced with a one-line note that Docker is the only supported path for these four fixtures + a fresh clone needs Docker Desktop and nothing else. Verified: whole-solution build 0 errors, AB CIP profile unit tests 6/6, AB CIP Docker smoke 4/4 (all family theory rows), S7 Docker smoke 3/3. Container lifecycle clean. The deleted native code surface was already redundant — every fixture the native paths served is now covered by Docker; keeping them invited drift between the two paths (the original AB CIP native profile had three undetected bugs per the #162 commit message: case-sensitive --plc, bracket tag notation, --path=1,0 requirement — noise the Docker path now avoids by never running the buggy code). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
118 lines
5.0 KiB
Markdown
118 lines
5.0 KiB
Markdown
# 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 at
|
||
`tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/Docker/`.
|
||
Docker is the only supported launch path.
|
||
- **Lifecycle**: `ModbusSimulatorFixture` (collection-scoped) TCP-probes
|
||
`localhost:5020` on first use. `MODBUS_SIM_ENDPOINT` env 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 under `Docker/profiles/` and are baked into the image.
|
||
- **Compose services**: one per profile (`standard` / `dl205` /
|
||
`mitsubishi` / `s7_1500`); only one binds `:5020` at a time.
|
||
- **Tests skip** via `Assert.Skip(sim.SkipReason)` when the probe fails; no
|
||
custom FactAttribute needed because `ModbusSimulatorCollection` carries the
|
||
skip reason.
|
||
|
||
## What it actually covers
|
||
|
||
### DL205 (Automation Direct)
|
||
|
||
- `DL205SmokeTests` — FC16 write → FC03 read round-trip on holding register
|
||
- `DL205CoilMappingTests` — Y-output / C-relay / X-input address mapping
|
||
(octal → Modbus offset)
|
||
- `DL205ExceptionCodeTests` — Modbus exception → OPC UA StatusCode mapping
|
||
- `DL205FloatCdabQuirkTests` — CDAB word-swap float encoding
|
||
- `DL205StringQuirkTests` — packed-string V-memory layout
|
||
- `DL205VMemoryQuirkTests` — V-memory octal addressing
|
||
- `DL205XInputTests` — X-register read-only enforcement
|
||
|
||
### Mitsubishi MELSEC
|
||
|
||
- `MitsubishiSmokeTests` — read + write round-trip
|
||
- `MitsubishiQuirkTests` — word-order, device-code mapping (D/M/X/Y ranges)
|
||
|
||
### Siemens S7-1500 (Modbus gateway flavor)
|
||
|
||
- `S7_1500SmokeTests` — read + write round-trip
|
||
- `S7_ByteOrderTests` — ABCD/DCBA/BADC/CDAB byte-order matrix
|
||
|
||
### Capability surfaces hit
|
||
|
||
- `IReadable` + `IWritable` — full round-trip
|
||
- `ISubscribable` — via the shared `PollGroupEngine` (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
|
||
|
||
1. Add `MODBUS_SIM_ENDPOINT` override documentation to
|
||
`docs/v2/test-data-sources.md` so operators can point the suite at a lab rig.
|
||
2. Extend `pymodbus` profiles to inject exception responses — a JSON flag per
|
||
register saying "next read returns exception 0x04."
|
||
3. Add an FX5U profile once a lab rig is available; the scaffolding is in place.
|
||
|
||
## Key fixture / config files
|
||
|
||
- `tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/ModbusSimulatorFixture.cs`
|
||
- `tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/DL205/DL205Profile.cs`
|
||
- `tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/Mitsubishi/MitsubishiProfile.cs`
|
||
- `tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/S7/S7_1500Profile.cs`
|
||
- `tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/Pymodbus/` — simulator
|
||
driver script + per-family JSON profiles
|