Production IHostProcessLauncher (ProcessHostLauncher.cs): Process.Start spawns OtOpcUa.Driver.FOCAS.Host.exe with OTOPCUA_FOCAS_PIPE / OTOPCUA_ALLOWED_SID / OTOPCUA_FOCAS_SECRET / OTOPCUA_FOCAS_BACKEND in the environment (supervisor-owned, never disk), polls FocasIpcClient.ConnectAsync at 250ms cadence until the pipe is up or the Host exits or the ConnectTimeout deadline passes, then wraps the connected client in an IpcFocasClient. TerminateAsync kills the entire process tree + disposes the IPC stream. ProcessHostLauncherOptions carries HostExePath + PipeName + AllowedSid plus optional SharedSecret (auto-generated from a GUID when omitted so install scripts don't have to), Arguments, Backend (fwlib32/fake/unconfigured default-unconfigured), ConnectTimeout (15s), and Series for CNC pre-flight. Post-mortem MMF (Host/Stability/PostMortemMmf.cs + Proxy/Supervisor/PostMortemReader.cs): ring-buffer of the last ~1000 IPC operations written by the Host into a memory-mapped file. On a Host crash the supervisor reads the MMF — which survives process death — to see what was in flight. File format: 16-byte header [magic 'OFPC' (0x4F465043) | version | capacity | writeIndex] + N × 256-byte entries [8-byte UTC unix ms | 8-byte opKind | 240-byte UTF-8 message + null terminator]. Magic distinguishes FOCAS MMFs from the Galaxy MMFs that ship the same format shape. Writer is single-producer (Host) with a lock_writeGate; reader is multi-consumer (Proxy + any diagnostic tool) using a separate MemoryMappedFile handle. NSSM install wrappers (scripts/install/Install-FocasHost.ps1 + Uninstall-FocasHost.ps1): idempotent service registration for OtOpcUaFocasHost. Resolves SID from the ServiceAccount, generates a fresh shared secret per install if not supplied, stages OTOPCUA_FOCAS_PIPE/SID/SECRET/BACKEND in AppEnvironmentExtra so they never hit disk, rotates 10MB stdout/stderr logs under %ProgramData%\OtOpcUa, DependOnService=OtOpcUa so startup order is deterministic. Backend selector defaults to unconfigured so a fresh install doesn't accidentally load a half-configured Fwlib32.dll on first start. Tests (7 new, 2 files): PostMortemMmfTests.cs in FOCAS.Host.Tests — round-trip write+read preserves order + content, ring-buffer wraps at capacity (writes 10 entries to a 3-slot buffer, asserts only op-7/8/9 survive in FIFO order), message truncation at the 240-byte cap is null-terminated + non-overflowing, reopening an existing file preserves entries. PostMortemReaderCompatibilityTests.cs in FOCAS.Tests — hand-writes a file in the exact host format (magic/entry layout) + asserts the Proxy reader decodes with correct ring-walk ordering when writeIndex != 0, empty-return on missing file + magic mismatch. Keeps the two codebases in format-lockstep without the net10 test project referencing the net48 Host assembly. Docs updated: docs/v2/implementation/focas-isolation-plan.md promoted from DRAFT to PRs A-E shipped status with per-PR citations + post-ship test counts (189 + 24 + 13 = 226 FOCAS-family tests green). docs/drivers/FOCAS-Test-Fixture.md §5 updated from "architecture scoped but not implemented" to listing the shipped components with the FwlibHostedBackend gap explicitly labeled as hardware-gated. Install-FocasHost.ps1 documents the OTOPCUA_FOCAS_BACKEND selector + points at docs/v2/focas-deployment.md for Fwlib32.dll licensing. What ISN'T in this PR: (1) the real FwlibHostedBackend implementing IFocasBackend with the P/Invoke — requires either a CNC on the bench or a licensed FANUC developer kit to validate, tracked under #220 as a single follow-up task; (2) Admin /hosts surface integration for FOCAS runtime status — Galaxy Tier-C already has the shape, FOCAS can slot in when someone wires ObservedCrashes/StickyAlertActive/BackoffAttempt to the FleetStatusHub; (3) a full integration test that actually spawns a real FOCAS Host process — ProcessHostLauncher is tested via its contract + the MMF is tested via round-trip, but no test spins up the real exe (the Galaxy Tier-C tests do this, but the FOCAS equivalent adds no new coverage over what's already in place). Total FOCAS-family tests green after this PR: 189 driver + 24 Shared + 13 Host = 226. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6.2 KiB
FOCAS test fixture
Coverage map + gap inventory for the FANUC FOCAS2 CNC driver.
TL;DR: there is no integration fixture. Every test uses a
FakeFocasClient injected via IFocasClientFactory. Fanuc's FOCAS library
(Fwlib32.dll) is closed-source proprietary with no public simulator;
CNC-side behavior is trusted from field deployments.
What the fixture is
Nothing at the integration layer.
tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/ is unit-only. The driver ships
as Tier C (process-isolated) per docs/v2/driver-stability.md because the
FANUC DLL has known crash modes; tests can't replicate those in-process.
What it actually covers (unit only)
FocasCapabilityTests— data-type mapping (PMC bit / word / float, macro variable types, parameter types)FocasCapabilityMatrixTests— per-CNC-series range validation (macro / parameter / PMC letter + number) across 16i / 0i-D / 0i-F / 30i / PowerMotion. Seedocs/v2/focas-version-matrix.mdfor the authoritative matrix. 46 theory cases lock every documented range boundary — widening a range without updating the doc fails a test.FocasReadWriteTests— read + write against the fake, FOCAS native status → OPC UA StatusCode mappingFocasScaffoldingTests—IDriverlifecycle + multi-device routingFocasPmcBitRmwTests— PMC bit read-modify-write synchronization (per-byteSemaphoreSlim, mirrors the AB / Modbus pattern from #181)FwlibNativeHelperTests—Focas32.dll→Fwlib32.dllbridge validation- P/Invoke signature validation
Capability surfaces whose contract is verified: IDriver, IReadable,
IWritable, ITagDiscovery, ISubscribable, IHostConnectivityProbe,
IPerCallHostResolver.
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. This closes the
cheap half of the hardware-free stability gap; Tier-C process
isolation (task #220) closes the expensive half — see
docs/v2/implementation/focas-isolation-plan.md.
What it does NOT cover
1. FOCAS wire traffic
No FOCAS TCP frame is sent. Fwlib32.dll's TCP-to-FANUC-gateway exchange is
closed-source; the driver trusts the P/Invoke layer per #193. Real CNC
correctness is trusted from field deployments.
2. Alarm / parameter-change callbacks
FOCAS has no push model — the driver polls via the shared PollGroupEngine.
There are no CNC-initiated callbacks to test; the absence is by design.
3. Macro / ladder variable types
FANUC has CNC-specific extensions (macro variables #100-#999, system
variables #1000-#5000, PMC timers / counters / keep-relays) whose
per-address semantics differ across 0i-F / 30i / 31i / 32i Series. Driver
covers the common address shapes; per-model quirks are not stressed.
4. Model-specific behavior
- Alarm retention across power cycles (model-specific CNC behavior)
- Parameter range enforcement (CNC rejects out-of-range writes)
- MTB (machine tool builder) custom screens that expose non-standard data
5. Tier-C process isolation — architecture shipped, Fwlib32 integration hardware-gated
The Tier-C architecture is now in place as of PRs #169–#173 (FOCAS PR A–E, task #220):
Driver.FOCAS.Sharedcarries MessagePack IPC contractsDriver.FOCAS.Host(.NET 4.8 x86 Windows service via NSSM) accepts a connection on a strictly-ACL'd named pipe + dispatches frames to anIFocasBackendDriver.FOCAS.Ipc.IpcFocasClientimplements theIFocasClientDI seam by forwarding over IPC — swap the DI registration and the driver runs Tier-C with zero other changesDriver.FOCAS.Supervisor.FocasHostSupervisorowns the spawn + heartbeat + respawn + 3-in-5min crash-loop breaker + sticky alertDriver.FOCAS.Host.Stability.PostMortemMmf↔Driver.FOCAS.Supervisor.PostMortemReader— ring-buffer of the last ~1000 IPC operations survives a Host crash
The one remaining gap is the production FwlibHostedBackend: an
IFocasBackend implementation that wraps the licensed
Fwlib32.dll P/Invoke. That's hardware-gated on task #222 — we
need a CNC on the bench (or the licensed FANUC developer kit DLL
with a test harness) to validate it. Until then, the Host ships
FakeFocasBackend + UnconfiguredFocasBackend. Setting
OTOPCUA_FOCAS_BACKEND=fake lets operators smoke-test the whole
Tier-C pipeline end-to-end without any CNC.
When to trust FOCAS tests, when to reach for a rig
| Question | Unit tests | Real CNC |
|---|---|---|
"Does PMC address R100.3 route to the right bit?" |
yes | yes |
| "Does the FANUC status → OPC UA StatusCode map cover every documented code?" | yes (contract) | yes |
| "Does a real read against a 30i Series return correct bytes?" | no | yes (required) |
"Does Fwlib32.dll crash on concurrent reads?" |
no | yes (stress) |
| "Do macro variables round-trip across power cycles?" | no | yes (required) |
Follow-up candidates
- Nothing public — Fanuc's FOCAS Developer Kit ships an emulator DLL but it's under NDA + tied to licensed dev-kit installations; can't redistribute for CI.
- Lab rig — used FANUC 0i-F simulator controller (or a retired machine tool) on a dedicated network; only path that covers real CNC behavior.
- Process isolation first — before trusting FOCAS in production at scale, shipping the Tier-C out-of-process Host architecture (similar to Galaxy) is higher value than a CI simulator.
Key fixture / config files
tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FakeFocasClient.cs— in-process fake implementingIFocasClienttests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FocasCapabilityMatrixTests.cs— parameterized theories locking the per-series matrixsrc/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs— ctor takesIFocasClientFactorysrc/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasCapabilityMatrix.cs— per-CNC-series range validator (the matrix the doc describes)docs/v2/focas-version-matrix.md— authoritative range referencedocs/v2/implementation/focas-isolation-plan.md— Tier-C isolation plan (task #220)docs/v2/driver-stability.md— Tier C scope + process-isolation rationale