Files
orca/.github/workflows/wsl-orcad-e2e.yml
T
Neil d38299290d refactor(managed-server): move the managed server's install and deploy code into src/main/managed-server/install/ (#30335)
orcad-remote-*, remote-install-*, remote-node-runtime-store-*, remote-versioned-install and
remote-deploy-timing leave src/main/ssh. git mv plus import and path-literal rewrites; one e2e route
regex learns the new folder. orcad-remote-deploy and orcad-remote-node-runtime-report stay for now
because open PRs #29834 and #29829 edit them.
2026-10-18 05:20:19 -07:00

260 lines
13 KiB
YAML

name: WSL orcad E2E
# WSL via orcad (step C10) on real WSL2 distros: a GitHub-hosted windows-2025 runner imports a
# pinned Ubuntu 24.04 rootfs twice (systemd off and on) and runs
# src/main/wsl/wsl-orcad-real-distro-lane.test.ts against both: deploy and launch, the wsl.exe
# stdio bridge, runtime RPC, liveness recovery, holder survival and teardown. Not a required check.
# Hosted WSL2 was proven in #27783: WSL 2.7.14, import 13-18 s, cold boot 8-14 s.
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- 'src/main/wsl/**'
- '!src/main/wsl/**/*.test.ts'
- 'src/main/wsl/wsl-orcad-real-distro-lane.test.ts'
# This lane executes only the real distro test above.
- 'src/main/ssh/orcad-*'
- 'src/main/managed-server/**'
- '!src/main/ssh/orcad-*.test.ts'
- '!src/main/managed-server/**/*.test.ts'
- 'src/main/managed-server/install/remote-install-transport.ts'
# orcad-entry runs this on every boot of the orcad the lane launches in the distro.
- 'src/main/orcad/orcad-wsl-*'
- '!src/main/orcad/orcad-wsl-*.test.ts'
- '.github/workflows/wsl-orcad-e2e.yml'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: wsl-orcad-e2e-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
# wsl.exe prints UTF-16LE to pipes unless told otherwise.
WSL_UTF8: '1'
ROOTFS_URL: https://releases.ubuntu.com/24.04.4/ubuntu-24.04.4-wsl-amd64.wsl
ROOTFS_SHA256: 9b2f7730dc68227dd04a9f3e5eab86ad85caf556b8606ad94f1f29ff5c4fd3f5
WSL_MSI_URL: https://github.com/microsoft/WSL/releases/download/3.0.1/wsl.3.0.1.0.x64.msi
WSL_MSI_SHA256: 28b1a0d013640a2ac95898ea705fa186e5b4ff767a1c1b49257161bc106599c6
PLAIN_DISTRO: orca-lane-plain
SYSTEMD_DISTRO: orca-lane-systemd
jobs:
# The distro is linux-x64-glibc; its node-pty slot only builds on Linux, and nothing else is needed.
template:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
name: Build the linux-x64-glibc orcad template
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: ./.github/actions/install-node-dependencies
with:
native-runtime: node
- uses: ./.github/actions/build-mobile-web-bundle
- name: Build the template
run: |
pnpm build:orcad-prebuilds
pnpm build:orcad-prebuilds --require-slots linux-x64-glibc
node config/scripts/build-orcad-template.mjs --targets linux-x64-glibc
- uses: actions/upload-artifact@v7
with:
name: wsl-orcad-template
path: out/orcad-template/
# The template carries dot-directories (`.claude-plugin`) its manifest checksums.
include-hidden-files: true
retention-days: 1
if-no-files-found: error
wsl2:
name: WSL2 distros (systemd off and on)
needs: template
runs-on: windows-2025
timeout-minutes: 45
env:
ORCA_BACKGROUND_LAUNCH: '1'
ORCA_WSL_ORCAD_LANE_RECEIPTS: ${{ github.workspace }}\test-results\wsl-orcad-lane
defaults:
run:
shell: pwsh
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
# WSL before 3.0.1 drops the VM-wide WSLInterop entry when a systemd distro stops
# (microsoft/WSL#13885). Images from 20261007 ship 3.0.1, where reinstalling the MSI fails
# with 1603, so install only when older; `wsl --update` gets rate-limited by the GitHub API.
- name: Check the installed WSL version
id: wsl-version
run: |
"ImageOS=$env:ImageOS ImageVersion=$env:ImageVersion"
$details = (wsl.exe --version) -join "`n"
$details
$match = [regex]::Match($details, '(?m)^WSL version:\s*(\d+(?:\.\d+){2,3})\s*$')
# Inbox WSL has no --version, so an unreadable version means the package is needed.
$installed = if ($LASTEXITCODE -eq 0 -and $match.Success) { [version]$match.Groups[1].Value } else { [version]'0.0.0' }
$needsInstall = $installed -lt [version]'3.0.1'
"Installed WSL $installed; minimum 3.0.1; install needed: $needsInstall"
"needs-install=$($needsInstall.ToString().ToLowerInvariant())" >> $env:GITHUB_OUTPUT
- name: Restore the pinned WSL package
id: wsl-msi-cache
if: steps.wsl-version.outputs.needs-install == 'true'
uses: actions/cache/restore@v5
with:
path: ${{ runner.temp }}/wsl-msi
key: wsl-orcad-msi-${{ env.WSL_MSI_SHA256 }}
- name: Install WSL 3.0.1
if: steps.wsl-version.outputs.needs-install == 'true'
timeout-minutes: 8
run: |
$dir = Join-Path $env:RUNNER_TEMP 'wsl-msi'
New-Item -ItemType Directory -Force $dir | Out-Null
$msi = Join-Path $dir 'wsl.msi'
if (-not (Test-Path $msi)) { & "$env:WINDIR\System32\curl.exe" --fail --location --silent --show-error --output $msi $env:WSL_MSI_URL }
if ((Get-FileHash $msi -Algorithm SHA256).Hash.ToLowerInvariant() -ne $env:WSL_MSI_SHA256) {
Remove-Item $msi; throw 'WSL package checksum mismatch'
}
# Kept beside the receipts so the always() upload carries it.
New-Item -ItemType Directory -Force $env:ORCA_WSL_ORCAD_LANE_RECEIPTS | Out-Null
$log = Join-Path $env:ORCA_WSL_ORCAD_LANE_RECEIPTS 'wsl-msi-install.log'
$install = Start-Process msiexec.exe -ArgumentList @('/i', $msi, '/qn', '/norestart', '/l*v', $log) -Wait -PassThru
if ($install.ExitCode -notin 0, 3010) {
if (Test-Path $log) {
$lines = @(Get-Content $log)
$failed = ($lines | Select-String -SimpleMatch 'Return value 3' | Select-Object -First 1).LineNumber
if ($failed) { '--- msiexec log before the failing action'; $lines[[math]::Max(0, $failed - 40)..($failed - 1)] }
'--- msiexec log tail'; $lines | Select-Object -Last 60
}
throw "WSL install failed: $($install.ExitCode)"
}
wsl.exe --shutdown
- name: Save the pinned WSL package
if: steps.wsl-version.outputs.needs-install == 'true' && steps.wsl-msi-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: ${{ runner.temp }}/wsl-msi
key: wsl-orcad-msi-${{ env.WSL_MSI_SHA256 }}
- name: Verify WSL is at least 3.0.1
run: |
$details = (wsl.exe --version) -join "`n"
if ($LASTEXITCODE -ne 0) { throw "Could not read the WSL version: $details" }
$details
$match = [regex]::Match($details, '(?m)^WSL version:\s*(\d+(?:\.\d+){2,3})\s*$')
if (-not $match.Success) { throw 'Could not parse the WSL version' }
if ([version]$match.Groups[1].Value -lt [version]'3.0.1') { throw "WSL $($match.Groups[1].Value) is older than 3.0.1" }
- name: Restore the pinned rootfs
id: rootfs-cache
uses: actions/cache/restore@v5
with:
path: ${{ runner.temp }}/wsl-rootfs
key: wsl-orcad-rootfs-${{ env.ROOTFS_SHA256 }}
# Runs beside the dependency install; the lane waits for it below.
- name: Import both distros as WSL2
id: import
background: true
timeout-minutes: 10
run: |
$cache = Join-Path $env:RUNNER_TEMP 'wsl-rootfs'
New-Item -ItemType Directory -Force $cache | Out-Null
$rootfs = Join-Path $cache 'ubuntu.wsl'
if (-not (Test-Path $rootfs)) { Invoke-WebRequest $env:ROOTFS_URL -OutFile $rootfs }
if ((Get-FileHash $rootfs -Algorithm SHA256).Hash.ToLowerInvariant() -ne $env:ROOTFS_SHA256) {
Remove-Item $rootfs; throw 'rootfs checksum mismatch'
}
foreach ($d in @(@{ Name = $env:PLAIN_DISTRO; Systemd = 'false' }, @{ Name = $env:SYSTEMD_DISTRO; Systemd = 'true' })) {
$sw = [Diagnostics.Stopwatch]::StartNew()
# No WSL1 fallback: the lane exists to prove WSL2.
wsl.exe --import $d.Name (Join-Path $env:RUNNER_TEMP $d.Name) $rootfs --version 2
if ($LASTEXITCODE -ne 0) { throw "WSL2 import of $($d.Name) failed: $LASTEXITCODE" }
"$($d.Name) import_ms=$($sw.ElapsedMilliseconds)"
if (-not ((wsl.exe --list --verbose) -match "$($d.Name)\s+\S+\s+2\s*$")) { throw "$($d.Name) is not a WSL2 distro" }
# A non-root default user, as on a real install; git for the lane's repo.
$setup = "set -e; id orca >/dev/null 2>&1 || useradd -m -s /bin/bash orca; " +
"printf '[boot]\nsystemd=$($d.Systemd)\n[user]\ndefault=orca\n' > /etc/wsl.conf; " +
"command -v git >/dev/null || (apt-get update -qq && apt-get install -y -qq git)"
wsl.exe -d $d.Name -u root --exec sh -c $setup
if ($LASTEXITCODE -ne 0) { throw "provisioning $($d.Name) failed: $LASTEXITCODE" }
wsl.exe --terminate $d.Name
$sw.Restart()
wsl.exe -d $d.Name --exec sh -c 'echo "user=$(id -un) pid1=$(ps -p 1 -o comm=) kernel=$(uname -r)"'
if ($LASTEXITCODE -ne 0) { throw "first boot of $($d.Name) failed: $LASTEXITCODE" }
"$($d.Name) boot_ms=$($sw.ElapsedMilliseconds)"
wsl.exe --terminate $d.Name
}
wsl.exe --list --verbose
- uses: ./.github/actions/install-node-dependencies
with:
native-runtime: node
- uses: actions/download-artifact@v8
with:
name: wsl-orcad-template
path: ${{ runner.temp }}/orcad-template
- wait: import
# Saved even when the lane fails; the import step verified its checksum.
- name: Save the pinned rootfs
if: steps.rootfs-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: ${{ runner.temp }}/wsl-rootfs
key: wsl-orcad-rootfs-${{ env.ROOTFS_SHA256 }}
- name: 'Phase 1: deploy, RPC, kill orcad, terminate distro'
timeout-minutes: 20
env:
ORCA_RUN_WSL_ORCAD_LANE: '1'
ORCA_WSL_ORCAD_LANE_PHASE: launch
ORCA_ORCAD_TEMPLATE_PATH: ${{ runner.temp }}/orcad-template
run: |
New-Item -ItemType Directory -Force $env:ORCA_WSL_ORCAD_LANE_RECEIPTS | Out-Null
node node_modules/vitest/vitest.mjs run --config config/vitest.config.ts src/main/wsl/wsl-orcad-real-distro-lane.test.ts --reporter=verbose 2>&1 | Tee-Object (Join-Path $env:ORCA_WSL_ORCAD_LANE_RECEIPTS 'launch.log')
if ($global:LASTEXITCODE -ne 0) { throw "launch phase failed: $global:LASTEXITCODE" }
# A new process: the holders must have kept both distros Running since phase 1 exited.
- name: 'Phase 2: holders survive, reconnect, teardown'
timeout-minutes: 10
env:
ORCA_RUN_WSL_ORCAD_LANE: '1'
ORCA_WSL_ORCAD_LANE_PHASE: survive
ORCA_ORCAD_TEMPLATE_PATH: ${{ runner.temp }}/orcad-template
run: |
node node_modules/vitest/vitest.mjs run --config config/vitest.config.ts src/main/wsl/wsl-orcad-real-distro-lane.test.ts --reporter=verbose 2>&1 | Tee-Object (Join-Path $env:ORCA_WSL_ORCAD_LANE_RECEIPTS 'survive.log')
if ($global:LASTEXITCODE -ne 0) { throw "survive phase failed: $global:LASTEXITCODE" }
# Belt and braces for the runner: a missing VM-wide interop entry is restored, then fails.
- name: WSLInterop is intact
if: ${{ !cancelled() && steps.import.outcome == 'success' }}
timeout-minutes: 3
run: |
$state = wsl.exe -d $env:PLAIN_DISTRO -u root --exec sh -c 'if [ -e /proc/sys/fs/binfmt_misc/WSLInterop ]; then echo present; else echo :WSLInterop:M::MZ::/init:P > /proc/sys/fs/binfmt_misc/register && echo restored; fi'
"WSLInterop: $state"
wsl.exe --terminate $env:PLAIN_DISTRO
if ("$state".Trim() -ne 'present') { throw "WSLInterop was missing ($state)" }
- name: Collect orcad logs
if: failure()
timeout-minutes: 3
run: |
$out = $env:ORCA_WSL_ORCAD_LANE_RECEIPTS
New-Item -ItemType Directory -Force $out | Out-Null
foreach ($d in @($env:PLAIN_DISTRO, $env:SYSTEMD_DISTRO)) {
wsl.exe -d $d --exec sh -c 'for f in $(find "$HOME/.orca-remote" -name "*.log*" -type f 2>/dev/null); do echo "=== $f"; tail -n 400 "$f"; done' > (Join-Path $out "$d-orcad-logs.txt") 2>&1
}
wsl.exe --list --verbose
- name: Summary
if: always()
run: |
$dir = $env:ORCA_WSL_ORCAD_LANE_RECEIPTS
$lines = @("### WSL orcad lane — $env:ImageOS $env:ImageVersion", '')
foreach ($phase in 'launch', 'survive') {
$file = Join-Path $dir "$phase.json"
if (Test-Path $file) { $lines += "#### $phase", '```json', (Get-Content $file -Raw), '```' }
}
$lines | Add-Content $env:GITHUB_STEP_SUMMARY
- uses: actions/upload-artifact@v7
if: always()
with:
name: wsl-orcad-lane-receipts
path: test-results/wsl-orcad-lane/
retention-days: 7
if-no-files-found: ignore