Skip to content
Open
Changes from 29 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
7697a6c
rewrite test retry logic
JonathanOppenheimer Jul 23, 2025
0f1e686
only rerun failed + non-run tests
JonathanOppenheimer Jul 23, 2025
d5f0d87
rewrite get all tests for package level
JonathanOppenheimer Jul 23, 2025
43f77e0
try retry at individual package level
JonathanOppenheimer Jul 23, 2025
4ec0caf
add debug
JonathanOppenheimer Jul 23, 2025
eeb1a25
rewrite get_all_tests
JonathanOppenheimer Jul 23, 2025
df4d5f2
enhancements
JonathanOppenheimer Jul 23, 2025
ffbe3e4
fix RAN TESTS
JonathanOppenheimer Jul 23, 2025
5db5447
hmmm
JonathanOppenheimer Jul 23, 2025
93d7334
try this
JonathanOppenheimer Jul 24, 2025
b7f68e6
fix env vars
JonathanOppenheimer Jul 24, 2025
b0fb02f
change coreth path
JonathanOppenheimer Jul 24, 2025
aac7eaf
more efficient get test list
JonathanOppenheimer Jul 24, 2025
6b3e797
add echo statements
JonathanOppenheimer Jul 24, 2025
998bbdf
ADSFADSF
JonathanOppenheimer Jul 24, 2025
388c630
typo
JonathanOppenheimer Jul 24, 2025
024ab2a
Merge branch 'master' into rewrite-test-retry-logic
JonathanOppenheimer Jul 24, 2025
5d18f5f
restore test coverage
JonathanOppenheimer Jul 24, 2025
3cd402a
simplify
JonathanOppenheimer Jul 24, 2025
3f28633
got
JonathanOppenheimer Jul 24, 2025
5226e5a
no eit
JonathanOppenheimer Jul 24, 2025
3496271
add -v
JonathanOppenheimer Jul 24, 2025
e476499
use json
JonathanOppenheimer Jul 24, 2025
7d38556
gotestsum
JonathanOppenheimer Jul 24, 2025
528c57c
RESET
JonathanOppenheimer Jul 24, 2025
fa233b2
try rerun at the package level
JonathanOppenheimer Jul 24, 2025
9a6f4fa
lint errors
JonathanOppenheimer Jul 24, 2025
a6893d0
seperate paths
JonathanOppenheimer Jul 24, 2025
50d090a
Merge branch 'master' into rewrite-test-retry-logic
JonathanOppenheimer Jul 24, 2025
3c46681
save package for test through mapping:
JonathanOppenheimer Jul 25, 2025
70a34c8
remove non-flaky test
JonathanOppenheimer Jul 25, 2025
58ca623
Merge branch 'master' into rewrite-test-retry-logic
JonathanOppenheimer Jul 25, 2025
48d8f60
Merge branch 'master' into rewrite-test-retry-logic
JonathanOppenheimer Jul 28, 2025
54ab582
See bash version
JonathanOppenheimer Jul 28, 2025
d78e10b
try other bash
JonathanOppenheimer Jul 28, 2025
ccd8f70
comply with old bash version
JonathanOppenheimer Jul 28, 2025
1dc934c
Merge branch 'master' into rewrite-test-retry-logic
JonathanOppenheimer Jul 30, 2025
dd94616
Merge branch 'master' into rewrite-test-retry-logic
JonathanOppenheimer Jul 31, 2025
5c8fdca
Merge branch 'master' into rewrite-test-retry-logic
JonathanOppenheimer Aug 5, 2025
b68485a
Merge branch 'master' into rewrite-test-retry-logic
JonathanOppenheimer Aug 5, 2025
2a6fdb9
Merge branch 'master' into rewrite-test-retry-logic
JonathanOppenheimer Aug 6, 2025
db3a694
Merge branch 'master' into rewrite-test-retry-logic
JonathanOppenheimer Aug 11, 2025
70512e3
Merge branch 'master' into rewrite-test-retry-logic
JonathanOppenheimer Aug 11, 2025
785468b
Merge branch 'master' into rewrite-test-retry-logic
JonathanOppenheimer Aug 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 177 additions & 28 deletions scripts/build_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,38 +21,187 @@ if [[ -n "${NO_RACE:-}" ]]; then
race=""
fi

