Files
lmxopcua/docs/drivers/FOCAS-Test-Fixture.md
Joseph Doherty 969b0847a1 docs: update path references for module-folder reorganization
Rewrite src/ and tests/ project paths in docs, CLAUDE.md, README.md, and
test-fixture READMEs to the new module-folder layout (Core/Server/Drivers/
Client/Tooling). References to retired v1 projects (Galaxy.Host/Proxy/Shared,
the legacy monolithic test projects) are left untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 02:10:29 -04:00

7.2 KiB

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 client. Integration tests run the managed driver end-to-end against the vendored focas-mock Python server (at tests/.../Docker/focas-mock/) 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) but the mock's wire responder covers every FOCAS call OtOpcUa issues.

What the fixture covers

Unit layer (no container required)

tests/Drivers/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.
  • FocasReadWriteTests — read / write contract against the fake, FOCAS native status → OPC UA StatusCode mapping
  • FocasScaffoldingTestsIDriver 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/Drivers/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 <compose-profile> for the Docker mock
  • -Series <csv> for per-series matrix mode
  • -HandleLeakCycles <N> 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

# 1) Start the mock on a chosen profile.
docker compose -f tests/Drivers/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/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/

Or use scripts/integration/run-focas.ps1 which wraps compose up / test / compose down and accepts -Profile <name> to pin a per-series run.

Key fixture / config files

  • tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/ — vendored focas-mock Python source + Dockerfile
  • tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/docker-compose.yml — per-series compose profiles
  • tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/FocasSimFixture.cs — collection fixture + mock admin API client
  • tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/FixedTreePopulatesTests.cs — fixed-tree end-to-end tests
  • tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendTests.cs — pure-wire-backend end-to-end tests
  • tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FakeFocasClient.cs — in-process unit fake
  • src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/WireFocasClient.cs — the managed wire client backing production deployments
  • src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasCapabilityMatrix.cs — per-series range validator
  • docs/v2/focas-version-matrix.md — authoritative range reference