From 2666a598aefabe595e79e57e95f06ff35ff1a25e Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 21 Apr 2026 10:55:15 -0400 Subject: [PATCH] =?UTF-8?q?Task=20#253=20follow-up=20=E2=80=94=20fix=20tes?= =?UTF-8?q?t-all.ps1=20StrictMode=20crash=20on=20missing=20JSON=20keys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Running `test-all.ps1` end-to-end with a partial sidecar (only modbus/ abcip/s7 populated, no focas/twincat/phase7) crashed: [FAIL] modbus runner crashed: The property 'opcUaUrl' cannot be found on this object. Verify that the property exists. Root cause: `_common.ps1` sets `Set-StrictMode -Version 3.0`, which turns missing-property access on PSCustomObject into a throw. Every `$config.. ?? $default` and `if ($config.)` check is therefore unsafe against a normal JSON where optional fields are omitted. Fix: switch to `ConvertFrom-Json -AsHashtable` and add a `Get-Or` helper. Hashtables tolerate `.ContainsKey()` / indexer access even under StrictMode, so the per-driver sections now read: $modbus = Get-Or $config "modbus" if ($modbus) { ... -OpcUaUrl (Get-Or $modbus "opcUaUrl" $OpcUaUrl) ... } Verified end-to-end with live docker-compose fixtures: - Modbus / AB CIP / S7 each run to completion, report 2/5 PASS (the driver-only stages) and FAIL the 3 server-bridge stages (expected — server-side factory wiring is blocked on #209). - The FINAL MATRIX header renders cleanly with SKIP rows for the drivers not present in the sidecar + FAIL rows for the present ones. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/e2e/test-all.ps1 | 103 +++++++++++++++++++++++---------------- 1 file changed, 61 insertions(+), 42 deletions(-) diff --git a/scripts/e2e/test-all.ps1 b/scripts/e2e/test-all.ps1 index 589ee80..979d332 100644 --- a/scripts/e2e/test-all.ps1 +++ b/scripts/e2e/test-all.ps1 @@ -32,9 +32,20 @@ if (-not (Test-Path $ConfigFile)) { exit 2 } -$config = Get-Content $ConfigFile -Raw | ConvertFrom-Json +# -AsHashtable + Get-Or below keeps access tolerant of missing keys even under +# Set-StrictMode -Version 3.0 (inherited from _common.ps1). Without this a +# missing "$config.ablegacy" throws "property cannot be found on this object". +$config = Get-Content $ConfigFile -Raw | ConvertFrom-Json -AsHashtable $summary = [ordered]@{} +# Return $Table[$Key] if present, else $Default. Nested tables are themselves +# hashtables so this composes: (Get-Or $config modbus)['opcUaUrl']. +function Get-Or { + param($Table, [string]$Key, $Default = $null) + if ($Table -and $Table.ContainsKey($Key)) { return $Table[$Key] } + return $Default +} + function Run-Suite { param( [string]$Name, @@ -54,13 +65,14 @@ function Run-Suite { # Modbus # --------------------------------------------------------------------------- -if ($config.modbus) { +$modbus = Get-Or $config "modbus" +if ($modbus) { Write-Header "== MODBUS ==" Run-Suite "modbus" { & "$PSScriptRoot/test-modbus.ps1" ` - -ModbusHost $config.modbus.endpoint ` - -OpcUaUrl ($config.modbus.opcUaUrl ?? $OpcUaUrl) ` - -BridgeNodeId $config.modbus.bridgeNodeId + -ModbusHost $modbus["endpoint"] ` + -OpcUaUrl (Get-Or $modbus "opcUaUrl" $OpcUaUrl) ` + -BridgeNodeId $modbus["bridgeNodeId"] } } else { $summary["modbus"] = "SKIP (no config entry)" } @@ -69,15 +81,16 @@ else { $summary["modbus"] = "SKIP (no config entry)" } # AB CIP # --------------------------------------------------------------------------- -if ($config.abcip) { +$abcip = Get-Or $config "abcip" +if ($abcip) { Write-Header "== AB CIP ==" Run-Suite "abcip" { & "$PSScriptRoot/test-abcip.ps1" ` - -Gateway $config.abcip.gateway ` - -Family ($config.abcip.family ?? "ControlLogix") ` - -TagPath ($config.abcip.tagPath ?? "TestDINT") ` - -OpcUaUrl ($config.abcip.opcUaUrl ?? $OpcUaUrl) ` - -BridgeNodeId $config.abcip.bridgeNodeId + -Gateway $abcip["gateway"] ` + -Family (Get-Or $abcip "family" "ControlLogix") ` + -TagPath (Get-Or $abcip "tagPath" "TestDINT") ` + -OpcUaUrl (Get-Or $abcip "opcUaUrl" $OpcUaUrl) ` + -BridgeNodeId $abcip["bridgeNodeId"] } } else { $summary["abcip"] = "SKIP (no config entry)" } @@ -86,15 +99,16 @@ else { $summary["abcip"] = "SKIP (no config entry)" } # AB Legacy # --------------------------------------------------------------------------- -if ($config.ablegacy) { +$ablegacy = Get-Or $config "ablegacy" +if ($ablegacy) { Write-Header "== AB LEGACY ==" Run-Suite "ablegacy" { & "$PSScriptRoot/test-ablegacy.ps1" ` - -Gateway $config.ablegacy.gateway ` - -PlcType ($config.ablegacy.plcType ?? "Slc500") ` - -Address ($config.ablegacy.address ?? "N7:5") ` - -OpcUaUrl ($config.ablegacy.opcUaUrl ?? $OpcUaUrl) ` - -BridgeNodeId $config.ablegacy.bridgeNodeId + -Gateway $ablegacy["gateway"] ` + -PlcType (Get-Or $ablegacy "plcType" "Slc500") ` + -Address (Get-Or $ablegacy "address" "N7:5") ` + -OpcUaUrl (Get-Or $ablegacy "opcUaUrl" $OpcUaUrl) ` + -BridgeNodeId $ablegacy["bridgeNodeId"] } } else { $summary["ablegacy"] = "SKIP (no config entry)" } @@ -103,16 +117,17 @@ else { $summary["ablegacy"] = "SKIP (no config entry)" } # S7 # --------------------------------------------------------------------------- -if ($config.s7) { +$s7 = Get-Or $config "s7" +if ($s7) { Write-Header "== S7 ==" Run-Suite "s7" { & "$PSScriptRoot/test-s7.ps1" ` - -S7Host $config.s7.endpoint ` - -Cpu ($config.s7.cpu ?? "S71500") ` - -Slot ($config.s7.slot ?? 0) ` - -Address ($config.s7.address ?? "DB1.DBW0") ` - -OpcUaUrl ($config.s7.opcUaUrl ?? $OpcUaUrl) ` - -BridgeNodeId $config.s7.bridgeNodeId + -S7Host $s7["endpoint"] ` + -Cpu (Get-Or $s7 "cpu" "S71500") ` + -Slot (Get-Or $s7 "slot" 0) ` + -Address (Get-Or $s7 "address" "DB1.DBW0") ` + -OpcUaUrl (Get-Or $s7 "opcUaUrl" $OpcUaUrl) ` + -BridgeNodeId $s7["bridgeNodeId"] } } else { $summary["s7"] = "SKIP (no config entry)" } @@ -121,15 +136,16 @@ else { $summary["s7"] = "SKIP (no config entry)" } # FOCAS # --------------------------------------------------------------------------- -if ($config.focas) { +$focas = Get-Or $config "focas" +if ($focas) { Write-Header "== FOCAS ==" Run-Suite "focas" { & "$PSScriptRoot/test-focas.ps1" ` - -CncHost $config.focas.host ` - -CncPort ($config.focas.port ?? 8193) ` - -Address ($config.focas.address ?? "R100") ` - -OpcUaUrl ($config.focas.opcUaUrl ?? $OpcUaUrl) ` - -BridgeNodeId $config.focas.bridgeNodeId + -CncHost $focas["host"] ` + -CncPort (Get-Or $focas "port" 8193) ` + -Address (Get-Or $focas "address" "R100") ` + -OpcUaUrl (Get-Or $focas "opcUaUrl" $OpcUaUrl) ` + -BridgeNodeId $focas["bridgeNodeId"] } } else { $summary["focas"] = "SKIP (no config entry)" } @@ -138,15 +154,16 @@ else { $summary["focas"] = "SKIP (no config entry)" } # TwinCAT # --------------------------------------------------------------------------- -if ($config.twincat) { +$twincat = Get-Or $config "twincat" +if ($twincat) { Write-Header "== TWINCAT ==" Run-Suite "twincat" { & "$PSScriptRoot/test-twincat.ps1" ` - -AmsNetId $config.twincat.amsNetId ` - -AmsPort ($config.twincat.amsPort ?? 851) ` - -SymbolPath ($config.twincat.symbolPath ?? "MAIN.iCounter") ` - -OpcUaUrl ($config.twincat.opcUaUrl ?? $OpcUaUrl) ` - -BridgeNodeId $config.twincat.bridgeNodeId + -AmsNetId $twincat["amsNetId"] ` + -AmsPort (Get-Or $twincat "amsPort" 851) ` + -SymbolPath (Get-Or $twincat "symbolPath" "MAIN.iCounter") ` + -OpcUaUrl (Get-Or $twincat "opcUaUrl" $OpcUaUrl) ` + -BridgeNodeId $twincat["bridgeNodeId"] } } else { $summary["twincat"] = "SKIP (no config entry)" } @@ -155,15 +172,17 @@ else { $summary["twincat"] = "SKIP (no config entry)" } # Phase 7 virtual tags + scripted alarms # --------------------------------------------------------------------------- -if ($config.phase7) { +$phase7 = Get-Or $config "phase7" +if ($phase7) { Write-Header "== PHASE 7 virtual tags + scripted alarms ==" Run-Suite "phase7" { + $defaultModbus = if ($modbus) { $modbus["endpoint"] } else { $null } & "$PSScriptRoot/test-phase7-virtualtags.ps1" ` - -ModbusHost ($config.phase7.modbusEndpoint ?? $config.modbus.endpoint) ` - -OpcUaUrl ($config.phase7.opcUaUrl ?? $OpcUaUrl) ` - -InputNodeId $config.phase7.inputNodeId ` - -VirtualNodeId $config.phase7.virtualNodeId ` - -AlarmNodeId ($config.phase7.alarmNodeId ?? $null) + -ModbusHost (Get-Or $phase7 "modbusEndpoint" $defaultModbus) ` + -OpcUaUrl (Get-Or $phase7 "opcUaUrl" $OpcUaUrl) ` + -InputNodeId $phase7["inputNodeId"] ` + -VirtualNodeId $phase7["virtualNodeId"] ` + -AlarmNodeId (Get-Or $phase7 "alarmNodeId" $null) } } else { $summary["phase7"] = "SKIP (no config entry)" }