Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,16 @@ exports.config = {
if (global.testResults) {
var entry = global.testResults.tests[global.testResults.tests.length - 1];
if (entry && entry.title === test.title) {
entry.status = passed ? 'passed' : 'failed';
// A test that called this.skip() is marked pending by mocha. The
// generated app.test.js does this when the app reports a test that
// registered 0 sub-tests (e.g. an env-gated benchmark shim) as
// "PASS (skipped ...)". Record it as skipped so the summary
// distinguishes an intentional skip from a real pass.
if (test.pending) {
entry.status = 'skipped';
} else {
entry.status = passed ? 'passed' : 'failed';
}
entry.duration = duration || 0;
if (error) {
entry.error = {
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/integration-mobile-test-tts-ggml.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ jobs:
addon-npm-name: '@qvac/tts-ggml'
prebuild-artifact-prefix: 'tts-ggml-'
pat-token: ${{ secrets.PAT_TOKEN }}
test-framework-ref: 'fix/skip-vs-crash-distinction'

# Benchmark opt-in (run_rtf_benchmarks=true): patch the addon's
# test/mobile/integration-runtime.cjs to stub `bare-os.getEnv` so the
Expand Down
237 changes: 223 additions & 14 deletions .github/workflows/on-pr-llm-llamacpp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,59 @@ jobs:
uses: ./.github/actions/label-gate
with:
github-token: ${{ secrets.PAT_TOKEN }}
ci-router:
name: CI Router (label detection)
runs-on: ubuntu-latest
timeout-minutes: 2
permissions:
contents: read
pull-requests: read
outputs:
run_verified_checks: ${{ steps.route.outputs.run_verified_checks }}
run_prebuilds: ${{ steps.route.outputs.run_prebuilds }}
run_cpp_tests: ${{ steps.route.outputs.run_cpp_tests }}
run_desktop: ${{ steps.route.outputs.run_desktop }}
run_mobile: ${{ steps.route.outputs.run_mobile }}
steps:
- name: Route CI stages
id: route
env:
EVENT_NAME: ${{ github.event_name }}
PR_LABELS_JSON: ${{ toJSON(github.event.pull_request.labels.*.name) }}
run: |
if [ "$EVENT_NAME" = "workflow_dispatch" ] || \
[ "$EVENT_NAME" = "workflow_call" ] || \
[ "$EVENT_NAME" = "push" ] || \
[ "$EVENT_NAME" = "schedule" ]; then
echo "Trusted event ($EVENT_NAME) — enabling all CI stages"
for out in run_verified_checks run_prebuilds run_cpp_tests run_desktop run_mobile; do
echo "$out=true" >> "$GITHUB_OUTPUT"
done
exit 0
fi
VERIFIED=false
PREBUILDS=false
CPP_TESTS=false
DESKTOP=false
MOBILE=false
if [ -n "$PR_LABELS_JSON" ] && [ "$PR_LABELS_JSON" != "null" ]; then
for label in $(echo "$PR_LABELS_JSON" | jq -r '.[]' 2>/dev/null); do
case "$label" in
prebuilds) PREBUILDS=true ;;
run-cpp-addon-tests) CPP_TESTS=true; PREBUILDS=true ;;
run-desktop-addon-tests) DESKTOP=true; PREBUILDS=true ;;
run-mobile-addon-tests) MOBILE=true; PREBUILDS=true ;;
verified) VERIFIED=true; PREBUILDS=true; CPP_TESTS=true; DESKTOP=true; MOBILE=true ;;
esac
done
fi
echo "CI Router: verified=$VERIFIED prebuilds=$PREBUILDS cpp=$CPP_TESTS desktop=$DESKTOP mobile=$MOBILE"
echo "run_verified_checks=$VERIFIED" >> "$GITHUB_OUTPUT"
echo "run_prebuilds=$PREBUILDS" >> "$GITHUB_OUTPUT"
echo "run_cpp_tests=$CPP_TESTS" >> "$GITHUB_OUTPUT"
echo "run_desktop=$DESKTOP" >> "$GITHUB_OUTPUT"
echo "run_mobile=$MOBILE" >> "$GITHUB_OUTPUT"

