mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
+16




![github-actions[bot] <github-actions[bot]@users.noreply.github.com>](/assets/img/avatar_default.png)




Mega Mind
GitHub
YellowSnnowmann
Steven Enamakel
github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Cyrus Gray
Horst1993
Cursor
James Gentes
Sam
Sami Rusani
oxoxDev
Muhammad Ismail
Claude Fable 5
nb213
binyangzhu000-sudo
Steven Enamakel
CodeGhost21
sanil-23
M3gA-Mind
oxoxDev
mysma-9403
mwakidenis
NgoQuocViet2001
viet.ngo
Maciej Myszkiewicz
2e5b5e7b23
Co-authored-by: YellowSnnowmann <167776381+YellowSnnowmann@users.noreply.github.com> Co-authored-by: Steven Enamakel <31011319+senamakel@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Cyrus Gray <144336577+graycyrus@users.noreply.github.com> Co-authored-by: Horst1993 <horst.w@gmicloud.ai> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: James Gentes <jgentes@users.noreply.github.com> Co-authored-by: Sam <samrusani@users.noreply.github.com> Co-authored-by: Sami Rusani <14844597+samrusani@users.noreply.github.com> Co-authored-by: oxoxDev <164490987+oxoxDev@users.noreply.github.com> Co-authored-by: Muhammad Ismail <78064250+myi1@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: nb213 <binyangzhu000@gmail.com> Co-authored-by: binyangzhu000-sudo <224954946+binyangzhu000-sudo@users.noreply.github.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> Co-authored-by: CodeGhost21 <164498022+CodeGhost21@users.noreply.github.com> Co-authored-by: sanil-23 <sanil@tinyhumans.ai> Co-authored-by: M3gA-Mind <elvin@mahadao.com> Co-authored-by: oxoxDev <oxoxdev@users.noreply.github.com> Co-authored-by: mysma-9403 <64923976+mysma-9403@users.noreply.github.com> Co-authored-by: mwakidenis <mwakidenice@gmail.com> Co-authored-by: NgoQuocViet2001 <123613986+NgoQuocViet2001@users.noreply.github.com> Co-authored-by: viet.ngo <viet.ngo@sotatek.com> Co-authored-by: Maciej Myszkiewicz <mmyszkiewicz@bwcoders.com>
1013 lines
41 KiB
YAML
1013 lines
41 KiB
YAML
---
|
|
# CI Lite — the quick lane (pushes to main + PRs targeting main or release).
|
|
#
|
|
# Runs quality checks per changed area and unit tests ONLY for the files the
|
|
# PR changed (vitest related / domain-scoped cargo-llvm-cov), with diff-cover
|
|
# enforcing >= 80% coverage on changed lines. Integration and E2E suites do
|
|
# NOT run here — they run in full via ci-full.yml on PRs targeting `release`
|
|
# and on every push to `release` (the maintainer-dispatched
|
|
# promote-main-to-release.yml merge and fix-PR merges). Fix PRs against
|
|
# release get this fast lane too, for quick lint/coverage feedback alongside
|
|
# the full suite.
|
|
#
|
|
# The gate job keeps its historical "PR CI Gate" check name so the main
|
|
# ruleset's required-status-check reference stays valid.
|
|
name: CI Lite
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
branches: [main, release]
|
|
|
|
permissions:
|
|
# `actions: read` lets scripts/ci-cancel-aware.sh poll the run status so
|
|
# cancelled builds inside container jobs stop themselves (docker exec
|
|
# swallows the runner's signals).
|
|
actions: read
|
|
contents: read
|
|
packages: read
|
|
pull-requests: read
|
|
|
|
env:
|
|
# Consumed by scripts/ci-cancel-aware.sh's cancellation watchdog.
|
|
GH_TOKEN: ${{ github.token }}
|
|
NPM_CONFIG_STORE_DIR: .pnpm-store
|
|
|
|
# Keep only the newest run per PR/branch. Long Rust/Tauri build commands must
|
|
# run through scripts/ci-cancel-aware.sh so cancellation reaches descendants.
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
|
|
jobs:
|
|
changes:
|
|
name: Detect Changed Areas
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
outputs:
|
|
frontend: ${{ steps.filter.outputs.frontend }}
|
|
# "true" when the change touches config that invalidates test scoping
|
|
# (lockfile, vitest/vite/ts config, test setup) → run the full suite.
|
|
frontend-full: ${{ steps.filter.outputs['frontend-full'] }}
|
|
# Shell-quoted list of changed app/src source files, consumed by
|
|
# scripts/ci/vitest-changed-coverage.sh (vitest related).
|
|
frontend-src-files: ${{ steps.filter.outputs['frontend-src_files'] }}
|
|
i18n: ${{ steps.filter.outputs.i18n }}
|
|
docs: ${{ steps.filter.outputs.docs }}
|
|
rust-core: ${{ steps.filter.outputs['rust-core'] }}
|
|
rust-core-full: ${{ steps.filter.outputs['rust-core-full'] }}
|
|
# The vendored engine owns migrated memory tests; Cargo does not run
|
|
# dependency tests as part of the OpenHuman crate suite.
|
|
tinycortex: ${{ steps.filter.outputs.tinycortex }}
|
|
# Shell-quoted list of changed src/ + tests/ files, consumed by
|
|
# scripts/ci/rust-coverage-changed.sh (domain-scoped llvm-cov).
|
|
rust-core-src-files: ${{ steps.filter.outputs['rust-core-src_files'] }}
|
|
rust-tauri: ${{ steps.filter.outputs['rust-tauri'] }}
|
|
# Mock backend + shared scripts → run the scripts self-test suite.
|
|
scripts: ${{ steps.filter.outputs.scripts }}
|
|
# Test wiring surface (workflows, package.json, all.rs, tests/, script
|
|
# tests) → run the test-inventory orphan + controller-domain guard.
|
|
inventory: ${{ steps.filter.outputs.inventory }}
|
|
# Windows installer or its unit test → run the Pester install lane.
|
|
install-ps1: ${{ steps.filter.outputs.install_ps1 }}
|
|
coverage: ${{ steps.filter.outputs.frontend == 'true' || steps.filter.outputs['rust-core'] == 'true' || steps.filter.outputs['rust-tauri'] == 'true' }}
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v7
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Detect changed paths
|
|
id: filter
|
|
uses: dorny/paths-filter@v4
|
|
with:
|
|
list-files: shell
|
|
filters: |
|
|
frontend:
|
|
- '.github/workflows/ci-lite.yml'
|
|
# The changed-files runner itself must trigger the lane that
|
|
# runs it (it is also in frontend-full → full-suite mode).
|
|
- 'scripts/ci/vitest-changed-coverage.sh'
|
|
- 'scripts/ci-cancel-aware.sh'
|
|
- 'package.json'
|
|
- 'pnpm-lock.yaml'
|
|
- 'app/package.json'
|
|
- 'app/index.html'
|
|
- 'app/public/**'
|
|
- 'app/src/**'
|
|
- 'app/scripts/**'
|
|
- 'app/test/vitest.config.ts'
|
|
- 'app/tsconfig*.json'
|
|
- 'app/vite.config.*'
|
|
- 'app/tailwind.config.*'
|
|
- 'app/postcss.config.*'
|
|
# Changes here invalidate per-file test scoping → full Vitest run.
|
|
frontend-full:
|
|
- '.github/workflows/ci-lite.yml'
|
|
- 'scripts/ci/vitest-changed-coverage.sh'
|
|
- 'package.json'
|
|
- 'pnpm-lock.yaml'
|
|
- 'app/package.json'
|
|
- 'app/index.html'
|
|
- 'app/public/**'
|
|
- 'app/test/vitest.config.ts'
|
|
- 'app/tsconfig*.json'
|
|
- 'app/vite.config.*'
|
|
- 'app/tailwind.config.*'
|
|
- 'app/postcss.config.*'
|
|
- 'app/scripts/**'
|
|
- 'app/src/test/**'
|
|
frontend-src:
|
|
- 'app/src/**/*.ts'
|
|
- 'app/src/**/*.tsx'
|
|
i18n:
|
|
- '.github/workflows/ci-lite.yml'
|
|
- 'app/src/**'
|
|
docs:
|
|
- '.github/workflows/ci-lite.yml'
|
|
- 'app/src/App.tsx'
|
|
- 'scripts/generate-architecture-docs.mjs'
|
|
- 'scripts/__tests__/generate-architecture-docs.test.mjs'
|
|
- 'gitbooks/developing/architecture/frontend.md'
|
|
- 'package.json'
|
|
rust-core:
|
|
- '.github/workflows/ci-lite.yml'
|
|
# The changed-files runner itself must trigger the lane that
|
|
# runs it (it is also in rust-core-full → full-suite mode).
|
|
- 'scripts/ci/rust-coverage-changed.sh'
|
|
- 'Cargo.toml'
|
|
- 'Cargo.lock'
|
|
- 'build.rs'
|
|
- '.cargo/config.toml'
|
|
- 'rust-toolchain.toml'
|
|
- 'src/**'
|
|
- 'tests/**'
|
|
- 'scripts/ci-cancel-aware.sh'
|
|
- 'scripts/test-rust-e2e.sh'
|
|
- 'scripts/test-rust-with-mock.sh'
|
|
# Changes here invalidate per-module test scoping → full suite.
|
|
rust-core-full:
|
|
- '.github/workflows/ci-lite.yml'
|
|
- 'scripts/ci/rust-coverage-changed.sh'
|
|
- 'Cargo.toml'
|
|
- 'Cargo.lock'
|
|
- 'build.rs'
|
|
- '.cargo/config.toml'
|
|
- 'rust-toolchain.toml'
|
|
- 'scripts/ci-cancel-aware.sh'
|
|
rust-core-src:
|
|
- 'src/**'
|
|
- 'tests/**'
|
|
tinycortex:
|
|
- '.github/workflows/ci-lite.yml'
|
|
- '.github/workflows/test-reusable.yml'
|
|
- '.gitmodules'
|
|
- 'scripts/ci-cancel-aware.sh'
|
|
- 'vendor/tinycortex'
|
|
rust-tauri:
|
|
- '.github/workflows/ci-lite.yml'
|
|
- 'Cargo.lock'
|
|
- '.cargo/config.toml'
|
|
- 'rust-toolchain.toml'
|
|
- 'app/src-tauri/**'
|
|
- 'scripts/ci-cancel-aware.sh'
|
|
# Shared mock backend + script self-tests. The mock backend every
|
|
# E2E suite depends on had zero PR-lane coverage before this (its
|
|
# own socket-auth tests never ran in CI) — arm the scripts
|
|
# self-test job on any mock/script change.
|
|
scripts:
|
|
- '.github/workflows/ci-lite.yml'
|
|
- 'scripts/mock-api/**'
|
|
- 'scripts/*.mjs'
|
|
- 'scripts/__tests__/**'
|
|
# Test-wiring surface: a change here can orphan a test or add an
|
|
# untested controller domain → run the inventory guard.
|
|
inventory:
|
|
- '.github/workflows/**'
|
|
- 'package.json'
|
|
- 'src/core/all.rs'
|
|
- 'tests/**'
|
|
- 'scripts/**/*.test.mjs'
|
|
- 'scripts/tests/**'
|
|
- 'scripts/generate-test-inventory.mjs'
|
|
# Windows installer + its Pester unit test.
|
|
install_ps1:
|
|
- '.github/workflows/ci-lite.yml'
|
|
- 'scripts/install.ps1'
|
|
- 'scripts/tests/OpenHumanWindowsInstall.Tests.ps1'
|
|
|
|
frontend-checks:
|
|
name: Frontend Checks (quality, i18n, docs, coverage)
|
|
needs: [changes]
|
|
if: needs.changes.outputs.frontend == 'true' || needs.changes.outputs.i18n == 'true' || needs.changes.outputs.docs == 'true'
|
|
runs-on: ubuntu-22.04
|
|
timeout-minutes: 60
|
|
container:
|
|
image: ghcr.io/tinyhumansai/openhuman_ci:rust-1.93.0
|
|
steps:
|
|
- name: Checkout code
|
|
# Pinned to the v7 commit SHA for supply-chain hardening (CodeRabbit
|
|
# review on #3892). The combined frontend lane keeps that hardening
|
|
# from the former docs-drift job.
|
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
|
with:
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
|
|
# The RPC-catalog drift-guard test (rpcMethods.test.ts) reads
|
|
# vendor/tinychannels/src/controllers/schemas.rs at test time. This lane
|
|
# otherwise checks out without submodules, so init just that one — not
|
|
# `submodules: recursive`, which would also pull the large tauri-cef fork
|
|
# this job never builds. Mirrors the same fix in test-reusable.yml's
|
|
# `unit-tests` job.
|
|
#
|
|
# `actions/checkout` scopes its own `safe.directory` config to a
|
|
# temporary HOME for the duration of the checkout step only — it does
|
|
# not persist into this container job's real HOME, so a bare `git`
|
|
# invocation in a later step hits "detected dubious ownership" here.
|
|
# Add the workspace as a safe directory ourselves before touching git.
|
|
- name: Init tinychannels submodule (drift-guard test reads its schemas)
|
|
if: needs.changes.outputs.frontend == 'true'
|
|
run: |
|
|
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
|
git submodule update --init vendor/tinychannels
|
|
|
|
- name: Cache pnpm store
|
|
id: pnpm-cache
|
|
if: needs.changes.outputs.frontend == 'true' || needs.changes.outputs.i18n == 'true'
|
|
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
|
|
with:
|
|
path: .pnpm-store
|
|
key: pnpm-store-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
|
|
restore-keys: |
|
|
pnpm-store-${{ runner.os }}-
|
|
|
|
- name: Install dependencies
|
|
if: needs.changes.outputs.frontend == 'true' || needs.changes.outputs.i18n == 'true'
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Save pnpm store cache
|
|
if: always() && (needs.changes.outputs.frontend == 'true' || needs.changes.outputs.i18n == 'true') && steps.pnpm-cache.outputs.cache-hit != 'true'
|
|
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
|
|
with:
|
|
path: .pnpm-store
|
|
key: ${{ steps.pnpm-cache.outputs.cache-primary-key }}
|
|
|
|
- name: Type check TypeScript files
|
|
if: needs.changes.outputs.frontend == 'true'
|
|
run: pnpm --filter openhuman-app compile
|
|
env:
|
|
NODE_ENV: test
|
|
|
|
- name: Check Prettier formatting
|
|
if: needs.changes.outputs.frontend == 'true'
|
|
run: pnpm --filter openhuman-app format:check
|
|
env:
|
|
NODE_ENV: test
|
|
|
|
- name: Run ESLint
|
|
if: needs.changes.outputs.frontend == 'true'
|
|
run: pnpm --filter openhuman-app lint
|
|
env:
|
|
NODE_ENV: test
|
|
|
|
- name: Verify i18n coverage (missing / extra / drifted keys across locales)
|
|
if: needs.changes.outputs.i18n == 'true'
|
|
run: pnpm i18n:check
|
|
|
|
# The generator + its tests use only Node built-ins (no workspace deps),
|
|
# so docs-only changes still skip `pnpm install`.
|
|
- name: Generator unit tests
|
|
if: needs.changes.outputs.docs == 'true'
|
|
run: pnpm docs:test
|
|
|
|
- name: Verify generated architecture docs are not stale
|
|
if: needs.changes.outputs.docs == 'true'
|
|
run: pnpm docs:check
|
|
|
|
- name: Run Vitest with coverage (changed files only)
|
|
if: needs.changes.outputs.frontend == 'true'
|
|
run: bash scripts/ci/vitest-changed-coverage.sh
|
|
env:
|
|
NODE_ENV: test
|
|
FULL: ${{ needs.changes.outputs['frontend-full'] }}
|
|
CHANGED_FILES: ${{ needs.changes.outputs['frontend-src-files'] }}
|
|
|
|
- name: Normalize lcov source paths to repo root
|
|
if: needs.changes.outputs.frontend == 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
test -f app/coverage/lcov.info
|
|
sed -i -E 's#^SF:(src/)#SF:app/\1#' app/coverage/lcov.info
|
|
sed -i -E 's#^SF:(\./src/)#SF:app/src/#' app/coverage/lcov.info
|
|
|
|
- name: Upload frontend lcov
|
|
if: needs.changes.outputs.frontend == 'true'
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: lcov-frontend
|
|
path: app/coverage/lcov.info
|
|
retention-days: 7
|
|
if-no-files-found: error
|
|
|
|
rust-quality:
|
|
name: Rust Quality (fmt, clippy)
|
|
needs: [changes]
|
|
if: needs.changes.outputs['rust-core'] == 'true' || needs.changes.outputs['rust-tauri'] == 'true'
|
|
runs-on: ubuntu-22.04
|
|
timeout-minutes: 20
|
|
container:
|
|
image: ghcr.io/tinyhumansai/openhuman_ci:rust-1.93.0
|
|
env:
|
|
CARGO_INCREMENTAL: "0"
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
submodules: recursive
|
|
|
|
- name: Cache Rust build artifacts
|
|
uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: |
|
|
. -> target
|
|
app/src-tauri -> target
|
|
cache-on-failure: true
|
|
# shared-key (not key) so the cache name is stable and not suffixed
|
|
# with the job id. Swatinem caches the full target/, which is why we
|
|
# no longer layer sccache on top (it logged 0% hits under a warm
|
|
# rust-cache and just doubled the cache footprint).
|
|
shared-key: pr-rust-quality
|
|
|
|
- name: Check Rust formatting
|
|
run: cargo fmt --all -- --check
|
|
|
|
- name: Run clippy (core crate)
|
|
if: needs.changes.outputs['rust-core'] == 'true'
|
|
run: bash scripts/ci-cancel-aware.sh cargo clippy -p openhuman -- -D warnings
|
|
|
|
- name: Cache CEF binary distribution
|
|
if: needs.changes.outputs['rust-tauri'] == 'true'
|
|
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
|
|
with:
|
|
path: |
|
|
~/Library/Caches/tauri-cef
|
|
~/.cache/tauri-cef
|
|
key: cef-x86_64-unknown-linux-gnu-v2-${{ hashFiles('app/src-tauri/Cargo.toml') }}
|
|
restore-keys: |
|
|
cef-x86_64-unknown-linux-gnu-v2-
|
|
|
|
- name: Run clippy (Tauri shell)
|
|
if: needs.changes.outputs['rust-tauri'] == 'true'
|
|
run: bash scripts/ci-cancel-aware.sh cargo clippy --manifest-path app/src-tauri/Cargo.toml -- -D warnings
|
|
|
|
# Feature-gate smoke: proves the core still compiles with a domain gate turned
|
|
# OFF. The disabled build is the ONLY thing that catches stub-facade signature
|
|
# drift (see AGENTS.md "Compile-time domain gates"), so it must run in CI, not
|
|
# just locally. Pathfinder lane for #4803 (voice); the explicit --features
|
|
# list (only tokenjuice-treesitter) turns every default gate OFF, so it also
|
|
# covers #4804 (media) and each sibling gate as it lands — no edit needed
|
|
# unless a new gate must stay ON here.
|
|
rust-feature-gate-smoke:
|
|
name: Rust Feature-Gate Smoke (gates off)
|
|
needs: [changes]
|
|
if: needs.changes.outputs['rust-core'] == 'true' || needs.changes.outputs['rust-tauri'] == 'true'
|
|
runs-on: ubuntu-22.04
|
|
# Was 25; raised because the lane now links (--all-targets) and runs a test
|
|
# binary, not just `cargo check`.
|
|
timeout-minutes: 40
|
|
container:
|
|
image: ghcr.io/tinyhumansai/openhuman_ci:rust-1.93.0
|
|
env:
|
|
CARGO_INCREMENTAL: "0"
|
|
# Deep-async-stack belt (same as the coverage lanes) — virtual reservation,
|
|
# zero cost. Keeps a gate-contract test with a deep stack from tripping.
|
|
RUST_MIN_STACK: "67108864"
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
submodules: recursive
|
|
|
|
- name: Cache Rust build artifacts
|
|
uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: |
|
|
. -> target
|
|
cache-on-failure: true
|
|
shared-key: pr-rust-feature-gate-smoke
|
|
|
|
- name: Check core builds with the default domain gates disabled
|
|
# Lib-scoped on purpose. `--all-targets` would additionally compile the
|
|
# `tests/` integration crates, which today name gated symbols
|
|
# (voice::reply_speech::test_seam, mcp_registry::store, x402::tools) without
|
|
# a #[cfg] and so do not compile with the gates off. That cleanup is tracked
|
|
# in #5021 alongside running the full gates-off suite; until it lands, this
|
|
# lane checks the lib and RUNS the gate-contract lib tests below.
|
|
run: bash scripts/ci-cancel-aware.sh cargo check --manifest-path Cargo.toml --no-default-features --features tokenjuice-treesitter
|
|
|
|
- name: Run the gate-contract tests with the default domain gates disabled
|
|
# `cargo check` (even --all-targets) never RUNS tests, so a gated family that
|
|
# is asserted WITHOUT a matching #[cfg] compiles fine and only fails at
|
|
# runtime — exactly how the two ungated `voice` asserts sat red on main
|
|
# (#5022). This step executes the gate-contract test modules so that class of
|
|
# regression fails CI, not just a local pre-push.
|
|
#
|
|
# Scope is deliberate: the full gates-off `--lib` run aborts on a pre-existing
|
|
# task_local stack overflow (`agent::harness::session::tests::
|
|
# turn_dispatches_spawn_subagent_through_full_path`) that reproduces in the
|
|
# gates-ON build too, and also OOMs unscoped. These filters route AROUND that
|
|
# test by scope, not by skip. Running the full suite is tracked in #5021.
|
|
run: |
|
|
bash scripts/ci-cancel-aware.sh cargo test --manifest-path Cargo.toml \
|
|
--no-default-features --features tokenjuice-treesitter --lib -- \
|
|
core::all:: core::cli:: core::jsonrpc:: core::legacy_aliases:: core::runtime:: agent_registry::agents::loader::
|
|
|
|
- name: Guard — new feature-gated test modules must be acknowledged
|
|
# Self-maintaining coverage: the set of source files that #[cfg]-gate a test on
|
|
# a domain feature must equal the allowlist below. When a new gate (or a new
|
|
# test in an existing gate's domain) adds a gated test file, this fails — the
|
|
# author then adds it here AND, if it can hold an ungated-assert regression of
|
|
# the #5022 class, extends the scoped `cargo test` filter above. Prevents the
|
|
# smoke lane from silently under-covering as the gate surface grows.
|
|
run: |
|
|
set -euo pipefail
|
|
EXPECTED=$(cat <<'EOF'
|
|
core/all_tests.rs
|
|
core/cli_tests.rs
|
|
core/jsonrpc_tests.rs
|
|
core/legacy_aliases.rs
|
|
core/runtime/services.rs
|
|
openhuman/agent/harness/builtin_definitions.rs
|
|
openhuman/agent/harness/definition_tests.rs
|
|
openhuman/agent/harness/session/tests.rs
|
|
openhuman/agent/harness/subagent_runner/tool_prep.rs
|
|
openhuman/agent_registry/agents/loader.rs
|
|
openhuman/composio/action_tool.rs
|
|
openhuman/inference/local/mod.rs
|
|
openhuman/tool_registry/ops_tests.rs
|
|
openhuman/tool_registry/schemas.rs
|
|
openhuman/tools/ops_tests.rs
|
|
openhuman/voice/compile_status.rs
|
|
openhuman/wallet/stub.rs
|
|
openhuman/web3/stub.rs
|
|
openhuman/x402/stub.rs
|
|
EOF
|
|
)
|
|
ACTUAL=$(grep -rlE '#\[cfg\((not\()?feature = "(voice|media|web3|meet|mcp|skills|flows|channels)"' src --include='*.rs' \
|
|
| xargs grep -lE '#\[test\]|#\[tokio::test\]|fn .*_test' 2>/dev/null | sed 's|^src/||' | sort -u)
|
|
if ! diff <(echo "$EXPECTED" | sed 's/^ *//' | sort -u) <(echo "$ACTUAL"); then
|
|
echo "::error::Gated-test file set changed. Update the EXPECTED allowlist in the rust-feature-gate-smoke lane, and extend the scoped 'cargo test' filter if the new module can carry an ungated-assert regression (see #5022)."
|
|
exit 1
|
|
fi
|
|
echo "gate-contract test coverage allowlist is current"
|
|
|
|
# Report-only (#5046). Measures steady-state RSS of an embedded openhuman_core
|
|
# agent roster and uploads the raw samples + a human summary. Deliberately NOT
|
|
# in `pr-ci-gate.needs`, so it can never fail a PR — it exists to accrue a
|
|
# baseline and its runner variance before the 30 MiB gate is flipped to
|
|
# blocking in a follow-up (add this job to pr-ci-gate + a threshold step then).
|
|
rust-rss-bench:
|
|
name: Rust RSS Benchmark (report-only)
|
|
needs: [changes]
|
|
if: needs.changes.outputs['rust-core'] == 'true'
|
|
runs-on: ubuntu-22.04
|
|
timeout-minutes: 40
|
|
container:
|
|
image: ghcr.io/tinyhumansai/openhuman_ci:rust-1.93.0
|
|
env:
|
|
CARGO_INCREMENTAL: "0"
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
submodules: recursive
|
|
|
|
- name: Cache Rust build artifacts
|
|
uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: |
|
|
. -> target
|
|
cache-on-failure: true
|
|
shared-key: pr-rust-rss-bench
|
|
|
|
# Fixture-contract signal: the gated bin's unit tests (build_roster,
|
|
# warm-up turn) never enter the default coverage lane, so run them here.
|
|
- name: Run rss-bench fixture tests
|
|
run: bash scripts/ci-cancel-aware.sh cargo test --features rss-bench --bin rss-bench
|
|
|
|
- name: Build stripped-release rss-bench
|
|
run: bash scripts/ci-cancel-aware.sh cargo build --release --features rss-bench --bin rss-bench
|
|
|
|
- name: Measure embedded RSS (5 fresh procs x {1,8} agents)
|
|
run: ./target/release/rss-bench --out bench-rss.json | tee -a "$GITHUB_STEP_SUMMARY"
|
|
|
|
- name: Upload raw RSS samples
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: rss-bench-report
|
|
path: bench-rss.json
|
|
|
|
rust-core-coverage:
|
|
name: Rust Core Coverage (cargo-llvm-cov)
|
|
needs: [changes, rust-quality]
|
|
if: always() && needs.changes.outputs['rust-core'] == 'true' && needs['rust-quality'].result == 'success'
|
|
runs-on: ubuntu-22.04
|
|
# 30 min was only ever enough because a stack overflow aborted the run early
|
|
# (see RUST_MIN_STACK below). With the overflow fixed the job runs the full
|
|
# instrumented suite (~13.6k tests, single-threaded build) end-to-end, which
|
|
# needs more wall-time.
|
|
timeout-minutes: 50
|
|
container:
|
|
image: ghcr.io/tinyhumansai/openhuman_ci:rust-1.93.0
|
|
env:
|
|
CARGO_INCREMENTAL: "0"
|
|
# Coverage-instrumented test binaries are dominated by DWARF debug info,
|
|
# which exhausts the runner disk and SIGBUSes the linker. Drop debug info
|
|
# entirely for this job — llvm-cov line/region coverage is read from the
|
|
# embedded __llvm_covmap/__llvm_covfun sections (filenames + line numbers
|
|
# included), NOT from DWARF, so coverage numbers are unaffected. This is
|
|
# the maximum-shrink setting; line-tables-only was still too large for the
|
|
# instrumented build.
|
|
CARGO_PROFILE_DEV_DEBUG: "0"
|
|
# cargo-llvm-cov instrumentation inflates per-frame stack usage, so deep
|
|
# async tests (e.g. cron::scheduler agent-job tests that drive the full
|
|
# harness) overflow the default ~2 MB test-thread stack and SIGABRT the
|
|
# whole run with "stack overflow". Give libtest's per-test threads a large
|
|
# stack so coverage runs don't abort. (Virtual reservation only — no real
|
|
# memory cost.)
|
|
RUST_MIN_STACK: "67108864"
|
|
steps:
|
|
- name: Free disk space
|
|
run: |
|
|
# Coverage-instrumented builds produce huge binaries that exhaust the
|
|
# runner's ~14GB free disk during linking (SIGBUS). Inside a container,
|
|
# host paths are bind-mounted:
|
|
# /opt/hostedtoolcache → /__t
|
|
# /home/runner/work → /__w
|
|
# Deleting /__t frees ~8GB on the same partition the build uses.
|
|
rm -rf /__t/* || true
|
|
echo "Disk after cleanup:"
|
|
df -h /__w || true
|
|
|
|
- name: Checkout code
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
submodules: recursive
|
|
|
|
- name: Cache Rust build artifacts
|
|
uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: . -> target
|
|
cache-on-failure: false
|
|
shared-key: pr-rust-core-cov
|
|
|
|
- name: Prune stale coverage test executables
|
|
run: |
|
|
# The restored target cache can contain prior llvm-cov integration-test
|
|
# executables. Those are enormous after coverage instrumentation and
|
|
# can exhaust the runner before the current cargo invocation finishes.
|
|
find target/llvm-cov-target -type f -path '*/debug/deps/*' -perm -111 -delete 2>/dev/null || true
|
|
echo "Disk after coverage target prune:"
|
|
df -h /__w || true
|
|
|
|
- name: Install cargo-llvm-cov
|
|
uses: taiki-e/install-action@cargo-llvm-cov
|
|
|
|
- name: Run cargo llvm-cov for openhuman core (changed modules only)
|
|
run: bash scripts/ci/rust-coverage-changed.sh
|
|
env:
|
|
CARGO_BUILD_JOBS: "1"
|
|
FULL: ${{ needs.changes.outputs['rust-core-full'] }}
|
|
CHANGED_FILES: ${{ needs.changes.outputs['rust-core-src-files'] }}
|
|
OUT: lcov-core.info
|
|
# The tinyagents harness is the agent engine on every build now
|
|
# (issue #4249); the suite exercises it by default. The legacy engine
|
|
# is being removed.
|
|
|
|
- name: Prune coverage test executables before cache save
|
|
if: always()
|
|
run: |
|
|
find target/llvm-cov-target -type f -path '*/debug/deps/*' -perm -111 -delete 2>/dev/null || true
|
|
echo "Disk after post-coverage prune:"
|
|
df -h /__w || true
|
|
|
|
- name: Upload core lcov
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: lcov-rust-core
|
|
path: lcov-core.info
|
|
retention-days: 7
|
|
if-no-files-found: error
|
|
|
|
rust-tauri-coverage:
|
|
name: Rust Tauri Coverage (cargo-llvm-cov)
|
|
needs: [changes, rust-quality]
|
|
if: always() && needs.changes.outputs['rust-tauri'] == 'true' && needs['rust-quality'].result == 'success'
|
|
runs-on: ubuntu-22.04
|
|
timeout-minutes: 30
|
|
container:
|
|
image: ghcr.io/tinyhumansai/openhuman_ci:rust-1.93.0
|
|
env:
|
|
CARGO_INCREMENTAL: "0"
|
|
# The instrumented Tauri shell binary (whole tauri/zbus/CEF graph) is
|
|
# dominated by DWARF debug info, which exhausts the runner disk and
|
|
# SIGBUSes the linker (collect2: ld terminated with signal 7) during the
|
|
# final link. Drop debug info — llvm-cov reads coverage from the embedded
|
|
# __llvm_covmap/__llvm_covfun sections, NOT from DWARF, so coverage
|
|
# numbers are unaffected. Mirrors rust-core-coverage.
|
|
CARGO_PROFILE_DEV_DEBUG: "0"
|
|
# Large test-thread stack so coverage instrumentation's inflated frames
|
|
# don't overflow deep async tests. Mirrors rust-core-coverage.
|
|
RUST_MIN_STACK: "67108864"
|
|
steps:
|
|
- name: Free disk space
|
|
run: |
|
|
# Coverage-instrumented builds produce huge binaries that exhaust the
|
|
# runner's ~14GB free disk during linking (SIGBUS). Inside a container,
|
|
# host paths are bind-mounted:
|
|
# /opt/hostedtoolcache → /__t
|
|
# /home/runner/work → /__w
|
|
# Deleting /__t frees ~8GB on the same partition the build uses.
|
|
# Mirrors rust-core-coverage.
|
|
rm -rf /__t/* || true
|
|
echo "Disk after cleanup:"
|
|
df -h /__w || true
|
|
|
|
- name: Checkout code
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
submodules: recursive
|
|
|
|
- name: Cache Rust build artifacts
|
|
uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: |
|
|
. -> target
|
|
app/src-tauri -> target
|
|
cache-on-failure: true
|
|
# Tauri shell coverage (instrumented) — its own stable cache; reuses
|
|
# its warm dependency tree run-over-run.
|
|
shared-key: pr-rust-tauri-cov
|
|
|
|
- name: Cache CEF binary distribution
|
|
id: cef-cache
|
|
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
|
|
with:
|
|
path: |
|
|
~/Library/Caches/tauri-cef
|
|
~/.cache/tauri-cef
|
|
key: cef-x86_64-unknown-linux-gnu-v2-${{ hashFiles('app/src-tauri/Cargo.toml') }}
|
|
restore-keys: |
|
|
cef-x86_64-unknown-linux-gnu-v2-
|
|
|
|
- name: Install cargo-llvm-cov
|
|
uses: taiki-e/install-action@cargo-llvm-cov
|
|
|
|
- name: Run cargo llvm-cov for Tauri shell
|
|
run: bash scripts/ci-cancel-aware.sh cargo llvm-cov --manifest-path app/src-tauri/Cargo.toml --lcov --output-path lcov-tauri.info
|
|
env:
|
|
# Serialize codegen/link to cap peak memory + disk during the
|
|
# instrumented Tauri-shell link (SIGBUS otherwise). Mirrors
|
|
# rust-core-coverage.
|
|
CARGO_BUILD_JOBS: "1"
|
|
|
|
- name: Save CEF binary distribution cache
|
|
if: always() && steps.cef-cache.outputs.cache-hit != 'true'
|
|
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
|
|
with:
|
|
path: |
|
|
~/Library/Caches/tauri-cef
|
|
~/.cache/tauri-cef
|
|
key: ${{ steps.cef-cache.outputs.cache-primary-key }}
|
|
|
|
- name: Upload tauri lcov
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: lcov-rust-tauri
|
|
path: lcov-tauri.info
|
|
retention-days: 7
|
|
if-no-files-found: error
|
|
|
|
scripts-tests:
|
|
name: Scripts Self-Tests (mock backend + CLIs)
|
|
needs: [changes]
|
|
if: needs.changes.outputs.scripts == 'true'
|
|
runs-on: ubuntu-22.04
|
|
timeout-minutes: 15
|
|
container:
|
|
image: ghcr.io/tinyhumansai/openhuman_ci:rust-1.93.0
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
|
|
- name: Cache pnpm store
|
|
id: pnpm-cache
|
|
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
|
|
with:
|
|
path: .pnpm-store
|
|
key: pnpm-store-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
|
|
restore-keys: |
|
|
pnpm-store-${{ runner.os }}-
|
|
|
|
# The socket self-tests resolve socket.io-client from app/, so the
|
|
# workspace deps must be installed.
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Save pnpm store cache
|
|
if: always() && steps.pnpm-cache.outputs.cache-hit != 'true'
|
|
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
|
|
with:
|
|
path: .pnpm-store
|
|
key: ${{ steps.pnpm-cache.outputs.cache-primary-key }}
|
|
|
|
- name: Run mock-backend + script self-tests
|
|
run: pnpm test:scripts
|
|
|
|
test-inventory:
|
|
name: Test Inventory (orphan + controller-domain guard)
|
|
needs: [changes]
|
|
if: needs.changes.outputs.inventory == 'true'
|
|
runs-on: ubuntu-22.04
|
|
timeout-minutes: 10
|
|
container:
|
|
image: ghcr.io/tinyhumansai/openhuman_ci:rust-1.93.0
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
|
|
# Node-only guard (no workspace deps): asserts every script-level test is
|
|
# invoked by a package.json script/workflow, and every controller domain
|
|
# in src/core/all.rs is referenced under tests/.
|
|
- name: Run test-inventory guard
|
|
run: pnpm test:inventory
|
|
|
|
orch-ip-gate:
|
|
name: Orchestration IP Gate (no local brain re-entry)
|
|
runs-on: ubuntu-22.04
|
|
timeout-minutes: 5
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
|
|
# Fail if the retired local orchestration brain (reasoning/wake graph,
|
|
# prompt assets, or per-agent model routing) re-enters the open repo.
|
|
- name: Run orchestration IP gate
|
|
run: bash scripts/ci/orch-ip-gate.sh
|
|
|
|
feature-forwarding-gate:
|
|
name: Feature Forwarding Gate (shell forwards core defaults)
|
|
runs-on: ubuntu-22.04
|
|
timeout-minutes: 5
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
|
|
# The shell sets `default-features = false` on `openhuman_core`, so it does
|
|
# NOT inherit the core's default gates — each must be forwarded by hand.
|
|
# When that list drifts, the domain is compiled out of the shipped app with
|
|
# no build error and no failing test: that is how #4901 (voice — 56 users,
|
|
# ~93k Sentry events) and #4918 (tokenjuice-treesitter — silent) shipped.
|
|
#
|
|
# Deliberately NOT filtered on `changes`: drift can be introduced by
|
|
# editing either manifest, so a path filter watching one would miss it, and
|
|
# a skipped job counts as a pass in the gate below. The check is a few
|
|
# seconds of pure Node with no dependencies.
|
|
- name: Verify the desktop shell forwards every default-ON core gate
|
|
run: node scripts/ci/check-feature-forwarding.mjs
|
|
|
|
pester-install:
|
|
name: PowerShell Install Test (Pester)
|
|
needs: [changes]
|
|
if: needs.changes.outputs['install-ps1'] == 'true'
|
|
# GitHub-hosted ubuntu-latest ships pwsh; the test only dot-sources
|
|
# install.ps1 helpers (no MSI, cross-platform), so no Windows runner needed.
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
|
|
# Invoked directly (not via `pnpm test:install-ps1`) so the lane needs no
|
|
# Node/pnpm toolchain — pwsh is preinstalled on ubuntu-latest. Keep this
|
|
# command in sync with the `test:install-ps1` package.json script.
|
|
- name: Run install.ps1 unit tests
|
|
run: pwsh -NoProfile -File scripts/tests/OpenHumanWindowsInstall.Tests.ps1
|
|
|
|
tinycortex-tests:
|
|
name: TinyCortex Memory Tests
|
|
needs: [changes]
|
|
if: needs.changes.outputs.tinycortex == 'true'
|
|
runs-on: ubuntu-22.04
|
|
timeout-minutes: 30
|
|
container:
|
|
image: ghcr.io/tinyhumansai/openhuman_ci:rust-1.93.0
|
|
env:
|
|
CARGO_INCREMENTAL: "0"
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
|
|
- name: Init tinycortex submodule
|
|
run: |
|
|
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
|
git submodule update --init --recursive vendor/tinycortex
|
|
|
|
- name: Cache TinyCortex build artifacts
|
|
uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: vendor/tinycortex -> vendor/tinycortex/target
|
|
cache-on-failure: true
|
|
key: tinycortex
|
|
|
|
- name: Run TinyCortex engine and Composio sync tests
|
|
run: bash scripts/ci-cancel-aware.sh cargo test --manifest-path vendor/tinycortex/Cargo.toml --features git-diff,sync,persona
|
|
|
|
pr-ci-gate:
|
|
name: PR CI Gate
|
|
needs:
|
|
- changes
|
|
- frontend-checks
|
|
- rust-quality
|
|
- rust-core-coverage
|
|
- rust-tauri-coverage
|
|
- scripts-tests
|
|
- test-inventory
|
|
- pester-install
|
|
- tinycortex-tests
|
|
- orch-ip-gate
|
|
- feature-forwarding-gate
|
|
if: always()
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
steps:
|
|
- name: Require PR CI jobs to pass
|
|
run: |
|
|
set -euo pipefail
|
|
declare -A results=(
|
|
["Detect Changed Areas"]="${{ needs.changes.result }}"
|
|
["Frontend Checks"]="${{ needs['frontend-checks'].result }}"
|
|
["Rust Quality"]="${{ needs['rust-quality'].result }}"
|
|
["Rust Core Coverage"]="${{ needs['rust-core-coverage'].result }}"
|
|
["Rust Tauri Coverage"]="${{ needs['rust-tauri-coverage'].result }}"
|
|
["Scripts Self-Tests"]="${{ needs['scripts-tests'].result }}"
|
|
["Test Inventory"]="${{ needs['test-inventory'].result }}"
|
|
["PowerShell Install Test"]="${{ needs['pester-install'].result }}"
|
|
["TinyCortex Memory Tests"]="${{ needs['tinycortex-tests'].result }}"
|
|
["Orchestration IP Gate"]="${{ needs['orch-ip-gate'].result }}"
|
|
["Feature Forwarding Gate"]="${{ needs['feature-forwarding-gate'].result }}"
|
|
)
|
|
|
|
failed=0
|
|
for name in "${!results[@]}"; do
|
|
result="${results[$name]}"
|
|
echo "${name}: ${result}"
|
|
if [ "${result}" != "success" ] && [ "${result}" != "skipped" ]; then
|
|
echo "::error::${name} did not pass (result=${result})."
|
|
failed=1
|
|
fi
|
|
done
|
|
|
|
if [ "${failed}" -ne 0 ]; then
|
|
exit 1
|
|
fi
|
|
|
|
- name: Verify coverage lanes completed
|
|
run: |
|
|
set -euo pipefail
|
|
coverage="${{ needs.changes.outputs.coverage }}"
|
|
frontend_needed="${{ needs.changes.outputs.frontend }}"
|
|
core_needed="${{ needs.changes.outputs['rust-core'] }}"
|
|
tauri_needed="${{ needs.changes.outputs['rust-tauri'] }}"
|
|
frontend="${{ needs['frontend-checks'].result }}"
|
|
core="${{ needs['rust-core-coverage'].result }}"
|
|
tauri="${{ needs['rust-tauri-coverage'].result }}"
|
|
|
|
echo "Coverage needed: ${coverage}"
|
|
echo "Frontend changed: ${frontend_needed} (job=${frontend})"
|
|
echo "Rust core changed: ${core_needed} (job=${core})"
|
|
echo "Rust Tauri changed: ${tauri_needed} (job=${tauri})"
|
|
|
|
if [ "${coverage}" != "true" ]; then
|
|
echo "No coverage-relevant changes detected; skipping diff-cover."
|
|
exit 0
|
|
fi
|
|
|
|
if [ "${frontend_needed}" = "true" ] && [ "${frontend}" != "success" ]; then
|
|
echo "::error::Frontend coverage was required but Frontend Checks did not succeed (result=${frontend})."
|
|
exit 1
|
|
fi
|
|
if [ "${core_needed}" = "true" ] && [ "${core}" != "success" ]; then
|
|
echo "::error::Rust core coverage was required but the lane did not succeed (result=${core})."
|
|
exit 1
|
|
fi
|
|
if [ "${tauri_needed}" = "true" ] && [ "${tauri}" != "success" ]; then
|
|
echo "::error::Rust Tauri coverage was required but the lane did not succeed (result=${tauri})."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Checkout code
|
|
if: needs.changes.outputs.coverage == 'true'
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 0
|
|
persist-credentials: false
|
|
|
|
- name: Resolve coverage compare ref
|
|
if: needs.changes.outputs.coverage == 'true'
|
|
id: coverage-compare
|
|
run: |
|
|
set -euo pipefail
|
|
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
|
git fetch origin "${{ github.base_ref }}" --depth=200
|
|
echo "ref=origin/${{ github.base_ref }}" >> "$GITHUB_OUTPUT"
|
|
elif [ -n "${{ github.event.before }}" ] && [ "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ]; then
|
|
echo "ref=${{ github.event.before }}" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "ref=HEAD^" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Setup Python
|
|
if: needs.changes.outputs.coverage == 'true'
|
|
uses: actions/setup-python@v6
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Install diff-cover
|
|
if: needs.changes.outputs.coverage == 'true'
|
|
run: pip install 'diff-cover>=9.2.0'
|
|
|
|
- name: Download all lcov artifacts
|
|
if: needs.changes.outputs.coverage == 'true'
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
path: lcov-artifacts
|
|
pattern: lcov-*
|
|
merge-multiple: false
|
|
|
|
- name: List collected lcov files
|
|
if: needs.changes.outputs.coverage == 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
find lcov-artifacts -type f -name '*.info' -print
|
|
|
|
- name: Enforce >= 80% coverage on changed lines
|
|
if: needs.changes.outputs.coverage == 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
mapfile -t LCOV_FILES < <(find lcov-artifacts -type f -name '*.info' | sort)
|
|
if [ "${#LCOV_FILES[@]}" -eq 0 ]; then
|
|
echo "::error::No lcov files found; coverage gate cannot run"
|
|
exit 1
|
|
fi
|
|
diff-cover "${LCOV_FILES[@]}" \
|
|
--compare-branch="${{ steps.coverage-compare.outputs.ref }}" \
|
|
--fail-under=80 \
|
|
--html-report diff-coverage.html \
|
|
--markdown-report diff-coverage.md
|
|
|
|
- name: Upload diff-cover report
|
|
if: always() && needs.changes.outputs.coverage == 'true'
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: diff-coverage-report
|
|
path: |
|
|
diff-coverage.html
|
|
diff-coverage.md
|
|
retention-days: 14
|
|
if-no-files-found: warn
|