mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
213 lines
7.5 KiB
Bash
Executable File
213 lines
7.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# PR CI Rust core coverage lane — changed-files-only cargo-llvm-cov.
|
|
#
|
|
# Fast-lane policy (PRs targeting main): instead of the full ~13k-test
|
|
# instrumented suite, run only the unit tests for the modules the PR touched:
|
|
# - src/<a>/<b>/... .rs → libtest filter "<a>::<b>" (domain-level scope, so
|
|
# sibling-module tests like store_tests.rs / ops.rs still run)
|
|
# - tests/<name>.rs → that integration-test target only (--test <name>)
|
|
# Coverage from all scoped runs is merged (--no-report + report) into a single
|
|
# lcov file; the PR CI Gate's diff-cover step enforces >= 80% on changed lines.
|
|
#
|
|
# NOTE: this means changed lines must be covered by tests in their own domain
|
|
# (or a changed integration test) — coverage contributed by unrelated suites
|
|
# no longer counts on the fast lane. The full suite still runs on main→release
|
|
# PRs (Release CI).
|
|
#
|
|
# Inputs (env):
|
|
# FULL "true" → run the full suite (build-config / lib.rs / script
|
|
# changes, detected by paths-filter)
|
|
# CHANGED_FILES shell-quoted, space-separated repo-relative paths from
|
|
# dorny/paths-filter (list-files: shell)
|
|
# OUT lcov output path (default lcov-core.info)
|
|
#
|
|
# Falls back to the FULL suite whenever scoping is not clearly safe.
|
|
set -euo pipefail
|
|
|
|
FULL="${FULL:-false}"
|
|
CHANGED_FILES="${CHANGED_FILES:-}"
|
|
OUT="${OUT:-lcov-core.info}"
|
|
MAX_CHANGED_FILES="${MAX_CHANGED_FILES:-200}"
|
|
|
|
log() { echo "[ci][rust-cov-changed] $*"; }
|
|
|
|
llvm_cov() {
|
|
bash scripts/ci-cancel-aware.sh cargo llvm-cov "$@"
|
|
}
|
|
|
|
integration_test_targets() {
|
|
find tests -maxdepth 1 -type f -name '*.rs' -print |
|
|
sed -e 's#^tests/##' -e 's#\.rs$##' |
|
|
sort
|
|
}
|
|
|
|
raw_coverage_modules() {
|
|
find tests/raw_coverage -maxdepth 1 -type f -name '*.rs' -print |
|
|
sed -e 's#^tests/raw_coverage/##' -e 's#\.rs$##' |
|
|
sort
|
|
}
|
|
|
|
run_integration_target() {
|
|
local target="$1"
|
|
if [ "${target}" = "raw_coverage_all" ]; then
|
|
# These suites used to be separate integration-test binaries. Aggregating
|
|
# them removes repeated full-crate links, but many still exercise process
|
|
# globals (env vars, event bus handlers, auth tokens, singleton stores).
|
|
# Run one process per generated module filter to preserve the former
|
|
# per-binary isolation contract while still paying only one link.
|
|
while IFS= read -r module; do
|
|
[ -n "${module}" ] || continue
|
|
log "running raw coverage module: ${module}"
|
|
llvm_cov --no-report --no-fail-fast -p openhuman --test "${target}" -- "${module}::" --test-threads=1
|
|
done < <(raw_coverage_modules)
|
|
else
|
|
llvm_cov --no-report --no-fail-fast -p openhuman --test "${target}"
|
|
fi
|
|
}
|
|
|
|
run_full() {
|
|
log "running FULL instrumented suite (reason: $1)"
|
|
llvm_cov clean --workspace
|
|
llvm_cov --no-report --no-fail-fast -p openhuman --lib
|
|
llvm_cov --no-report --no-fail-fast -p openhuman --bins
|
|
while IFS= read -r target; do
|
|
[ -n "${target}" ] || continue
|
|
log "running full-suite integration target: ${target}"
|
|
run_integration_target "${target}"
|
|
done < <(integration_test_targets)
|
|
log "merging coverage into ${OUT}"
|
|
llvm_cov report --lcov --output-path "${OUT}"
|
|
exit 0
|
|
}
|
|
|
|
if [ "${FULL}" = "true" ]; then
|
|
run_full "build-config/workflow-level change detected by paths-filter"
|
|
fi
|
|
|
|
# Portable across bash 3.2 (macOS) and 5.x (CI containers): no declare -A,
|
|
# no mapfile, and no empty-array "${arr[@]}" expansion under set -u.
|
|
#
|
|
# CHANGED_FILES is the shell-quoted list from dorny/paths-filter
|
|
# (list-files: shell). Filenames are PR-controlled, so never eval it —
|
|
# xargs unquotes tokens as data without ever invoking a shell. If xargs
|
|
# can't parse it (e.g. hostile quoting), we get an empty list and fall
|
|
# back to the full suite.
|
|
declare -a files=()
|
|
while IFS= read -r f; do
|
|
[ -n "${f}" ] && files+=("${f}")
|
|
done < <(printf '%s\n' "${CHANGED_FILES}" | xargs -n1 printf '%s\n' 2>/dev/null || true)
|
|
log "received ${#files[@]} changed rust file(s)"
|
|
|
|
if [ "${#files[@]}" -eq 0 ]; then
|
|
run_full "empty changed-file list — scoping unsafe"
|
|
fi
|
|
if [ "${#files[@]}" -gt "${MAX_CHANGED_FILES}" ]; then
|
|
run_full "${#files[@]} changed files exceed MAX_CHANGED_FILES=${MAX_CHANGED_FILES}"
|
|
fi
|
|
|
|
lib_filters_raw=""
|
|
test_targets_raw=""
|
|
for f in "${files[@]}"; do
|
|
case "${f}" in
|
|
src/lib.rs | src/main.rs)
|
|
run_full "root module ${f} changed — whole-crate scope"
|
|
;;
|
|
src/bin/*)
|
|
# Standalone backfill binaries (slack-backfill, gmail-backfill-3d) have
|
|
# no unit tests; nothing to scope to.
|
|
log "ignoring standalone-binary file: ${f}"
|
|
;;
|
|
src/*.rs)
|
|
p="${f#src/}"
|
|
p="${p%.rs}"
|
|
IFS='/' read -r -a segs <<<"${p}"
|
|
n="${#segs[@]}"
|
|
if [ "${segs[n - 1]}" = "mod" ]; then
|
|
segs=("${segs[@]:0:n-1}")
|
|
n="${#segs[@]}"
|
|
fi
|
|
if [ "${n}" -ge 2 ]; then
|
|
key="${segs[0]}::${segs[1]}"
|
|
else
|
|
key="${segs[0]}"
|
|
fi
|
|
lib_filters_raw="${lib_filters_raw}${key}
|
|
"
|
|
log "${f} → libtest filter '${key}'"
|
|
;;
|
|
src/*/*)
|
|
# Non-.rs asset embedded in a domain (e.g. agent prompt markdown under
|
|
# src/openhuman/agent/prompts/) — scope to that domain's tests.
|
|
p="${f#src/}"
|
|
IFS='/' read -r -a segs <<<"${p}"
|
|
n="${#segs[@]}"
|
|
if [ "${n}" -ge 3 ]; then
|
|
key="${segs[0]}::${segs[1]}"
|
|
else
|
|
key="${segs[0]}"
|
|
fi
|
|
lib_filters_raw="${lib_filters_raw}${key}
|
|
"
|
|
log "${f} → libtest filter '${key}' (embedded asset)"
|
|
;;
|
|
tests/raw_coverage/*.rs)
|
|
# The ~76 *_raw_coverage_e2e.rs suites are aggregated into the single
|
|
# `raw_coverage_all` target (see tests/raw_coverage_all.rs + build.rs), so
|
|
# a change to any of them scopes to that one target rather than the full
|
|
# suite. libtest filters within the aggregate binary still work, but the
|
|
# simplest correct scope is running the whole aggregate target.
|
|
test_targets_raw="${test_targets_raw}raw_coverage_all
|
|
"
|
|
log "${f} → aggregated integration target '--test raw_coverage_all'"
|
|
;;
|
|
tests/*.rs)
|
|
name="${f#tests/}"
|
|
name="${name%.rs}"
|
|
if [[ "${name}" == */* ]]; then
|
|
# Nested support module — can affect any integration target.
|
|
run_full "shared integration-test support file ${f} changed"
|
|
fi
|
|
test_targets_raw="${test_targets_raw}${name}
|
|
"
|
|
log "${f} → integration target '--test ${name}'"
|
|
;;
|
|
*)
|
|
run_full "unclassified rust-relevant file ${f} changed"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
declare -a lib_filters=()
|
|
while IFS= read -r k; do
|
|
[ -n "${k}" ] && lib_filters+=("${k}")
|
|
done < <(printf '%s' "${lib_filters_raw}" | sort -u)
|
|
|
|
declare -a test_targets=()
|
|
while IFS= read -r k; do
|
|
[ -n "${k}" ] && test_targets+=("${k}")
|
|
done < <(printf '%s' "${test_targets_raw}" | sort -u)
|
|
|
|
if [ "${#lib_filters[@]}" -eq 0 ] && [ "${#test_targets[@]}" -eq 0 ]; then
|
|
run_full "no scoped test targets derivable from the change set"
|
|
fi
|
|
|
|
# Drop artifacts from previous coverage runs so merged profdata only reflects
|
|
# this run (build cache for dependencies is unaffected).
|
|
llvm_cov clean --workspace
|
|
|
|
if [ "${#lib_filters[@]}" -gt 0 ]; then
|
|
log "running scoped lib unit tests with filters: ${lib_filters[*]}"
|
|
# libtest ORs multiple positional filters — one run covers all domains.
|
|
llvm_cov --no-report --no-fail-fast -p openhuman --lib -- "${lib_filters[@]}"
|
|
fi
|
|
|
|
if [ "${#test_targets[@]}" -gt 0 ]; then
|
|
for t in "${test_targets[@]}"; do
|
|
log "running changed integration-test target: ${t}"
|
|
run_integration_target "${t}"
|
|
done
|
|
fi
|
|
|
|
log "merging coverage into ${OUT}"
|
|
llvm_cov report --lcov --output-path "${OUT}"
|