#!/usr/bin/env bash set -euo pipefail ROOT="$(git rev-parse --show-toplevel)" CONFIG_PATH="${ROOT}/files/config/runtime-edge-coverage-domains.json" DEFAULT_REPORT="${ROOT}/target/llvm-cov/report.json" GREEN=$'\033[32m' RED=$'\033[31m' CYAN=$'\033[36m' RESET=$'\033[0m' line_percent() { jq -r --arg domain "${1}" --arg root "${ROOT}" ' .domains[$domain].paths as $paths | def matches_domain: . as $file | any($paths[]; . as $p | ($file.filename | startswith($root + "/" + $p) or startswith("./" + $p) or startswith($p))); def pct($covered; $count): if $count > 0 then (($covered * 10000 / $count | floor) / 100) else 0 end; reduce ( input.data[]?.files[]? | select(matches_domain) ) as $file ( {lines: {count: 0, covered: 0}}; .lines.count += ($file.summary.lines.count // 0) | .lines.covered += ($file.summary.lines.covered // 0) ) | pct(.lines.covered; .lines.count) ' "${CONFIG_PATH}" "${2}" } usage() { cat <<'EOF' Usage: coverage-domain-evidence.sh --list coverage-domain-evidence.sh [--check] [--show-summary-details] [--show-paths] [--show-tracked-files] [--show-matched-files] [--verbose] [report.json] coverage-domain-evidence.sh [--show-summary-details] [--show-paths] [--show-tracked-files] [--show-matched-files] [--verbose] --check-all [report.json] The report path defaults to target/llvm-cov/report.json and is expected to be generated by `make ci` or `make coverage-report-json`. EOF } if [[ ! -f "${CONFIG_PATH}" ]]; then echo "missing coverage domain config: ${CONFIG_PATH}" >&2 exit 1 fi if [[ $# -eq 0 ]]; then usage exit 1 fi if [[ "$1" == "--list" ]]; then jq -r '.domains | keys[]' "${CONFIG_PATH}" exit 0 fi CHECK_MODE=0 CHECK_ALL=0 SHOW_PATHS=0 SHOW_TRACKED_FILES=0 SHOW_MATCHED_FILES=0 SHOW_SUMMARY_DETAILS=0 while [[ $# -gt 0 ]]; do case "$1" in --check) CHECK_MODE=1 shift ;; --check-all) CHECK_MODE=1 CHECK_ALL=1 shift ;; --show-paths) SHOW_PATHS=1 shift ;; --show-summary-details) SHOW_SUMMARY_DETAILS=1 shift ;; --show-tracked-files) SHOW_TRACKED_FILES=1 shift ;; --show-matched-files) SHOW_MATCHED_FILES=1 shift ;; --verbose) SHOW_SUMMARY_DETAILS=1 SHOW_PATHS=1 SHOW_TRACKED_FILES=1 SHOW_MATCHED_FILES=1 shift ;; --) shift break ;; -*) echo "unknown option: $1" >&2 usage exit 1 ;; *) break ;; esac done if [[ ${CHECK_ALL} -eq 1 ]]; then REPORT_PATH="${1:-${DEFAULT_REPORT}}" FAIL=0 while IFS= read -r domain; do ARGS=(--check) [[ ${SHOW_SUMMARY_DETAILS} -eq 1 ]] && ARGS+=(--show-summary-details) [[ ${SHOW_PATHS} -eq 1 ]] && ARGS+=(--show-paths) [[ ${SHOW_TRACKED_FILES} -eq 1 ]] && ARGS+=(--show-tracked-files) [[ ${SHOW_MATCHED_FILES} -eq 1 ]] && ARGS+=(--show-matched-files) "${BASH_SOURCE[0]}" "${ARGS[@]}" "${domain}" "${REPORT_PATH}" || FAIL=1 echo done < <(jq -r '.domains | keys[]' "${CONFIG_PATH}") exit "${FAIL}" fi DOMAIN="${1:-}" REPORT_PATH="${2:-${DEFAULT_REPORT}}" if [[ -z "${DOMAIN}" ]]; then usage exit 1 fi if ! jq -e --arg domain "${DOMAIN}" '.domains[$domain]' "${CONFIG_PATH}" >/dev/null; then echo "unknown domain: ${DOMAIN}" >&2 echo "available domains:" >&2 jq -r '.domains | keys[]' "${CONFIG_PATH}" >&2 exit 1 fi BASELINE="$(jq -r --arg domain "${DOMAIN}" '.domains[$domain].baseline_percent' "${CONFIG_PATH}")" if [[ ${SHOW_PATHS} -eq 1 ]]; then echo "Mapped repository paths:" jq -r --arg domain "${DOMAIN}" '.domains[$domain].paths[]' "${CONFIG_PATH}" | while IFS= read -r prefix; do echo " - ${prefix}" done echo fi if [[ ${SHOW_TRACKED_FILES} -eq 1 ]]; then echo "Tracked files present in repository:" while IFS= read -r path; do ( cd "${ROOT}" rg --files . | sed 's#^\./##' ) | rg "^${path//\//\\/}" || true done < <(jq -r --arg domain "${DOMAIN}" '.domains[$domain].paths[]' "${CONFIG_PATH}") echo fi if [[ ! -f "${REPORT_PATH}" ]]; then echo "Coverage report not found: ${REPORT_PATH}" echo "Generate it with: make coverage-report-json" if [[ ${CHECK_MODE} -eq 1 ]]; then exit 1 fi exit 0 fi jq -r --arg domain "${DOMAIN}" --arg root "${ROOT}" --argjson baseline "${BASELINE}" --argjson show_matched_files "${SHOW_MATCHED_FILES}" --argjson show_summary_details "${SHOW_SUMMARY_DETAILS}" ' .domains[$domain].paths as $paths | def matches_domain: . as $file | any($paths[]; . as $p | ($file.filename | startswith($root + "/" + $p) or startswith("./" + $p) or startswith($p))); def pct($covered; $count): if $count > 0 then (($covered * 10000 / $count | floor) / 100) else 0 end; reduce ( input.data[]?.files[]? | select(matches_domain) ) as $file ( { files: [], lines: {count: 0, covered: 0}, functions: {count: 0, covered: 0}, regions: {count: 0, covered: 0} }; .files += [$file.filename] | .lines.count += ($file.summary.lines.count // 0) | .lines.covered += ($file.summary.lines.covered // 0) | .functions.count += ($file.summary.functions.count // 0) | .functions.covered += ($file.summary.functions.covered // 0) | .regions.count += ($file.summary.regions.count // 0) | .regions.covered += ($file.summary.regions.covered // 0) ) | . + {line_percent: pct(.lines.covered; .lines.count)} | (if $show_summary_details == 1 then "lines: " + (.line_percent | tostring) + "% (" + (.lines.covered|tostring) + "/" + (.lines.count|tostring) + ")" else empty end), (if $show_summary_details == 1 then " files_in_report: " + (.files | length | tostring) else empty end), (if $show_summary_details == 1 then " functions: " + (pct(.functions.covered; .functions.count) | tostring) + "% (" + (.functions.covered|tostring) + "/" + (.functions.count|tostring) + ")" else empty end), (if $show_summary_details == 1 then " regions: " + (pct(.regions.covered; .regions.count) | tostring) + "% (" + (.regions.covered|tostring) + "/" + (.regions.count|tostring) + ")" else empty end), (if $show_matched_files == 1 then "" else empty end), (if $show_matched_files == 1 then "Matched report files:" else empty end), (if $show_matched_files == 1 then (.files[]? | " - " + .) else empty end) ' "${CONFIG_PATH}" "${REPORT_PATH}" LINE_PERCENT="$(line_percent "${DOMAIN}" "${REPORT_PATH}")" if awk "BEGIN { exit !(${LINE_PERCENT} >= ${BASELINE}) }"; then GATE_STATUS="${GREEN}PASS${RESET}" else GATE_STATUS="${RED}FAIL${RESET}" fi LINE="Domain: ${DOMAIN} :: GATE(lines>=baseline) [${CYAN}${LINE_PERCENT}%${RESET} >= ${RED}${BASELINE}%${RESET}]: ${GATE_STATUS}" VISIBLE_LINE="Domain: ${DOMAIN} :: GATE(lines>=baseline) [${LINE_PERCENT}% >= ${BASELINE}%]: PASS" if awk "BEGIN { exit !(${LINE_PERCENT} >= ${BASELINE}) }"; then VISIBLE_LINE="Domain: ${DOMAIN} :: GATE(lines>=baseline) [${LINE_PERCENT}% >= ${BASELINE}%]: PASS" else VISIBLE_LINE="Domain: ${DOMAIN} :: GATE(lines>=baseline) [${LINE_PERCENT}% >= ${BASELINE}%]: FAIL" fi LINE_WIDTH="${#VISIBLE_LINE}" BORDER="$(printf '═%.0s' $(seq 1 "${LINE_WIDTH}"))" echo "╔${BORDER}╗" echo "║${LINE}║" echo "╚${BORDER}╝" if [[ ${CHECK_MODE} -eq 1 ]]; then awk "BEGIN { exit !(${LINE_PERCENT} < ${BASELINE}) }" && { echo "Domain coverage gate failed for ${DOMAIN}: ${LINE_PERCENT}% is below baseline ${BASELINE}%" exit 1 } || true fi