# FOCAS test fixture Coverage map + gap inventory for the FANUC FOCAS2 CNC driver. **Status:** as of 2026-04-24, OtOpcUa speaks FOCAS2 directly over TCP via the pure-managed [`Focas.Wire`](https://github.com/Ladder99/focas-mock/tree/main/dotnet/Focas.Wire) client. Integration tests run the managed driver end-to-end against the vendored `focas-mock` Python server (at [`tests/.../Docker/focas-mock/`](../../tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/VENDORED.md)) whose native FOCAS Ethernet responder is verified PDU-by-PDU against the real `fwlibe64.dll`. No shim DLL, no P/Invoke, no licensed binary — any dev box or CI runner with Docker can run the full fixture end-to-end. Hardware validation against a real CNC is still useful to catch series-specific firmware quirks (see [§ Hardware-only gaps](#hardware-only-gaps)) but the mock's wire responder covers every FOCAS call OtOpcUa issues. ## What the fixture covers ### Unit layer (no container required) `tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/` uses `FakeFocasClient` injected via `IFocasClientFactory`: - `FocasCapabilityTests` — data-type mapping (PMC bit / byte / word / long / float / double, macro variable types, parameter types) - `FocasCapabilityMatrixTests` — per-CNC-series range validation across 16i / 0i-D / 0i-F / 30i / Power Motion, 46 theory cases locking every documented range boundary. See [`docs/v2/focas-version-matrix.md`](../v2/focas-version-matrix.md). - `FocasReadWriteTests` — read / write contract against the fake, FOCAS native status → OPC UA `StatusCode` mapping - `FocasScaffoldingTests` — `IDriver` lifecycle + multi-device routing - `FocasPmcBitRmwTests` — PMC bit read-modify-write synchronisation - `FocasAlarmProjectionTests` — raise / clear diffing, severity mapping - `FocasHandleRecycleTests` — proactive session recycle cadence Capability surfaces whose contract is verified: `IDriver`, `IReadable`, `ITagDiscovery`, `ISubscribable`, `IHostConnectivityProbe`, `IPerCallHostResolver`, `IAlarmSource`. `IWritable` intentionally returns `BadNotWritable` — OtOpcUa is read-only against FOCAS. Pre-flight validation runs in `FocasDriver.InitializeAsync` — configs referencing out-of-range addresses fail at load time with a diagnostic message naming the CNC series + documented limit. ### Integration layer (mock only, no CNC, no shim) `tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/` drives the managed `FocasDriver` end-to-end. A single gate: **Docker compose up** — tests skip when the TCP probe to `localhost:8193` fails with a pointer to the compose command. When the mock is up, `WireFocasClient` dials it over TCP exactly like a real CNC, and the mock's native FOCAS Ethernet responder replies with binary PDUs against the documented command IDs. Covered assertions: - Session open / close (`cnc_allclibhndl3` + `cnc_freelibhndl`) - Parameter read-back after `mock_patch` seed → `cnc_rdparam` - Macro read-back after seed → `cnc_rdmacro` (scaled-decimal translation verified) - PMC range read after seed → `pmc_rdpmcrng` - `IAlarmSource` raise + clear transitions after `mock_patch` alarm-list changes → `cnc_rdalmmsg2` - Fixed-tree bootstrap: identity / axes / spindle / program / timers / servo meters populate via `cnc_sysinfo`, `cnc_rdaxisname`, `cnc_rdspdlname`, `cnc_rddynamic2`, `cnc_exeprgname2`, `cnc_rdblkcount`, `cnc_rdopmode`, `cnc_rdsvmeter`, `cnc_rdspload`, `cnc_rdspmaxrpm`, `cnc_rdtimer` - Per-series profile selection via `mock_load_profile` — tests can pin one profile and assert series-gated capability suppression ### E2E script (CLI) `scripts/e2e/test-focas.ps1` drives the Client.CLI against a running OtOpcUa server. Accepts: - `-CncHost` / `-CncPort` for real hardware - `-ProfileName ` for the Docker mock - `-Series ` for per-series matrix mode - `-HandleLeakCycles ` for handle-leak stress ## Hardware-only gaps The mock has parity with the real `fwlibe64.dll` for the calls OtOpcUa issues, but a real CNC can still surface things a reference implementation can't: 1. **Series-specific firmware quirks** — alarm retention across power cycles, parameter range enforcement by the CNC (not the driver), MTB custom screens, series-specific option bits. Each series has documented behaviours that only a bench CNC exercises. 2. **Wire-level stress** — burst reads, concurrent device writes, network-partition recovery under load. The mock handles these correctly but production behaviour is the source of truth. 3. **Transient operational states** — alarm floods, emergency-stop transitions, power-on resync. These are easy to stub but hard to cover comprehensively in the mock. Track the close-out under task #54 (live-CNC smoke). When the rig lands, the hardware path runs alongside the mock path; the mock stays as the CI quality gate. ## When to trust each layer | Question | Unit | Integration (mock) | Real CNC | | --- | :---: | :---: | :---: | | "Does PMC address `R100.3` route to the right bit?" | ✅ | ✅ | ✅ | | "Does the Fanuc status → OPC UA StatusCode map cover every documented code?" | ✅ (contract) | ✅ | ✅ | | "Does `FocasDriver.ReadAsync` correctly decode a seeded parameter?" | no | ✅ | ✅ | | "Does `IAlarmSource` fire raise + clear events?" | ✅ (Fake) | ✅ (wire) | ✅ | | "Does a real read against a 30i Series return correct bytes?" | no | ✅ (via profile) | ✅ (required) | | "Do series-specific firmware quirks behave as documented?" | no | no | ✅ (required) | | "Does the driver survive real network partitions?" | no | partial (socket kill) | ✅ (required) | ## Running the integration fixture ```powershell # 1) Start the mock on a chosen profile. docker compose -f tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/docker-compose.yml up -d # 2) Run the tests. No shim build, no DLL copy — the driver dials the mock directly. dotnet test tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/ ``` Or use `scripts/integration/run-focas.ps1` which wraps compose up / test / compose down and accepts `-Profile ` to pin a per-series run. ## Key fixture / config files - `tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/` — vendored `focas-mock` Python source + Dockerfile - `tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/docker-compose.yml` — per-series compose profiles - `tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/FocasSimFixture.cs` — collection fixture + mock admin API client - `tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/FixedTreePopulatesTests.cs` — fixed-tree end-to-end tests - `tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendTests.cs` — pure-wire-backend end-to-end tests - `tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FakeFocasClient.cs` — in-process unit fake - `src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/WireFocasClient.cs` — the managed wire client backing production deployments - `src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasCapabilityMatrix.cs` — per-series range validator - `docs/v2/focas-version-matrix.md` — authoritative range reference