Skip to content

Commit 715e201

Browse files
authored
CI: stabilize compare-baseline exits and determinism (#577)
* Stabilize compare workflow exits without debug tracing * Neutralize bash_logout safely in compare step * Set fixed PYTHONHASHSEED for CI determinism
1 parent 3b32225 commit 715e201

1 file changed

Lines changed: 60 additions & 28 deletions

File tree

.github/workflows/build-ultraplot.yml

Lines changed: 60 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ jobs:
3333
TEST_MODE: ${{ inputs.test-mode }}
3434
TEST_NODEIDS: ${{ inputs.test-nodeids }}
3535
PYTEST_WORKERS: 4
36+
PYTHONHASHSEED: "0"
3637
steps:
3738
- name: Set up swap space
3839
uses: pierotofy/set-swap-space@master
@@ -78,6 +79,7 @@ jobs:
7879
TEST_MODE: ${{ inputs.test-mode }}
7980
TEST_NODEIDS: ${{ inputs.test-nodeids }}
8081
PYTEST_WORKERS: 4
82+
PYTHONHASHSEED: "0"
8183
defaults:
8284
run:
8385
shell: bash -el {0}
@@ -152,13 +154,16 @@ jobs:
152154
python -c "import ultraplot as plt; plt.config.Configurator()._save_yaml('ultraplot.yml')"
153155
if [ "${TEST_MODE}" = "selected" ] && [ -s /tmp/pr_selected_nodeids.txt ]; then
154156
status=0
155-
mapfile -t FILTERED_NODEIDS < <(
156-
while IFS= read -r nodeid; do
157-
[ -z "$nodeid" ] && continue
158-
path="${nodeid%%::*}"
159-
[ -f "$path" ] && printf '%s\n' "$nodeid"
160-
done < /tmp/pr_selected_nodeids.txt
161-
)
157+
FILTERED_NODEIDS=()
158+
while IFS= read -r nodeid; do
159+
if [ -z "$nodeid" ]; then
160+
continue
161+
fi
162+
path="${nodeid%%::*}"
163+
if [ -f "$path" ]; then
164+
FILTERED_NODEIDS+=("$nodeid")
165+
fi
166+
done < /tmp/pr_selected_nodeids.txt
162167
echo "FILTERED_NODEIDS_BASE_COUNT=${#FILTERED_NODEIDS[@]}"
163168
if [ "${#FILTERED_NODEIDS[@]}" -eq 0 ]; then
164169
echo "No valid nodeids found on base; skipping baseline generation."
@@ -200,22 +205,37 @@ jobs:
200205
# Image Comparison (Uses cached or newly generated baseline)
201206
- name: Image Comparison Ultraplot
202207
run: |
208+
set -uo pipefail
209+
# This workflow runs in a login shell (bash -el), which executes
210+
# ~/.bash_logout on exit. Neutralize that file to prevent runner
211+
# teardown commands (e.g. clear_console) from overriding step status.
212+
if [ -f "${HOME}/.bash_logout" ]; then
213+
cp "${HOME}/.bash_logout" "${HOME}/.bash_logout.bak" || true
214+
: > "${HOME}/.bash_logout" || true
215+
fi
216+
203217
# Re-install the Ultraplot version from the current PR branch
204218
pip install --no-build-isolation --no-deps .
205219
206220
mkdir -p results
207221
python -c "import ultraplot as plt; plt.config.Configurator()._save_yaml('ultraplot.yml')"
208222
echo "TEST_MODE=${TEST_MODE}"
209223
echo "TEST_NODEIDS=${TEST_NODEIDS}"
224+
parse_junit_counts() {
225+
python -c "import sys,xml.etree.ElementTree as ET; root=ET.parse(sys.argv[1]).getroot(); suites=[root] if root.tag=='testsuite' else root.findall('testsuite'); failures=sum(int(s.attrib.get('failures', 0)) for s in suites); errors=sum(int(s.attrib.get('errors', 0)) for s in suites); print(f'{failures} {errors}')" "$1" 2>/dev/null || echo "0 0"
226+
}
210227
if [ "${TEST_MODE}" = "selected" ] && [ -s /tmp/pr_selected_nodeids.txt ]; then
211228
status=0
212-
mapfile -t FILTERED_NODEIDS < <(
213-
while IFS= read -r nodeid; do
214-
[ -z "$nodeid" ] && continue
215-
path="${nodeid%%::*}"
216-
[ -f "$path" ] && printf '%s\n' "$nodeid"
217-
done < /tmp/pr_selected_nodeids.txt
218-
)
229+
FILTERED_NODEIDS=()
230+
while IFS= read -r nodeid; do
231+
if [ -z "$nodeid" ]; then
232+
continue
233+
fi
234+
path="${nodeid%%::*}"
235+
if [ -f "$path" ]; then
236+
FILTERED_NODEIDS+=("$nodeid")
237+
fi
238+
done < /tmp/pr_selected_nodeids.txt
219239
echo "FILTERED_NODEIDS_PR_COUNT=${#FILTERED_NODEIDS[@]}"
220240
if [ "${#FILTERED_NODEIDS[@]}" -eq 0 ]; then
221241
echo "No valid nodeids found on PR branch; skipping image comparison."
@@ -233,14 +253,20 @@ jobs:
233253
--junitxml=./results/junit.xml \
234254
"${FILTERED_NODEIDS[@]}"
235255
status=$?
236-
set -e
237256
echo "=== Memory after image comparison ===" && free -h
238-
if [ "$status" -ne 0 ] && [ -f ./results/junit.xml ]; then
239-
if python -c "import sys, xml.etree.ElementTree as ET; root = ET.parse('./results/junit.xml').getroot(); suites = list(root.findall('testsuite')) if root.tag == 'testsuites' else [root]; failures = sum(int(s.attrib.get('failures', 0) or 0) for s in suites); errors = sum(int(s.attrib.get('errors', 0) or 0) for s in suites); sys.exit(0 if (failures == 0 and errors == 0) else 1)"
240-
then
241-
echo "pytest exited with $status but junit reports no failures/errors; overriding exit status to 0."
242-
status=0
243-
fi
257+
junit_failures=0
258+
junit_errors=0
259+
if [ -f ./results/junit.xml ]; then
260+
junit_counts="$(parse_junit_counts ./results/junit.xml || echo '0 0')"
261+
junit_failures="${junit_counts%% *}"
262+
junit_errors="${junit_counts##* }"
263+
fi
264+
case "$junit_failures" in ''|*[!0-9]*) junit_failures=0 ;; esac
265+
case "$junit_errors" in ''|*[!0-9]*) junit_errors=0 ;; esac
266+
echo "pytest_status=$status junit_failures=$junit_failures junit_errors=$junit_errors"
267+
if [ "$status" -ne 0 ] && [ "$junit_failures" -eq 0 ] && [ "$junit_errors" -eq 0 ]; then
268+
echo "pytest exited with $status but junit reports no failures/errors; overriding exit status to 0."
269+
status=0
244270
fi
245271
if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then
246272
echo "No tests collected from selected nodeids; skipping image comparison."
@@ -261,14 +287,20 @@ jobs:
261287
--junitxml=./results/junit.xml \
262288
ultraplot/tests
263289
status=$?
264-
set -e
265290
echo "=== Memory after image comparison ===" && free -h
266-
if [ "$status" -ne 0 ] && [ -f ./results/junit.xml ]; then
267-
if python -c "import sys, xml.etree.ElementTree as ET; root = ET.parse('./results/junit.xml').getroot(); suites = list(root.findall('testsuite')) if root.tag == 'testsuites' else [root]; failures = sum(int(s.attrib.get('failures', 0) or 0) for s in suites); errors = sum(int(s.attrib.get('errors', 0) or 0) for s in suites); sys.exit(0 if (failures == 0 and errors == 0) else 1)"
268-
then
269-
echo "pytest exited with $status but junit reports no failures/errors; overriding exit status to 0."
270-
status=0
271-
fi
291+
junit_failures=0
292+
junit_errors=0
293+
if [ -f ./results/junit.xml ]; then
294+
junit_counts="$(parse_junit_counts ./results/junit.xml || echo '0 0')"
295+
junit_failures="${junit_counts%% *}"
296+
junit_errors="${junit_counts##* }"
297+
fi
298+
case "$junit_failures" in ''|*[!0-9]*) junit_failures=0 ;; esac
299+
case "$junit_errors" in ''|*[!0-9]*) junit_errors=0 ;; esac
300+
echo "pytest_status=$status junit_failures=$junit_failures junit_errors=$junit_errors"
301+
if [ "$status" -ne 0 ] && [ "$junit_failures" -eq 0 ] && [ "$junit_errors" -eq 0 ]; then
302+
echo "pytest exited with $status but junit reports no failures/errors; overriding exit status to 0."
303+
status=0
272304
fi
273305
if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then
274306
echo "No tests collected; skipping image comparison."

0 commit comments

Comments
 (0)