Skip to content
Open
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
8 changes: 4 additions & 4 deletions .agents/skills/test-writer/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,12 @@ If a path cannot be tested without a real GPU/window and cannot be reached throu
## Where to Write Tests

- Create or extend `*_tests.zig` files **alongside** the source files (same directory)
- Example: tests for `modules/engine-graphics/src/vulkan/swapchain.zig` go in `modules/engine-graphics/src/vulkan/swapchain_tests.zig`
- After creating a new test file, register it in `src/tests.zig`:
- Example: tests for `modules/engine-graphics/src/vulkan/pipeline_manager.zig` go in `modules/engine-graphics/src/vulkan/pipeline_manager_tests.zig`
- After creating a new test file, register it with a file-relative import in the owning module's `test_root.zig`:
```zig
_ = @import("engine-graphics").vulkan.swapchain_tests;
_ = @import("vulkan/pipeline_manager_tests.zig");
```
Prefer importing the owning module root from `src/tests.zig`.
Named dependency-module imports in `src/tests.zig` do not register module tests.

## What to Test — Priorities

Expand Down
2 changes: 1 addition & 1 deletion .githooks/pre-push
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ echo "To bypass these checks in an emergency, use: git push --no-verify"
echo ""

echo "[1/2] Checking formatting..."
devenv shell --profile unit -- zig fmt --check src/
devenv shell --profile unit -- zig fmt --check src/ modules/ build.zig

echo "[2/2] Running full test suite..."
devenv shell --profile unit -- zig build test
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/run-with-log/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ runs:
chmod +x "$COMMAND_FILE"

