yams/scripts/run-local-coverage.sh
trvon c1e5ec0da6
fix(docs): Replace
missing paths where feasible, fail fast where support is
   not real, and sync docs with live behavior.

   - add vector/metadata and buffer extraction coverage
   - narrow storage and s3/plugin loader contracts
   - refresh pipeline, build, and test docs
2026-06-27 16:28:08 -04:00

245 lines
7.9 KiB
Bash
Executable file

#!/usr/bin/env bash
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"
if [[ "${YAMS_SKIP_COVERAGE_HOOK:-0}" == "1" ]]; then
echo "[coverage] YAMS_SKIP_COVERAGE_HOOK=1; skipping local coverage gate" >&2
exit 0
fi
case "$(uname -s)" in
Linux | Darwin) ;;
*)
echo "[coverage] unsupported platform for tracked coverage hook; skipping" >&2
exit 0
;;
esac
for tool in bash git python3 gcovr; do
if ! command -v "$tool" >/dev/null 2>&1; then
echo "[coverage] required tool '$tool' is missing; install it or set YAMS_SKIP_COVERAGE_HOOK=1 to bypass temporarily" >&2
exit 1
fi
done
changed_tmp="$(mktemp)"
trap 'rm -f "$changed_tmp"' EXIT
had_input=0
collect_changed() {
local range="$1"
local -a diff_args=()
if [[ -n "$range" ]]; then
diff_args+=("$range")
fi
git diff --name-only --diff-filter=ACMRT "${diff_args[@]}" |
grep -E '(\.(c|cc|cpp|cxx|h|hh|hpp)$|(^|/)(meson\.build|meson_options\.txt|setup\.sh|setup\.ps1)$)' \
>>"$changed_tmp" || true
}
while IFS=' ' read -r local_ref local_sha remote_ref remote_sha; do
had_input=1
[[ -z "${local_sha:-}" ]] && continue
if [[ "$local_sha" =~ ^0+$ ]]; then
continue
fi
if [[ -n "${remote_sha:-}" && ! "$remote_sha" =~ ^0+$ ]]; then
collect_changed "${remote_sha}..${local_sha}"
elif git rev-parse "${local_sha}^" >/dev/null 2>&1; then
collect_changed "${local_sha}^..${local_sha}"
else
collect_changed "$local_sha"
fi
done
if [[ "$had_input" == "0" ]]; then
if git rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' >/dev/null 2>&1; then
upstream_ref="$(git rev-parse --abbrev-ref --symbolic-full-name '@{upstream}')"
collect_changed "${upstream_ref}..HEAD"
elif git rev-parse HEAD^ >/dev/null 2>&1; then
collect_changed 'HEAD^..HEAD'
else
collect_changed 'HEAD'
fi
fi
changed=()
while IFS= read -r path; do
[[ -n "$path" ]] && changed+=("$path")
done < <(sort -u "$changed_tmp")
if [[ ${#changed[@]} -eq 0 ]]; then
echo "[coverage] no pushed C/C++ or Meson/build-script changes; skipping" >&2
exit 0
fi
echo "[coverage] running local coverage gate for ${#changed[@]} changed file(s)" >&2
printf ' %s\n' "${changed[@]}" >&2
BUILD_DIR="${YAMS_COVERAGE_BUILD_DIR:-build/coverage}"
JOBS="${YAMS_COVERAGE_HOOK_JOBS:-4}"
TEST_ARGS=(--suite unit --print-errorlogs --timeout-multiplier 2)
if [[ "$BUILD_DIR" != "build/coverage" ]]; then
echo "[coverage] custom build dir '$BUILD_DIR' selected; warm cache comparisons only make sense when reusing the same build dir because gcov metadata stays build-dir specific" >&2
fi
if [[ "${YAMS_COVERAGE_INCLUDE_INTEGRATION:-0}" == "1" ]]; then
echo "[coverage] including integration suite (excluding isolated smoke)" >&2
TEST_ARGS+=(--suite integration --no-suite isolated)
else
echo "[coverage] unit-only fast path; set YAMS_COVERAGE_INCLUDE_INTEGRATION=1 for integration coverage" >&2
fi
export YAMS_BUILD_DIR="$BUILD_DIR"
export YAMS_ENABLE_MOBILE_BINDINGS=false
export YAMS_EXTRA_MESON_FLAGS="${YAMS_COVERAGE_MESON_FLAGS:--Dbuild-tests=true -Denable-onnx=disabled -Denable-bench-tests=false -Dbuild-benchmarks=false}"
# Keep the tracked coverage lane sanitizer-free. setup.sh preserves ENABLE_TSAN /
# ENABLE_ASAN from the parent shell, and reusing a builddir configured with TSAN
# or ASAN can destabilize gcov/gcda writeout on macOS.
export ENABLE_TSAN=false
export ENABLE_ASAN=false
if [[ -z "${SOURCE_DATE_EPOCH:-}" ]]; then
if source_date_epoch="$(git log -1 --format=%ct HEAD 2>/dev/null)" && [[ -n "$source_date_epoch" ]]; then
export SOURCE_DATE_EPOCH="$source_date_epoch"
echo "[coverage] using SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH for reproducible version metadata" >&2
fi
fi
CCACHE_BIN="$(command -v ccache || true)"
C_COMPILER="$(command -v cc || true)"
CPP_COMPILER="$(command -v c++ || true)"
if command -v clang >/dev/null 2>&1 && command -v clang++ >/dev/null 2>&1; then
C_COMPILER="$(command -v clang)"
CPP_COMPILER="$(command -v clang++)"
fi
mkdir -p "$BUILD_DIR"
CCACHE_STATS_FILE="$BUILD_DIR/ccache-stats.txt"
GCOVR_SUMMARY_FILE="$BUILD_DIR/gcovr-summary.txt"
if [[ -n "$CCACHE_BIN" ]]; then
export CCACHE_BASEDIR="${CCACHE_BASEDIR:-$REPO_ROOT}"
export CCACHE_NOHASHDIR="${CCACHE_NOHASHDIR:-1}"
echo "[coverage] ccache path normalization enabled (base_dir=$CCACHE_BASEDIR, hash_dir=off)" >&2
fi
FAST_LINKER=""
case "$(uname -s)" in
Darwin)
if command -v ld.lld >/dev/null 2>&1; then
FAST_LINKER="lld"
fi
;;
*)
if command -v mold >/dev/null 2>&1; then
FAST_LINKER="mold"
elif command -v ld.lld >/dev/null 2>&1; then
FAST_LINKER="lld"
fi
;;
esac
LOCAL_NATIVE_FILE="$BUILD_DIR/local-coverage-native.ini"
if [[ -d "$BUILD_DIR" && (-n "$CCACHE_BIN" || -n "$FAST_LINKER") ]]; then
toolchain_mismatch=0
if [[ ! -f "$BUILD_DIR/build.ninja" ]]; then
toolchain_mismatch=1
else
if [[ -n "$CCACHE_BIN" ]] && ! rg -q -- '(^|[[:space:]/])ccache([[:space:]]|$)' "$BUILD_DIR/build.ninja"; then
toolchain_mismatch=1
fi
if [[ -n "$FAST_LINKER" ]] && ! rg -q -- "-fuse-ld=$FAST_LINKER" "$BUILD_DIR/build.ninja"; then
toolchain_mismatch=1
fi
fi
if [[ "$toolchain_mismatch" == "1" ]]; then
echo "[coverage] removing stale build dir so Meson can apply compiler/linker overrides at initial setup" >&2
rm -rf "$BUILD_DIR"
fi
fi
INTRO_BUILDOPTIONS_JSON="$BUILD_DIR/meson-info/intro-buildoptions.json"
if [[ -f "$INTRO_BUILDOPTIONS_JSON" ]]; then
profile_mismatch="$({
python3 - "$INTRO_BUILDOPTIONS_JSON" <<'PY'
import json
import sys
with open(sys.argv[1], 'r', encoding='utf-8') as fh:
options = json.load(fh)
values = {}
for option in options:
values.setdefault(option.get('name'), []).append(option.get('value'))
issues = []
if any(value not in (None, '', 'none', []) for value in values.get('b_sanitize', [])):
issues.append(f"b_sanitize={values.get('b_sanitize')}")
if any(value in (True, 'true') for value in values.get('enable-tsan', [])):
issues.append('enable-tsan=true')
if any(value in (True, 'true') for value in values.get('enable-asan', [])):
issues.append('enable-asan=true')
coverage_values = values.get('b_coverage', [])
if coverage_values and not any(value in (True, 'true') for value in coverage_values):
issues.append(f"b_coverage={coverage_values}")
print('; '.join(issues))
PY
} || true)"
if [[ -n "$profile_mismatch" ]]; then
echo "[coverage] removing stale build dir due to coverage profile drift ($profile_mismatch)" >&2
rm -rf "$BUILD_DIR"
fi
fi
if [[ -n "$CCACHE_BIN" || -n "$FAST_LINKER" ]]; then
mkdir -p "$BUILD_DIR"
{
echo "[binaries]"
if [[ -n "$CCACHE_BIN" ]]; then
printf "c = ['%s', '%s']\n" "$CCACHE_BIN" "$C_COMPILER"
printf "cpp = ['%s', '%s']\n" "$CCACHE_BIN" "$CPP_COMPILER"
else
printf "c = '%s'\n" "$C_COMPILER"
printf "cpp = '%s'\n" "$CPP_COMPILER"
fi
if [[ -n "$FAST_LINKER" ]]; then
echo "[built-in options]"
printf "c_link_args = ['-fuse-ld=%s']\n" "$FAST_LINKER"
printf "cpp_link_args = ['-fuse-ld=%s']\n" "$FAST_LINKER"
fi
} >"$LOCAL_NATIVE_FILE"
export YAMS_EXTRA_MESON_NATIVE_FILES="$LOCAL_NATIVE_FILE"
echo "[coverage] applying compiler/linker override via initial Meson setup ($LOCAL_NATIVE_FILE)" >&2
fi
./setup.sh Debug --coverage
MESON_BIN="meson"
if [[ -x "$BUILD_DIR/mesonw" ]]; then
MESON_BIN="$BUILD_DIR/mesonw"
fi
if [[ -n "$CCACHE_BIN" ]]; then
ccache -z >/dev/null 2>&1 || true
fi
"$MESON_BIN" compile -C "$BUILD_DIR" -j"$JOBS"
if command -v ccache >/dev/null 2>&1; then
echo "[coverage] ccache stats after compile:" >&2
ccache -s | tee "$CCACHE_STATS_FILE" >&2 || true
fi
# Coverage counters are build-dir local state. Clear stale *.gcda files before the
# test run so changed sources do not try to merge against incompatible counter
# layouts from an older build.
find "$BUILD_DIR" -name '*.gcda' -delete
"$MESON_BIN" test -C "$BUILD_DIR" "${TEST_ARGS[@]}"
gcovr \
--root "$REPO_ROOT" \
--object-directory "$BUILD_DIR" \
--exclude 'tests/.*' \
--exclude 'third_party/.*' \
--merge-mode-functions merge-use-line-min \
--txt-summary | tee "$GCOVR_SUMMARY_FILE"
echo "[coverage] local coverage gate passed" >&2