Files
lmxopcua/scripts/validation/opcua-lifecycle-memory/windows/analyze.py
T
Joseph Doherty aa60b3e34e
v2-ci / unit-tests (push) Has been cancelled
test(soak): #799 place the soak's UNS folder after #808; Windows x64 port of the lifecycle soak harness
Acceptance runs for #799 (owner scope 2026-11-12: Windows x64 + Linux) on current master.

Fixture: since #808 an UNS folder the authority snapshot does not place is denied under enforcement (fail
closed). The canonical soak creates its own "SoakEquipment" folder but never placed it, so on master the very
first cycle's UNS monitored item came back bad (retained failed Linux launch on b61f3e05, cycle 1). The rig
gains AdditionalFolderScopes (merged into FolderScopes by Enforce) and the soak places its folder there, as a
deployed generation does - the same fixture gap #888 fixed for the SDK matrix. No server authorization code,
cap, baseline or canonical operation changed; the soak test bodies are otherwise byte-identical to b18f9723.

Windows port (test/harness only, opt-in):
- DescriptorCountsAsync gains a Windows branch: total = every handle in the process handle table, PKI = disk
  file/directory handles whose GetFinalPathNameByHandle path lies under a canonicalised PKI root; dedicated
  thread under the same 5 s bound as the macOS lsof probe.
- Descriptor controls enabled on Windows (symlink alias; timeout control via cmd.exe + ping).
- LateSlopeRun.ValidateRequest accepts Linux ARM64 or Windows x64; duration/runtime/leak tracking unchanged.
- late-slope/analyze.py: expected architecture is a module constant (Linux default Arm64 unchanged).
- windows/: prepare.py (win-x64 self-contained 10.0.14 bundle + manifest), launch.ps1 (container.sh
  equivalent: bundle re-verification, 2/2 controls first, PID/apphost/coreclr verification, external
  sampler, 7440 s outer deadline), analyze.py (Linux row/window logic, X64), README with every adaptation.

Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
2026-11-12 12:42:16 -05:00

73 lines
3.9 KiB
Python

#!/usr/bin/env python3
"""Validate a recorded Windows x64 late-slope run (lmxopcua#799) with the Linux analyzer's own row/window logic.
The row ledger, cold cycle-3 caps, first-rejection check, fixed windows and least-squares slopes come unchanged from
../late-slope/analyze.py (ARCHITECTURE switched to X64). Only the run-envelope checks differ: there is no container, so
the launcher's PowerShell receipts (run-receipt.json, target-runtime.txt) take the place of the Docker receipts.
"""
import argparse
import json
from pathlib import Path
import re
import sys
sys.dont_write_bytecode = True
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "late-slope"))
import analyze as core # noqa: E402
core.ARCHITECTURE = "X64"
APP = core.APP
def read_text(path):
return path.read_text(encoding="utf-8-sig")
def analyze(run, bundle_receipt, source_revision):
bundle = json.loads(read_text(bundle_receipt))
core.require(bundle["sourceRevision"] == source_revision and bundle["rid"] == "win-x64"
and bundle["runtimeVersion"] == core.RUNTIME and bundle["limits"] == core.BUDGETS, "Wrong Windows bundle")
rows = [json.loads(line) for line in read_text(run / "resources.jsonl").splitlines() if line.strip()]
start, samples, completions, rejection, result = core.validate_rows(iter(rows), source_revision)
mapping = dict(line.split("=", 1) for line in read_text(run / "target-runtime.txt").splitlines() if "=" in line)
core.require(mapping.get("targetPid") == str(start["processId"])
and mapping.get("actualExecutable", "").lower().endswith(f"\\app\\{APP}.exe".lower())
and mapping.get("mappedCoreClr", "").lower().endswith("\\app\\coreclr.dll")
and mapping.get("coreClrSha256") == bundle["coreClrSha256"],
"Published Windows apphost or mapped CLR was not verified for the recorded PID")
test_exit = int(read_text(run / "test-exit-status.txt").strip())
core.require((test_exit != 0) == bool(rejection), "Original test exit was not preserved")
controls = core.check_trx(run / "results/descriptor-controls.trx", core.DESCRIPTOR_TESTS)
workload = core.check_trx(run / "results/late-slope.trx", {core.TEST_NAME}, rejection and rejection["assertionMessage"])
receipt = json.loads(read_text(run / "run-receipt.json"))
core.require(receipt["targetVerified"] is True and receipt["testExitStatus"] == test_exit
and receipt["leftoverTaskProcesses"] == 0, "Launcher did not verify the target or left processes behind")
wall = receipt["wallSeconds"]
core.require(start["startupSeconds"] + result["cycleSeconds"] + result["shutdownSeconds"] <= wall,
"Startup, cycle and shutdown phases exceed measured wall time")
return {"protocol": core.PROTOCOL, "platform": "windows-x64", "sourceRevision": source_revision,
"bundleCoreClrSha256": bundle["coreClrSha256"], "diagnosticOutcome": result, "firstColdRejection": rejection,
"descriptorTests": controls, "experimentTests": workload, "testExitStatus": test_exit, "wallSeconds": wall,
**core.describe(samples, completions)}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--run", type=Path, required=True)
parser.add_argument("--bundle-receipt", type=Path, required=True)
parser.add_argument("--source-revision", required=True)
options = parser.parse_args()
core.require(re.fullmatch(r"[0-9a-f]{40}", options.source_revision), "Invalid revision")
try:
print(json.dumps(analyze(options.run.resolve(strict=True), options.bundle_receipt, options.source_revision),
indent=2, allow_nan=False))
except (OSError, ValueError, KeyError, TypeError) as error:
print(f"Incomplete Windows late-slope evidence: {error}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())