Skip to content

Commit 53bc819

Browse files
authored
Report unit test files with no result (#3105)
<!-- .github/pull_request_template.md --> ## 📌 Description <!-- What does this PR do? Briefly describe the changes and why they’re needed. --> Currently, we have a handful of tests during parallel runs that are being OOM killed due to high memory usage. I previously attempted to resolve it in #2961, but some of the tests will need a little more pruning before they can be added in solo as they take too long in addition to consuming tons of memory. ## 🔍 Related Issues <!-- Link any related issues here --> ## 🚀 Pull Request Checklist Thank you for contributing to FlashInfer! Before we review your pull request, please make sure the following items are complete. ### ✅ Pre-commit Checks - [x] I have installed `pre-commit` by running `pip install pre-commit` (or used your preferred method). - [x] I have installed the hooks with `pre-commit install`. - [ ] I have run the hooks manually with `pre-commit run --all-files` and fixed any reported issues. > If you are unsure about how to set up `pre-commit`, see [the pre-commit documentation](https://pre-commit.com/). ## 🧪 Tests - [ ] Tests have been added or updated as needed. - [ ] All tests are passing (`unittest`, etc.). ## Reviewer Notes <!-- Optional: anything you'd like reviewers to focus on, concerns, etc. --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Clearly separate passed/failed tests from tests that produced no result artifacts; exclude "no result" from failed counts and ensure exit status reflects both failures and no-result conditions. * Remove stale result files before runs to prevent misleading outcomes. * **Chores** * Improved sequential and parallel test runners and enhanced execution summary reporting, including explicit counts and listings for test files with no result. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent b809821 commit 53bc819

1 file changed

Lines changed: 132 additions & 52 deletions

File tree

scripts/test_utils.sh

Lines changed: 132 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ PYTEST_FLAGS="--continue-on-collection-errors"
4040

4141
# Global variables for test execution
4242
FAILED_TESTS=""
43+
NO_RESULT_TESTS=""
4344
TOTAL_TESTS=0
4445
PASSED_TESTS=0
46+
NO_RESULT_COUNT=0
4547
TOTAL_TEST_CASES=0
4648
SAMPLED_TEST_CASES=0
4749
# shellcheck disable=SC2034 # EXIT_CODE is used by calling scripts
@@ -187,6 +189,47 @@ collect_tests() {
187189
ALL_NODE_IDS=$(echo "$COLLECTION_OUTPUT" | grep "::" || true)
188190
}
189191

192+
# Return the expected JUnit XML path for a test file
193+
junit_file_for_test() {
194+
local test_file=$1
195+
local flattened_test_file=${test_file//\//_}
196+
local test_hash
197+
test_hash=$(printf '%s' "$test_file" | cksum | awk '{print $1}')
198+
echo "${JUNIT_DIR}/${flattened_test_file}.${test_hash}.xml"
199+
}
200+
201+
# Record a failed test file in the execution summary
202+
record_failed_test() {
203+
local test_file=$1
204+
FAILED_TESTS="$FAILED_TESTS\n - $test_file"
205+
# shellcheck disable=SC2034 # EXIT_CODE is used by calling scripts
206+
EXIT_CODE=1
207+
}
208+
209+
# Record a test file that produced no result artifacts
210+
record_no_result_test() {
211+
local test_file=$1
212+
NO_RESULT_TESTS="$NO_RESULT_TESTS\n - $test_file"
213+
NO_RESULT_COUNT=$((NO_RESULT_COUNT + 1))
214+
}
215+
216+
# Describe which execution artifacts are missing for a test file
217+
describe_missing_artifacts() {
218+
local result_file=$1
219+
local junit_file=$2
220+
local -a missing=()
221+
222+
if [ -n "$result_file" ] && [ ! -f "$result_file" ]; then
223+
missing+=("result marker")
224+
fi
225+
if [ ! -f "$junit_file" ]; then
226+
missing+=("JUnit XML: $junit_file")
227+
fi
228+
229+
local IFS=', '
230+
echo "${missing[*]}"
231+
}
232+
190233
# Sample tests based on SAMPLE_RATE and SAMPLE_OFFSET
191234
sample_tests() {
192235
local all_node_ids=$1
@@ -240,8 +283,9 @@ dry_run_full_file() {
240283
local test_file=$1
241284

242285
TOTAL_TESTS=$((TOTAL_TESTS + 1))
243-
JUNIT_FILENAME="${test_file//\//_}.xml"
244-
JUNIT_FLAG="--junitxml=${JUNIT_DIR}/${JUNIT_FILENAME}"
286+
local junit_file
287+
junit_file=$(junit_file_for_test "$test_file")
288+
JUNIT_FLAG="--junitxml=${junit_file}"
245289
# shellcheck disable=SC2086 # PYTEST_COMMAND_PREFIX needs word splitting
246290
echo "$TOTAL_TESTS. ${PYTEST_COMMAND_PREFIX} pytest $PYTEST_FLAGS ${JUNIT_FLAG} \"${test_file}\""
247291
}
@@ -289,6 +333,8 @@ print_dry_run_summary() {
289333
run_sanity_test_file() {
290334
local test_file=$1
291335
local file_count=$2
336+
local junit_file
337+
junit_file=$(junit_file_for_test "$test_file")
292338

293339
echo "=========================================="
294340
echo "[$file_count] Processing: $test_file"
@@ -328,21 +374,29 @@ run_sanity_test_file() {
328374
# Create a bash array with the node IDs
329375
mapfile -t SAMPLED_NODE_IDS_ARRAY <<< "$SAMPLED_NODE_IDS"
330376

331-
JUNIT_FILENAME="${test_file//\//_}.xml"
332-
JUNIT_FLAG="--junitxml=${JUNIT_DIR}/${JUNIT_FILENAME}"
377+
JUNIT_FLAG="--junitxml=${junit_file}"
333378

334379
# Run pytest with the sampled node IDs
335380
TOTAL_TESTS=$((TOTAL_TESTS + 1))
381+
rm -f "$junit_file"
336382

337383
# shellcheck disable=SC2086 # PYTEST_COMMAND_PREFIX and PYTEST_FLAGS need word splitting
338384
if ${PYTEST_COMMAND_PREFIX} pytest $PYTEST_FLAGS "${JUNIT_FLAG}" "${SAMPLED_NODE_IDS_ARRAY[@]}"; then
339-
echo "✅ PASSED: $test_file ($SAMPLED_IN_FILE/$TOTAL_IN_FILE tests)"
340-
PASSED_TESTS=$((PASSED_TESTS + 1))
385+
if [ -f "$junit_file" ]; then
386+
echo "✅ PASSED: $test_file ($SAMPLED_IN_FILE/$TOTAL_IN_FILE tests)"
387+
PASSED_TESTS=$((PASSED_TESTS + 1))
388+
else
389+
echo "⚠️ NO RESULT: $test_file ($SAMPLED_IN_FILE/$TOTAL_IN_FILE tests, missing JUnit XML: $junit_file)"
390+
record_no_result_test "$test_file"
391+
fi
341392
else
342-
echo "❌ FAILED: $test_file ($SAMPLED_IN_FILE/$TOTAL_IN_FILE tests)"
343-
FAILED_TESTS="$FAILED_TESTS\n - $test_file"
344-
# shellcheck disable=SC2034 # EXIT_CODE is used by calling scripts
345-
EXIT_CODE=1
393+
if [ -f "$junit_file" ]; then
394+
echo "❌ FAILED: $test_file ($SAMPLED_IN_FILE/$TOTAL_IN_FILE tests)"
395+
record_failed_test "$test_file"
396+
else
397+
echo "⚠️ NO RESULT: $test_file ($SAMPLED_IN_FILE/$TOTAL_IN_FILE tests, missing JUnit XML: $junit_file)"
398+
record_no_result_test "$test_file"
399+
fi
346400
fi
347401

348402
echo ""
@@ -351,25 +405,35 @@ run_sanity_test_file() {
351405
# Run a single test file in full mode
352406
run_full_test_file() {
353407
local test_file=$1
408+
local junit_file
409+
junit_file=$(junit_file_for_test "$test_file")
354410

355411
echo "=========================================="
356-
JUNIT_FILENAME="${test_file//\//_}.xml"
357-
JUNIT_FLAG="--junitxml=${JUNIT_DIR}/${JUNIT_FILENAME}"
412+
JUNIT_FLAG="--junitxml=${junit_file}"
358413
# shellcheck disable=SC2086 # PYTEST_COMMAND_PREFIX needs word splitting
359414
echo "Running: ${PYTEST_COMMAND_PREFIX} pytest $PYTEST_FLAGS ${JUNIT_FLAG} \"${test_file}\""
360415
echo "=========================================="
361416

362417
TOTAL_TESTS=$((TOTAL_TESTS + 1))
418+
rm -f "$junit_file"
363419

364420
# shellcheck disable=SC2086 # PYTEST_COMMAND_PREFIX and PYTEST_FLAGS need word splitting
365421
if ${PYTEST_COMMAND_PREFIX} pytest $PYTEST_FLAGS "${JUNIT_FLAG}" "${test_file}"; then
366-
echo "✅ PASSED: $test_file"
367-
PASSED_TESTS=$((PASSED_TESTS + 1))
422+
if [ -f "$junit_file" ]; then
423+
echo "✅ PASSED: $test_file"
424+
PASSED_TESTS=$((PASSED_TESTS + 1))
425+
else
426+
echo "⚠️ NO RESULT: $test_file (missing JUnit XML: $junit_file)"
427+
record_no_result_test "$test_file"
428+
fi
368429
else
369-
echo "❌ FAILED: $test_file"
370-
FAILED_TESTS="$FAILED_TESTS\n - $test_file"
371-
# shellcheck disable=SC2034 # EXIT_CODE is used by calling scripts
372-
EXIT_CODE=1
430+
if [ -f "$junit_file" ]; then
431+
echo "❌ FAILED: $test_file"
432+
record_failed_test "$test_file"
433+
else
434+
echo "⚠️ NO RESULT: $test_file (missing JUnit XML: $junit_file)"
435+
record_no_result_test "$test_file"
436+
fi
373437
fi
374438

375439
echo ""
@@ -475,13 +539,16 @@ run_tests_parallel() {
475539
local file_index=$3
476540
local result_file="$PARALLEL_TMP_DIR/result_${file_index}"
477541
local log_file="$PARALLEL_TMP_DIR/log_${file_index}"
542+
local junit_file
543+
junit_file=$(junit_file_for_test "$test_file")
478544

479545
(
480546
# Set GPU for this test
481547
export CUDA_VISIBLE_DEVICES=$gpu_id
482548

483549
# Redirect output to log file
484550
exec > "$log_file" 2>&1
551+
rm -f "$junit_file"
485552

486553
echo "=========================================="
487554
echo "[$file_index/$total_files] Processing: $test_file"
@@ -513,8 +580,7 @@ run_tests_parallel() {
513580
fi
514581

515582
mapfile -t SAMPLED_NODE_IDS_ARRAY <<< "$SAMPLED_NODE_IDS"
516-
JUNIT_FILENAME="${test_file//\//_}.xml"
517-
JUNIT_FLAG="--junitxml=${JUNIT_DIR}/${JUNIT_FILENAME}"
583+
JUNIT_FLAG="--junitxml=${junit_file}"
518584

519585
# shellcheck disable=SC2086
520586
if ${PYTEST_COMMAND_PREFIX} pytest $PYTEST_FLAGS "${JUNIT_FLAG}" "${SAMPLED_NODE_IDS_ARRAY[@]}"; then
@@ -526,8 +592,7 @@ run_tests_parallel() {
526592
fi
527593
else
528594
# Run full test
529-
JUNIT_FILENAME="${test_file//\//_}.xml"
530-
JUNIT_FLAG="--junitxml=${JUNIT_DIR}/${JUNIT_FILENAME}"
595+
JUNIT_FLAG="--junitxml=${junit_file}"
531596

532597
# shellcheck disable=SC2086
533598
if ${PYTEST_COMMAND_PREFIX} pytest $PYTEST_FLAGS "${JUNIT_FLAG}" "${test_file}"; then
@@ -541,7 +606,7 @@ run_tests_parallel() {
541606
) &
542607

543608
local pid=$!
544-
echo "$pid:$test_file:$result_file:$log_file:$file_index"
609+
echo "$pid:$test_file:$result_file:$log_file:$file_index:$junit_file"
545610
}
546611

547612
# Launch tests in parallel with GPU queue
@@ -582,9 +647,9 @@ run_tests_parallel() {
582647
job_info=$(run_single_test_background "$test_file" "$gpu_id" "$file_index")
583648

584649
# Parse job info
585-
local pid result_file log_file
586-
IFS=':' read -r pid test_file result_file log_file file_index <<< "$job_info"
587-
test_result_files[$pid]="$result_file:$test_file:$log_file:$file_index"
650+
local pid result_file log_file junit_file
651+
IFS=':' read -r pid test_file result_file log_file file_index junit_file <<< "$job_info"
652+
test_result_files[$pid]="$result_file:$test_file:$log_file:$file_index:$junit_file"
588653
test_pid_map[$pid]="$test_file"
589654
test_gpu_map[$pid]="$gpu_id"
590655

@@ -609,8 +674,8 @@ run_tests_parallel() {
609674
# Sort results by file_index for deterministic output
610675
local -a sorted_pids=()
611676
for pid in "${!test_result_files[@]}"; do
612-
local result_file test_file log_file file_index
613-
IFS=':' read -r result_file test_file log_file file_index <<< "${test_result_files[$pid]}"
677+
local result_file test_file log_file file_index junit_file
678+
IFS=':' read -r result_file test_file log_file file_index junit_file <<< "${test_result_files[$pid]}"
614679
sorted_pids+=("$file_index:$pid")
615680
done
616681
local sorted_list
@@ -620,8 +685,8 @@ run_tests_parallel() {
620685
# Process results in order
621686
for entry in "${sorted_pids[@]}"; do
622687
local pid="${entry#*:}"
623-
local result_file test_file log_file file_index
624-
IFS=':' read -r result_file test_file log_file file_index <<< "${test_result_files[$pid]}"
688+
local result_file test_file log_file file_index junit_file
689+
IFS=':' read -r result_file test_file log_file file_index junit_file <<< "${test_result_files[$pid]}"
625690

626691
# Show log output
627692
if [ -f "$log_file" ]; then
@@ -633,45 +698,52 @@ run_tests_parallel() {
633698
if [ -f "$result_file" ]; then
634699
local result
635700
result=$(cat "$result_file")
701+
if [[ "$result" == SKIPPED* ]]; then
702+
continue
703+
fi
704+
636705
TOTAL_TESTS=$((TOTAL_TESTS + 1))
637706

707+
if [ "$mode" = "sanity" ] && [[ "$result" == PASSED:* || "$result" == FAILED:* ]]; then
708+
local total_in_file sampled_in_file
709+
# shellcheck disable=SC2034 # status is part of the read but unused
710+
IFS=':' read -r _ total_in_file sampled_in_file <<< "$result"
711+
TOTAL_TEST_CASES=$((TOTAL_TEST_CASES + total_in_file))
712+
SAMPLED_TEST_CASES=$((SAMPLED_TEST_CASES + sampled_in_file))
713+
fi
714+
715+
if [ ! -f "$junit_file" ]; then
716+
echo "⚠️ NO RESULT: $test_file (missing JUnit XML: $junit_file)"
717+
record_no_result_test "$test_file"
718+
continue
719+
fi
720+
638721
if [[ "$result" == PASSED* ]]; then
639722
PASSED_TESTS=$((PASSED_TESTS + 1))
640-
if [ "$mode" = "sanity" ]; then
641-
local total_in_file sampled_in_file
642-
# shellcheck disable=SC2034 # status is part of the read but unused
643-
IFS=':' read -r _ total_in_file sampled_in_file <<< "$result"
644-
TOTAL_TEST_CASES=$((TOTAL_TEST_CASES + total_in_file))
645-
SAMPLED_TEST_CASES=$((SAMPLED_TEST_CASES + sampled_in_file))
646-
fi
647723
elif [[ "$result" == FAILED* ]]; then
648-
FAILED_TESTS="$FAILED_TESTS\n - $test_file"
649-
# shellcheck disable=SC2034 # EXIT_CODE is used by calling scripts
650-
EXIT_CODE=1
651-
if [ "$mode" = "sanity" ]; then
652-
local total_in_file sampled_in_file
653-
# shellcheck disable=SC2034 # status is part of the read but unused
654-
IFS=':' read -r _ total_in_file sampled_in_file <<< "$result"
655-
TOTAL_TEST_CASES=$((TOTAL_TEST_CASES + total_in_file))
656-
SAMPLED_TEST_CASES=$((SAMPLED_TEST_CASES + sampled_in_file))
657-
fi
658-
elif [[ "$result" == SKIPPED* ]]; then
659-
# Don't count skipped tests as passed
660-
TOTAL_TESTS=$((TOTAL_TESTS - 1))
724+
record_failed_test "$test_file"
661725
fi
726+
else
727+
TOTAL_TESTS=$((TOTAL_TESTS + 1))
728+
local missing_artifacts
729+
missing_artifacts=$(describe_missing_artifacts "$result_file" "$junit_file")
730+
echo "⚠️ NO RESULT: $test_file (missing ${missing_artifacts})"
731+
record_no_result_test "$test_file"
662732
fi
663733
done
664734
}
665735

666736
# Print execution summary
667737
print_execution_summary() {
668738
if [ "$SANITY_TEST" == "true" ]; then
739+
local failed_count=$((TOTAL_TESTS - PASSED_TESTS - NO_RESULT_COUNT))
669740
echo "=========================================="
670741
echo "SANITY TEST SUMMARY"
671742
echo "=========================================="
672743
echo "Total test files executed: $TOTAL_TESTS"
673744
echo "Test files passed: $PASSED_TESTS"
674-
echo "Test files failed: $((TOTAL_TESTS - PASSED_TESTS))"
745+
echo "Test files failed: $failed_count"
746+
echo "Test files with no result: $NO_RESULT_COUNT"
675747
echo ""
676748
echo "Total test cases (full suite): $TOTAL_TEST_CASES"
677749
echo "Sampled test cases (executed): $SAMPLED_TEST_CASES"
@@ -685,19 +757,27 @@ print_execution_summary() {
685757
echo "To reproduce this exact run:"
686758
echo " SAMPLE_RATE=${SAMPLE_RATE} SAMPLE_OFFSET=${SAMPLE_OFFSET} $0 --sanity-test"
687759
else
760+
local failed_count=$((TOTAL_TESTS - PASSED_TESTS - NO_RESULT_COUNT))
688761
echo "=========================================="
689762
echo "TEST SUMMARY"
690763
echo "=========================================="
691764
echo "Total test files executed: $TOTAL_TESTS"
692765
echo "Passed: $PASSED_TESTS"
693-
echo "Failed: $((TOTAL_TESTS - PASSED_TESTS))"
766+
echo "Failed: $failed_count"
767+
echo "No result: $NO_RESULT_COUNT"
694768
fi
695769

696770
if [ -n "$FAILED_TESTS" ]; then
697771
echo ""
698772
echo "Failed test files:"
699773
echo -e "$FAILED_TESTS"
700774
fi
775+
776+
if [ -n "$NO_RESULT_TESTS" ]; then
777+
echo ""
778+
echo "Test files with no result:"
779+
echo -e "$NO_RESULT_TESTS"
780+
fi
701781
}
702782

703783
# Main execution function for dry run mode

0 commit comments

Comments
 (0)