Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 83 additions & 17 deletions .github/workflows/wsl-orcad-e2e.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
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
# pinned Ubuntu 24.04 rootfs twice (systemd off and on), plus Debian 12 and Fedora 41 (systemd off,
# rootfs exported from their container images), 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, structured chat 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.
Expand Down Expand Up @@ -53,7 +54,6 @@ env:
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.
Expand Down Expand Up @@ -84,14 +84,66 @@ jobs:
retention-days: 1
if-no-files-found: error

# Container images ship no systemd, so these families run the systemd-off distro only.
container-rootfs:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
name: Export ${{ matrix.family }} rootfs
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
include:
- family: debian-12
image: debian:12
- family: fedora-41
image: fedora:41
steps:
- name: Export the image filesystem
run: |
docker pull --platform linux/amd64 '${{ matrix.image }}'
echo "digest: $(docker image inspect --format '{{index .RepoDigests 0}}' '${{ matrix.image }}')"
id="$(docker create --platform linux/amd64 '${{ matrix.image }}')"
mkdir -p rootfs
docker export "$id" > rootfs/rootfs.tar
docker rm "$id" >/dev/null
ls -l rootfs/rootfs.tar
- uses: actions/upload-artifact@v7
with:
name: wsl-orcad-rootfs-${{ matrix.family }}
path: rootfs/rootfs.tar
retention-days: 1
if-no-files-found: error

wsl2:
name: WSL2 distros (systemd off and on)
needs: template
name: ${{ matrix.name }}
needs: [template, container-rootfs]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Keep Ubuntu coverage independent of container exports.

If either container-rootfs matrix leg fails, GitHub Actions skips the entire wsl2 matrix. The Ubuntu leg does not use a container artifact, but it also loses coverage. Separate the Ubuntu job from the container-dependent legs, or otherwise make each leg depend only on its required rootfs export. (docs.github.com)

