gui-dod (per-PR: scroll-60fps + unhappy-path) and gui-dod-nightly (rss-flat, schedule/workflow_dispatch) are live in ci.yml, driving GUI's newly-landed gui/tests/dod/run.sh + gui-dod-harness. No Xvfb step: run.sh already runs QT_QPA_PLATFORM=offscreen itself. Each gate forced red once before being trusted (tests/integration/README.md has the transcripts): VELOX_DOD_FRAME_BUDGET_MS=0.01 for scroll-60fps, VELOX_DOD_RSS_SLACK_KIB=-999999999 for rss-flat, and — since run.sh always starts a working mockd — a direct gui-dod-harness invocation against an unreachable socket for unhappy-path, which hit the harness's own 75s watchdog exactly as documented. Recorded GUI's live finding (gui/docs/proto-requests-m1.md) that mockd --drop-connection is a no-op over the UDS transport, so unhappy-path's drop-connection phase can't yet exercise a real drop — coordinating with PROTO on the fix rather than working around it locally. gui-dod stays required regardless: its other two phases and the crash/hang/watchdog paths still catch real regressions. Added gui-dod to BRANCH_PROTECTION.md's required-checks table. ADR 0019: the live-veloxd conformance runner (run.sh step 3b, already unconditional inside the already-required conformance job) stays required as PROTO's xfail list shrinks (18 entries now, down from 34; 57/57 fixtures passing on main). No CI change needed — it was already inside a required check; this records the decision not to carve out an exception for it. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01RBPR7iM3YPyxrjWsVtZDPJ
304 lines
12 KiB
YAML
304 lines
12 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
schedule:
|
|
- cron: '17 3 * * *' # nightly-integration only; every other job stays PR/push-triggered
|
|
workflow_dispatch: # lets a human fire nightly-integration on demand
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
# GitHub-hosted runners are Ubuntu 24.04; the project targets 26.04. bootstrap.sh warns
|
|
# but proceeds. Revisit when 26.04 runners exist.
|
|
DEBIAN_FRONTEND: noninteractive
|
|
|
|
jobs:
|
|
# --- fast lint jobs: no compiler, no heavy deps -------------------------------------
|
|
clang-format:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Install clang-format
|
|
run: sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends clang-format
|
|
- name: Check formatting
|
|
run: |
|
|
shopt -s globstar nullglob
|
|
files=(core/**/*.{cpp,hpp} daemon/**/*.{cpp,hpp} cli/**/*.{cpp,hpp} nmhost/**/*.{cpp,hpp})
|
|
if [ ${#files[@]} -eq 0 ]; then echo "no C++ sources yet — skipping"; exit 0; fi
|
|
printf '%s\n' "${files[@]}"
|
|
clang-format --dry-run --Werror "${files[@]}"
|
|
|
|
testserver:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-python@v5
|
|
with:
|
|
python-version: '3.12'
|
|
- name: testserver self-test
|
|
run: python3 tools/testserver/selftest.py
|
|
|
|
bootstrap-script:
|
|
# Keeps tools/bootstrap.sh honest on the runner image: it must run clean and its
|
|
# --check must pass. ubuntu-latest is 24.04; the project ships on 26.04, so this
|
|
# exercises the 24.04 archive only. bootstrap-script-2604 below is what validates
|
|
# the package names against the release the project actually targets.
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- run: sudo ./tools/bootstrap.sh --with-clang
|
|
- run: ./tools/bootstrap.sh --check --with-clang
|
|
# Cheap: apt-cache only. Validates the M6 packaging names now so they can't rot
|
|
# unnoticed until M6.
|
|
- run: ./tools/bootstrap.sh --check --with-clang --packaging
|
|
|
|
bootstrap-script-2604:
|
|
# The project targets 26.04 and GitHub has no 26.04 runner image yet, so the one
|
|
# automated place bootstrap.sh runs is on the wrong release to catch a name that is
|
|
# valid on 24.04 and gone on 26.04 — which is exactly how libqt6svg6-dev reached a
|
|
# contributor's VM (gui/docs/pkg-qa-requests-m1.md R1/R2). Run the real install in a
|
|
# 26.04 container, with --with-clang: the fuzz toolchain had never been exercised
|
|
# anywhere (CORE ran clang++-21 directly because it can't sudo).
|
|
runs-on: ubuntu-latest
|
|
container: ubuntu:26.04
|
|
steps:
|
|
- name: Base tools for checkout
|
|
run: |
|
|
apt-get update -qq
|
|
apt-get install -y --no-install-recommends ca-certificates git sudo
|
|
- uses: actions/checkout@v4
|
|
- name: Full bootstrap on 26.04 (--with-clang)
|
|
run: ./tools/bootstrap.sh --with-clang
|
|
- name: Re-verify
|
|
run: ./tools/bootstrap.sh --check --with-clang
|
|
|
|
extension-lint:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- id: check
|
|
# "Has EXT landed?" is answered by a manifest, not by extension/package.json:
|
|
# a guard keyed to one filename passes vacuously the day EXT ships the lane
|
|
# under any other name. Skip only when the lane genuinely is not here; once a
|
|
# manifest exists, a missing lint entrypoint is a hard failure, not a skip.
|
|
run: |
|
|
manifest=""
|
|
for m in extension/manifest.json extension/src/manifest.json extension/public/manifest.json; do
|
|
if [ -f "$m" ]; then manifest="$m"; break; fi
|
|
done
|
|
if [ -z "$manifest" ]; then
|
|
echo "extension/ has not landed yet (no manifest.json) — skipping web-ext lint."
|
|
echo "present=false" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
echo "EXT has landed: $manifest"
|
|
echo "present=true" >> "$GITHUB_OUTPUT"
|
|
if [ ! -f extension/package.json ]; then
|
|
echo "::error::$manifest exists but extension/package.json does not — this job" \
|
|
"cannot lint the extension. Wire web-ext lint in here; do not let the check" \
|
|
"pass green over an unlinted lane."
|
|
exit 1
|
|
fi
|
|
- uses: actions/setup-node@v4
|
|
if: steps.check.outputs.present == 'true'
|
|
with:
|
|
node-version: '22'
|
|
- name: eslint (no-download-logic gate + general rules)
|
|
if: steps.check.outputs.present == 'true'
|
|
working-directory: extension
|
|
run: |
|
|
npm ci
|
|
npx eslint .
|
|
- name: web-ext lint
|
|
if: steps.check.outputs.present == 'true'
|
|
working-directory: extension
|
|
run: |
|
|
npx web-ext lint --source-dir .
|
|
|
|
# --- build + test matrix ----------------------------------------------------------
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
compiler: [gcc, clang]
|
|
env:
|
|
CC: ${{ matrix.compiler == 'gcc' && 'gcc' || 'clang' }}
|
|
CXX: ${{ matrix.compiler == 'gcc' && 'g++' || 'clang++' }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Bootstrap toolchain
|
|
run: sudo ./tools/bootstrap.sh --with-clang
|
|
- name: Configure
|
|
run: cmake --preset ci
|
|
- name: Build
|
|
run: cmake --build --preset ci
|
|
- name: Test
|
|
# -E '^conformance$' drops the end-to-end run.sh test (npm installs, its own
|
|
# mockd, ~24 s); the dedicated `conformance` job owns that one run. The native
|
|
# `conformance_cpp` test is not excluded and still runs on every matrix leg.
|
|
run: ctest --preset ci --output-on-failure -E '^conformance$'
|
|
|
|
sanitizers:
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
preset: [dev, tsan] # dev = ASan + UBSan
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Bootstrap toolchain
|
|
run: sudo ./tools/bootstrap.sh --with-clang
|
|
- name: Configure
|
|
run: cmake --preset ${{ matrix.preset }}
|
|
- name: Build
|
|
run: cmake --build --preset ${{ matrix.preset }}
|
|
- name: Test
|
|
# See the build job: the end-to-end run.sh test is the dedicated `conformance`
|
|
# job's; sanitizing a suite that shells out to its own unsanitized g++ build and
|
|
# a node process buys nothing. `conformance_cpp` still runs here under the sanitizer.
|
|
run: ctest --preset ${{ matrix.preset }} --output-on-failure -E '^conformance$'
|
|
env:
|
|
ASAN_OPTIONS: detect_leaks=1:halt_on_error=1
|
|
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
|
|
TSAN_OPTIONS: halt_on_error=1
|
|
|
|
clang-tidy:
|
|
# Advisory through M1 (see .clang-tidy WarningsAsErrors: ''); becomes required at M2.
|
|
runs-on: ubuntu-latest
|
|
continue-on-error: true
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- id: check
|
|
run: |
|
|
if ls core/CMakeLists.txt daemon/CMakeLists.txt >/dev/null 2>&1; then
|
|
echo "present=true" >> "$GITHUB_OUTPUT"
|
|
else echo "present=false" >> "$GITHUB_OUTPUT"; fi
|
|
- name: Bootstrap toolchain
|
|
if: steps.check.outputs.present == 'true'
|
|
run: sudo ./tools/bootstrap.sh
|
|
- name: Configure (for compile_commands.json)
|
|
if: steps.check.outputs.present == 'true'
|
|
run: cmake --preset dev
|
|
- name: Run clang-tidy on changed files
|
|
if: steps.check.outputs.present == 'true'
|
|
run: |
|
|
mapfile -t files < <(git diff --name-only --diff-filter=ACM \
|
|
"${{ github.event.pull_request.base.sha || 'HEAD~1' }}" HEAD \
|
|
-- '*.cpp' '*.hpp' || true)
|
|
[ ${#files[@]} -eq 0 ] && { echo "no C++ changes"; exit 0; }
|
|
printf '%s\n' "${files[@]}"
|
|
clang-tidy -p build/dev "${files[@]}"
|
|
- name: skipped
|
|
if: steps.check.outputs.present == 'false'
|
|
run: echo "no C++ lane has landed a CMakeLists yet — skipping clang-tidy"
|
|
|
|
conformance:
|
|
# The M0 exit gate. Proves the generated C++ daemon surface and the generated TS
|
|
# extension surface agree with contracts/fixtures without either side having run
|
|
# against the other. Required on every PR — branch protection is a repo setting,
|
|
# recorded in .github/BRANCH_PROTECTION.md.
|
|
#
|
|
# Canonical entry point is `ctest -L conformance`. tests/conformance/CMakeLists.txt
|
|
# (owned by PROTO) registers two tests under that label: `conformance`, which shells
|
|
# out to run.sh end to end, and `conformance_cpp`, the finer-grained native runner.
|
|
# CI drives it exactly as a developer does — one definition of "the suite passed",
|
|
# and PROTO's registration is on the exercised path so it cannot rot. See
|
|
# docs/adr/0014-conformance-runs-through-ctest.md.
|
|
#
|
|
# `noTestsAction: error` in the dev test preset is the rot guard: if the label ever
|
|
# matches nothing (registration deleted, typo), ctest exits non-zero instead of
|
|
# passing vacuously.
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Bootstrap toolchain
|
|
run: sudo ./tools/bootstrap.sh
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '22' # apt ships < 20; run.sh's TS replay runner needs >= 20
|
|
- name: Configure
|
|
run: cmake --preset dev
|
|
- name: Build the native conformance runner
|
|
run: cmake --build --preset dev --target velox_conformance_cpp
|
|
- name: Run conformance (ctest -L conformance)
|
|
run: ctest --preset dev -L conformance --output-on-failure
|
|
|
|
nightly-integration:
|
|
# Real veloxd + tools/testserver, 50 concurrent downloads mixing hostile modes,
|
|
# every completed file's SHA-256 checked against testserver's own /sha256/ route,
|
|
# veloxd's open-FD count checked flat across the run. Nightly, not per-PR: it's
|
|
# ~2 minutes of real network I/O against a local server, not a schema check.
|
|
# See tests/integration/README.md#nightly-integration-run for what each assertion
|
|
# catches and the forced-failure transcript proving it isn't vacuous.
|
|
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Bootstrap toolchain
|
|
run: sudo ./tools/bootstrap.sh
|
|
- name: Configure
|
|
run: cmake --preset dev
|
|
- name: Build veloxd
|
|
run: cmake --build --preset dev --target veloxd
|
|
- name: Nightly integration run
|
|
run: python3 tests/integration/nightly_run.py --veloxd build/dev/bin/veloxd --tasks 50 --timeout 180
|
|
|
|
gui-dod:
|
|
# Per-PR GUI M1 DoD gates (gui/docs/pkg-qa-requests-m1.md R3): scroll-60fps and
|
|
# unhappy-path. The 10-minute rss-flat gate is gui-dod-nightly, not here. GUI's
|
|
# harness defaults QT_QPA_PLATFORM=offscreen itself, so no Xvfb/compositor needed.
|
|
# See tests/integration/README.md#gui-m1-definition-of-done-gates-r3 for what each
|
|
# gate catches and the forced-failure transcript proving it isn't vacuous.
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Bootstrap toolchain
|
|
run: sudo ./tools/bootstrap.sh
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '22' # tools/mockd
|
|
- name: Configure + build
|
|
run: |
|
|
cmake --preset dev
|
|
cmake --build --preset dev --target gui-dod-harness
|
|
- name: Install mockd
|
|
run: cd tools/mockd && npm ci
|
|
- name: Gates
|
|
run: |
|
|
gui/tests/dod/run.sh scroll-60fps --json scroll.json
|
|
gui/tests/dod/run.sh unhappy-path --json unhappy.json
|
|
- uses: actions/upload-artifact@v4
|
|
if: always()
|
|
with:
|
|
name: gui-dod-${{ github.run_id }}
|
|
path: "*.json"
|
|
|
|
gui-dod-nightly:
|
|
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Bootstrap toolchain
|
|
run: sudo ./tools/bootstrap.sh
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '22'
|
|
- name: Configure + build
|
|
run: |
|
|
cmake --preset dev
|
|
cmake --build --preset dev --target gui-dod-harness
|
|
- run: cd tools/mockd && npm ci
|
|
- name: RSS soak (10 min)
|
|
run: gui/tests/dod/run.sh rss-flat --json rss.json
|
|
- uses: actions/upload-artifact@v4
|
|
if: always()
|
|
with:
|
|
name: gui-dod-rss-${{ github.run_id }}
|
|
path: rss.json
|