authorize:
runs-on: ubuntu-latest
permissions:
Expand Down Expand Up @@ -86,11 +139,11 @@ jobs:
run: 'echo "Verified qvac-fabric version: ${{ steps.lockstep.outputs.version }}"'

sanity-checks:
if: needs.label-gate.outputs.authorised == 'true' && (needs.authorize.outputs.allowed == 'true')
if: needs.ci-router.outputs.run_verified_checks == 'true' && needs.authorize.outputs.allowed == 'true'
needs:
- authorize
- verify-fabric-lockstep
- label-gate
- ci-router
runs-on: ubuntu-latest
steps:
- name: Checkout code
Expand All @@ -107,11 +160,11 @@ jobs:
workdir: packages/llm-llamacpp

cpp-tests:
if: needs.label-gate.outputs.authorised == 'true' && (needs.authorize.outputs.allowed == 'true')
if: needs.ci-router.outputs.run_cpp_tests == 'true' && needs.authorize.outputs.allowed == 'true'
needs:
- authorize
- sanity-checks
- label-gate
- ci-router
uses: ./.github/workflows/cpp-tests-llm.yml
secrets: inherit
with:
Expand All @@ -120,11 +173,11 @@ jobs:
ref: ${{ github.event.pull_request.head.ref || github.ref }}

cpp-lint:
if: needs.label-gate.outputs.authorised == 'true' && (needs.authorize.outputs.allowed == 'true')
if: needs.ci-router.outputs.run_verified_checks == 'true' && needs.authorize.outputs.allowed == 'true'
uses: ./.github/workflows/cpp-lint.yaml
needs:
- authorize
- label-gate
- ci-router
secrets: inherit
with:
sha: ${{ github.event.pull_request.base.sha || github.sha }}
Expand Down Expand Up @@ -160,12 +213,139 @@ jobs:
registry-type: gpr
workdir: packages/llm-llamacpp

