Gauntlet: put failing check names on the run summary page #8
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Per-commit adversarial test gauntlet for the SVG thumbnail provider. | |
| # | |
| # Where unload-crash-repro targets one specific known bug, this workflow tries to | |
| # break the provider in every other way: malformed and hostile SVG/SVGZ input, | |
| # COM contract abuse, lying IStream implementations, XML external entities, | |
| # concurrency, thread churn and resource retention, plus a real-world icon | |
| # corpus. | |
| # | |
| # Structure: | |
| # build - compiles the DLL and the gauntlet once, publishes them as artifacts | |
| # corpus - fetches and caches a pinned slice of microsoft/fluentui-system-icons | |
| # gauntlet - one job per suite, in parallel, each with crash dumps and a watchdog | |
| # verdict - aggregates the matrix into a single pass/fail and a job summary | |
| # | |
| # Every suite runs in its own child process under a watchdog inside the gauntlet | |
| # supervisor, so a crash or deadlock in one suite is reported with the exact | |
| # input that caused it rather than taking down the run. | |
| name: test-gauntlet | |
| on: | |
| push: | |
| branches: | |
| - master | |
| - 'claude/**' | |
| paths: | |
| - 'src/**' | |
| - 'Testing/gauntlet/**' | |
| - 'Testing/unload-harness/**' | |
| - 'Cargo.toml' | |
| - 'Cargo.lock' | |
| - 'build.rs' | |
| - 'rust-toolchain.toml' | |
| - '.cargo/**' | |
| - '.github/workflows/test-gauntlet.yml' | |
| pull_request: | |
| paths: | |
| - 'src/**' | |
| - 'Testing/gauntlet/**' | |
| - 'Testing/unload-harness/**' | |
| - 'Cargo.toml' | |
| - 'Cargo.lock' | |
| - 'build.rs' | |
| - 'rust-toolchain.toml' | |
| - '.cargo/**' | |
| - '.github/workflows/test-gauntlet.yml' | |
| workflow_dispatch: | |
| inputs: | |
| scale: | |
| description: 'Workload multiplier (1 = per-commit default; raise for a deeper run)' | |
| type: string | |
| default: '1' | |
| seed: | |
| description: 'Fixed RNG seed to replay a previous run (blank = new random seed)' | |
| type: string | |
| default: '' | |
| suites: | |
| description: 'Space-separated suite names to run (blank = all)' | |
| type: string | |
| default: '' | |
| permissions: | |
| contents: read | |
| # A push that supersedes an in-flight run should cancel it: the gauntlet is | |
| # expensive and only the newest commit's result matters. | |
| concurrency: | |
| group: test-gauntlet-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| # Pinned so the corpus is reproducible and the cache key is stable. | |
| # microsoft/fluentui-system-icons is MIT licensed; nothing from it is vendored | |
| # into this repository, it is only downloaded for testing. | |
| FLUENT_ICONS_SHA: 5ecd79ea56f2be0169859b3b881dcc890be932fc | |
| CARGO_TERM_COLOR: always | |
| # Keep incremental artifacts out of the cache: they are large and useless | |
| # across machines. | |
| CARGO_INCREMENTAL: 0 | |
| jobs: | |
| # --------------------------------------------------------------- | |
| build: | |
| name: Build (${{ matrix.arch }}) | |
| runs-on: ${{ matrix.runs-on }} | |
| timeout-minutes: 60 | |
| strategy: | |
| # One architecture failing to build must not hide the results for the | |
| # others: the whole point of this matrix is to compare them. | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # The primary architecture, and the one the full per-suite matrix runs on. | |
| - arch: x64 | |
| target: x86_64-pc-windows-msvc | |
| runs-on: windows-latest | |
| dll: win_svg_thumbs_x64.dll | |
| # The x64 MSI installs this alongside the 64-bit DLL, because 32-bit | |
| # applications hosting the shell's thumbnail path load the 32-bit | |
| # provider. It is also where the pointer- and usize-width arithmetic in | |
| # the bitmap copy actually differs. | |
| - arch: x86 | |
| target: i686-pc-windows-msvc | |
| runs-on: windows-latest | |
| dll: win_svg_thumbs_x86.dll | |
| # Native ARM64, built and tested on real ARM hardware rather than | |
| # cross-compiled and left unexercised. | |
| - arch: arm64 | |
| target: aarch64-pc-windows-msvc | |
| runs-on: windows-11-arm | |
| dll: win_svg_thumbs_arm64.dll | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Cache cargo registry and git checkouts | |
| uses: actions/cache@v6 | |
| with: | |
| path: | | |
| ~/.cargo/registry/index | |
| ~/.cargo/registry/cache | |
| ~/.cargo/git/db | |
| # Cargo.lock is gitignored in this repository, so the manifests are the | |
| # best available key. restore-keys lets a changed manifest still start | |
| # from the previous download set instead of a cold registry. | |
| key: cargo-registry-${{ matrix.arch }}-${{ hashFiles('**/Cargo.toml', 'rust-toolchain.toml') }} | |
| restore-keys: | | |
| cargo-registry-${{ matrix.arch }}- | |
| - name: Cache build outputs | |
| uses: actions/cache@v6 | |
| with: | |
| path: target | |
| key: cargo-target-${{ matrix.arch }}-${{ hashFiles('**/Cargo.toml', 'rust-toolchain.toml') }}-${{ hashFiles('src/**/*.rs', 'Testing/gauntlet/**/*.rs', 'build.rs') }} | |
| restore-keys: | | |
| cargo-target-${{ matrix.arch }}-${{ hashFiles('**/Cargo.toml', 'rust-toolchain.toml') }}- | |
| cargo-target-${{ matrix.arch }}- | |
| - name: Toolchain info and target install | |
| run: | | |
| rustc -Vv | |
| cargo -V | |
| rustup target add ${{ matrix.target }} | |
| - name: Build provider DLL (release) | |
| run: cargo build --release --target ${{ matrix.target }} -p win_svg_thumbs | |
| - name: Build gauntlet (release) | |
| # The gauntlet loads the DLL with LoadLibraryW, so its own bitness must | |
| # match: a 64-bit process cannot load the 32-bit provider. Building it | |
| # per-target is what makes the x86 lane meaningful rather than a | |
| # 64-bit harness pretending. | |
| # | |
| # Release so the harness is not the bottleneck in the throughput and | |
| # churn suites; the DLL under test is what we want to measure. | |
| run: cargo build --release --target ${{ matrix.target }} -p gauntlet | |
| - name: Record binary hashes | |
| run: | | |
| $out = "target/${{ matrix.target }}/release" | |
| $dll = Get-FileHash "$out/${{ matrix.dll }}" -Algorithm SHA256 | |
| $exe = Get-FileHash "$out/gauntlet.exe" -Algorithm SHA256 | |
| Write-Host "arch : ${{ matrix.arch }} (${{ matrix.target }})" | |
| Write-Host "dll : $($dll.Hash)" | |
| Write-Host "gauntlet: $($exe.Hash)" | |
| - name: List available suites | |
| # Only meaningful when the harness can actually run on this host: the | |
| # arm64 harness runs natively here, x86 runs under WOW64, and both are | |
| # fine on their build machines. | |
| run: ./target/${{ matrix.target }}/release/gauntlet.exe list | |
| - name: Stage binaries under a stable layout | |
| run: | | |
| New-Item -ItemType Directory -Force -Path staged | Out-Null | |
| Copy-Item "target/${{ matrix.target }}/release/${{ matrix.dll }}" staged/ | |
| Copy-Item "target/${{ matrix.target }}/release/gauntlet.exe" staged/ | |
| Get-ChildItem "target/${{ matrix.target }}/release/*.pdb" -ErrorAction SilentlyContinue | | |
| Where-Object { $_.Name -match 'win_svg_thumbs|gauntlet' } | | |
| Copy-Item -Destination staged/ | |
| # Record the provider filename so the test lanes do not have to know | |
| # the per-architecture naming from build.rs. | |
| "${{ matrix.dll }}" | Out-File staged/dll-name.txt -Encoding ascii -NoNewline | |
| Get-ChildItem staged | Format-Table Name, Length | |
| - name: Upload binaries | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: binaries-${{ matrix.arch }} | |
| path: staged/** | |
| if-no-files-found: error | |
| retention-days: 7 | |
| # --------------------------------------------------------------- | |
| corpus: | |
| name: Fetch real-world SVG corpus | |
| runs-on: windows-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Restore cached corpus | |
| id: corpus-cache | |
| uses: actions/cache@v6 | |
| with: | |
| path: svg-corpus | |
| # Keyed on the pinned commit and the selection recipe, so changing | |
| # either produces a fresh corpus and nothing else does. | |
| key: svg-corpus-fluent-${{ env.FLUENT_ICONS_SHA }}-v1 | |
| - name: Fetch and select corpus (cache miss only) | |
| if: steps.corpus-cache.outputs.cache-hit != 'true' | |
| run: | | |
| # Sparse checkout of only the SVG variants we sample, at a pinned | |
| # commit. Blob-filtered so the huge non-SVG assets are never fetched. | |
| git clone --filter=blob:none --no-checkout https://github.com/microsoft/fluentui-system-icons.git fluent | |
| Push-Location fluent | |
| git sparse-checkout init --no-cone | |
| git sparse-checkout set 'assets/*/SVG/*_24_regular.svg' 'assets/*/SVG/*_48_filled.svg' 'assets/*/SVG/*_20_regular.svg' | |
| git checkout $env:FLUENT_ICONS_SHA | |
| Pop-Location | |
| $all = Get-ChildItem fluent/assets -Recurse -Filter *.svg | Sort-Object FullName | |
| Write-Host "fetched $($all.Count) SVGs from fluentui-system-icons@$env:FLUENT_ICONS_SHA" | |
| if ($all.Count -lt 200) { throw "too few SVGs fetched ($($all.Count)); the sparse-checkout patterns are probably stale" } | |
| # Stride sampling across the alphabetically sorted set, so the corpus | |
| # spans the whole icon library instead of the first N names. | |
| $target = 800 | |
| $stride = [Math]::Max(1, [int]($all.Count / $target)) | |
| $sel = for ($i = 0; $i -lt $all.Count; $i += $stride) { $all[$i] } | |
| New-Item -ItemType Directory -Force -Path svg-corpus | Out-Null | |
| $manifest = @("# microsoft/fluentui-system-icons @ $env:FLUENT_ICONS_SHA (MIT); downloaded for testing only, not vendored", 'name,repo_path,bytes,sha256') | |
| $root = (Resolve-Path fluent).Path | |
| $i = 0 | |
| foreach ($f in $sel) { | |
| $icon = ($f.Directory.Parent.Name -replace '[^A-Za-z0-9_-]', '_') | |
| $name = '{0:d4}_{1}_{2}' -f $i, $icon, ($f.Name -replace '[^A-Za-z0-9._-]', '_') | |
| Copy-Item $f.FullName (Join-Path svg-corpus $name) | |
| $rel = $f.FullName.Substring($root.Length + 1) -replace '\\', '/' | |
| $manifest += "$name,$rel,$($f.Length),$((Get-FileHash $f.FullName -Algorithm SHA256).Hash)" | |
| $i++ | |
| } | |
| $manifest | Out-File svg-corpus/MANIFEST.csv -Encoding utf8 | |
| Write-Host "selected $i SVGs (stride $stride) into svg-corpus/" | |
| Remove-Item -Recurse -Force fluent | |
| - name: Report corpus size | |
| run: | | |
| $n = (Get-ChildItem svg-corpus -Filter *.svg -ErrorAction SilentlyContinue).Count | |
| Write-Host "corpus contains $n SVG files" | |
| if ($n -lt 100) { throw "corpus too small ($n files)" } | |
| - name: Upload corpus | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: svg-corpus | |
| path: svg-corpus | |
| if-no-files-found: error | |
| retention-days: 7 | |
| # --------------------------------------------------------------- | |
| gauntlet: | |
| name: x64 / ${{ matrix.suite }} | |
| needs: [build, corpus] | |
| runs-on: windows-latest | |
| timeout-minutes: ${{ matrix.timeout }} | |
| strategy: | |
| # One failing suite must not cancel the others: the whole point is to get | |
| # a complete picture of what broke in a single run. | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - suite: api-misuse | |
| timeout: 20 | |
| - suite: stream-faults | |
| timeout: 30 | |
| - suite: render | |
| timeout: 30 | |
| - suite: adversarial | |
| timeout: 45 | |
| - suite: svgz | |
| timeout: 30 | |
| - suite: size-limits | |
| timeout: 30 | |
| - suite: lifecycle | |
| timeout: 30 | |
| - suite: concurrency | |
| timeout: 30 | |
| - suite: churn | |
| timeout: 45 | |
| - suite: breadth | |
| timeout: 45 | |
| steps: | |
| - name: Skip suites not selected by workflow_dispatch | |
| id: gate | |
| run: | | |
| $requested = '${{ inputs.suites }}'.Trim() | |
| if ($requested -and ($requested -split '\s+') -notcontains '${{ matrix.suite }}') { | |
| Write-Host "suite '${{ matrix.suite }}' not in the requested set ($requested); skipping" | |
| "run=false" >> $env:GITHUB_OUTPUT | |
| } else { | |
| "run=true" >> $env:GITHUB_OUTPUT | |
| } | |
| - uses: actions/download-artifact@v8 | |
| if: steps.gate.outputs.run == 'true' | |
| with: | |
| name: binaries-x64 | |
| path: bin | |
| - name: Download corpus (breadth suite only) | |
| if: steps.gate.outputs.run == 'true' && matrix.suite == 'breadth' | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: svg-corpus | |
| path: svg-corpus | |
| - name: Enable WER LocalDumps for the gauntlet process | |
| if: steps.gate.outputs.run == 'true' | |
| run: | | |
| # Full dumps for anything that faults, so a crash in a child suite | |
| # process leaves a debuggable artifact rather than just an exit code. | |
| New-Item -ItemType Directory -Force -Path "$env:GITHUB_WORKSPACE\dumps" | Out-Null | |
| reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting" /v DontShowUI /t REG_DWORD /d 1 /f | |
| reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\gauntlet.exe" /v DumpType /t REG_DWORD /d 2 /f | |
| reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\gauntlet.exe" /v DumpCount /t REG_DWORD /d 10 /f | |
| reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\gauntlet.exe" /v DumpFolder /t REG_EXPAND_SZ /d "$env:GITHUB_WORKSPACE\dumps" /f | |
| - name: Record run start | |
| if: steps.gate.outputs.run == 'true' | |
| run: "\"RUN_START=$(Get-Date -Format o)\" >> $env:GITHUB_ENV" | |
| - name: Run suite | |
| id: run | |
| if: steps.gate.outputs.run == 'true' | |
| continue-on-error: true | |
| run: | | |
| $seed = '${{ inputs.seed }}'.Trim() | |
| $scale = '${{ inputs.scale }}'.Trim() | |
| if (-not $scale) { $scale = '1' } | |
| $args = @('run', '--suite', '${{ matrix.suite }}', | |
| '--dll', "$env:GITHUB_WORKSPACE\bin\win_svg_thumbs_x64.dll", | |
| '--scale', $scale, | |
| '--corpus', "$env:GITHUB_WORKSPACE\svg-corpus", | |
| '--work-dir', "$env:GITHUB_WORKSPACE\gauntlet-work") | |
| if ($seed) { $args += @('--seed', $seed) } | |
| Write-Host "gauntlet $($args -join ' ')" | |
| & "$env:GITHUB_WORKSPACE\bin\gauntlet.exe" @args | |
| exit $LASTEXITCODE | |
| - name: Check the Windows event log for crash events | |
| if: always() && steps.gate.outputs.run == 'true' | |
| run: | | |
| # A suite child that faults produces an Application Error entry even | |
| # though the supervisor survives, so the event log is the independent | |
| # confirmation that the exit-code classification was right. | |
| $since = [datetime]::Parse($env:RUN_START) | |
| $events = Get-WinEvent -FilterHashtable @{ LogName = 'Application'; StartTime = $since } -ErrorAction SilentlyContinue | | |
| Where-Object { $_.ProviderName -in @('Application Error', 'Windows Error Reporting') -and $_.Message -match 'gauntlet' } | |
| if ($events) { | |
| foreach ($e in $events) { | |
| Write-Host "======== $($e.ProviderName) event $($e.Id) @ $($e.TimeCreated) ========" | |
| Write-Host $e.Message | |
| } | |
| } else { | |
| Write-Host 'No Application Error / WER events for gauntlet.exe during this run.' | |
| } | |
| exit 0 | |
| - name: Analyze crash dumps with cdb | |
| if: always() && steps.gate.outputs.run == 'true' | |
| run: | | |
| $dumps = Get-ChildItem "$env:GITHUB_WORKSPACE\dumps" -Filter *.dmp -ErrorAction SilentlyContinue | |
| if (-not $dumps) { Write-Host 'No crash dumps were produced.'; exit 0 } | |
| $cdb = 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe' | |
| if (-not (Test-Path $cdb)) { Write-Host 'cdb.exe not found; dumps uploaded unanalyzed.'; exit 0 } | |
| $env:_NT_SYMBOL_PATH = "srv*C:\symbols*https://msdl.microsoft.com/download/symbols;$env:GITHUB_WORKSPACE\bin" | |
| foreach ($d in ($dumps | Select-Object -First 4)) { | |
| Write-Host "================ $($d.Name) ================" | |
| & $cdb -z $d.FullName -c '!analyze -v; lmv; ~*k 40; q' 2>&1 | Select-Object -First 200 | Write-Host | |
| } | |
| exit 0 | |
| - name: Upload diagnostics | |
| if: always() && steps.gate.outputs.run == 'true' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: diagnostics-${{ matrix.suite }} | |
| path: | | |
| dumps/*.dmp | |
| gauntlet-work/** | |
| bin/*.pdb | |
| if-no-files-found: ignore | |
| retention-days: 7 | |
| - name: Record suite verdict | |
| if: always() | |
| run: | | |
| if ('${{ steps.gate.outputs.run }}' -ne 'true') { | |
| "skipped" | Out-File verdict.txt -Encoding utf8 -NoNewline | |
| } else { | |
| "${{ steps.run.outcome }}" | Out-File verdict.txt -Encoding utf8 -NoNewline | |
| } | |
| - name: Upload verdict | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: verdict-x64-${{ matrix.suite }} | |
| path: verdict.txt | |
| retention-days: 7 | |
| - name: Fail the job if the suite failed | |
| if: always() && steps.gate.outputs.run == 'true' && steps.run.outcome != 'success' | |
| run: | | |
| Write-Host 'Suite ${{ matrix.suite }} did not pass. See the suite output above for the ' | |
| Write-Host 'per-check breakdown, the last case in flight if it crashed or hung, and the ' | |
| Write-Host 'exact command to replay it locally.' | |
| exit 1 | |
| # --------------------------------------------------------------- | |
| # Architecture coverage. | |
| # | |
| # The x64 matrix above splits by suite so a failure points at one suite's job. | |
| # These lanes instead run every suite in a single job per architecture, because | |
| # the question here is "does this build behave the same everywhere", not "which | |
| # suite failed" - and the supervisor already isolates each suite in its own | |
| # child process with its own watchdog, so one crashing suite still leaves the | |
| # rest of the lane's results intact. | |
| arch: | |
| name: ${{ matrix.lane }} | |
| needs: [build, corpus] | |
| runs-on: ${{ matrix.runs-on }} | |
| timeout-minutes: 60 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # 32-bit provider on a 64-bit host, under WOW64. This is what the x64 | |
| # MSI installs for 32-bit applications, and it is the configuration | |
| # where usize is 32 bits - so the overflow guards around pitch and | |
| # buffer-size arithmetic in the bitmap copy are load-bearing here and | |
| # nowhere else. | |
| - lane: x86 (WOW64) | |
| arch: x86 | |
| runs-on: windows-latest | |
| # Native ARM64 on ARM hardware. | |
| - lane: arm64 (native) | |
| arch: arm64 | |
| runs-on: windows-11-arm | |
| # The x64 binaries on an ARM host, i.e. running under Windows' | |
| # x64-on-ARM emulation. This is a real end-user configuration - an ARM | |
| # PC running a 64-bit x86 shell host - and it exercises the emulator's | |
| # handling of the D2D/WARP path rather than any new provider code. | |
| - lane: x64 on ARM (emulated) | |
| arch: x64 | |
| runs-on: windows-11-arm | |
| steps: | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: binaries-${{ matrix.arch }} | |
| path: bin | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: svg-corpus | |
| path: svg-corpus | |
| - name: Report host and binary architecture | |
| id: hostinfo | |
| run: | | |
| # Worth recording explicitly: on the emulated lane the host is ARM64 | |
| # and the process is x64, and that mismatch is the entire point. | |
| Write-Host "host processor architecture : $env:PROCESSOR_ARCHITECTURE" | |
| Write-Host "host identifier : $env:PROCESSOR_IDENTIFIER" | |
| Write-Host "lane : ${{ matrix.lane }}" | |
| $dllName = (Get-Content bin/dll-name.txt -Raw).Trim() | |
| Write-Host "provider DLL : $dllName" | |
| "dll_name=$dllName" >> $env:GITHUB_OUTPUT | |
| - name: Enable WER LocalDumps for the gauntlet process | |
| run: | | |
| New-Item -ItemType Directory -Force -Path "$env:GITHUB_WORKSPACE\dumps" | Out-Null | |
| reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting" /v DontShowUI /t REG_DWORD /d 1 /f | |
| reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\gauntlet.exe" /v DumpType /t REG_DWORD /d 2 /f | |
| reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\gauntlet.exe" /v DumpCount /t REG_DWORD /d 10 /f | |
| reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\gauntlet.exe" /v DumpFolder /t REG_EXPAND_SZ /d "$env:GITHUB_WORKSPACE\dumps" /f | |
| - name: Record run start | |
| run: "\"RUN_START=$(Get-Date -Format o)\" >> $env:GITHUB_ENV" | |
| - name: Run every suite | |
| id: run | |
| continue-on-error: true | |
| run: | | |
| $seed = '${{ inputs.seed }}'.Trim() | |
| $scale = '${{ inputs.scale }}'.Trim() | |
| if (-not $scale) { $scale = '1' } | |
| $args = @('run', | |
| '--dll', "$env:GITHUB_WORKSPACE\bin\${{ steps.hostinfo.outputs.dll_name }}", | |
| '--scale', $scale, | |
| '--corpus', "$env:GITHUB_WORKSPACE\svg-corpus", | |
| '--work-dir', "$env:GITHUB_WORKSPACE\gauntlet-work") | |
| if ($seed) { $args += @('--seed', $seed) } | |
| Write-Host "gauntlet $($args -join ' ')" | |
| & "$env:GITHUB_WORKSPACE\bin\gauntlet.exe" @args | |
| exit $LASTEXITCODE | |
| - name: Check the Windows event log for crash events | |
| if: always() | |
| run: | | |
| $since = [datetime]::Parse($env:RUN_START) | |
| $events = Get-WinEvent -FilterHashtable @{ LogName = 'Application'; StartTime = $since } -ErrorAction SilentlyContinue | | |
| Where-Object { $_.ProviderName -in @('Application Error', 'Windows Error Reporting') -and $_.Message -match 'gauntlet' } | |
| if ($events) { | |
| foreach ($e in $events) { | |
| Write-Host "======== $($e.ProviderName) event $($e.Id) @ $($e.TimeCreated) ========" | |
| Write-Host $e.Message | |
| } | |
| } else { | |
| Write-Host 'No Application Error / WER events for gauntlet.exe during this run.' | |
| } | |
| exit 0 | |
| - name: Upload diagnostics | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: diagnostics-${{ matrix.arch }}-${{ matrix.runs-on }} | |
| path: | | |
| dumps/*.dmp | |
| gauntlet-work/** | |
| bin/*.pdb | |
| if-no-files-found: ignore | |
| retention-days: 7 | |
| - name: Record lane verdict | |
| if: always() | |
| run: '"${{ steps.run.outcome }}" | Out-File verdict.txt -Encoding utf8 -NoNewline' | |
| - name: Upload verdict | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: verdict-${{ matrix.arch }}-on-${{ matrix.runs-on }} | |
| path: verdict.txt | |
| retention-days: 7 | |
| - name: Fail the job if any suite failed | |
| if: always() && steps.run.outcome != 'success' | |
| run: | | |
| Write-Host 'Lane ${{ matrix.lane }} did not pass. The gauntlet summary above lists which ' | |
| Write-Host 'suites failed, the last case in flight for any crash or hang, and a replay command.' | |
| exit 1 | |
| # --------------------------------------------------------------- | |
| verdict: | |
| name: Gauntlet verdict | |
| needs: [gauntlet, arch] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| pattern: verdict-* | |
| path: verdicts | |
| - name: Summarize | |
| run: | | |
| set -u | |
| summary() { echo "$@" >> "$GITHUB_STEP_SUMMARY"; } | |
| summary "## SVG thumbnail provider - test gauntlet" | |
| summary "" | |
| failed=0 | |
| ran=0 | |
| # Per-suite results on the primary architecture. | |
| summary "### x64 suites" | |
| summary "" | |
| summary "| Suite | Result |" | |
| summary "|---|---|" | |
| for dir in verdicts/verdict-x64-*; do | |
| [ -d "$dir" ] || continue | |
| suite="${dir#verdicts/verdict-x64-}" | |
| outcome="$(cat "$dir/verdict.txt" 2>/dev/null || echo unknown)" | |
| case "$outcome" in | |
| success) icon="PASS" ;; | |
| skipped) icon="skipped" ;; | |
| *) icon="FAIL"; failed=$((failed + 1)) ;; | |
| esac | |
| [ "$outcome" = "skipped" ] || ran=$((ran + 1)) | |
| summary "| \`$suite\` | $icon |" | |
| echo "x64/$suite: $outcome" | |
| done | |
| # Whole-gauntlet results per architecture lane. The naming is | |
| # verdict-<arch>-on-<runner>, which distinguishes native arm64 from | |
| # x64 running under ARM emulation even though both use windows-11-arm. | |
| summary "" | |
| summary "### Architecture lanes (all suites)" | |
| summary "" | |
| summary "| Binaries | Host runner | Result |" | |
| summary "|---|---|---|" | |
| for dir in verdicts/verdict-*-on-*; do | |
| [ -d "$dir" ] || continue | |
| rest="${dir#verdicts/verdict-}" | |
| arch="${rest%%-on-*}" | |
| host="${rest#*-on-}" | |
| outcome="$(cat "$dir/verdict.txt" 2>/dev/null || echo unknown)" | |
| case "$outcome" in | |
| success) icon="PASS" ;; | |
| skipped) icon="skipped" ;; | |
| *) icon="FAIL"; failed=$((failed + 1)) ;; | |
| esac | |
| [ "$outcome" = "skipped" ] || ran=$((ran + 1)) | |
| summary "| \`$arch\` | \`$host\` | $icon |" | |
| echo "$arch on $host: $outcome" | |
| done | |
| summary "" | |
| if [ "$ran" -eq 0 ]; then | |
| summary "No suites ran." | |
| exit 1 | |
| fi | |
| if [ "$failed" -gt 0 ]; then | |
| summary "**$failed of $ran lanes/suites failed.** Open the failing job for the per-check breakdown; a crash or hang names the exact input and prints a replay command." | |
| exit 1 | |
| fi | |
| summary "All $ran lanes/suites passed." |