# MAX_RUNS bounds the attempts to retry the tests before giving up
# This is useful for flaky tests
# MAX_RUNS bounds the attempts to retry individual tests before giving up
MAX_RUNS=4
for ((i = 1; i <= MAX_RUNS; i++));
do
# shellcheck disable=SC2046
go test -shuffle=on ${race:-} -timeout="${TIMEOUT:-600s}" -coverprofile=coverage.out -covermode=atomic "$@" $(go list ./... | grep -v github.com/ava-labs/coreth/tests) | tee test.out || command_status=$?

# If the test passed, exit
if [[ ${command_status:-0} == 0 ]]; then
rm test.out
exit 0
else
unset command_status # Clear the error code for the next run

# Function to extract failed test names from output
extract_failed_tests() {
local output_file="$1"
# Extract test failures and panics
(grep "^--- FAIL" "$output_file" | awk '{print $3}' || grep -E '^\s+Test.+ \(' "$output_file" | awk '{print $1}') |
sort -u
}

# Function to check if a test is a known flake
is_known_flake() {
local test_name="$1"
grep -q "^${test_name}$" ./scripts/known_flakes.txt
}

# Function to check for panics in output
detect_panics() {
local output_file="$1"
grep -q "panic:" "$output_file" || grep -q "fatal error:" "$output_file"
}

# Function to get all tests in a package
get_package_tests() {
local package="$1"
go test -list=".*" "$package" 2>/dev/null | grep "^Test" || true
}

# Function to get tests that didn't run due to panic
get_missing_tests_after_panic() {
local output_file="$1"
local package="$2"
local failed_tests="$3"

# Get all tests in the package
local all_tests
all_tests=$(get_package_tests "$package")

# Find tests that didn't run (not in failed_tests and not in successful output)
local missing_tests=""
for test in $all_tests; do
# Check if test is in failed_tests
if echo "$failed_tests" | grep -q "$test"; then
continue
fi

# Check if test passed (appears in output with PASS)
if grep -q "PASS.*$test" "$output_file"; then
continue
fi

# Test didn't run
missing_tests="$missing_tests $test"
done

echo "$missing_tests"
}

# Function to run specific tests and return status
run_specific_test() {
local test_name="$1"
local output_file="$2"

# Extract package name from test name (assuming format: PackageName.TestName)
local package
package="${test_name%.*}"

# Run the specific test
go test -shuffle=on ${race:-} -timeout="${TIMEOUT:-600s}" -run "^${test_name}$" "$package" 2>&1 | tee "$output_file"
return "${PIPESTATUS[0]}"
}

# Initial test run
echo "Running initial test suite..."
# shellcheck disable=SC2046
go test -shuffle=on ${race:-} -timeout="${TIMEOUT:-600s}" -coverprofile=coverage.out -covermode=atomic "$@" $(go list ./... | grep -v github.com/ava-labs/coreth/tests) | tee test.out || command_status=$?
Comment on lines +107 to +108
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it's possible to write this line so it both
A. works
B. passes lint

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about separating creation of the array from its use?

