Files
lmxopcua/docs/drivers/Modbus-Test-Fixture.md
Joseph Doherty 0e1dcc119e Remove native-launcher fallbacks for the four Dockerized fixtures — Docker is the only supported path for Modbus / S7 / AB CIP / OpcUaClient integration. Native paths stay in place only where Docker isn't compatible (Galaxy: MXAccess COM + Windows-only; TwinCAT: Beckhoff runtime vs Hyper-V; FOCAS: closed-source Fanuc Fwlib32.dll; AB Legacy: PCCC has no OSS simulator). Simplifies the fixture landscape + removes the "which path do I run" ambiguity; removes two full native-launcher directories + the AB CIP native-spawn path; removes the parallel profile-as-CLI-arg-builder code from AbServerFixture.
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>
2026-04-20 12:27:44 -04:00

5.0 KiB
Raw Blame History

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 0x010x0B) 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