# 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. See [`docs/v2/focas-version-matrix.md`](../v2/focas-version-matrix.md)
  for 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 mapping
- `FocasScaffoldingTests` — `IDriver` lifecycle + multi-device routing
- `FocasPmcBitRmwTests` — PMC bit read-modify-write synchronization (per-byte
  `SemaphoreSlim`, mirrors the AB / Modbus pattern from #181)
- `FwlibNativeHelperTests` — `Focas32.dll` → `Fwlib32.dll` bridge 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`](../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.Shared` carries MessagePack IPC contracts
- `Driver.FOCAS.Host` (.NET 4.8 x86 Windows service via NSSM) accepts
  a connection on a strictly-ACL'd named pipe + dispatches frames to
  an `IFocasBackend`
- `Driver.FOCAS.Ipc.IpcFocasClient` implements the `IFocasClient` DI
  seam by forwarding over IPC — swap the DI registration and the
  driver runs Tier-C with zero other changes
- `Driver.FOCAS.Supervisor.FocasHostSupervisor` owns the spawn +
  heartbeat + respawn + 3-in-5min crash-loop breaker + sticky alert
- `Driver.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) |

## Alarm history (`cnc_rdalmhistry`) — issue #267, plan PR F3-a

`FocasAlarmProjection` ships two modes:

- **`ActiveOnly`** (default) — surfaces only currently-active alarms.
  No history poll. Same back-compat shape every prior FOCAS deployment used.
- **`ActivePlusHistory`** — additionally polls `cnc_rdalmhistry` on connect
  + on the configured cadence (`HistoryPollInterval`, default 5 min). Each
  unseen entry fires an `OnAlarmEvent` with `SourceTimestampUtc` set from
  the CNC's reported timestamp, not Now.

Unit-test coverage in `FocasAlarmProjectionTests`:

- mode `ActiveOnly` — no `ReadAlarmHistoryAsync` call ever issued
- mode `ActivePlusHistory` — first poll fires on subscribe (== "on connect")
- dedup — same `(OccurrenceTime, AlarmNumber, AlarmType)` triple across two
  polls only emits once
- distinct entries with different timestamps each emit separately
- same alarm number / different type still emits both (type is part of the
  dedup key)
- `OccurrenceTime` is the wire timestamp (round-trips a year-old stamp
  without bleeding into Now)
- `HistoryDepth` clamp — user-supplied 500 collapses to 250 on the wire;
  zero / negative falls back to the 100 default
- `FocasAlarmHistoryDecoder` — round-trips through `Encode` / `Decode` and
  pins the simulator command id at `0x0F1A`

Future integration coverage (not yet shipped — no FOCAS integration test
project exists):

- a focas-mock with a per-profile ring buffer and `mock_patch_alarmhistory`
  admin endpoint will let `cnc_rdalmhistry` round-trip end-to-end through
  the wire protocol
- `FocasSimFixture.SeedAlarmHistoryAsync` will let series tests prime canned
  history without per-test JSON

## Follow-up candidates

1. **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.
2. **Lab rig** — used FANUC 0i-F simulator controller (or a retired machine
   tool) on a dedicated network; only path that covers real CNC behavior.
3. **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 implementing `IFocasClient`
- `tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FocasCapabilityMatrixTests.cs`
  — parameterized theories locking the per-series matrix
- `src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs` — ctor takes
  `IFocasClientFactory`
- `src/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 reference
- `docs/v2/implementation/focas-isolation-plan.md` — Tier-C isolation
  plan (task #220)
- `docs/v2/driver-stability.md` — Tier C scope + process-isolation rationale