runs-on: windows-2025
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- name: WSL2 distros (systemd off and on)
family: ubuntu-24.04
container: false
systemd-distro: orca-lane-systemd
install: command -v git >/dev/null || (apt-get update -qq && apt-get install -y -qq git)
- name: WSL2 distro (debian-12)
family: debian-12
container: true
systemd-distro: ''
install: apt-get update -qq && apt-get install -y -qq git procps
- name: WSL2 distro (fedora-41)
family: fedora-41
container: true
systemd-distro: ''
install: dnf -y -q install git procps-ng shadow-utils util-linux findutils tar gzip
env:
ORCA_BACKGROUND_LAUNCH: '1'
ORCA_WSL_ORCAD_LANE_RECEIPTS: ${{ github.workspace }}\test-results\wsl-orcad-lane
SYSTEMD_DISTRO: ${{ matrix.systemd-distro }}
ORCA_WSL_ORCAD_LANE_SYSTEMD_DISTRO: ${{ matrix.systemd-distro }}
defaults:
run:
shell: pwsh
Expand Down Expand Up @@ -162,35 +214,49 @@ jobs:
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
if: ${{ !matrix.container }}
uses: actions/cache/restore@v5
with:
path: ${{ runner.temp }}/wsl-rootfs
key: wsl-orcad-rootfs-${{ env.ROOTFS_SHA256 }}
- uses: actions/download-artifact@v8
if: ${{ matrix.container }}
with:
name: wsl-orcad-rootfs-${{ matrix.family }}
path: ${{ runner.temp }}/wsl-rootfs
# Runs beside the dependency install; the lane waits for it below.
- name: Import both distros as WSL2
- name: Import the 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'
if ('${{ matrix.container }}' -eq 'true') {
$rootfs = Join-Path $cache 'rootfs.tar'
} else {
$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' })) {
$distros = @(@{ Name = $env:PLAIN_DISTRO; Systemd = 'false' })
if ($env:SYSTEMD_DISTRO) { $distros += @{ Name = $env:SYSTEMD_DISTRO; Systemd = 'true' } }
foreach ($d in $distros) {
$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; " +
# The env exports go to the file a login bash reads first; Fedora's skel ships .bash_profile.
$setup = "set -e; ${{ matrix.install }}; 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; " +
"printf 'export ORCA_BACKGROUND_LAUNCH=1\nexport USERPROFILE=`$HOME\nexport APPDATA=`$HOME/.config\nexport LOCALAPPDATA=`$HOME/.local/share\nexport XDG_CONFIG_HOME=`$HOME/.config\nexport XDG_DATA_HOME=`$HOME/.local/share\nexport XDG_CACHE_HOME=`$HOME/.cache\nexport XDG_STATE_HOME=`$HOME/.local/state\nexport CLAUDE_CONFIG_DIR=`$HOME/.claude\nexport CODEX_HOME=`$HOME/.codex\n' >> /home/orca/.profile; " +
"command -v git >/dev/null || (apt-get update -qq && apt-get install -y -qq git)"
"rc=/home/orca/.profile; for f in /home/orca/.bash_profile /home/orca/.bash_login; do " +
"if [ -f `$f ]; then rc=`$f; break; fi; done; " +
"printf 'export ORCA_BACKGROUND_LAUNCH=1\nexport USERPROFILE=`$HOME\nexport APPDATA=`$HOME/.config\nexport LOCALAPPDATA=`$HOME/.local/share\nexport XDG_CONFIG_HOME=`$HOME/.config\nexport XDG_DATA_HOME=`$HOME/.local/share\nexport XDG_CACHE_HOME=`$HOME/.cache\nexport XDG_STATE_HOME=`$HOME/.local/state\nexport CLAUDE_CONFIG_DIR=`$HOME/.claude\nexport CODEX_HOME=`$HOME/.codex\n' >> `$rc"
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
Expand All @@ -211,7 +277,7 @@ jobs:
- 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'
if: ${{ !matrix.container && steps.rootfs-cache.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v5
with:
path: ${{ runner.temp }}/wsl-rootfs
Expand Down Expand Up @@ -275,15 +341,15 @@ jobs:
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)) {
foreach ($d in @($env:PLAIN_DISTRO, $env:SYSTEMD_DISTRO) | Where-Object { $_ }) {
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", '')
$lines = @("### WSL orcad lane (${{ matrix.family }}) — $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), '```' }
Expand All @@ -292,7 +358,7 @@ jobs:
- uses: actions/upload-artifact@v7
if: always()
with:
name: wsl-orcad-lane-receipts
name: wsl-orcad-lane-receipts-${{ matrix.family }}
path: test-results/wsl-orcad-lane/
retention-days: 7
if-no-files-found: ignore
18 changes: 12 additions & 6 deletions src/main/wsl/wsl-orcad-real-distro-lane.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,11 @@ import { createWslOrcadLivenessSupervisor } from './wsl-orcad-liveness-wiring'
const RUN = process.env.ORCA_RUN_WSL_ORCAD_LANE === '1'
const PHASE = process.env.ORCA_WSL_ORCAD_LANE_PHASE ?? 'launch'
const PLAIN = process.env.ORCA_WSL_ORCAD_LANE_PLAIN_DISTRO ?? 'orca-lane-plain'
// Empty for distro families whose rootfs ships no systemd; those run the plain distro only.
const SYSTEMD = process.env.ORCA_WSL_ORCAD_LANE_SYSTEMD_DISTRO ?? 'orca-lane-systemd'
const DISTROS = [
{ name: PLAIN, systemd: false },
{ name: SYSTEMD, systemd: true }
...(SYSTEMD ? [{ name: SYSTEMD, systemd: true }] : [])
]
const WORK_DIR = process.env.ORCA_WSL_ORCAD_LANE_DIR ?? join(tmpdir(), 'orca-wsl-orcad-lane')
const USER_DATA = join(WORK_DIR, 'userData')
Expand Down Expand Up @@ -465,10 +466,13 @@ async function launchPhase(distro: string, systemd: boolean): Promise<Receipt> {
if (!supervisor) {
throw new Error('supervisor missing')
}
// A login bash reads only the first of these that exists; Fedora and Arch ship ~/.bash_profile.
await guest(
distro,
`grep -q ${LOGIN_ENV_TAG} "$HOME/.profile" 2>/dev/null || ` +
`printf 'export SSH_AUTH_SOCK=${LOGIN_ENV_SOCK} # ${LOGIN_ENV_TAG}\\n' >> "$HOME/.profile"`
`rc="$HOME/.profile"; for f in "$HOME/.bash_profile" "$HOME/.bash_login"; do ` +
`if [ -f "$f" ]; then rc="$f"; break; fi; done; ` +
`grep -q ${LOGIN_ENV_TAG} "$rc" 2>/dev/null || ` +
`printf 'export SSH_AUTH_SOCK=${LOGIN_ENV_SOCK} # ${LOGIN_ENV_TAG}\\n' >> "$rc"`
)
let started = performance.now()
const connection = await supervisor.connect(distro)
Expand Down Expand Up @@ -595,8 +599,10 @@ async function survivePhase(): Promise<void> {
)
}
// The systemd distro stops while the plain one still runs, so the interop entry is shared.
expect((await wslExe(['--terminate', SYSTEMD])).code).toBe(0)
await assertWslInteropIntact(PLAIN, `after final terminate of ${SYSTEMD}`)
if (SYSTEMD) {
expect((await wslExe(['--terminate', SYSTEMD])).code).toBe(0)
await assertWslInteropIntact(PLAIN, `after final terminate of ${SYSTEMD}`)
}
expect((await wslExe(['--terminate', PLAIN])).code).toBe(0)
const leftovers = await waitFor(
'lane wsl.exe processes to exit',
Expand Down Expand Up @@ -660,7 +666,7 @@ describe.runIf(RUN && process.platform === 'win32')('WSL orcad on real WSL2 dist
)

it.runIf(PHASE === 'survive')(
'keeps both distros held after the launching process exited, then tears down cleanly',
'keeps every distro held after the launching process exited, then tears down cleanly',
survivePhase,
DISTRO_TIMEOUT_MS
)
Expand Down
Loading