Files
lmxopcua/docs/v2/modbus-test-plan.md
Joseph Doherty cb7b81a87a Phase 3 PR 30 — Modbus integration-test project scaffold. New tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests project is the harness modbus-test-plan.md called for: a skip-when-unreachable fixture that TCP-probes a Modbus simulator endpoint (MODBUS_SIM_ENDPOINT, default localhost:502) once per test session, a DL205 device profile stub (single writable holding register at address 100, probe disabled to avoid racing with assertions), and one happy-path smoke test that initializes the real ModbusDriver + real ModbusTcpTransport, writes a known Int16 value, reads it back, and asserts status=0 + value round-trip. No DL205 quirk assertions yet — those land one-per-PR as the user validates each behavior in ModbusPal (word order for 32-bit, register-zero access, coil addressing base, max registers per FC03, response framing under load, exception code on protected-bit coil write).
ModbusSimulatorFixture is a collection fixture so the 2s TCP probe runs once per run, not per test; SkipReason gets a clear operator-facing message ('start ModbusPal or override MODBUS_SIM_ENDPOINT'). Tests call Assert.Skip(sim.SkipReason) rather than silently returning — matches the test-plan convention and reads cleanly in CI logs. DL205Profile.BuildOptions deliberately disables the background probe loop since integration tests drive reads explicitly and the probe would race with assertions. Tag naming uses the DL205_ prefix so filter 'DisplayName~DL205' surfaces device-specific failures at a glance.
Project references: xunit.v3 + Shouldly + Microsoft.NET.Test.Sdk + xunit.runner.visualstudio (matches the existing Driver.Modbus.Tests unit project), project ref to src/Driver.Modbus. Registered in ZB.MOM.WW.OtOpcUa.slnx under tests/. ModbusPal/README.md documents the dev loop (install ModbusPal jar, load profile, start simulator, dotnet test), explains MODBUS_SIM_ENDPOINT override for real-PLC benchwork, and flags DL205.xmpp as the first profile to add in a follow-up PR.
dotnet test run against the scaffold (no simulator running) skips cleanly: 0 failed, 0 passed, 1 skipped, with the SkipReason surfaced. dotnet build clean (0 warnings, 0 errors). Updated docs/v2/modbus-test-plan.md to mark the scaffold PR done and renumbered future PRs from 'PR 27+' to 'PR 31+' to stay in sync with the actual PR chain.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 15:02:39 -04:00

5.9 KiB
Raw Blame History

Modbus driver — test plan + device-quirk catalog

The Modbus TCP driver unit tests (PRs 2124) cover the protocol surface against an in-memory fake transport. They validate the codec, state machine, and function-code routing against a textbook Modbus server. That's necessary but not sufficient: real PLC populations disagree with the spec in small, device-specific ways, and a driver that passes textbook tests can still misbehave against actual equipment.

This doc is the harness-and-quirks playbook. The project it describes lives at tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/ — scaffolded in PR 30 with the simulator fixture, DL205 profile stub, and one write/read smoke test. Each confirmed DL205 quirk lands in a follow-up PR as a named test in that project.

Harness

Chosen simulator: ModbusPal (Java, scriptable). Rationale:

  • Scriptable enough to mimic device-specific behaviors (non-standard register layouts, custom exception codes, intentional response delays).
  • Runs locally, no CI dependency. Tests skip when localhost:502 (or the configured simulator endpoint) isn't reachable.
  • Free + long-maintained — physical PLC bench is unavailable in most dev environments, and renting cloud PLCs isn't worth the per-test cost.

Setup pattern (not yet codified in a script — will land alongside the integration test project):

  1. Install ModbusPal, load the per-device .xmpp profile from tests/Driver.Modbus.IntegrationTests/ModbusPal/ (TBD directory).
  2. Start the simulator listening on localhost:502 (or override via MODBUS_SIM_ENDPOINT env var).
  3. dotnet test the integration project — tests auto-skip when the endpoint is unreachable, so forgetting to start the simulator doesn't wedge CI.

