Skip to content

CI Windows

CI Windows #342

Workflow file for this run

name: CI Windows
on:
workflow_dispatch:
inputs:
ref:
description: "Branch / tag / SHA to run against"
required: false
default: ""
iterations:
description: "Soak iteration count (default 50 on dispatch)"
required: false
default: "50"
schedule:
- cron: '0 6 * * *'
push:
branches:
- master
- windows-iter
- 'ci-debug'
- '*-debug'
paths:
- 'CMakeLists.txt'
- 'include/sys/*'
- 'src/common/*'
- 'src/windows/*'
- 'test/*'
- '.github/workflows/ci-windows.yml'
pull_request:
branches:
- master
paths:
- 'CMakeLists.txt'
- 'src/common/*'
- 'src/windows/*'
- 'test/*'
- '.github/workflows/ci-windows.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
KQUEUE_DEBUG: yes
jobs:
windows-build:
#
# Job-level cap is generous so that when "Run tests" trips its
# own per-step timeout (and fails the job), the tmate-on-failure
# step below has room to come up and emit the SSH URL. Without
# the headroom the runner is killed before tmate runs.
#
timeout-minutes: 90
runs-on: windows-2022
name: CI-windows (build)
strategy:
fail-fast: false
matrix:
include:
- build_type: Release
sanitize: ""
- build_type: Debug
sanitize: ""
# MSVC's /fsanitize=address has been GA since VS 2019
# 16.9. No LSan equivalent in MSVC's ASan port, but the
# heap/UAF half is enough to catch the kinds of bugs the
# synthetic file paths and IOCP overlap-as-knote dispatch
# have hit historically (issues #155 etc.).
- build_type: Debug
sanitize: "asan"
steps:
- uses: actions/checkout@v5
- name: Set up MSVC environment
if: matrix.sanitize == 'asan'
uses: ilammy/msvc-dev-cmd@v1
with:
arch: x64
- name: Configure build system
run: |
cmake --version
$extra = ""
if ("${{ matrix.sanitize }}" -eq "asan") {
$extra = "-DCMAKE_C_FLAGS=/fsanitize=address /Zi"
}
cmake -S . -B build_x64 -A x64 -G "Visual Studio 17 2022" -DSTATICLIB=ON -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_VERBOSE_MAKEFILE=ON -DENABLE_TESTING=YES $extra
- name: Build libkqueue
run: |
cmake --build build_x64 --target install --config ${{ matrix.build_type }} --parallel 1
- name: Build tests
run: |
cmake --build build_x64 --target libkqueue-test --config ${{ matrix.build_type }} --parallel 1
- name: Gated tests
if: always()
shell: pwsh
run: |
$ErrorActionPreference = 'Continue'
# libkqueue-test.exe links against libkqueue.dll, which the build
# places in build_x64\<config>; without it on PATH the exe fails to
# load (STATUS_DLL_NOT_FOUND) before main(), so --list-gated emits
# nothing. The test-run steps prepend the same directory.
$env:PATH = "$PWD\build_x64\${{ matrix.build_type }};$env:PATH"
$bin = ".\build_x64\test\${{ matrix.build_type }}\libkqueue-test.exe"
$gated = $null
if (Test-Path $bin) {
$gated = & $bin --list-gated=windows 2>$null
$LASTEXITCODE = 0
}
$lines = [System.Collections.Generic.List[string]]@(
"## Gated on windows",
"| Suite | Test | Reason |",
"| --- | --- | --- |"
)
if ($gated) {
$gated | ForEach-Object {
$cols = $_ -split "`t", 3
$cols[2] = $cols[2] -replace '\|', '&#124;'
$lines.Add("| $($cols[0]) | $($cols[1]) | $($cols[2]) |")
}
} else {
$lines.Add("_none_")
}
if ($env:GITHUB_STEP_SUMMARY) {
$lines | Out-File -Append -Encoding utf8 $env:GITHUB_STEP_SUMMARY
}
- name: "Locate cdb.exe"
id: cdb
shell: pwsh
run: |
$cdb = Get-ChildItem -Path "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64" -Filter cdb.exe -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName
if (-not $cdb) {
$cdb = Get-ChildItem -Path "C:\Program Files (x86)\Windows Kits" -Recurse -Filter cdb.exe -ErrorAction SilentlyContinue | Where-Object { $_.FullName -like "*x64*" } | Select-Object -First 1 -ExpandProperty FullName
}
if (-not $cdb) { Write-Error "cdb.exe not found"; exit 1 }
Write-Host "cdb at: $cdb"
"path=$cdb" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
- name: "Probe: threading_close in isolation (with watchdog stack dump)"
timeout-minutes: 3
shell: cmd
env:
KQUEUE_DEBUG: 1
KQUEUE_DEBUG_OUTPUT: stderr
run: |
set PATH=%CD%\build_x64\${{ matrix.build_type }};%PATH%
.\build_x64\test\${{ matrix.build_type }}\libkqueue-test.exe -n 1 --watchdog-timeout=60 "--watchdog-cmd=\"${{ steps.cdb.outputs.path }}\" -pv -c \"~*kb;qd\" -p" threading:0 > test_iso.log 2>&1
set RC=%ERRORLEVEL%
type test_iso.log
exit /b %RC%
continue-on-error: true
- name: "Upload isolation log"
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: test-iso-${{ matrix.build_type }}-${{ matrix.sanitize || 'plain' }}
path: test_iso.log
retention-days: 3
if-no-files-found: ignore
- name: Run tests
timeout-minutes: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'schedule') && 60 || 8 }}
shell: cmd
env:
KQUEUE_DEBUG: 1
KQUEUE_DEBUG_OUTPUT: stderr
run: |
set PATH=%CD%\build_x64\${{ matrix.build_type }};%PATH%
.\build_x64\test\${{ matrix.build_type }}\libkqueue-test.exe -n ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.iterations || (github.event_name == 'schedule' && 50 || 1) }} --watchdog-timeout=120 "--watchdog-cmd=\"${{ steps.cdb.outputs.path }}\" -pv -c \"~*kb;qd\" -p" > test_full.log 2>&1
set RC=%ERRORLEVEL%
type test_full.log
exit /b %RC%
- name: "Upload test log on failure"
if: ${{ failure() }}
uses: actions/upload-artifact@v4
with:
name: test-log-${{ matrix.build_type }}-${{ matrix.sanitize || 'plain' }}
path: test_full.log
retention-days: 3
#
# Interactive debug session, FreeRADIUS-style: push to a branch
# named exactly `ci-debug` and any failing
# job pauses with an SSH rendezvous point. Limited to the
# actor's GitHub SSH keys. Detached mode + URL artifact so
# the connection point is fetchable via gh api - the live log
# is gated and not scrapeable from outside the web UI.
#
- name: "Debug: Start tmate on failure"
id: tmate
uses: mxschmitt/action-tmate@v3
with:
limit-access-to-actor: true
detached: true
if: ${{ failure() && github.ref == 'refs/heads/ci-debug' }}
- name: "Debug: capture tmate URL"
if: ${{ failure() && github.ref == 'refs/heads/ci-debug' }}
shell: bash
env:
TMATE_SSH: ${{ steps.tmate.outputs.ssh-command }}
run: |
# action-tmate exposes the rendezvous URL via the step output
# ssh-command (see mxschmitt/action-tmate src/index.js:221).
# That avoids querying the tmate socket directly, which on
# Windows runners landed under /c/msys64/tmp and required
# waiting for the format variable to be populated.
echo "JOB: ${{ matrix.build_type }} ${{ matrix.sanitize }}" > tmate_url.txt
echo "URL: $TMATE_SSH" >> tmate_url.txt
cat tmate_url.txt
- name: "Debug: upload tmate URL"
if: ${{ failure() && github.ref == 'refs/heads/ci-debug' }}
uses: actions/upload-artifact@v4
with:
name: tmate-url-${{ matrix.build_type }}-${{ matrix.sanitize || 'plain' }}
path: tmate_url.txt
retention-days: 1
- name: "Debug: hold runner for SSH session"
if: ${{ failure() && github.ref == 'refs/heads/ci-debug' }}
shell: bash
run: |
# Keep the runner alive for the SSH session. Bounded by the
# job's timeout-minutes. Exit early if /tmp/done exists so
# the SSH user can finish gracefully.
for i in $(seq 1 60); do
[ -f /tmp/done ] && break
sleep 60
done