Skip to content
Closed
Changes from 1 commit
Commits
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
Prev Previous commit
Next Next commit
Added ignored_libraries for multiprocessing and concurrent libraries
Added known limitations
  • Loading branch information
terryluan12 committed Jan 12, 2026
commit fdb476d657a4b8ca0f101a56e727d4ef14e82f14
47 changes: 36 additions & 11 deletions scripts/update_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ Usage: $0 [OPTIONS]

Copy tests from CPython to RustPython.
Optionally copy untracked tests, and dynamic annotation of failures.
This updater is not meant to be used as a standalone updater. Care needs to be taken to review everything done

Options:
-c/--cpython-path <path> Path to the CPython source tree (older version)
-r/--rpython-path <path> Path to the RustPython source tree (newer version)
-u/--copy-untracked Copy untracked tests only
-s/--check-skipped Check existing skipped tests (must be run separate from updating the tests)
-s/--update-skipped Update existing skipped tests (must be run separate from updating the tests)
-t/--timeout Set a timeout for a test
-a/--annotate While copying tests, run them and annotate failures dynamically
-j/--jobs How many libraries can be processed at a time
Expand All @@ -21,10 +22,17 @@ Example Usage:
$0 -c ~/cpython -r .
$0 -r . --check-skipped

** Notes:
*Notes:
* When using the update skip functionality
* Updating only looks for files with the format "test_*.py". Everything else (including __init__.py and __main__.py files are ignored)
* Care needs to be taken when updating. All tests with the same name will be updated at the same time
**Known limitations:
* In multithreaded tests, if the tests are orphaned, then the updater can deadlock, as threads can accumulate and block the semaphore
* In multithreaded tests, when annotating, multiple decorators can accumulate on one test
* The updater does not add skips to classes, only on tests
* If there are multiple tests with the same name, a decorator will be added to all of them
* The updater does not retain anything more specific than a general skip (skipIf/Unless will be replaced by a general skip)
* Currently, the updater does not take unexpected successes into account

EOF
exit 1
}
Expand All @@ -40,8 +48,9 @@ copy_untracked=false
annotate=false
timeout=300
check_skip_flag=false
num_jobs=5
num_jobs=20
libraries=()
ignored_libraries=("multiprocessing" "concurrent")