prebuild:
detect-native-changes:
if: needs.ci-router.outputs.run_prebuilds == 'true' && needs.authorize.outputs.allowed == 'true'
needs:
- authorize
- ci-router
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
outputs:
native_changed: ${{ steps.detect.outputs.native_changed }}
native_hash: ${{ steps.detect.outputs.native_hash }}
steps:
- name: Checkout base branch
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2
- name: Detect native file changes
id: detect
env:
WORKDIR: packages/llm-llamacpp
EVENT_NAME: ${{ github.event_name }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
run: |
if [ "$EVENT_NAME" = "workflow_dispatch" ] || \
[ "$EVENT_NAME" = "workflow_call" ] || \
[ "$EVENT_NAME" = "push" ]; then
echo "Trusted event ($EVENT_NAME) — treating native files as changed"
echo "native_changed=true" >> "$GITHUB_OUTPUT"
echo "native_hash=dispatch-$(date +%s)" >> "$GITHUB_OUTPUT"
exit 0
fi

# Fetch the PR head commit for read-only git operations (diff + ls-tree).
# Same-repo PRs: fetch by SHA from origin.
# Fork PRs: SHA may not be fetchable from origin — fetch from
# the fork repo by ref instead. Does NOT check out PR code.
FETCHED=false
if [ -n "$HEAD_SHA" ]; then
if git fetch origin "$HEAD_SHA" --depth=1 2>/dev/null; then
FETCHED=true
elif [ -n "$HEAD_REPO" ] && [ -n "$HEAD_REF" ]; then
echo "SHA not fetchable from origin (likely fork PR) — trying $HEAD_REPO/$HEAD_REF"
if git fetch "https://github.com/${HEAD_REPO}.git" "$HEAD_REF" --depth=1 2>/dev/null; then
HEAD_SHA=$(git rev-parse FETCH_HEAD)
FETCHED=true
fi
fi
fi

if [ "$FETCHED" != "true" ]; then
echo "::warning::Could not fetch PR head — treating as changed (cache disabled for this push)"
echo "native_changed=true" >> "$GITHUB_OUTPUT"
echo "native_hash=unfetchable-$(date +%s)" >> "$GITHUB_OUTPUT"
exit 0
fi

CHANGED=false
if [ -n "$BASE_SHA" ] && [ -n "$HEAD_SHA" ]; then
DIFF_FILES=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" -- 2>&1) || {
echo "::warning::git diff failed — treating as changed"
echo "native_changed=true" >> "$GITHUB_OUTPUT"
echo "native_hash=diff-failed-$(date +%s)" >> "$GITHUB_OUTPUT"
exit 0
}
if [ -n "$DIFF_FILES" ]; then
while IFS= read -r file; do
case "$file" in
${WORKDIR}/*.cpp|${WORKDIR}/*.hpp|${WORKDIR}/*.c|${WORKDIR}/*.h) CHANGED=true; break ;;
${WORKDIR}/*CMakeLists.txt) CHANGED=true; break ;;
${WORKDIR}/vcpkg.json|${WORKDIR}/vcpkg-configuration.json) CHANGED=true; break ;;
${WORKDIR}/vcpkg/*) CHANGED=true; break ;;
esac
done <<< "$DIFF_FILES"
fi
else
echo "No base/head SHA — treating as changed"
CHANGED=true
fi

# Hash from PR HEAD tree — includes ALL native build inputs:
# addon source (*.cpp, *.h), CMakeLists.txt at package root
# and in subdirs, vcpkg config. git ls-tree reads the tree
# object without checkout — secure and correct.
NATIVE_HASH=$(git ls-tree -r "$HEAD_SHA" -- \
"$WORKDIR/addon" "$WORKDIR/CMakeLists.txt" \
"$WORKDIR/vcpkg.json" "$WORKDIR/vcpkg-configuration.json" \
"$WORKDIR/vcpkg" 2>/dev/null \
| grep -E '\.(cpp|hpp|c|h)$|CMakeLists\.txt$|vcpkg\.json$|vcpkg-configuration\.json$' \
| sha256sum | cut -d' ' -f1)
[ -z "$NATIVE_HASH" ] && NATIVE_HASH="empty-$(date +%s)"

echo "Native changed: $CHANGED | Hash: ${NATIVE_HASH:0:16}"
echo "native_changed=$CHANGED" >> "$GITHUB_OUTPUT"
echo "native_hash=${NATIVE_HASH:0:16}" >> "$GITHUB_OUTPUT"

prebuild-cache-restore:
if: needs.detect-native-changes.outputs.native_changed == 'false'
needs:
- detect-native-changes
- sanity-checks
- label-gate
if: needs.label-gate.outputs.authorised == 'true' && (needs.authorize.outputs.allowed == 'true')
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
outputs:
cache_hit: ${{ steps.cache.outputs.cache-hit }}
steps:
- name: Restore prebuilds from cache
id: cache
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # 4.2.3
with:
path: cached-prebuilds
key: prebuilds-llm-pr-${{ github.event.pull_request.number }}-${{ needs.detect-native-changes.outputs.native_hash }}
- name: Upload cached prebuilds as artifact
if: steps.cache.outputs.cache-hit == 'true'
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # 7.0.0
with:
name: prebuilds
path: cached-prebuilds

prebuild:
needs:
- authorize
- ci-router
- detect-native-changes
- prebuild-cache-restore
if: |
always() &&
needs.ci-router.outputs.run_prebuilds == 'true' &&
needs.authorize.outputs.allowed == 'true' &&
(needs.detect-native-changes.outputs.native_changed == 'true' || needs.prebuild-cache-restore.outputs.cache_hit != 'true')
permissions:
contents: write
packages: write
Expand All @@ -177,12 +357,39 @@ jobs:
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
ref: ${{ github.event.pull_request.head.ref || github.ref }}

prebuild-cache-save:
# Save whenever prebuild succeeds — regardless of native_changed.
# This ensures the first build on ANY PR populates the cache,
# so subsequent JS-only pushes can restore it.
# Cache key is scoped by PR number + native hash, so PRs cannot
# poison each other's cache and native changes invalidate it.
if: always() && needs.prebuild.result == 'success'
needs:
- prebuild
- detect-native-changes
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- name: Download prebuilds artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # 8.0.1
with:
name: prebuilds
path: cached-prebuilds
- name: Save prebuilds to cache
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # 4.2.3
with:
path: cached-prebuilds
key: prebuilds-llm-pr-${{ github.event.pull_request.number }}-${{ needs.detect-native-changes.outputs.native_hash }}

run-integration-tests:
if: needs.label-gate.outputs.authorised == 'true' && (needs.authorize.outputs.allowed == 'true')
if: always() && needs.ci-router.outputs.run_desktop == 'true' && needs.authorize.outputs.allowed == 'true' && (needs.prebuild.result == 'success' || needs.prebuild-cache-restore.outputs.cache_hit == 'true')
needs:
- authorize
- prebuild
- label-gate
- prebuild-cache-restore
- ci-router
permissions:
contents: read
packages: read
Expand All @@ -199,11 +406,13 @@ jobs:
packages: read
pull-requests: write # Allow commenting on PRs
id-token: write
if: needs.label-gate.outputs.authorised == 'true' && (needs.authorize.outputs.allowed == 'true')
if: always() && needs.ci-router.outputs.run_mobile == 'true' && needs.label-gate.outputs.authorised == 'true' && needs.authorize.outputs.allowed == 'true' && (needs.prebuild.result == 'success' || needs.prebuild-cache-restore.outputs.cache_hit == 'true')
needs:
- authorize
- prebuild
- prebuild-cache-restore
- label-gate
- ci-router
uses: ./.github/workflows/integration-mobile-test-llm-llamacpp.yml
secrets: inherit
with:
Expand Down Expand Up @@ -445,9 +654,9 @@ jobs:
echo "" >> $GITHUB_STEP_SUMMARY

merge-guard:
needs: [authorize, verify-fabric-lockstep, run-integration-tests, run-mobile-integration-tests, sanity-checks, prebuild, cpp-tests, cpp-lint, ts-checks]
needs: [authorize, verify-fabric-lockstep, run-integration-tests, run-mobile-integration-tests, sanity-checks, prebuild, prebuild-cache-restore, prebuild-cache-save, detect-native-changes, cpp-tests, cpp-lint, ts-checks, ci-router, label-gate]
if: always() && !cancelled()
uses: ./.github/workflows/public-pr.yml
with:
sanity-checks-status: ${{ needs.verify-fabric-lockstep.result == 'success' && needs.sanity-checks.result == 'success' }}
build-status: ${{ needs.prebuild.result == 'success'}}
build-status: ${{ needs.prebuild.result == 'success' || needs.prebuild-cache-restore.outputs.cache_hit == 'true' }}
7 changes: 7 additions & 0 deletions packages/tts-ggml/test/integration/rtf-benchmark.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,12 @@ const enabled = flag === '1' || flag.toLowerCase() === 'true' || flag.toLowerCas
if (enabled) {
require('../benchmark/rtf-benchmark.test.js')
} else {
// Declare an INTENTIONAL skip by registering a real brittle skipped test.
// brittle is the one module that crosses into the bundled mobile runtime, so
// this both keeps the build green AND lets the harness report it as skipped
// (the harness wraps brittle.skip to tag the shared runner). Registering a
// real test is the safety net: a module that registers NOTHING is a 0/0 FAIL,
// so a genuine addon-load crash can never masquerade as a green skip.
console.log('[rtf-benchmark mobile shim] QVAC_TTS_GGML_RUN_BENCHMARK_ON_MOBILE not set; skipping benchmark.')
require('brittle').skip('RTF benchmark — QVAC_TTS_GGML_RUN_BENCHMARK_ON_MOBILE not set', () => {})
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,9 @@ const enabled = flag === '1' || flag.toLowerCase() === 'true' || flag.toLowerCas
if (enabled) {
require('../benchmark/streaming-benchmark.test.js')
} else {
// Declare an INTENTIONAL skip by registering a real brittle skip (→ total > 0
// → reported skipped, never a silent green pass, and a genuine 0/0 still
// FAILs). See ./rtf-benchmark.test.js for the full rationale.
console.log('[streaming-benchmark mobile shim] QVAC_TTS_GGML_RUN_BENCHMARK_ON_MOBILE not set; skipping benchmark.')
require('brittle').skip('Streaming benchmark — QVAC_TTS_GGML_RUN_BENCHMARK_ON_MOBILE not set', () => {})
}
Loading