1#!/usr/bin/env bash
2# SPDX-License-Identifier: LGPL-2.1-or-later
3set -eux
4set -o pipefail
5
6NPROC=$(nproc)
7MAX_QUEUE_SIZE=${NPROC:-2}
8TESTS_GLOB=${TESTS_GLOB:-test-*}
9mapfile -t TEST_LIST < <(find /usr/lib/systemd/tests/ -maxdepth 1 -type f -name "${TESTS_GLOB}")
10
11# reset state
12rm -fv /failed-tests /skipped-tests /skipped
13
14# Check & report test results
15# Arguments:
16#   $1: test path
17#   $2: test exit code
18function report_result() {
19    if [[ $# -ne 2 ]]; then
20        echo >&2 "check_result: missing arguments"
21        exit 1
22    fi
23
24    local name="${1##*/}"
25    local ret=$2
26
27    if [[ $ret -ne 0 && $ret != 77 ]]; then
28        echo "$name failed with $ret"
29        echo "$name" >>/failed-tests
30        {
31            echo "--- $name begin ---"
32            cat "/$name.log"
33            echo "--- $name end ---"
34        } >>/failed
35    elif [[ $ret == 77 ]]; then
36        echo "$name skipped"
37        echo "$name" >>/skipped-tests
38        {
39            echo "--- $name begin ---"
40            cat "/$name.log"
41            echo "--- $name end ---"
42        } >>/skipped
43    else
44        echo "$name OK"
45        echo "$name" >>/testok
46    fi
47
48    systemd-cat echo "--- $name ---"
49    systemd-cat cat "/$name.log"
50}
51
52# Associative array for running tasks, where running[test-path]=PID
53declare -A running=()
54for task in "${TEST_LIST[@]}"; do
55    # If there's MAX_QUEUE_SIZE running tasks, keep checking the running queue
56    # until one of the tasks finishes, so we can replace it.
57    while [[ ${#running[@]} -ge $MAX_QUEUE_SIZE ]]; do
58        for key in "${!running[@]}"; do
59            if ! kill -0 "${running[$key]}" &>/dev/null; then
60                # Task has finished, report its result and drop it from the queue
61                wait "${running[$key]}" && ec=0 || ec=$?
62                report_result "$key" $ec
63                unset running["$key"]
64                # Break from inner for loop and outer while loop to skip
65                # the sleep below when we find a free slot in the queue
66                break 2
67            fi
68        done
69
70        # Precisely* calculated constant to keep the spinlock from burning the CPU(s)
71        sleep 0.01
72    done
73
74    if [[ -x $task ]]; then
75        log_file="/${task##*/}.log"
76        $task &>"$log_file" &
77        running[$task]=$!
78    fi
79done
80
81# Wait for remaining running tasks
82for key in "${!running[@]}"; do
83    wait ${running[$key]} && ec=0 || ec=$?
84    report_result "$key" $ec
85    unset running["$key"]
86done
87
88# Test logs are sometimes lost, as the system shuts down immediately after
89journalctl --sync
90
91exit 0
92