Files
mxaccessgw/scripts/ci/windev-worker-ci.ps1
T
Joseph Doherty d769244ac0
ci / java (push) Successful in 2m2s
ci / windows-x86 (push) Successful in 1m10s
ci / nightly-windev (push) Has been skipped
ci / portable (push) Successful in 8m52s
fix(TST-25,TST-26): restore Windows/x86 CI tier via SSH-driven windev job
The `windows`/`live-mxaccess` CI jobs were removed in abb0930 because Gitea
act_runner host-mode on Windows is broken and a runs-on gate with no runner
wedges the queue. This left the entire x86/net48 Worker + Worker.Tests tier
(and the live-MXAccess smoke) unguarded by CI.

Restore it without a native Windows runner: Linux jobs on ubuntu-latest (which
always schedule) SSH to windev (10.100.0.48), fetch+checkout the SHA under test
in an isolated clone C:\build\mxaccessgw-ci, run the x86 build/tests there, and
propagate the remote exit code back — so a Worker regression turns the job red
and an unreachable host fails loud (never stuck).

- scripts/ci/run-windev-ci.sh: Linux driver (key/known-hosts, UTF-16LE base64
  EncodedCommand bootstrap, ssh, exit-code passthrough).
- scripts/ci/windev-worker-ci.ps1: windev stage — worktree lock, re-fetch/
  checkout under lock, build|test|live modes; PS 5.1-safe, checks $LASTEXITCODE.
- scripts/ci/windev.known_hosts: pinned host keys for StrictHostKeyChecking.
- scripts/ci/README.md: operator bring-up + acceptance checklist.
- ci.yml: windows-x86 (per-push `test`) + nightly-windev (scheduled `live` +
  on-failure Gitea issue); drop the removal note; fix header/cron comments.

TST-26 (same commit, by rule): correct docs/scripts that still describe the
removed jobs — GatewayTesting.md, Contracts.md, check-codegen.ps1 — and
reattribute the Generated/-commit guard (primary = check-codegen diff in the
portable job; secondary = windows-x86 net48 compile).

Mechanism hand-verified on windev: build->0, bogus-SHA->nonzero (lock released),
test->356 passed/0 failed in ~50s, ssh exit-code propagation confirmed. Merge
requires operator bring-up first (dedicated ci@ key + Gitea secrets) or the
per-push job is red every push — see scripts/ci/README.md.
2026-07-13 10:09:49 -04:00

148 lines
6.0 KiB
PowerShell