echo "${{ inputs.name }} start: $(date -u +%Y-%m-%dT%H:%M:%SZ)" | tee "${{ inputs.log-file }}"
timeout --preserve-status "${{ inputs.timeout }}" bash "$COMMAND_FILE" 2>&1 | tee -a "${{ inputs.log-file }}"
timeout --preserve-status --kill-after=30s "${{ inputs.timeout }}" bash -euo pipefail "$COMMAND_FILE" 2>&1 | tee -a "${{ inputs.log-file }}"
END=$(date +%s)
{
echo "### ${{ inputs.name }}"
Expand Down
10 changes: 5 additions & 5 deletions .github/actions/setup-devenv/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ runs:
- name: Install Nix (primary)
id: nix_install_primary
continue-on-error: true
uses: DeterminateSystems/nix-installer-action@v16
uses: DeterminateSystems/nix-installer-action@e50d5f73bfe71c2dd0aa4218de8f4afa59f8f81d # v16

- name: Install Nix (fallback)
if: steps.nix_install_primary.outcome == 'failure'
uses: cachix/install-nix-action@v31
uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31
with:
extra_nix_config: |
experimental-features = nix-command flakes
Expand All @@ -41,22 +41,22 @@ runs:
# Pulling from it avoids building devenv and its module dependencies.
# Public cache: no authToken needed; skipPush because we only pull.
- name: Configure devenv Cachix cache
uses: cachix/cachix-action@v16
uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16
with:
name: devenv
skipPush: true

- name: Install devenv
shell: bash
run: nix profile add nixpkgs#devenv
run: timeout --kill-after=30s 10m nix profile add github:NixOS/nixpkgs/42f17a57f4f6e33b3de3dca0a2a5ea5233169d02#devenv

- name: Verify devenv installation
shell: bash
run: devenv version

- name: Cache Nix Store
continue-on-error: true
uses: nix-community/cache-nix-action@v7
uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7
with:
primary-key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-${{ hashFiles('devenv.nix', 'devenv.yaml', 'devenv.lock') }}
restore-prefixes-first-match: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-
Expand Down
25 changes: 15 additions & 10 deletions .github/actions/setup-lavapipe/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,21 @@ runs:
shell: bash
run: |
set -euo pipefail
# Lavapipe ICD and the Khronos validation layers are resolved from the
# floating nixpkgs-unstable flake registry, matching how dev's CI has
# always resolved them. They are NOT pinned to devenv.lock's nixpkgs:
# the pinned (nixos-unstable) rev does not keep vulkan-validation-layers
# in the binary cache, so pinning forces a from-source build that fails
# (missing git in the sandbox). The integration-test correctness signal
# is governed by the binary's SDL3/vulkan-loader versions, which the
# devenv nixpkgs pin already locks to the pre-migration versions.
LVP_PATH=$(nix build --no-link --print-out-paths nixpkgs#mesa.drivers)/share/vulkan/icd.d/lvp_icd.x86_64.json
LAYER_PATH=$(nix build --no-link --print-out-paths nixpkgs#vulkan-validation-layers)/share/vulkan/explicit_layer.d
# Pin the complete driver/layer closure independently of application libs.
# Update deliberately and re-run present + no-present validation together.
nixpkgs=github:NixOS/nixpkgs/42f17a57f4f6e33b3de3dca0a2a5ea5233169d02
mesa=$(timeout --kill-after=30s 10m nix build --no-link --print-out-paths "$nixpkgs#mesa.drivers")
layers=$(timeout --kill-after=30s 10m nix build --no-link --print-out-paths "$nixpkgs#vulkan-validation-layers")
shopt -s nullglob
icds=("$mesa"/share/vulkan/icd.d/lvp_icd*.json)
if (( ${#icds[@]} != 1 )); then
echo "Expected exactly one Lavapipe ICD in $mesa" >&2
exit 1
fi
LVP_PATH=${icds[0]}
LAYER_PATH=$layers/share/vulkan/explicit_layer.d
test -s "$LVP_PATH"
test -s "$LAYER_PATH/VkLayer_khronos_validation.json"
{
echo "VK_ICD_FILENAMES=$LVP_PATH"
echo "VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation"
Expand Down
3 changes: 2 additions & 1 deletion .github/vulkan/vk_layer_settings.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
khronos_validation.validate_core = true
khronos_validation.enables = VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT
khronos_validation.debug_action = VK_DBG_LAYER_ACTION_LOG_MSG
# Keep stdout reserved for Zig's binary test protocol; the callback logs to stderr.
khronos_validation.debug_action = VK_DBG_LAYER_ACTION_CALLBACK
44 changes: 29 additions & 15 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ env:
GIT_CONFIG_COUNT: 1
GIT_CONFIG_KEY_0: init.defaultBranch
GIT_CONFIG_VALUE_0: main
BENCHER_API_TOKEN: ${{ secrets.BENCHER_API_TOKEN }}
BENCHER_ENABLED: ${{ secrets.BENCHER_API_TOKEN != '' && secrets.BENCHER_PROJECT != '' }}
BENCHER_PROJECT: ${{ secrets.BENCHER_PROJECT }}
BENCHER_DASHBOARD_URL: ${{ vars.BENCHER_DASHBOARD_URL }}

Expand Down Expand Up @@ -111,31 +111,45 @@ jobs:
BENCHMARK_DURATION: ${{ github.event_name == 'schedule' && '60' || github.event_name == 'workflow_dispatch' && github.event.inputs.duration || '5' }}

- name: Validate benchmark results
id: validate_results
if: steps.gate.outputs.run == 'true'
run: |
for result in benchmark-results/*/*.json; do
bash scripts/validate_benchmark_artifact.sh --result "$result"
for preset in low medium high; do
for scenario in stationary traversal rapid-turn teleport-eviction; do
bash scripts/validate_benchmark_artifact.sh --result "benchmark-results/$preset/$scenario.json"
done
done

- name: Stop headless Wayland compositor
if: always() && steps.gate.outputs.run == 'true'
uses: ./.github/actions/stop-weston

- name: Reject Vulkan validation errors in benchmark log
id: validate_vulkan
if: steps.gate.outputs.run == 'true'
run: bash scripts/check_vulkan_log.sh benchmark.log

- name: Require complete benchmark acceptance
id: acceptance
if: always() && steps.gate.outputs.run == 'true'
env:
RUN: ${{ steps.run_benchmark.outcome }}
RESULTS: ${{ steps.validate_results.outcome }}
VULKAN: ${{ steps.validate_vulkan.outcome }}
run: |
if rg -n -i 'vuid-|validation.*(error|failed)|(error|failed).*validation' benchmark.log; then
printf 'Vulkan validation errors were reported during the benchmark.\n' >&2
if [[ "$RUN" != success || "$RESULTS" != success || "$VULKAN" != success ]]; then
echo "::error::Benchmark acceptance failed: run=$RUN results=$RESULTS vulkan=$VULKAN"
exit 1
fi

- name: Install Bencher CLI
if: steps.gate.outputs.run == 'true' && env.BENCHER_API_TOKEN != '' && env.BENCHER_PROJECT != ''
if: github.event_name != 'pull_request' && steps.acceptance.outcome == 'success' && env.BENCHER_ENABLED == 'true'
uses: bencherdev/bencher@30a740a2e4246560b1a5fd424057d84aa2b188d6

- name: Publish Bencher trends
if: steps.gate.outputs.run == 'true' && env.BENCHER_API_TOKEN != '' && env.BENCHER_PROJECT != ''
if: github.event_name != 'pull_request' && steps.acceptance.outcome == 'success' && env.BENCHER_ENABLED == 'true'
env:
BENCHER_API_TOKEN: ${{ secrets.BENCHER_API_TOKEN }}
BENCHER_BRANCH: ${{ github.head_ref || github.ref_name }}
run: |
for preset in low medium high; do for scenario in stationary traversal rapid-turn teleport-eviction; do
Expand All @@ -150,7 +164,7 @@ jobs:
done; done

- name: Comment Bencher dashboard
if: steps.gate.outputs.run == 'true' && github.event_name == 'pull_request' && env.BENCHER_DASHBOARD_URL != ''
if: steps.acceptance.outcome == 'success' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && env.BENCHER_DASHBOARD_URL != ''
uses: actions/github-script@v9
with:
script: |
Expand All @@ -173,22 +187,22 @@ jobs:
retention-days: 30

- name: Publish commit status
if: always() && steps.gate.outputs.run == 'true'
if: always() && steps.gate.outputs.run == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
uses: actions/github-script@v9
env:
BENCHMARK_RUN_OUTCOME: ${{ steps.run_benchmark.outcome }}
BENCHMARK_ACCEPTANCE: ${{ steps.acceptance.outcome }}
BENCHMARK_JOB_STATUS: ${{ job.status }}
with:
script: |
const runOutcome = process.env.BENCHMARK_RUN_OUTCOME;
const failed = runOutcome === 'failure';
const failed = process.env.BENCHMARK_ACCEPTANCE !== 'success' || process.env.BENCHMARK_JOB_STATUS !== 'success';
const description = failed
? 'Benchmark regression or runtime failure'
: 'Benchmark completed';
? 'Benchmark acceptance or publication failed'
: 'Benchmark runtime, artifacts and Vulkan checks passed';

await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: context.sha,
sha: context.payload.pull_request?.head.sha || context.sha,
state: failed ? 'failure' : 'success',
context: 'performance/benchmark',
description,
Expand Down
76 changes: 26 additions & 50 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,10 @@ name: Build

on:
push:
branches: [dev]
paths:
- "src/**"
- "modules/**"
- "libs/**"
- "assets/shaders/**"
- "scripts/**"
- "docs/benchmarks/**"
- "build.zig"
- "build.zig.zon"
- "devenv.nix"
- "devenv.yaml"
- "devenv.lock"
- ".github/actions/setup-devenv/**"
- ".github/actions/setup-zig-cache/**"
- ".github/actions/setup-lavapipe/**"
- ".github/vulkan/**"
- ".github/workflows/build.yml"
- ".github/workflows/benchmark.yml"
branches: [dev, main]
tags: ["v*"]
pull_request:
branches: [dev]
paths:
- "src/**"
- "modules/**"
- "libs/**"
- "assets/shaders/**"
- "scripts/**"
- "docs/benchmarks/**"
- "build.zig"
- "build.zig.zon"
- "devenv.nix"
- "devenv.yaml"
- "devenv.lock"
- ".github/actions/setup-devenv/**"
- ".github/actions/setup-zig-cache/**"
- ".github/actions/setup-lavapipe/**"
- ".github/vulkan/**"
- ".github/workflows/build.yml"
- ".github/workflows/benchmark.yml"
branches: [dev, main]
workflow_dispatch:
inputs:
ref:
Expand Down Expand Up @@ -69,7 +34,7 @@ jobs:
pull-requests: read
runs-on: ubuntu-latest
outputs:
code_changes: ${{ github.event_name == 'workflow_dispatch' || steps.filter.outputs.code_changes }}
code_changes: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'push' || github.base_ref == 'main' || steps.filter.outputs.code_changes }}
platform_changes: ${{ github.event_name == 'workflow_dispatch' || steps.filter.outputs.platform_changes }}
steps:
- uses: actions/checkout@v4
Expand All @@ -88,14 +53,13 @@ jobs:
- 'assets/shaders/**'
- 'scripts/**'
- 'docs/benchmarks/**'
- 'docs/shaders/**'
- 'build.zig'
- 'build.zig.zon'
- 'devenv.nix'
- 'devenv.yaml'
- 'devenv.lock'
- '.github/actions/setup-devenv/**'
- '.github/actions/setup-zig-cache/**'
- '.github/actions/setup-lavapipe/**'
- '.github/actions/**'
- '.github/vulkan/**'
- '.github/workflows/build.yml'
- '.github/workflows/benchmark.yml'
Expand All @@ -119,7 +83,7 @@ jobs:
uses: ./.github/actions/setup-devenv

- name: Check Zig formatting
run: devenv shell --profile unit -- zig fmt --check src/ modules/
run: devenv shell --profile unit -- zig fmt --check src/ modules/ build.zig

build:
permissions:
Expand Down Expand Up @@ -285,13 +249,24 @@ jobs:

- name: Fail on Vulkan validation log errors
if: needs.changes.outputs.code_changes == 'true'
run: |
set -euo pipefail
if rg -n -e 'Vulkan validation error|Validation Error:|VUID-' integration-test.log world-smoke-test.log; then
echo "Vulkan validation errors were found in integration logs." >&2
exit 1
fi
echo "No Vulkan validation errors found in integration logs."
run: bash scripts/check_vulkan_log.sh integration-test.log world-smoke-test.log

- name: Run present-enabled Lavapipe smoke test
if: needs.changes.outputs.code_changes == 'true'
uses: ./.github/actions/run-with-log
with:
name: Present Smoke Test
timeout: 10m
log-file: present-smoke-test.log
command: devenv shell --profile graphics -- zig build run -Dsmoke-test=true -Dskip-present=false -Dauto-world=test
env:
ZIGCRAFT_SMOKE_FRAMES: "3"
ZIGCRAFT_SAFE_MODE: "1"
SDL_VIDEODRIVER: wayland

- name: Validate present-enabled smoke log
if: needs.changes.outputs.code_changes == 'true'
run: bash scripts/check_vulkan_log.sh present-smoke-test.log

- name: Stop headless Wayland compositor
if: always() && needs.changes.outputs.code_changes == 'true'
Expand All @@ -305,6 +280,7 @@ jobs:
path: |
integration-test.log
world-smoke-test.log
present-smoke-test.log
weston.log
if-no-files-found: ignore
retention-days: 7
Expand Down
Loading
Loading