Skip to content

Commit 90d3e74

Browse files
committed
Add tests/check-local.sh — pre-push verification gate
Packages the local pre-push checks into one command so the working tree can be self-verified before pushing. Phases (~5 min total): 1. zig build Cross-compile sanity across aarch64-macos, aarch64-linux, x86_64-linux-{gnu,musl}. Catches GCC-vs-Clang divergence. 2. cmake + ctest Native macOS Release build + 15 ctests. 3. podman ASAN Ubuntu container with kalign built under ASAN and the full ctest suite. Catches Linux glibc behaviour that Apple's malloc hides. 4. pytest Python bindings + ecosystem integration (~171 tests). Each phase is independent — no short-circuit, so a single failing phase doesn't prevent the others from running and reporting. Skips gracefully if a tool isn't installed (e.g. podman) or its environment isn't ready (machine not running). Usage: tests/check-local.sh # run all four (~5 min) tests/check-local.sh --quick # skip Linux ASAN (~30s) tests/check-local.sh --help Exits 0 only if every non-skipped phase passes.
1 parent f5baaf3 commit 90d3e74

1 file changed

Lines changed: 198 additions & 0 deletions

File tree

tests/check-local.sh

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
#!/usr/bin/env bash
2+
#
3+
# tests/check-local.sh — pre-push verification gate for kalign.
4+
#
5+
# Runs four independent test layers; each catches a different class of
6+
# bug. If every required phase passes, the working tree is safe to push.
7+
#
8+
# Usage:
9+
# tests/check-local.sh # run all four phases (~5 min)
10+
# tests/check-local.sh --quick # skip the Linux ASAN container (~30s)
11+
# tests/check-local.sh --help
12+
#
13+
# Phases:
14+
# 1. zig build — cross-compile sanity across aarch64-macos,
15+
# aarch64-linux, x86_64-linux-{gnu,musl}.
16+
# Catches GCC-vs-Clang divergence (e.g. an unused
17+
# variable that is in fact used inside an #ifdef).
18+
# 2. cmake + ctest — native macOS Release build + 15 ctests.
19+
# Algorithmic correctness, ABI, integration.
20+
# 3. podman ASAN — Ubuntu container with kalign built under ASAN
21+
# and the full ctest suite. Catches Linux glibc
22+
# behaviour that Apple's malloc hides (e.g.
23+
# uninitialised MMALLOC fields that read as zero
24+
# on macOS but garbage on Linux).
25+
# 4. pytest — Python bindings, mode presets, ecosystem
26+
# integration. ~171 tests.
27+
#
28+
# Exits 0 only if every non-skipped phase passes.
29+
30+
set -u
31+
set -o pipefail
32+
33+
cd "$(dirname "$0")/.." # repo root
34+
35+
# ---- options ----------------------------------------------------------
36+
QUICK=0
37+
for arg in "$@"; do
38+
case "$arg" in
39+
--quick) QUICK=1 ;;
40+
-h|--help)
41+
sed -n '1,30p' "$0" | sed -n 's/^# \{0,1\}//p'
42+
exit 0
43+
;;
44+
*)
45+
printf 'Unknown option: %s\n' "$arg" >&2
46+
printf 'Try: %s --help\n' "$0" >&2
47+
exit 2
48+
;;
49+
esac
50+
done
51+
52+
# ---- output helpers ---------------------------------------------------
53+
if [ -t 1 ]; then
54+
YELLOW=$'\033[1;33m'
55+
GREEN=$'\033[0;32m'
56+
RED=$'\033[0;31m'
57+
NC=$'\033[0m'
58+
else
59+
YELLOW=''
60+
GREEN=''
61+
RED=''
62+
NC=''
63+
fi
64+
65+
banner() {
66+
printf '\n%s═══════════════════════════════════════════════════════════════%s\n' "$YELLOW" "$NC"
67+
printf '%s %s%s\n' "$YELLOW" "$1" "$NC"
68+
printf '%s═══════════════════════════════════════════════════════════════%s\n\n' "$YELLOW" "$NC"
69+
}
70+
71+
# ---- result tracking --------------------------------------------------
72+
PASS_LIST=()
73+
FAIL_LIST=()
74+
SKIP_LIST=()
75+
76+
record_pass() { PASS_LIST+=("$1"); }
77+
record_fail() { FAIL_LIST+=("$1"); }
78+
record_skip() { SKIP_LIST+=("$1$2"); }
79+
80+
# ---- Phase 1: zig build ----------------------------------------------
81+
phase_zig() {
82+
banner "Phase 1/4: zig build (cross-compile sanity)"
83+
if ! command -v zig >/dev/null 2>&1; then
84+
printf '%s⊘ zig not installed — skipping%s\n' "$YELLOW" "$NC"
85+
record_skip "Phase 1 (zig build)" "zig not installed"
86+
return 0
87+
fi
88+
if zig build; then
89+
record_pass "Phase 1 (zig build)"
90+
return 0
91+
fi
92+
record_fail "Phase 1 (zig build)"
93+
return 1
94+
}
95+
96+
# ---- Phase 2: native CMake + ctest -----------------------------------
97+
phase_cmake() {
98+
banner "Phase 2/4: native CMake build + ctest"
99+
local ncpu
100+
ncpu="$(sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 4)"
101+
mkdir -p build
102+
if ( cd build \
103+
&& cmake .. -DCMAKE_BUILD_TYPE=Release > /dev/null \
104+
&& make -j"$ncpu" \
105+
&& ctest --output-on-failure ); then
106+
record_pass "Phase 2 (cmake + ctest)"
107+
return 0
108+
fi
109+
record_fail "Phase 2 (cmake + ctest)"
110+
return 1
111+
}
112+
113+
# ---- Phase 3: Linux ASAN container -----------------------------------
114+
phase_memcheck() {
115+
banner "Phase 3/4: Linux ASAN ctest (podman memcheck container)"
116+
if [ "$QUICK" = "1" ]; then
117+
printf '%s⊘ skipped (--quick)%s\n' "$YELLOW" "$NC"
118+
record_skip "Phase 3 (Linux ASAN)" "--quick"
119+
return 0
120+
fi
121+
if ! command -v podman >/dev/null 2>&1; then
122+
printf '%s⊘ podman not installed — skipping%s\n' "$YELLOW" "$NC"
123+
record_skip "Phase 3 (Linux ASAN)" "podman not installed"
124+
return 0
125+
fi
126+
if ! podman info >/dev/null 2>&1; then
127+
printf '%s⊘ podman machine not running — try: podman machine start%s\n' "$YELLOW" "$NC"
128+
record_skip "Phase 3 (Linux ASAN)" "podman machine not running"
129+
return 0
130+
fi
131+
if ! podman build -f Containerfile.memcheck -t kalign-memcheck . ; then
132+
record_fail "Phase 3 (Linux ASAN — container build)"
133+
return 1
134+
fi
135+
if podman run --rm kalign-memcheck bash -c \
136+
"cd /kalign/build-asan && ASAN_OPTIONS='detect_leaks=0:halt_on_error=1:abort_on_error=1' ctest --output-on-failure"; then
137+
record_pass "Phase 3 (Linux ASAN)"
138+
return 0
139+
fi
140+
record_fail "Phase 3 (Linux ASAN)"
141+
return 1
142+
}
143+
144+
# ---- Phase 4: pytest -------------------------------------------------
145+
phase_python() {
146+
banner "Phase 4/4: Python tests (pytest)"
147+
if ! command -v uv >/dev/null 2>&1; then
148+
printf '%s⊘ uv not installed — skipping%s\n' "$YELLOW" "$NC"
149+
record_skip "Phase 4 (pytest)" "uv not installed"
150+
return 0
151+
fi
152+
if ! uv pip install -e . \
153+
--config-settings cmake.args="-DUSE_OPENMP=OFF;-DUSE_THREADPOOL=ON" \
154+
--force-reinstall --no-deps --quiet; then
155+
record_fail "Phase 4 (pytest — install)"
156+
return 1
157+
fi
158+
if uv run pytest tests/python/ -q --no-header; then
159+
record_pass "Phase 4 (pytest)"
160+
return 0
161+
fi
162+
record_fail "Phase 4 (pytest)"
163+
return 1
164+
}
165+
166+
# ---- run all phases (each independent; no short-circuit) -------------
167+
phase_zig || true
168+
phase_cmake || true
169+
phase_memcheck || true
170+
phase_python || true
171+
172+
# ---- summary ---------------------------------------------------------
173+
banner "Summary"
174+
if [ "${#PASS_LIST[@]}" -gt 0 ]; then
175+
for item in "${PASS_LIST[@]}"; do
176+
printf ' %s✓%s %s\n' "$GREEN" "$NC" "$item"
177+
done
178+
fi
179+
if [ "${#SKIP_LIST[@]}" -gt 0 ]; then
180+
for item in "${SKIP_LIST[@]}"; do
181+
printf ' %s⊘%s %s\n' "$YELLOW" "$NC" "$item"
182+
done
183+
fi
184+
if [ "${#FAIL_LIST[@]}" -gt 0 ]; then
185+
for item in "${FAIL_LIST[@]}"; do
186+
printf ' %s✗%s %s\n' "$RED" "$NC" "$item"
187+
done
188+
fi
189+
echo
190+
191+
n_fail="${#FAIL_LIST[@]}"
192+
if [ "$n_fail" -eq 0 ]; then
193+
printf '%sAll required phases passed. Safe to push.%s\n' "$GREEN" "$NC"
194+
exit 0
195+
else
196+
printf '%s%d phase(s) failed. Do NOT push.%s\n' "$RED" "$n_fail" "$NC"
197+
exit 1
198+
fi

0 commit comments

Comments
 (0)