fix(abcip): unblock the controller-browse gate, and fix the decoder it exposed (#468)
The AbCip controller browse could never be verified end to end: libplctag's ab_server answers CIP service 0x55 (Get Instance Attribute List) on the Symbol Object class with "unsupported", so the `@tags` walk had no backend. #468's own definition of done offered a sim that serves `@tags` as an alternative to real hardware; this is that. The fixture now applies a vendored patch teaching ab_server to serve the Symbol Object listing, with partial-transfer paging. Correctness is checked against libplctag's own `list_tags_logix` rather than against our decoder — our decoder is the thing under test, so its agreement would prove nothing. The clone is also pinned to a concrete tag: the Dockerfile called `--branch release` "pinned" while tracking a branch that moves. That immediately exposed a real defect. CipSymbolObjectDecoder advanced to an even byte boundary after each symbol name, but entries are packed with no padding — libplctag walks them by sizeof(entry) + string_len, and a captured reply confirms it. Any odd-length name therefore desynchronised every entry behind it, and odd-length names are ordinary. In the live browse below, `Motor_Run` is 9 bytes and first on the wire, so the old decoder would have shown that one tag and silently dropped the other eight. Every existing multi-entry test passed throughout, because the test builder padded exactly as the decoder did: the two agreed with each other and neither agreed with a controller. The builder no longer pads, and the layout is pinned by a captured reply (CipSymbolObjectWireCaptureTests) instead of by a second statement of the assumption. Restoring the alignment step reddens exactly the three new tests and none of the 29 others. Live gate on docker-dev, all three of #468's criteria: - populated tree — all nine seeded tags render as leaves, in wire order - refresh re-captures - leaf commit — Pump_1_Speed + TestDINT saved with their CIP tag paths The gate also surfaced two things it was not asked about: browse-commit drops the discovered data type, so a browsed REAL commits as a DINT and the driver decodes it as one (filed as #536), and Test Connect fails against this fixture regardless of the device because it probes `@raw_cpu_type` (documented). TwinCAT and FOCAS still have no browsable backend; #468's AbCip half is closed, theirs is not. Claude-Session: https://claude.ai/code/session_015p7wGqy3YpZNCpDzTpGMKo
This commit is contained in:
@@ -1105,12 +1105,43 @@ Equipment-kind namespace and bound to an equipment; a tag was added on the Tags-
|
||||
- ⚠️ **Not verifiable on this fixture** — a *populated* tree render + leaf-commit. The libplctag `ab_server`
|
||||
test sim returns `ErrorUnsupported` for controller symbol enumeration (it implements tag read/write, not the
|
||||
`@tags` Symbol Object walk). This is a **fixture limitation, not a code defect** — the binding is proven alive.
|
||||
**CLOSED 2026-08-01 — see the second gate below.**
|
||||
|
||||
### Follow-ups discovered during the gate
|
||||
## Second live-gate result (Gitea #468 — docker-dev, 2026-08-01)
|
||||
|
||||
1. **Full tree-render verification is fixture-blocked.** `ab_server` doesn't implement the `@tags` controller
|
||||
walk. To live-verify a populated tree + `TagConfig.FullName` leaf commit, point the universal browser at a
|
||||
symbol-enumeration-capable AB backend (a real ControlLogix, or a sim that serves `@tags`).
|
||||
The fixture gap was closed by teaching `ab_server` to serve the Symbol Object listing
|
||||
(`tests/Drivers/…AbCip.IntegrationTests/Docker/patches/`), which was the option #468's own
|
||||
definition of done offered alongside real hardware. All three criteria ran green against the
|
||||
patched fixture on `10.100.0.35:44818`:
|
||||
|
||||
- ✅ **Populated controller-tag tree** — the AbCip browse rendered all nine seeded tags as leaves
|
||||
under `AbCip → Device1 → Discovered`, in wire order: `Motor_Run`, `Line1_Count`, `Pump_1_Speed`,
|
||||
`TestArray`, `TestString`, `TestSINT`, `TestBOOL`, `TestREAL`, `TestDINT`.
|
||||
- ✅ **Refresh re-captures** — Refresh collapsed to the root and re-expansion returned the same
|
||||
nine leaves from a fresh capture.
|
||||
- ✅ **Leaf commit round-trip** — selecting `Pump_1_Speed` + `TestDINT` and committing wrote both
|
||||
into the raw tree with the CIP tag path in `TagConfig` (`{"tagPath":"TestDINT",…}`) and saved.
|
||||
|
||||
**The gate earned its keep — it found a defect no unit test could have.** `CipSymbolObjectDecoder`
|
||||
advanced to an even byte boundary after each symbol name, but Symbol Object entries are packed with
|
||||
no padding. `Motor_Run` is 9 bytes, so under the old decoder the browse above would have shown
|
||||
**`Motor_Run` alone** and silently dropped the other eight. Every existing multi-entry test passed,
|
||||
because the test builder padded exactly as the decoder did — the pair agreed with each other and
|
||||
neither agreed with a controller. Fixed, with the layout now pinned by a captured reply
|
||||
(`CipSymbolObjectWireCaptureTests`) rather than by a restatement of the assumption.
|
||||
|
||||
### Follow-ups discovered during the second gate
|
||||
|
||||
1. **Browse-commit discards the discovered data type** — a browsed REAL commits with
|
||||
`"dataType":"DInt"` in its `TagConfig`, and the driver decodes off that field rather than the
|
||||
row's `DataType`. Filed as **Gitea #536**; affects AbCip and (by inspection) TwinCAT.
|
||||
2. **Test Connect fails against this fixture regardless of the device** — the probe falls back to
|
||||
`@raw_cpu_type` when no tags are authored, which `ab_server` does not implement. Fixture
|
||||
limitation, documented in the fixture README; browse is unaffected.
|
||||
|
||||
### Follow-ups discovered during the first gate
|
||||
|
||||
1. ~~**Full tree-render verification is fixture-blocked.**~~ **CLOSED** by the second gate above.
|
||||
2. **Driver-page address pickers pass no live config.** `AbCipDriverPage`/`TwinCATDriverPage`/`FOCASDriverPage`
|
||||
host the same `*AddressPickerBody` but do **not** pass `DriverType`/`GetConfigJson`, so their (now-visible)
|
||||
Browse button would capture against `"{}"` (no device). Out of Task 14's scope (which listed only the
|
||||
|
||||
Reference in New Issue
Block a user