mapfile -t pkgs < <(go list ./... | grep -v github.com/ava-labs/coreth/tests)
go test -shuffle=on "${race:-}" -timeout="${TIMEOUT:-600s}" -coverprofile=coverage.out -covermode=atomic "$@" "${pkgs[@]}" | tee test.\
out || command_status=$?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If only! Writing this script was actually quite difficult because while bash is a decent programming language (imo). mapfile was added in Bash 4.0 and all supported macOS releases (including the ones behind GitHub Actions’ macos‑latest runner) still ship with Bash 3.2.x—currently 3.2.57—because Apple never adopted the GPL v3‑licensed Bash 4+ and instead moved its interactive shell to zsh while leaving /bin/bash frozen at 3.2.57
(see https://jmmv.dev/2019/11/macos-bash-baggage.html)

Thus all of our testing scripts need to be compatible with bash circa 2014, and I was hindered from using a lot of newer bash features that would have made this easier to write.

If you endorse it, I can refactor the whole testing script to use Python (or GoLang) instead of bash but it would be a much more significant change.

Copy link
Contributor

@maru-ava maru-ava Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no obligation to be compatible with macos's ancient bash version. In avalanchego, we explicitly require a modern bash. One way to ensure the use of modern bash could be to run the command with scripts/dev_shell.sh, since the nix shell guarantees a compatible bash version.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting! That's definitely something that is worth bringing over to the evm repositories in my opinion.


# If initial run passes, exit successfully
if [[ ${command_status:-0} == 0 ]]; then
rm test.out
echo "All tests passed on first run!"
exit 0
fi

# Extract failed tests
failed_tests=$(extract_failed_tests test.out)
echo "Failed tests: $failed_tests"

# Check for unexpected failures
unexpected_failures=""
for test in $failed_tests; do
if ! is_known_flake "$test"; then
unexpected_failures="$unexpected_failures $test"
fi
done

if [ -n "$unexpected_failures" ]; then
echo "Unexpected test failures: $unexpected_failures"
exit 1
fi

# All failures are known flakes, retry them individually
echo "All failures are known flakes. Retrying failed tests individually..."

# If the test failed, print the output
unexpected_failures=$(
# First grep pattern corresponds to test failures, second pattern corresponds to test panics due to timeouts
(grep "^--- FAIL" test.out | awk '{print $3}' || grep -E '^\s+Test.+ \(' test.out | awk '{print $1}') |
sort -u | comm -23 - <(sed 's/\r$//' ./scripts/known_flakes.txt)
)
if [ -n "${unexpected_failures}" ]; then
echo "Unexpected test failures: ${unexpected_failures}"
# Create a temporary file for retry output
retry_output="retry_test.out"
rm -f "$retry_output"

# Track tests that need retry
tests_to_retry="$failed_tests"

# Retry loop for failed tests
for ((attempt = 1; attempt <= MAX_RUNS; attempt++)); do
echo "Retry attempt $attempt for tests: $tests_to_retry"

still_failing=""
panic_affected_packages=""

# Retry each failed test
for test in $tests_to_retry; do
echo "Retrying test: $test"

# Run the specific test
run_specific_test "$test" "$retry_output"
test_status=$?

# Check if test passed
if [[ $test_status == 0 ]]; then
echo "Test $test passed on attempt $attempt"
continue
fi

# Check if this is an unexpected failure
if ! is_known_flake "$test"; then
echo "Test $test failed and is not a known flake"
exit 1
fi

# Check for panics
if detect_panics "$retry_output"; then
echo "Test $test panicked on attempt $attempt"
package="${test%.*}"
panic_affected_packages="$panic_affected_packages $package"

# Get tests that didn't run due to panic
missing_tests=$(get_missing_tests_after_panic "$retry_output" "$package" "$test")
if [ -n "$missing_tests" ]; then
echo "Tests that didn't run due to panic: $missing_tests"
# Add missing tests to retry list
for missing_test in $missing_tests; do
if ! echo "$tests_to_retry" | grep -q "$missing_test"; then
tests_to_retry="$tests_to_retry $missing_test"
fi
done
fi
fi

# Test still failing
still_failing="$still_failing $test"
done

# Update tests to retry for next iteration
tests_to_retry="$still_failing"

# If no tests are still failing, we're done
if [ -z "$tests_to_retry" ]; then
echo "All tests passed after retries!"
rm -f test.out "$retry_output"
exit 0
fi

echo "Tests still failing after attempt $attempt: $tests_to_retry"

# If this was the last attempt, fail
if [[ $attempt == "$MAX_RUNS" ]]; then
echo "Tests failed all $MAX_RUNS attempts: $tests_to_retry"
exit 1
fi

# Note the absence of unexpected failures cannot be indicative that we only need to run the tests that failed,
# for example a test may panic and cause subsequent tests in that package to not run.
# So we loop here.
echo "Test run $i failed with known flakes, retrying..."
done

# If we reach here, we have failed all retries
exit 1
echo "All known flaky tests passed after retries!"
rm -f test.out "$retry_output"
exit 0
Loading