Per-device quirk catalog

AutomationDirect DL205

First known target device. Quirks to document and cover with named tests (to be filled in when user validates each behavior in ModbusPal with a DL205 profile):

  • Word order for 32-bit values: pending — confirm whether DL205 uses ABCD (Modbus TCP standard) or CDAB (Siemens-style word-swap) for Int32/UInt32/Float32. Test name: DL205_Float32_word_order_is_CDAB (or ABCD, whichever proves out).
  • Register-zero access: pending — some DL205 configurations reject FC03 at register 0 with exception code 02 (illegal data address). If confirmed, the integration test suite verifies ModbusProbeOptions.ProbeAddress default of 0 triggers the rejection and operators must override; test name: DL205_FC03_at_register_0_returns_IllegalDataAddress.
  • Coil addressing base: pending — DL205 documentation sometimes uses 1-based coil addresses; verify the driver's zero-based addressing matches the physical PLC without an off-by-one adjustment.
  • Maximum registers per FC03: pending — Modbus spec caps at 125; some DL205 models enforce a lower limit (e.g., 64). Test name: DL205_FC03_beyond_max_registers_returns_IllegalDataValue.
  • Response framing under sustained load: pending — the driver's single-flight semaphore assumes the server pairs requests/responses by transaction id; at least one DL205 firmware revision is reported to drop the TxId under load. If reproduced in ModbusPal we add a retry + log-and-continue path to ModbusTcpTransport.
  • Exception code on coil write to a protected bit: pending — some DL205 setups protect internal coils; the driver should surface the PLC's exception PDU as BadNotWritable rather than BadInternalError.

User action item: as each quirk is validated in ModbusPal, replace the pending marker with the confirmed behavior and file a named test in the integration suite.

Future devices

One section per device class, same shape as DL205. Quirks that apply across multiple devices (e.g., "all AB PLCs use CDAB") can be noted in the cross-device patterns section below once we have enough data points.

Cross-device patterns

Once multiple device catalogs accumulate, quirks that recur across two or more vendors get promoted into driver defaults or opt-in options:

  • (empty — filled in as catalogs grow)

Test conventions

  • One named test per quirk. DL205_word_order_is_CDAB_for_Float32 is easier to diagnose on failure than a generic Float32_roundtrip. The DL205_ prefix makes filtering by device class trivial (--filter "DisplayName~DL205").
  • Skip with a clear SkipReason. Follow the pattern from GalaxyRepositoryLiveSmokeTests: check reachability in the fixture, capture a SkipReason string, and have each test call Assert.Skip(SkipReason) when it's set. Don't throw — skipped tests read cleanly in CI logs.
  • Use the real ModbusTcpTransport. Integration tests exercise the wire protocol end-to-end. The in-memory FakeTransport from the unit test suite is deliberately not used here — its value is speed + determinism, which doesn't help reproduce device-specific issues.
  • Don't depend on ModbusPal state between tests. Each test resets the simulator's register bank or uses a unique address range. Avoid relying on "previous test left value at register 10" setups that flake when tests run in parallel or re-order.

Next concrete PRs

  • PR 30 — Integration test project + DL205 profile scaffoldDONE. Shipped tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests with ModbusSimulatorFixture (TCP-probe, skips with a clear SkipReason when the endpoint is unreachable), DL205/DL205Profile.cs (tag map stub — one writable holding register at address 100), and DL205/DL205SmokeTests.cs (write-then-read round-trip). ModbusPal/ directory holds the README pointing at the to-be-committed DL205.xmpp profile.
  • PR 31+: one PR per confirmed DL205 quirk, landing the named test + any driver-side adjustment (e.g., retry on dropped TxId) needed to pass it. Drop the DL205.xmpp profile into ModbusPal/ alongside the first quirk PR.