[vlc-commits] [Git][videolan/vlc][master] test: add run_parallel_tests.sh helper
Jean-Baptiste Kempf (@jbk)
gitlab at videolan.org
Sat Jul 18 10:31:17 UTC 2026
Jean-Baptiste Kempf pushed to branch master at VideoLAN / VLC
Commits:
605e98e2 by Thomas Guillem at 2026-07-18T12:11:12+02:00
test: add run_parallel_tests.sh helper
Repeatedly runs a test in parallel until its first failure.
- - - - -
1 changed file:
- + test/run_parallel_tests.sh
Changes:
=====================================
test/run_parallel_tests.sh
=====================================
@@ -0,0 +1,234 @@
+#!/usr/bin/env bash
+
+# Repeatedly runs a test in parallel until its first failure, prints a short
+# backtrace. This helps reproduce intermittent CI failures.
+
+NUM_PARALLEL=100
+
+usage() {
+ printf 'Usage: %s [-j JOBS] TEST_EXECUTABLE\n' "${0##*/}"
+}
+
+while [ "$#" -gt 0 ]; do
+ case "$1" in
+ -j|--jobs)
+ if [ "$#" -lt 2 ]; then
+ printf '%s: %s requires a value\n' "${0##*/}" "$1" >&2
+ usage >&2
+ exit 2
+ fi
+ NUM_PARALLEL=$2
+ shift 2
+ ;;
+ -h|--help)
+ usage
+ exit 0
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*)
+ printf '%s: unknown option: %s\n' "${0##*/}" "$1" >&2
+ usage >&2
+ exit 2
+ ;;
+ *)
+ break
+ ;;
+ esac
+done
+
+if [ "$#" -ne 1 ]; then
+ usage >&2
+ exit 2
+fi
+
+if [[ ! $NUM_PARALLEL =~ ^[1-9][0-9]*$ ]]; then
+ printf '%s: jobs must be a positive integer: %s\n' "${0##*/}" "$NUM_PARALLEL" >&2
+ exit 2
+fi
+
+TEST_BIN=$1
+case "$TEST_BIN" in
+ */*) ;;
+ *) TEST_BIN="./$TEST_BIN" ;;
+esac
+
+if [ ! -f "$TEST_BIN" ] || [ ! -x "$TEST_BIN" ]; then
+ printf '%s: not an executable file: %s\n' "${0##*/}" "$1" >&2
+ exit 1
+fi
+
+GDB=$(command -v gdb 2>/dev/null || true)
+TEST_DIR=${TEST_BIN%/*}
+TEST_NAME=${TEST_BIN##*/}
+DEBUG_BIN=$TEST_BIN
+if [ -x "$TEST_DIR/.libs/$TEST_NAME" ]; then
+ DEBUG_BIN="$TEST_DIR/.libs/$TEST_NAME"
+fi
+
+if [ ! -r /proc/sys/kernel/core_pattern ] ||
+ [ ! -r /proc/sys/kernel/core_uses_pid ] ||
+ [ "$(< /proc/sys/kernel/core_pattern)" != "core" ] ||
+ [ "$(< /proc/sys/kernel/core_uses_pid)" != "1" ]; then
+ printf '%s: automatic core handling requires core_pattern=core and core_uses_pid=1\n' \
+ "${0##*/}" >&2
+ exit 1
+fi
+
+if ! ulimit -c unlimited; then
+ printf '%s: cannot enable core dumps\n' "${0##*/}" >&2
+ exit 1
+fi
+
+if ! STATE_DIR=$(mktemp -d); then
+ printf '%s: cannot create temporary directory\n' "${0##*/}" >&2
+ exit 1
+fi
+
+CORE_DIR=$PWD
+FAILURE_LOG="$STATE_DIR/first_failure.log"
+FAILURE_PID="$STATE_DIR/first_failure.pid"
+PARENT_PID=$BASHPID
+failure_notified=0
+FIRST_FAILURE_PID=
+declare -A PREEXISTING_CORES=()
+
+for core_file in "$CORE_DIR"/core.*; do
+ if [ -e "$core_file" ] || [ -L "$core_file" ]; then
+ PREEXISTING_CORES["$core_file"]=1
+ fi
+done
+
+stop_workers() {
+ local pid
+ local job_pids=()
+
+ mapfile -t job_pids < <(jobs -p)
+ if [ "${#job_pids[@]}" -ne 0 ]; then
+ for pid in "${job_pids[@]}"; do
+ kill -KILL -- "-$pid" 2>/dev/null || true
+ done
+ wait "${job_pids[@]}" 2>/dev/null || true
+ fi
+}
+
+remove_other_cores() {
+ local pid_file pid core_file
+
+ for pid_file in "$STATE_DIR"/instance_*.pids; do
+ [ -f "$pid_file" ] || continue
+ while IFS= read -r pid; do
+ [[ $pid =~ ^[1-9][0-9]*$ ]] || continue
+ [ "$pid" = "$FIRST_FAILURE_PID" ] && continue
+
+ core_file="$CORE_DIR/core.$pid"
+ if [ -f "$core_file" ] &&
+ [[ ! ${PREEXISTING_CORES[$core_file]+present} ]]; then
+ rm -f -- "$core_file" ||
+ printf '%s: cannot remove core file: %s\n' \
+ "${0##*/}" "$core_file" >&2
+ fi
+ done < "$pid_file"
+ done
+}
+
+cleanup() {
+ trap '' HUP INT TERM USR1
+ stop_workers
+ remove_other_cores
+ rm -rf -- "$STATE_DIR"
+}
+
+handle_signal() {
+ local status=$1
+
+ if [ "$BASHPID" -eq "$PARENT_PID" ]; then
+ cleanup
+ fi
+ exit "$status"
+}
+
+run_test() {
+ local instance=$1
+ local log_file="$STATE_DIR/instance_${instance}.log"
+ local pid_file="$STATE_DIR/instance_${instance}.pids"
+ local test_pid=
+
+ set +m
+ while :; do
+ VLC_TEST_TIMEOUT=-1 "$TEST_BIN" > "$log_file" 2>&1 &
+ test_pid=$!
+ if ! printf '%s\n' "$test_pid" > "$pid_file"; then
+ kill -KILL "$test_pid" 2>/dev/null || true
+ wait "$test_pid" 2>/dev/null || true
+ printf '%s: cannot record test process ID\n' "${0##*/}" > "$log_file"
+ break
+ fi
+
+ if wait "$test_pid" 2>> "$log_file"; then
+ rm -f -- "$log_file" "$pid_file"
+ else
+ break
+ fi
+ done
+
+ if ln -s -- "${log_file##*/}" "$FAILURE_LOG" 2>/dev/null; then
+ if ! printf '%s\n' "$test_pid" > "$FAILURE_PID"; then
+ printf '%s: cannot record first failure process ID\n' "${0##*/}" >> "$log_file"
+ fi
+ kill -USR1 "$PARENT_PID" 2>/dev/null || true
+ fi
+ return 1
+}
+
+trap 'handle_signal 129' HUP
+trap 'handle_signal 130' INT
+trap 'handle_signal 143' TERM
+trap 'failure_notified=1' USR1
+
+printf 'Starting %s parallel test instances. Press Ctrl+C to stop.\n' "$NUM_PARALLEL"
+
+# Put each worker in its own process group so hung test children can be stopped.
+set -m
+for ((i = 1; i <= NUM_PARALLEL; i++)); do
+ [ "$failure_notified" -eq 0 ] || break
+ run_test "$i" &
+done
+set +m
+
+while [ "$failure_notified" -eq 0 ]; do
+ wait -n 2>/dev/null || true
+ [ "$failure_notified" -ne 0 ] || [ -n "$(jobs -p)" ] || break
+done
+stop_workers
+
+if [ ! -L "$FAILURE_LOG" ] ||
+ ! IFS= read -r FIRST_FAILURE_PID < "$FAILURE_PID" ||
+ [[ ! $FIRST_FAILURE_PID =~ ^[1-9][0-9]*$ ]]; then
+ printf '%s: a worker stopped without recording a failure\n' "${0##*/}" >&2
+ cleanup
+ exit 1
+fi
+
+printf '\nFirst failure:\n'
+cat -- "$FAILURE_LOG"
+
+FIRST_CORE="$CORE_DIR/core.$FIRST_FAILURE_PID"
+if [ -f "$FIRST_CORE" ]; then
+ if [ -n "$GDB" ]; then
+ "$GDB" -q --batch "$DEBUG_BIN" -c "$FIRST_CORE" \
+ -ex 'set pagination off' \
+ -ex 'bt' || true
+ fi
+ cleanup
+ printf '\nFull manual inspection:\ngdb %q -c %q -ex '\''thread apply all bt full'\''\n' \
+ "$DEBUG_BIN" "$FIRST_CORE"
+else
+ printf '\n%s: no core dump found for PID %s\n' \
+ "${0##*/}" "$FIRST_FAILURE_PID" >&2
+ cleanup
+fi
+
+exit 1
View it on GitLab: https://code.videolan.org/videolan/vlc/-/commit/605e98e20e342eb85efba996f824945d4984fd64
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/commit/605e98e20e342eb85efba996f824945d4984fd64
You're receiving this email because of your account on code.videolan.org. Manage all notifications: https://code.videolan.org/-/profile/notifications | Help: https://code.videolan.org/help
More information about the vlc-commits
mailing list