Files
LocalAI/scripts/run-coverage.sh
Richard Palethorpe 8d70855ea6 test: add Go + React UI coverage gates and fill test gaps (#9989)
- Strict monotonic Go coverage gate (make test-coverage-check, 45% baseline)
  run in CI; fixes ginkgo dropping all-but-one coverprofile across multiple
  recursive roots, builds with -tags auth, and folds in the in-process
  tests/e2e suite via --coverpkg.
- React UI e2e coverage (make test-ui-coverage: vite-plugin-istanbul + nyc,
  nix-provided Chromium) plus e2e specs for 6 previously-untested pages, and a
  UI coverage gate (make test-ui-coverage-check) with a small tolerance since
  e2e line coverage jitters ~0.5pp run-to-run.
- pre-commit hook: lint + coverage on Go changes, Playwright e2e + UI coverage
  gate on react-ui changes; install with make install-hooks.
- New Go handler tests (settings, branding), hermetic base64 download test.
- fix(ui): model editor reads vram_display (snake_case), so the VRAM estimate
  renders again; covered by a regression test.

Assisted-by: Claude:claude-opus-4-7

Signed-off-by: Richard Palethorpe <io@richiejp.com>
2026-05-26 22:06:10 +02:00

102 lines
4.3 KiB
Bash
Executable File

#!/usr/bin/env sh
#
# run-coverage.sh OUTPUT_DIR MERGED_PROFILE FLAKE_ATTEMPTS UNIT_ROOT [UNIT_ROOT...]
#
# Runs the unit/suite tests under each UNIT_ROOT (recursively) plus the
# in-process integration suites in $COVERAGE_E2E_ROOTS, all instrumented to
# attribute coverage to $COVERAGE_COVERPKG, and merges everything into
# MERGED_PROFILE.
#
# Environment:
# GINKGO_TAGS build tags, e.g. "debug auth"
# COVERAGE_COVERPKG packages coverage is attributed to (comma-separated
# go patterns). Required so the in-process e2e suites
# credit the core/http handlers they drive over HTTP —
# with default per-package cover they'd only credit the
# e2e_test package itself.
# COVERAGE_E2E_ROOTS space-separated in-process integration suites, run
# NON-recursively (so tests/e2e/distributed, which needs
# containers, is excluded) with --label-filter.
# COVERAGE_E2E_LABELS ginkgo --label-filter for the e2e roots, e.g.
# "!real-models" (those specs need a downloaded model).
# COVERAGE_EXCLUDE_RE egrep pattern of profile lines to drop before merging,
# e.g. generated protobuf (grpc/proto/.*\.pb\.go).
#
# Why one ginkgo invocation per root: passing several recursive roots to a
# single ginkgo run only merges ONE root's coverprofile into --output-dir
# (verified ginkgo 2.29.0) — the rest are silently dropped. So each root runs
# separately. Because --coverpkg makes the same package appear in multiple
# root profiles, the merge SUMS per-block hit counts (a plain concat would
# duplicate blocks and miscount); summing is valid for covermode=atomic.
#
# POSIX sh (no bash): no arrays. Roots are space-free tokens iterated with
# word-splitting; the only flag that can contain a space (--tags="debug auth")
# is held in the positional parameters ("$@") so its quoting survives.
set -u
out_dir="${1:?usage: run-coverage.sh OUTPUT_DIR MERGED_PROFILE FLAKES UNIT_ROOT...}"
merged="${2:?missing MERGED_PROFILE}"
flakes="${3:?missing FLAKE_ATTEMPTS}"
shift 3
unit_roots="$*" # space-free tokens (./pkg ./core)
mkdir -p "$out_dir"
# Clear per-root profiles from a previous run: the merge collects them by glob,
# so a stale profile (e.g. from a root that failed to rebuild this run) must not
# leak into the merged result.
rm -f "$out_dir"/cover-*.out
fail=0
# Common optional flags go into "$@"; unquoted ${VAR:+...} would word-split a
# --tags value that contains a space. The unit roots were captured above, so
# overwriting the positional parameters here is safe.
set --
[ -n "${GINKGO_TAGS:-}" ] && set -- "$@" "--tags=$GINKGO_TAGS"
[ -n "${COVERAGE_COVERPKG:-}" ] && set -- "$@" "--coverpkg=$COVERAGE_COVERPKG"
# cover-<root>.out, with path separators flattened (POSIX BRE, no \+).
profile_name() {
printf 'cover-%s.out' "$(printf '%s' "$1" | sed 's#[./][./]*#_#g; s#^_##; s#_$##')"
}
# Unit/suite roots: recursive.
for root in $unit_roots; do
base="$(profile_name "$root")"
go run github.com/onsi/ginkgo/v2/ginkgo --flake-attempts "$flakes" -v -r "$@" \
--cover --covermode=atomic --coverprofile="$base" --output-dir="$out_dir" "$root" || fail=1
done
# In-process integration roots: NON-recursive + optional label filter.
for root in ${COVERAGE_E2E_ROOTS:-}; do
base="$(profile_name "$root")"
if [ -n "${COVERAGE_E2E_LABELS:-}" ]; then
go run github.com/onsi/ginkgo/v2/ginkgo --flake-attempts "$flakes" -v "$@" \
--label-filter="$COVERAGE_E2E_LABELS" \
--cover --covermode=atomic --coverprofile="$base" --output-dir="$out_dir" "$root" || fail=1
else
go run github.com/onsi/ginkgo/v2/ginkgo --flake-attempts "$flakes" -v "$@" \
--cover --covermode=atomic --coverprofile="$base" --output-dir="$out_dir" "$root" || fail=1
fi
done
# Collect the per-root profiles by glob (space-safe, no list to track).
set -- "$out_dir"/cover-*.out
if [ ! -e "$1" ]; then
echo "run-coverage: no coverprofiles produced" >&2
exit 2
fi
# Merge: drop the per-file mode headers and any excluded (generated) lines,
# then sum hit counts per identical block, emitting a single mode header.
{
echo "mode: atomic"
awk -v exre="${COVERAGE_EXCLUDE_RE:-}" '
/^mode:/ { next }
exre != "" && $1 ~ exre { next }
{ stmts[$1] = $2; cnt[$1] += $3 }
END { for (k in stmts) print k, stmts[k], cnt[k] }
' "$@"
} > "$merged"
exit "$fail"