<#
.SYNOPSIS
Windows/x86 Worker CI stage — runs ON windev (10.100.0.48), invoked over SSH by
scripts/ci/run-windev-ci.sh at the exact SHA under test. Restores the x86/net48
Worker test tier that the removed `windows` CI job used to cover (TST-25).
.DESCRIPTION
The Linux `windows-x86` / nightly CI jobs cannot build the x86/net48 Worker, so they
ssh here and run this script inside the isolated CI clone C:\build\mxaccessgw-ci.
The script is deliberately self-contained and versioned with the code under test: the
SSH bootstrap only fetches + checks out the SHA (enough to load *this* file), then this
script re-fetches and re-checks-out $Sha INSIDE the worktree lock so the checkout+build
are fully serialized against a concurrent run and can never stomp each other's tree.
Modes (cumulative):
build x86 Worker build only (net48/CS0246/x86 compile guard — always fast).
test build + Worker.Tests (x86). The per-push default.
live test + the live-MXAccess smoke (MXGATEWAY_RUN_LIVE_MXACCESS_TESTS=1) and a full
slnx build (surfaces Windows-only analyzer diagnostics, e.g. xUnit1030). Nightly.
Runs under Windows PowerShell 5.1 (`powershell.exe`), so it avoids PS7-only syntax and
checks $LASTEXITCODE after every native command rather than relying on stderr-as-error
(git writes progress to stderr — the known windev gotcha).
Exits nonzero on the first failure so the SSH exit code propagates to the CI job.
.PARAMETER Sha
The commit SHA under test. HEAD is force-checked-out to it and verified.
.PARAMETER Mode
build | test | live.
.PARAMETER RepoRoot
The CI clone root. Defaults to C:\build\mxaccessgw-ci.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)][string]$Sha,
[Parameter(Mandatory = $true)][ValidateSet('build', 'test', 'live')][string]$Mode,
[string]$RepoRoot = 'C:\build\mxaccessgw-ci'
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$lockDir = "$RepoRoot.lock"
$logFile = Join-Path $RepoRoot 'ci.log'
$lockTimeout = [TimeSpan]::FromMinutes(20)
function Write-Log {
param([string]$Message)
$line = ('[{0}] {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Message)
Write-Host $line
# Best-effort file log for the "is the tier down?" diagnosis in docs/GatewayTesting.md.
try { Add-Content -Path $logFile -Value $line -ErrorAction SilentlyContinue } catch { }
}
# Run a native command and throw on nonzero exit. Native stderr (git progress) does NOT
# set $LASTEXITCODE, so it is not treated as failure — only a real nonzero exit is.
function Invoke-Native {
param([Parameter(Mandatory = $true)][string]$What, [Parameter(Mandatory = $true)][scriptblock]$Command)
Write-Log "RUN: $What"
& $Command
if ($LASTEXITCODE -ne 0) {
throw "$What failed with exit code $LASTEXITCODE"
}
}
# --- acquire the worktree lock (mkdir is atomic; retry until timeout) ---
$deadline = (Get-Date).Add($lockTimeout)
$haveLock = $false
Write-Log "Acquiring worktree lock $lockDir (mode=$Mode sha=$Sha)"
while (-not $haveLock) {
try {
New-Item -Path $lockDir -ItemType Directory -ErrorAction Stop | Out-Null
$haveLock = $true
}
catch {
if ((Get-Date) -ge $deadline) {
throw "Timed out after $($lockTimeout.TotalMinutes) min waiting for worktree lock $lockDir. A stuck run may have left it; if no CI job is active, remove it by hand."
}
Start-Sleep -Seconds 10
}
}
try {
Push-Location $RepoRoot
try {
# Re-fetch + re-checkout under the lock so a concurrent run's bootstrap checkout
# (done before we held the lock) cannot leave us on the wrong tree.
Invoke-Native 'git fetch origin' { git fetch --prune origin }
Invoke-Native "git checkout --force $Sha" { git checkout --force --detach $Sha }
# Drop any stray tracked/untracked residue from a prior run so the build is clean.
Invoke-Native 'git reset --hard' { git reset --hard $Sha }
Invoke-Native 'git clean -fdx (build outputs)' { git clean -fdx -e ci.log }
$head = (& git rev-parse HEAD).Trim()
if ($head -ne $Sha) {
throw "Worktree HEAD $head does not match requested SHA $Sha after checkout."
}
Write-Log "Worktree at $head"
$workerProj = 'src/ZB.MOM.WW.MxGateway.Worker/ZB.MOM.WW.MxGateway.Worker.csproj'
$workerTests = 'src/ZB.MOM.WW.MxGateway.Worker.Tests/ZB.MOM.WW.MxGateway.Worker.Tests.csproj'
$integrationTests = 'src/ZB.MOM.WW.MxGateway.IntegrationTests/ZB.MOM.WW.MxGateway.IntegrationTests.csproj'
# build: always. The x86/net48 compile is the floor guard (catches CS0246 on
# unregenerated Generated/, x86-only breaks) even when the test step is demoted.
Invoke-Native 'x86 Worker build' { dotnet build $workerProj -c Release -p:Platform=x86 }
if ($Mode -eq 'test' -or $Mode -eq 'live') {
Invoke-Native 'x86 Worker.Tests' { dotnet test $workerTests -c Release -p:Platform=x86 }
}
if ($Mode -eq 'live') {
# Full-solution build surfaces Windows-only analyzer diagnostics (xUnit1030 etc.)
# that never fire on the macOS/Linux NonWindows build.
Invoke-Native 'full slnx build' { dotnet build src/ZB.MOM.WW.MxGateway.slnx -c Release }
$env:MXGATEWAY_RUN_LIVE_MXACCESS_TESTS = '1'
try {
Invoke-Native 'live-MXAccess smoke' {
dotnet test $integrationTests -c Release --filter 'FullyQualifiedName~WorkerLiveMxAccessSmokeTests'
}
}
finally {
Remove-Item Env:\MXGATEWAY_RUN_LIVE_MXACCESS_TESTS -ErrorAction SilentlyContinue
}
}
Write-Log "SUCCESS mode=$Mode sha=$Sha"
}
finally {
Pop-Location
}
}
catch {
Write-Log "FAILURE mode=$Mode sha=$Sha : $($_.Exception.Message)"
throw
}
finally {
if ($haveLock) {
Remove-Item -Path $lockDir -Recurse -Force -ErrorAction SilentlyContinue
Write-Log "Released worktree lock $lockDir"
}
}