Skip to content

Commit 4ba0344

Browse files
scripts: Add check_test_parity.sh to verify sync-async test coverage
- Introduced a new Bash script to check test coverage parity for sync and async test cases. - Compares modified test files between two Git references and identifies missing sync/async counterparts and payload-style tests. - Generates a detailed test parity report, including total tests, sync and async counts, and counterpart gaps. - Ensures script cleanliness and removes temporary files after execution. Assisted-by: Codex
1 parent ce5e2de commit 4ba0344

1 file changed

Lines changed: 158 additions & 0 deletions

File tree

scripts/check_test_parity.sh

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
IFS=$'\n\t'
4+
5+
base_ref="${1:-upstream/main}"
6+
head_ref="${2:-HEAD}"
7+
8+
if ! git rev-parse --verify "$base_ref" > /dev/null 2>&1; then
9+
echo "Base ref '$base_ref' not found." >&2
10+
exit 1
11+
fi
12+
13+
if ! git rev-parse --verify "$head_ref" > /dev/null 2>&1; then
14+
echo "Head ref '$head_ref' not found." >&2
15+
exit 1
16+
fi
17+
18+
mapfile -t test_files < <(
19+
git diff --name-only "$base_ref..$head_ref" -- tests | grep -E '\.py$' || true
20+
)
21+
22+
if [[ ${#test_files[@]} -eq 0 ]]; then
23+
echo "No changed test files under tests/ for $base_ref..$head_ref."
24+
exit 0
25+
fi
26+
27+
tmp_output="$(mktemp)"
28+
tmp_tests="$(mktemp)"
29+
tmp_counts="$(mktemp)"
30+
tmp_missing_sync="$(mktemp)"
31+
tmp_missing_async="$(mktemp)"
32+
tmp_payload="$(mktemp)"
33+
trap 'rm -f "$tmp_output" "$tmp_tests" "$tmp_counts" "$tmp_missing_sync" "$tmp_missing_async" "$tmp_payload"' EXIT
34+
35+
echo "Running pytest on changed tests:"
36+
printf ' - %s\n' "${test_files[@]}"
37+
38+
uv run pytest -vv -rA -n auto "${test_files[@]}" | tee "$tmp_output"
39+
40+
awk '
41+
{
42+
line = $0;
43+
sub(/^\[[^]]+\][[:space:]]+/, "", line);
44+
if (line ~ /^(PASSED|FAILED|SKIPPED|XFAIL|XPASS|ERROR)[[:space:]]+tests\/.*::/) {
45+
sub(/^(PASSED|FAILED|SKIPPED|XFAIL|XPASS|ERROR)[[:space:]]+/, "", line);
46+
print line;
47+
} else if (line ~ /^tests\/.*::.*[[:space:]]+(PASSED|FAILED|SKIPPED|XFAIL|XPASS|ERROR)$/) {
48+
sub(/[[:space:]]+(PASSED|FAILED|SKIPPED|XFAIL|XPASS|ERROR)$/, "", line);
49+
print line;
50+
}
51+
}
52+
' "$tmp_output" > "$tmp_tests"
53+
54+
if [[ ! -s "$tmp_tests" ]]; then
55+
echo "No test node IDs detected in pytest output; try rerunning with -vv." >&2
56+
exit 1
57+
fi
58+
59+
awk -v sync_file="$tmp_missing_sync" \
60+
-v async_file="$tmp_missing_async" \
61+
-v payload_file="$tmp_payload" \
62+
-v counts_file="$tmp_counts" '
63+
function is_async(nodeid) {
64+
return (nodeid ~ /::test_.*async_/);
65+
}
66+
function normalize(nodeid) {
67+
sub(/::test_live_async_/, "::test_live_", nodeid);
68+
sub(/::test_async_/, "::test_", nodeid);
69+
return nodeid;
70+
}
71+
{
72+
total++;
73+
if ($0 ~ /::test_.*(payload|validation)/) {
74+
payload_like[$0] = 1;
75+
}
76+
if (is_async($0)) {
77+
async_count++;
78+
norm = normalize($0);
79+
async_norm[norm] = 1;
80+
async_orig[norm] = $0;
81+
} else {
82+
sync_count++;
83+
norm = normalize($0);
84+
sync_norm[norm] = 1;
85+
sync_orig[norm] = $0;
86+
}
87+
}
88+
END {
89+
missing_sync = 0;
90+
missing_async = 0;
91+
92+
for (n in async_norm) {
93+
if (!(n in sync_norm)) {
94+
missing_sync++;
95+
print async_orig[n] >> sync_file;
96+
}
97+
}
98+
for (n in sync_norm) {
99+
if (!(n in async_norm)) {
100+
missing_async++;
101+
print sync_orig[n] >> async_file;
102+
}
103+
}
104+
payload_count = 0;
105+
for (t in payload_like) {
106+
payload_count++;
107+
print t >> payload_file;
108+
}
109+
110+
print "total=" total > counts_file;
111+
print "sync_count=" sync_count >> counts_file;
112+
print "async_count=" async_count >> counts_file;
113+
print "missing_sync=" missing_sync >> counts_file;
114+
print "missing_async=" missing_async >> counts_file;
115+
print "payload_count=" payload_count >> counts_file;
116+
}
117+
' "$tmp_tests"
118+
119+
total=0
120+
sync_count=0
121+
async_count=0
122+
missing_sync=0
123+
missing_async=0
124+
payload_count=0
125+
while IFS='=' read -r key value; do
126+
case "$key" in
127+
total) total="$value" ;;
128+
sync_count) sync_count="$value" ;;
129+
async_count) async_count="$value" ;;
130+
missing_sync) missing_sync="$value" ;;
131+
missing_async) missing_async="$value" ;;
132+
payload_count) payload_count="$value" ;;
133+
esac
134+
done < "$tmp_counts"
135+
136+
echo ""
137+
echo "Test parity report"
138+
echo "Total tests: $total"
139+
echo "Sync tests: $sync_count"
140+
echo "Async tests: $async_count"
141+
echo "Missing sync counterparts: $missing_sync"
142+
if [[ "$missing_sync" -gt 0 ]]; then
143+
sort "$tmp_missing_sync" | while read -r line; do
144+
echo " - $line"
145+
done
146+
fi
147+
echo "Missing async counterparts: $missing_async"
148+
if [[ "$missing_async" -gt 0 ]]; then
149+
sort "$tmp_missing_async" | while read -r line; do
150+
echo " - $line"
151+
done
152+
fi
153+
echo "Payload/validation-style tests (name contains payload/validation): $payload_count"
154+
if [[ "$payload_count" -gt 0 ]]; then
155+
sort "$tmp_payload" | while read -r line; do
156+
echo " - $line"
157+
done
158+
fi

0 commit comments

Comments
 (0)