ClientTLS was the most security-sensitive package in the tree and the least covered, and it was not in the coverage gate at all (the gate's 4-package set predates it). 48 -> 84 tests against the real macOS keychain, serialized with a custom Testing trait after a @globalActor proved insufficient (actors yield at await, so cross-await critical sections got interleaved by other cases' cleanup). CI: the app/ipad/ios17 legs ran a bundle containing LiveServerSmokeTests, which spawns tsx, with no npm ci -- a hard failure, not a skip, on a bare checkout. Adds the missing iPad UI-test leg, and makes a missing iOS 17 runtime fail loudly instead of silently reporting green.
79 lines
3.7 KiB
Bash
Executable File
79 lines
3.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# coverage-gate.sh — T-iOS-16 per-package OWN-SOURCES coverage gate (>= 80%).
|
|
#
|
|
# Usage: coverage-gate.sh <PackageName> # e.g. coverage-gate.sh WireProtocol
|
|
# Env: COVERAGE_THRESHOLD=<percent> # default 80 (red/green demo knob)
|
|
#
|
|
# GATED PACKAGES (kept in sync with .github/workflows/ios.yml's package-tests
|
|
# matrix): WireProtocol, SessionCore, HostRegistry, APIClient — the four of plan
|
|
# §9 — plus ClientTLS, added by B4 (it holds the mTLS identity/keychain code, the
|
|
# most security-sensitive package in the tree, and was the only ungated one).
|
|
# TestSupport stays ungated: it IS the test doubles.
|
|
#
|
|
# WHY THIS EXISTS (plan §9 flaw, fix assigned to T-iOS-16): the raw §9 command
|
|
# xcrun llvm-cov export -summary-only ... -ignore-filename-regex '(Tests|TestSupport|\.build)/'
|
|
# | jq '.data[0].totals.lines.percent >= 80'
|
|
# reads the export TOTALS, which include every STATICALLY-LINKED DEPENDENCY
|
|
# source compiled into the test binary (e.g. WireProtocol sources inside
|
|
# SessionCore's test run). That lets a package's own weak coverage hide behind a
|
|
# well-covered dependency (or vice versa). The corrected filter below keeps ONLY
|
|
# files under Packages/<P>/Sources/ (excluding *Placeholder* scaffolding) and
|
|
# recomputes line coverage from the per-file summaries.
|
|
#
|
|
# Exit codes: 0 = gate passed; 1 = below threshold, filter matched no sources,
|
|
# or any build/test failure (set -e).
|
|
|
|
set -euo pipefail
|
|
|
|
PKG="${1:?usage: coverage-gate.sh <PackageName> (WireProtocol|SessionCore|HostRegistry|APIClient|ClientTLS)}"
|
|
THRESHOLD="${COVERAGE_THRESHOLD:-80}"
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
|
PKG_DIR="$REPO_ROOT/ios/Packages/$PKG"
|
|
|
|
if [ ! -d "$PKG_DIR" ]; then
|
|
echo "coverage-gate($PKG): package dir not found: $PKG_DIR" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# 1) Run the package tests with coverage instrumentation (test failure => gate red).
|
|
swift test --package-path "$PKG_DIR" --enable-code-coverage
|
|
|
|
# 2) Locate the test binary and the merged profile (paths per plan §9).
|
|
BIN="$(swift build --package-path "$PKG_DIR" --show-bin-path)/${PKG}PackageTests.xctest/Contents/MacOS/${PKG}PackageTests"
|
|
PROF="$(dirname "$(swift test --package-path "$PKG_DIR" --show-codecov-path)")/default.profdata"
|
|
|
|
# 3) Export per-file summaries and keep ONLY this package's own production
|
|
# sources: path must contain /Packages/<PKG>/Sources/ and must not be a
|
|
# *Placeholder* scaffold file. Dependency sources (other packages), Tests/,
|
|
# TestSupport and .build shims all fail the path filter automatically.
|
|
read -r COVERED TOTAL < <(
|
|
xcrun llvm-cov export -summary-only "$BIN" -instr-profile "$PROF" \
|
|
| jq -r --arg pkg "$PKG" '
|
|
[.data[0].files[]
|
|
| select(.filename | contains("/Packages/" + $pkg + "/Sources/"))
|
|
| select(.filename | contains("Placeholder") | not)
|
|
| .summary.lines]
|
|
| "\(map(.covered) | add // 0) \(map(.count) | add // 0)"'
|
|
)
|
|
|
|
if [ "$TOTAL" -eq 0 ]; then
|
|
echo "coverage-gate($PKG): FAIL — filter matched no own-source files under" \
|
|
"Packages/$PKG/Sources/ (filter bug or empty package?)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# LC_ALL=C on both awk calls: a comma-decimal locale would print "100,00",
|
|
# which awk then compares as a STRING against the threshold (silent false FAIL).
|
|
PCT="$(LC_ALL=C awk -v c="$COVERED" -v t="$TOTAL" 'BEGIN { printf "%.2f", c * 100 / t }')"
|
|
echo "coverage-gate($PKG): own-sources line coverage ${PCT}% (${COVERED}/${TOTAL} lines, threshold ${THRESHOLD}%)"
|
|
|
|
if ! LC_ALL=C awk -v p="$PCT" -v th="$THRESHOLD" 'BEGIN { exit !(p >= th) }'; then
|
|
echo "coverage-gate($PKG): FAIL — below threshold ${THRESHOLD}%" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "coverage-gate($PKG): PASS"
|