Skip to content

Commit 56f5a97

Browse files
sbryngelsonclaude
andcommitted
Speed up precheck: parallel phase 2 + skip render tests (35s -> 21s)
Run format first (modifies files), then lint, spelling, source lint, and doc refs in parallel. Skip slow matplotlib rendering tests during precheck via MFC_SKIP_RENDER_TESTS=1 (CI still runs the full suite). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6d24267 commit 56f5a97

2 files changed

Lines changed: 90 additions & 40 deletions

File tree

toolchain/bootstrap/precheck.sh

Lines changed: 77 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -55,81 +55,118 @@ while [[ $# -gt 0 ]]; do
5555
shift
5656
done
5757

58-
FAILED=0
58+
# Skip slow rendering tests (matplotlib/imageio) during local precheck.
59+
# CI runs the full suite via ./mfc.sh lint without this variable.
60+
export MFC_SKIP_RENDER_TESTS=1
5961

6062
log "Running$MAGENTA precheck$COLOR_RESET (same checks as CI lint-gate)..."
6163
echo ""
6264

63-
# 1. Check formatting
64-
log "[$CYAN 1/5$COLOR_RESET] Checking$MAGENTA formatting$COLOR_RESET..."
65-
# Capture state before formatting
65+
# Temp files for collecting results from parallel jobs
66+
TMPDIR_PC=$(mktemp -d)
67+
trap "rm -rf $TMPDIR_PC" EXIT
68+
69+
# --- Phase 1: Format (modifies files, must run alone) ---
6670
BEFORE_HASH=$(git diff -- '*.f90' '*.fpp' '*.py' 2>/dev/null | compute_hash)
6771
if ! ./mfc.sh format -j "$JOBS" > /dev/null 2>&1; then
68-
error "Formatting check failed to run."
69-
FAILED=1
72+
FORMAT_OK=1
7073
else
71-
# Check if formatting changed any Fortran/Python files
7274
AFTER_HASH=$(git diff -- '*.f90' '*.fpp' '*.py' 2>/dev/null | compute_hash)
7375
if [ "$BEFORE_HASH" != "$AFTER_HASH" ]; then
74-
error "Code was not formatted. Files have been auto-formatted; review and stage the changes."
75-
echo ""
76-
git diff --stat -- '*.f90' '*.fpp' '*.py' 2>/dev/null || true
77-
echo ""
78-
FAILED=1
76+
FORMAT_OK=2
77+
else
78+
FORMAT_OK=0
79+
fi
80+
fi
81+
82+
# --- Phase 2: All remaining checks in parallel (read-only) ---
83+
84+
# Spell check
85+
(
86+
if ./mfc.sh spelling > /dev/null 2>&1; then
87+
echo "0" > "$TMPDIR_PC/spell_exit"
88+
else
89+
echo "1" > "$TMPDIR_PC/spell_exit"
90+
fi
91+
) &
92+
PID_SPELL=$!
93+
94+
# Lint (ruff + unit tests) — safe after format since files are stable
95+
(
96+
if ./mfc.sh lint > /dev/null 2>&1; then
97+
echo "0" > "$TMPDIR_PC/lint_exit"
7998
else
80-
ok "Formatting check passed."
99+
echo "1" > "$TMPDIR_PC/lint_exit"
81100
fi
101+
) &
102+
PID_LINT=$!
103+
104+
# Source lint (fast grep checks — run inline)
105+
SOURCE_FAILED=0
106+
if grep -qiR '!\$acc\|!\$omp' --exclude="parallel_macros.fpp" --exclude="acc_macros.fpp" --exclude="omp_macros.fpp" --exclude="shared_parallel_macros.fpp" --exclude="syscheck.fpp" ./src/* 2>/dev/null; then
107+
SOURCE_FAILED=1
108+
fi
109+
if grep -qiR 'double_precision\|dsqrt\|dexp\|dlog\|dble\|dabs\|double\ precision\|real(8)\|real(4)\|dprod\|dmin\|dmax\|dfloat\|dreal\|dcos\|dsin\|dtan\|dsign\|dtanh\|dsinh\|dcosh\|d0' --exclude-dir=syscheck --exclude="*nvtx*" --exclude="*precision_select*" ./src/* 2>/dev/null; then
110+
SOURCE_FAILED=1
111+
fi
112+
if grep -qiR -e '\.\.\.' -e '\-\-\-' -e '===' ./src/* 2>/dev/null; then
113+
SOURCE_FAILED=1
114+
fi
115+
116+
# Doc reference check
117+
DOC_FAILED=0
118+
if ! python3 toolchain/mfc/lint_docs.py > /dev/null 2>&1; then
119+
DOC_FAILED=1
82120
fi
83121

84-
# 2. Spell check
122+
# --- Collect results ---
123+
124+
FAILED=0
125+
126+
log "[$CYAN 1/5$COLOR_RESET] Checking$MAGENTA formatting$COLOR_RESET..."
127+
if [ "$FORMAT_OK" = "1" ]; then
128+
error "Formatting check failed to run."
129+
FAILED=1
130+
elif [ "$FORMAT_OK" = "2" ]; then
131+
error "Code was not formatted. Files have been auto-formatted; review and stage the changes."
132+
echo ""
133+
git diff --stat -- '*.f90' '*.fpp' '*.py' 2>/dev/null || true
134+
echo ""
135+
FAILED=1
136+
else
137+
ok "Formatting check passed."
138+
fi
139+
140+
wait $PID_SPELL
85141
log "[$CYAN 2/5$COLOR_RESET] Running$MAGENTA spell check$COLOR_RESET..."
86-
if ./mfc.sh spelling > /dev/null 2>&1; then
142+
SPELL_RC=$(cat "$TMPDIR_PC/spell_exit" 2>/dev/null || echo "1")
143+
if [ "$SPELL_RC" = "0" ]; then
87144
ok "Spell check passed."
88145
else
89146
error "Spell check failed. Run$MAGENTA ./mfc.sh spelling$COLOR_RESET for details."
90147
FAILED=1
91148
fi
92149

93-
# 3. Lint toolchain (Python)
150+
wait $PID_LINT
94151
log "[$CYAN 3/5$COLOR_RESET] Running$MAGENTA toolchain lint$COLOR_RESET..."
95-
if ./mfc.sh lint > /dev/null 2>&1; then
152+
LINT_RC=$(cat "$TMPDIR_PC/lint_exit" 2>/dev/null || echo "1")
153+
if [ "$LINT_RC" = "0" ]; then
96154
ok "Toolchain lint passed."
97155
else
98156
error "Toolchain lint failed. Run$MAGENTA ./mfc.sh lint$COLOR_RESET for details."
99157
FAILED=1
100158
fi
101159

102-
# 4. Source code lint checks
103160
log "[$CYAN 4/5$COLOR_RESET] Running$MAGENTA source lint$COLOR_RESET checks..."
104-
SOURCE_FAILED=0
105-
106-
# Check for raw OpenACC/OpenMP directives
107-
if grep -qiR '!\$acc\|!\$omp' --exclude="parallel_macros.fpp" --exclude="acc_macros.fpp" --exclude="omp_macros.fpp" --exclude="shared_parallel_macros.fpp" --exclude="syscheck.fpp" ./src/* 2>/dev/null; then
108-
error "Found raw OpenACC/OpenMP directives. Use macros instead."
109-
SOURCE_FAILED=1
110-
fi
111-
112-
# Check for double precision intrinsics
113-
if grep -qiR 'double_precision\|dsqrt\|dexp\|dlog\|dble\|dabs\|double\ precision\|real(8)\|real(4)\|dprod\|dmin\|dmax\|dfloat\|dreal\|dcos\|dsin\|dtan\|dsign\|dtanh\|dsinh\|dcosh\|d0' --exclude-dir=syscheck --exclude="*nvtx*" --exclude="*precision_select*" ./src/* 2>/dev/null; then
114-
error "Found double precision intrinsics. Use generic intrinsics."
115-
SOURCE_FAILED=1
116-
fi
117-
118-
# Check for junk code patterns
119-
if grep -qiR -e '\.\.\.' -e '\-\-\-' -e '===' ./src/* 2>/dev/null; then
120-
error "Found junk code patterns (..., ---, ===) in source."
121-
SOURCE_FAILED=1
122-
fi
123-
124161
if [ $SOURCE_FAILED -eq 0 ]; then
125162
ok "Source lint passed."
126163
else
164+
error "Source lint failed."
127165
FAILED=1
128166
fi
129167

130-
# 5. Doc reference check
131168
log "[$CYAN 5/5$COLOR_RESET] Checking$MAGENTA doc references$COLOR_RESET..."
132-
if python3 toolchain/mfc/lint_docs.py 2>&1; then
169+
if [ $DOC_FAILED -eq 0 ]; then
133170
ok "Doc references are valid."
134171
else
135172
error "Doc reference check failed. Run$MAGENTA python3 toolchain/mfc/lint_docs.py$COLOR_RESET for details."

toolchain/mfc/viz/test_viz.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212

1313
FIXTURES = os.path.join(os.path.dirname(__file__), "fixtures")
1414

15+
# Rendering tests import matplotlib and are slow (~8s). Skip them during
16+
# pre-commit (MFC_SKIP_RENDER_TESTS=1) and run the full suite in CI only.
17+
_SKIP_RENDER = os.environ.get("MFC_SKIP_RENDER_TESTS", "") == "1"
18+
_SKIP_RENDER_MSG = "MFC_SKIP_RENDER_TESTS=1 — skipping rendering tests"
19+
1520
# Fixture paths for each dimension + format
1621
FIX_1D_BIN = os.path.join(FIXTURES, "1d_binary")
1722
FIX_1D_SILO = os.path.join(FIXTURES, "1d_silo")
@@ -135,6 +140,7 @@ def test_hyphen_range_raises_clean_error(self):
135140
# ---------------------------------------------------------------------------
136141

137142

143+
@unittest.skipIf(_SKIP_RENDER, _SKIP_RENDER_MSG)
138144
class TestPrettyLabel(unittest.TestCase):
139145
"""Test pretty_label() LaTeX label generation."""
140146

@@ -461,6 +467,7 @@ def test_1d_same_values(self):
461467
# ---------------------------------------------------------------------------
462468

463469

470+
@unittest.skipIf(_SKIP_RENDER, _SKIP_RENDER_MSG)
464471
class TestRender1D(unittest.TestCase):
465472
"""Smoke test: render 1D plots from fixture data."""
466473

@@ -495,6 +502,7 @@ def test_render_tiled_png(self):
495502
os.unlink(out)
496503

497504

505+
@unittest.skipIf(_SKIP_RENDER, _SKIP_RENDER_MSG)
498506
class TestRender2D(unittest.TestCase):
499507
"""Smoke test: render a 2D PNG from fixture data."""
500508

@@ -514,6 +522,7 @@ def test_render_2d_png(self):
514522
os.unlink(out)
515523

516524

525+
@unittest.skipIf(_SKIP_RENDER, _SKIP_RENDER_MSG)
517526
class TestRender3DSlice(unittest.TestCase):
518527
"""Smoke test: render a 3D slice PNG from fixture data."""
519528

@@ -661,6 +670,7 @@ def test_seed_clears_and_populates(self):
661670
# ---------------------------------------------------------------------------
662671

663672

673+
@unittest.skipIf(_SKIP_RENDER, _SKIP_RENDER_MSG)
664674
class TestRenderLogScale(unittest.TestCase):
665675
"""Smoke test: log scale option produces valid PNG output."""
666676

@@ -789,6 +799,7 @@ def test_very_large_extent_dedup_negative_decimals(self):
789799
# ---------------------------------------------------------------------------
790800

791801

802+
@unittest.skipIf(_SKIP_RENDER, _SKIP_RENDER_MSG)
792803
class TestRender2DTiled(unittest.TestCase):
793804
"""Smoke test: render_2d_tiled produces a valid PNG from 2D fixture data."""
794805

@@ -813,6 +824,7 @@ def test_render_2d_tiled_png(self):
813824
# ---------------------------------------------------------------------------
814825

815826

827+
@unittest.skipIf(_SKIP_RENDER, _SKIP_RENDER_MSG)
816828
class TestRender3DSliceAxes(unittest.TestCase):
817829
"""Test render_3d_slice with non-default slice axes and selectors."""
818830

@@ -856,6 +868,7 @@ def test_slice_by_value(self):
856868
# ---------------------------------------------------------------------------
857869

858870

871+
@unittest.skipIf(_SKIP_RENDER, _SKIP_RENDER_MSG)
859872
class TestRenderMp4(unittest.TestCase):
860873
"""Smoke test: render_mp4 exercises frame rendering and returns a bool."""
861874

0 commit comments

Comments
 (0)