docs(modbus-tests): document profile-cycling gotcha; sweep #2 was a false alarm
v2-ci / build (pull_request) Successful in 4m13s
v2-ci / unit-tests (pull_request) Failing after 9m41s

The 2026-07 integration sweep flagged the Modbus non-standard profiles as
8F/5F/3F/8F and guessed 'pymodbus 4.x drift'. Re-investigation shows that was
wrong on both counts:

- The image runs the PINNED pymodbus 3.13.0 (verified via import pymodbus;
  __version__). The '...removed in v4' log is 3.13.0's forward-deprecation
  warning, misread as running 4.x.
- The failures were a fixture-cycling artifact: each profile is a separate
  profile-gated compose service sharing :5020, and a plain 'docker compose down'
  (no --profile) does NOT stop the running profile container. It kept holding
  :5020; the next '--profile up' container silently failed to bind (compose
  still says 'Started'), so every later profile run hit the STALE sim. Proven by
  a raw Modbus probe: the 'mitsubishi' sim served DL205's 0xCAFE at HR0.

After force-removing all modbus containers and bringing up each profile cleanly
(verifying it actually published :5020), all four pass: dl205 16/16,
mitsubishi 13/13, s7_1500 10/10, exception_injection 17/17. No OtOpcUa or test
code change needed.

Added a profile-cycling gotcha + reliable-cycle recipe to Docker/README.md and
corrected the sweep record. Integration-sweep follow-up #2.
This commit is contained in:
Joseph Doherty
2026-07-15 05:12:52 -04:00
parent 271562e00e
commit 00f39c849e
2 changed files with 55 additions and 11 deletions
@@ -48,6 +48,31 @@ service + starting another. The integration tests discriminate by a
separate `MODBUS_SIM_PROFILE` env var so they skip correctly when the
wrong profile is live.
> **⚠️ Profile-cycling gotcha (bit us in the 2026-07 sweep — see
> `archreview/plans/INTEGRATION-SWEEP-STATUS.md`).** Each profile is a
> *separate compose service* selected by `--profile`, so a plain
> `docker compose down` (no `--profile`) does **not** stop the
> currently-running profile container — it keeps holding `:5020`. Start
> another profile and the new container silently fails to bind the port
> (compose still reports "Started"); every test then hits the **stale**
> profile's data and fails with wrong values / Illegal-Data-Address. The
> symptom is deceptive: reads succeed but return the previous profile's
> seed (e.g. Mitsubishi's `D0` reads DL205's `0xCAFE`). Always tear the
> old container down **by name / with its profile** before bringing up the
> next, and verify the new container actually published `:5020`:
>
> ```bash
> # reliable cycle (force-remove ALL modbus containers, then bring one up)
> docker rm -f $(docker ps -aq --filter name=otopcua-modbus --filter name=otopcua-pymodbus)
> docker compose --profile <profile> up -d --force-recreate
> docker ps --filter name=otopcua --format '{{.Names}} {{.Ports}}' # MUST show 0.0.0.0:5020->5020
> ```
>
> The `exception_injection` service uses a different container name
> (`otopcua-modbus-exception-injector`, not `otopcua-pymodbus-*`), so a
> name-filtered `docker rm` that only matches `otopcua-pymodbus` will miss
> it — the `--force-recreate` above covers that case.
### Profile coverage matrix
The two general-purpose profiles cover disjoint test sets. A full pass