[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