while [[ $# -gt 0 ]]; do
case "$1" in
Expand All @@ -61,7 +70,7 @@ while [[ $# -gt 0 ]]; do
usage
exit 1
;;
-s|--check-skipped)
-s|--update-skipped)
check_skip_flag=true
shift
;;
Expand Down Expand Up @@ -159,13 +168,17 @@ annotate_lib() {
local attempts=0
while ! grep -q "Tests result: SUCCESS" <<< "$output"; do
((attempts++))
echo "$lib failing, annotating..."
# echo "$lib failing, annotating..."
readarray -t failed_tests <<< "$(echo "$output" | awk '/^(FAIL:|ERROR:)/ {print $2}' | sort -u)"

# If the test fails/errors, then expectedFailure it
for test in "${failed_tests[@]}"
do
add_above_test $rlib_path $test "@unittest.expectedFailure # TODO: RUSTPYTHON"
if already_failed $rlib_path $test; then
replace_expected_with_skip $rlib_path $test
else
add_above_test $rlib_path $test "@unittest.expectedFailure # TODO: RUSTPYTHON"
fi
done

# If the test crashes/hangs, then skip it
Expand All @@ -183,13 +196,26 @@ annotate_lib() {

if [[ attempts -gt 10 ]]; then
echo "Issue annotating $lib" >&2
break;
return;
fi
done
echo "Successfully updated $lib"

unset SKIP_BACKUP
}
Comment on lines +201 to +250
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Declare and assign separately to avoid masking return values.

The combined declaration and assignment on line 204 masks the exit status of rustpython. If rustpython fails catastrophically, you won't detect it.

Also, the 15-attempt limit is reasonable but the logic could still get stuck if tests alternate between different failure modes.

Suggested fix for SC2155
 annotate_lib() {
     local lib=${1//\//.}
     local rlib_path=$2
-    local output=$(rustpython $lib 2>&1)
+    local output
+    output=$(rustpython "$lib" 2>&1)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
annotate_lib() {
local lib=${1//\//.}
local rlib_path=$2
local output=$(rustpython $lib 2>&1)
if grep -q "NO TESTS RAN" <<< "$output"; then
echo "No tests ran in $lib. skipping annotation"
return
fi
echo "Annotating $lib"
local attempts=0
while ! grep -q "Tests result: SUCCESS" <<< "$output"; do
((attempts++))
# echo "$lib failing, annotating..."
readarray -t failed_tests <<< "$(echo "$output" | awk '/^(FAIL:|ERROR:)/ {print $2}' | sort -u)"
# If the test fails/errors, then expectedFailure it
for test in "${failed_tests[@]}"
do
if already_failed $rlib_path $test; then
replace_expected_with_skip $rlib_path $test
else
add_above_test $rlib_path $test "$RUSTPYTHON_CANONICAL_EX_FAILURE"
fi
done
# If the test crashes/hangs, then skip it
if grep -q "\.\.\.$" <<< "$output"; then
crashing_test=$(echo "$output" | grep '\.\.\.$' | head -n 1 | awk '{print $1}')
if grep -q "Timeout" <<< "$output"; then
hanging=true
else
hanging=false
fi
apply_skip "$rlib_path" "$crashing_test" $hanging
fi
output=$(rustpython $lib 2>&1)
if [[ $attempts -gt 15 ]]; then
echo "Issue annotating $lib" >&2
return;
fi
done
echo "Successfully updated $lib"
unset SKIP_BACKUP
}
annotate_lib() {
local lib=${1//\//.}
local rlib_path=$2
local output
output=$(rustpython "$lib" 2>&1)
if grep -q "NO TESTS RAN" <<< "$output"; then
echo "No tests ran in $lib. skipping annotation"
return
fi
echo "Annotating $lib"
local attempts=0
while ! grep -q "Tests result: SUCCESS" <<< "$output"; do
((attempts++))
# echo "$lib failing, annotating..."
readarray -t failed_tests <<< "$(echo "$output" | awk '/^(FAIL:|ERROR:)/ {print $2}' | sort -u)"
# If the test fails/errors, then expectedFailure it
for test in "${failed_tests[@]}"
do
if already_failed $rlib_path $test; then
replace_expected_with_skip $rlib_path $test
else
add_above_test $rlib_path $test "$RUSTPYTHON_CANONICAL_EX_FAILURE"
fi
done
# If the test crashes/hangs, then skip it
if grep -q "\.\.\.$" <<< "$output"; then
crashing_test=$(echo "$output" | grep '\.\.\.$' | head -n 1 | awk '{print $1}')
if grep -q "Timeout" <<< "$output"; then
hanging=true
else
hanging=false
fi
apply_skip "$rlib_path" "$crashing_test" $hanging
fi
output=$(rustpython $lib 2>&1)
if [[ $attempts -gt 15 ]]; then
echo "Issue annotating $lib" >&2
return;
fi
done
echo "Successfully updated $lib"
unset SKIP_BACKUP
}
🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 204-204: Declare and assign separately to avoid masking return values.

(SC2155)

🤖 Prompt for AI Agents
In @scripts/update_tests.sh around lines 201 - 250, In annotate_lib, avoid
combining declaration and assignment for output (currently local
output=$(rustpython ...)) so the rustpython exit code isn’t masked; instead
declare local output first, run output=$(rustpython $lib 2>&1) and capture its
exit status ($?) immediately, logging and returning on a non-zero catastrophic
failure; also harden the retry loop around attempts by detecting unchanged or
oscillating output (e.g., keep previous_output and if output == previous_output
or it alternates between two states for multiple iterations then abort with an
error) so the while loop can’t hang indefinitely even within the 15-attempt cap.


replace_expected_with_skip() {
file=$1
test_name=$2
sed -E "/^\s*@unittest\.expectedFailure\s+# TODO: RUSTPYTHON/ { N; /\n\s*def $test_name/ { s/^(\s*)@unittest\.expectedFailure\s+# TODO: RUSTPYTHON/\1@unittest.skip\('TODO: RUSTPYTHON'\)/ } }" -i $file
}
Comment on lines +271 to +275
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing variable quoting and lost skip reason.

  1. $file should be quoted to handle paths with spaces.
  2. The replacement loses any specific skip reason, converting to a generic message. Consider preserving the reason if one exists.
Suggested fix
 replace_expected_with_skip() {
-    file=$1
-    test_name=$2
-    sed -E "/$RUSTPYTHON_CANONICAL_EX_FAILURE_RE/ { N; /\n\s*def $test_name/ { s/^(\s*)@unittest\.expectedFailure\s+# TODO: RUSTPYTHON/\1@unittest.skip\('TODO: RUSTPYTHON'\)/ } }" -i $file
+    local file=$1
+    local test_name=$2
+    sed -E "/$RUSTPYTHON_CANONICAL_EX_FAILURE_RE/ { N; /\n\s*def $test_name/ { s/^(\s*)@unittest\.expectedFailure\s+# TODO: RUSTPYTHON/\1@unittest.skip('TODO: RUSTPYTHON')/ } }" -i "$file"
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
replace_expected_with_skip() {
file=$1
test_name=$2
sed -E "/$RUSTPYTHON_CANONICAL_EX_FAILURE_RE/ { N; /\n\s*def $test_name/ { s/^(\s*)@unittest\.expectedFailure\s+# TODO: RUSTPYTHON/\1@unittest.skip\('TODO: RUSTPYTHON'\)/ } }" -i $file
}
replace_expected_with_skip() {
local file=$1
local test_name=$2
sed -E "/$RUSTPYTHON_CANONICAL_EX_FAILURE_RE/ { N; /\n\s*def $test_name/ { s/^(\s*)@unittest\.expectedFailure\s+# TODO: RUSTPYTHON/\1@unittest.skip('TODO: RUSTPYTHON')/ } }" -i "$file"
}
🤖 Prompt for AI Agents
In @scripts/update_tests.sh around lines 271 - 275, The helper function
replace_expected_with_skip should quote the $file argument and preserve any
existing skip reason instead of injecting a generic one; update
replace_expected_with_skip to call sed on "$file" (quoted) and change the
substitution to capture the original inline comment/reason (e.g., the text after
the # marker or after "TODO: RUSTPYTHON") and reuse that captured reason inside
the @unittest.skip(...) replacement so the original reason is preserved while
converting @unittest.expectedFailure to @unittest.skip.


already_failed() {
file=$1
test_name=$2
grep -Pz "\s*@unittest\.expectedFailure # TODO: RUSTPYTHON\n\s*def\s+${test_name}\(" $file
}

files_equal() {
cmp --silent "$1" "$2"
}
Expand Down Expand Up @@ -230,7 +256,6 @@ apply_skip() {
# Check if the test has a backup skip
if [[ -n "${SKIP_BACKUP[$test_name]}" ]]; then
message="${SKIP_BACKUP[$test_name]//\'/\"}"
echo "Message is $message"
elif $hanging; then
message="hanging"
fi
Expand Down Expand Up @@ -259,14 +284,14 @@ if ! $check_skip_flag; then
echo "Updating Tests"

if [[ ${#libraries[@]} -eq 0 ]]; then
readarray -t libraries <<< $(find ${cpython_path} -type f -printf "%P\n")
readarray -t libraries <<< $(find ${cpython_path} -type f -printf "%P\n" | grep -vE "$(IFS=\|; echo "${ignored_libraries[*]}")")
fi
update_tests "${libraries[@]}"
else
echo "Checking Skips"

if [[ ${#libraries[@]} -eq 0 ]]; then
readarray -t libraries <<< $(find ${rpython_path} -iname "test_*.py" -type f -printf "%P\n")
readarray -t libraries <<< $(find ${rpython_path} -iname "test_*.py" -type f -printf "%P\n" | grep -vE "$(IFS=\|; echo "${ignored_libraries[*]}")")
fi
check_skips "${libraries[@]}"
fi
Loading