diff options
| author | Egor Tensin <egor@tensin.name> | 2026-10-04 21:41:24 +0500 |
|---|---|---|
| committer | Egor Tensin <egor@tensin.name> | 2026-10-04 21:41:24 +0500 |
| commit | 57109eace4733153d4897cce42e44520f4efffae (patch) | |
| tree | a220463377c288cad059fc587078b08a1e7d7f63 /test | |
| parent | debian: ditch Vagrant, switch to Docker (diff) | |
| parent | workflows/ci: minor formatting fixes (diff) | |
| download | linux-status-57109eace4733153d4897cce42e44520f4efffae.tar.gz linux-status-57109eace4733153d4897cce42e44520f4efffae.zip | |
Merge tag 'v2.4.13' into debian
v2.4.13
Diffstat (limited to 'test')
| -rw-r--r-- | test/lib/log.sh | 139 | ||||
| -rw-r--r-- | test/lib/test.sh | 62 | ||||
| -rwxr-xr-x | test/main.sh | 77 | ||||
| -rwxr-xr-x | test/test.sh | 205 | ||||
| -rw-r--r-- | test/tst-cgi.sh | 48 | ||||
| -rw-r--r-- | test/tst-curl.sh | 80 |
6 files changed, 406 insertions, 205 deletions
diff --git a/test/lib/log.sh b/test/lib/log.sh new file mode 100644 index 0000000..35b7a2d --- /dev/null +++ b/test/lib/log.sh @@ -0,0 +1,139 @@ +# Copyright (c) 2026 Egor Tensin <egor@tensin.name> +# This file is part of the "linux-status" project. +# For details, see https://github.com/egor-tensin/linux-status +# Distributed under the MIT License. + +log() { + local msg + for msg; do + echo -e "$test_file | $msg" >&2 + done +} + +log_run() { + if [ "$#" -lt 1 ]; then + log "usage: ${FUNCNAME[0]} ARGV0 [ARG...]" + return 1 + fi + + local msg='Running:' + msg="$msg$( printf -- ' %q' "$@" )" + log "$msg" +} + +fail() { + local msg + for msg; do + log "FAIL: $msg" + done +} + +fail_details() { + local msg + for msg; do + fail " $msg" + done +} + +log_numof_tests_ok=0 +log_numof_tests_failed=0 +declare -a log_tests_failed=() +declare -A log_tests_should_fail=() + +log_test_start() { + if [ "$#" -ne 1 ]; then + echo "usage: ${FUNCNAME[0]} TEST_FILE" >&2 + return 1 + fi + + local test_file="$1" + + if [ -n "${test_should_fail:-}" ]; then + log_tests_should_fail["$test_file"]=1 + fi + + cat <<EOF >&2 + +====================================================================== +TEST: $test_file +====================================================================== +EOF + + log "Should fail: ${test_should_fail:-No}" +} + +log_test_ok() { + if [ "$#" -ne 1 ]; then + echo "usage: ${FUNCNAME[0]} TEST_FILE" >&2 + return 1 + fi + + log_numof_tests_ok=$((log_numof_tests_ok + 1)) + + cat <<EOF >&2 +---------------------------------------------------------------------- +OK: $1 +---------------------------------------------------------------------- +EOF +} + +log_test_fail() { + if [ "$#" -ne 1 ]; then + echo "usage: ${FUNCNAME[0]} TEST_FILE" >&2 + return 1 + fi + + log_numof_tests_failed=$((log_numof_tests_failed + 1)) + log_tests_failed+=("$1") + + cat <<EOF >&2 +---------------------------------------------------------------------- +FAIL: $1 +---------------------------------------------------------------------- +EOF +} + +log_test_finish() { + if [ "$#" -ne 2 ]; then + echo "usage: ${FUNCNAME[0]} TEST_FILE EXIT_CODE" >&2 + return 1 + fi + + local test_file="$1" + local ret="$2" + local test_should_fail + test_should_fail="${log_tests_should_fail["$test_file"]:+x}" + + if [ "$ret" -eq 0 ] || [ -n "$test_should_fail" ]; then + log_test_ok "$test_file" + else + log_test_fail "$test_file" + fi +} + +log_tests_summary() { + cat <<EOF >&2 + +====================================================================== +SUMMARY +====================================================================== +OK: $log_numof_tests_ok +FAIL: $log_numof_tests_failed +EOF + + local test_file + for test_file in ${log_tests_failed[@]+"${log_tests_failed[@]}"}; do + cat <<EOF >&2 +FAIL: $test_file +EOF + done + + cat <<EOF >&2 +---------------------------------------------------------------------- +EOF + + local ret=0 + [ "$log_numof_tests_failed" -gt 0 ] && ret=1 + + return "$ret" +} diff --git a/test/lib/test.sh b/test/lib/test.sh new file mode 100644 index 0000000..ee35b05 --- /dev/null +++ b/test/lib/test.sh @@ -0,0 +1,62 @@ +# Copyright (c) 2026 Egor Tensin <egor@tensin.name> +# This file is part of the "linux-status" project. +# For details, see https://github.com/egor-tensin/linux-status +# Distributed under the MIT License. + +test_should_fail= +test_root_dir= + +test_server_port=35605 +test_server_pid= + +_test_run_server() { + log "Starting up server..." + log_run "$script_dir/../src/server.py" --port "$test_server_port" + "$script_dir/../src/server.py" --port "$test_server_port" >&2 & + test_server_pid="$!" + log "Server's PID: $test_server_pid" + sleep 5 +} + +_test_kill_server() { + if [ -n "$test_server_pid" ]; then + log "Stopping server: $test_server_pid" + kill "$test_server_pid" + log "Waiting for it to terminate..." + wait "$test_server_pid" || true + fi +} + +test_setup() { + test_root_dir="$( mktemp -d )" + _test_run_server +} + +test_cleanup_default() { + if [ -n "$test_root_dir" ]; then + log "Removing test's root directory: $test_root_dir" + rm -rf -- "$test_root_dir" + fi + _test_kill_server +} + +test_check_output() { + if [ "$#" -lt 1 ]; then + log "usage: ${FUNCNAME[0]} PATH [KEYWORD...]" + return 1 + fi + + local path="$1" + shift + + local keyword + for keyword; do + if ! grep --fixed-strings --quiet -- "$keyword" "$path"; then + fail "The following pattern hasn't been found in $path:" + fail_details "$keyword" + log "Output:" + cat -- "$path" >&2 + return 1 + fi + done +} diff --git a/test/main.sh b/test/main.sh new file mode 100755 index 0000000..a9e2feb --- /dev/null +++ b/test/main.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash + +# Copyright (c) 2026 Egor Tensin <egor@tensin.name> +# This file is part of the "linux-status" project. +# For details, see https://github.com/egor-tensin/linux-status +# Distributed under the MIT License. + +set -o errexit -o nounset -o pipefail +shopt -s inherit_errexit lastpipe + +script_dir="$( dirname -- "${BASH_SOURCE[0]}" )" +script_dir="$( cd -- "$script_dir" && pwd )" +readonly script_dir +script_name="$( basename -- "${BASH_SOURCE[0]}" )" +readonly script_name + +source "$script_dir/lib/log.sh" + +run_test_file() { + local test_file + for test_file; do + source "$script_dir/lib/test.sh" + source "$script_dir/$test_file" + + log_test_start "$test_file" + + set +e + ( + set -o errexit -o nounset -o pipefail + shopt -s inherit_errexit lastpipe + + if [ "$( type -t test_cleanup )" == function ]; then + trap test_cleanup EXIT + else + trap test_cleanup_default EXIT + fi + + test_run + ) + local ret="$?" + set -e + + log_test_finish "$test_file" "$ret" + done +} + +main() { + if [ "$#" -gt 1 ]; then + echo "usage: $script_name [PATTERN]" >&2 + return 1 + fi + + local -a test_files=() + local test_file + + local pattern='*' + [ "$#" -gt 0 ] && pattern="$1" + + find "$script_dir" \ + -mindepth 1 \ + -maxdepth 1 \ + -type f \ + -regex '.*/tst-.*\.sh$' \ + -regextype posix-basic \ + -name "$pattern" \ + -printf '%P\0' \ + | sort -z \ + | \ + while IFS= read -d '' -r test_file; do + test_files+=("$test_file") + done + + run_test_file ${test_files[@]+"${test_files[@]}"} + log_tests_summary +} + +main "$@" diff --git a/test/test.sh b/test/test.sh deleted file mode 100755 index 32d2767..0000000 --- a/test/test.sh +++ /dev/null @@ -1,205 +0,0 @@ -#!/usr/bin/env bash - -# Copyright (c) 2021 Egor Tensin <egor@tensin.name> -# This file is part of the "linux-status" project. -# For details, see https://github.com/egor-tensin/linux-status -# Distributed under the MIT License. - -set -o errexit -o nounset -o pipefail -shopt -s inherit_errexit lastpipe - -script_dir="$( dirname -- "${BASH_SOURCE[0]}" )" -script_dir="$( cd -- "$script_dir" && pwd )" -readonly script_dir -script_name="$( basename -- "${BASH_SOURCE[0]}" )" -readonly script_name - -readonly server_port=18101 -server_pid= -curl_header_file= -curl_output_file= - -dump() { - local msg - for msg; do - echo "$script_name: $msg" - done -} - -run_server() { - dump "Starting up server..." - "$script_dir/../src/server.py" --port "$server_port" & - server_pid="$!" - dump "Its PID is $server_pid" - sleep 5 -} - -kill_server() { - [ -z "$server_pid" ] && return - dump "Killing server with PID $server_pid..." - kill "$server_pid" - dump "Waiting for it to terminate..." - wait "$server_pid" || true - dump "Done" -} - -create_files() { - curl_header_file="$( mktemp )" - curl_output_file="$( mktemp )" - dump "curl header file: $curl_header_file" - dump "curl output file: $curl_output_file" -} - -cleanup_files() { - dump "Cleaning up curl files..." - [ -n "$curl_header_file" ] && rm -f -- "$curl_header_file" - [ -n "$curl_output_file" ] && rm -f -- "$curl_output_file" -} - -prepare() { - run_server - create_files -} - -cleanup() { - kill_server || true - cleanup_files || true -} - -run_curl() { - if [ "$#" -ne 1 ]; then - echo "usage: ${FUNCNAME[0]} URL" >&2 - return 1 - fi - local url="$1" - curl \ - --silent --show-error \ - --dump-header "$curl_header_file" \ - --output "$curl_output_file" \ - --connect-timeout 3 \ - -- "http://localhost:$server_port$url" || true -} - -curl_check_status() { - if [ "$#" -ne 1 ]; then - echo "usage: ${FUNCNAME[0]} HTTP_STATUS" >&2 - return 1 - fi - - local expected="$1" - expected="HTTP/1.0 $expected"$'\r' - local actual - actual="$( head -n 1 -- "$curl_header_file" )" - - [ "$expected" == "$actual" ] && return 0 - - dump "Actual HTTP response: $actual" >&2 - dump "Expected: $expected" >&2 - - dump 'HTTP headers:' >&2 - cat -- "$curl_header_file" >&2 - dump 'HTTP response:' >&2 - cat -- "$curl_output_file" >&2 - return 1 -} - -curl_check_keyword() { - local keyword - for keyword; do - if ! grep --fixed-strings --quiet -- "$keyword" "$curl_output_file"; then - dump "The following pattern hasn't been found:" - dump "$keyword" - dump "The output was:" - cat -- "$curl_output_file" - return 1 - fi - done -} - -run_curl_test() { - if [ "$#" -lt 1 ]; then - echo "usage: ${FUNCNAME[0]} URL [KEYWORD...]" >&2 - return 1 - fi - - local url="$1" - shift - dump "Running test for URL: $url" - - run_curl "$url" - curl_check_status '200 OK' - - local keyword - for keyword; do - curl_check_keyword "$keyword" - done -} - -run_curl_tests() { - # / and /index.html are identical: - run_curl_test '/' '<link rel="stylesheet" href="css/bootstrap.min.css">' 'var status_refresh_interval_seconds' - run_curl_test '/index.html' '<link rel="stylesheet" href="css/bootstrap.min.css">' 'var status_refresh_interval_seconds' - - # /status returns a JSON with a number of fields: - run_curl_test '/status' '"hostname":' '"thermal":' '"system":' '"user":' - # /top is `top` output: - run_curl_test '/top' 'load average:' - # /thermal is also an endpoint: - run_curl_test '/thermal' -} - -cgi_check_header() { - local expected='200 OK' - local actual - actual="$( head -n 1 -- "$curl_output_file" )" - - [ "$expected" == "$actual" ] && return 0 - - dump "Actual CGI header: $actual" >&2 - dump "Expected: $expected" >&2 - - diff <( echo "$actual" ) <( echo "$expected" ) | cat -te - return 1 -} - -run_cgi_test() { - if [ "$#" -lt 1 ]; then - echo "usage: ${FUNCNAME[0]} WHAT [KEYWORD...]" >&2 - return 1 - fi - - local what="$1" - shift - - local query_string="what=$what" - dump "Running CGI test for query string: $query_string" - - QUERY_STRING="$query_string" "$script_dir/../src/app.py" > "$curl_output_file" - - cgi_check_header - - local keyword - for keyword; do - curl_check_keyword "$keyword" - done -} - -run_cgi_tests() { - # Check that app.py still works as a CGI script. - - # /status returns a JSON with a number of fields: - run_cgi_test 'status' '"hostname":' '"thermal":' '"system":' '"user":' - # /top is `top` output: - run_cgi_test 'top' 'load average:' - # /thermal is also an endpoint: - run_cgi_test 'thermal' -} - -main() { - trap cleanup EXIT - prepare - run_curl_tests - run_cgi_tests -} - -main "$@" diff --git a/test/tst-cgi.sh b/test/tst-cgi.sh new file mode 100644 index 0000000..bb2eb01 --- /dev/null +++ b/test/tst-cgi.sh @@ -0,0 +1,48 @@ +test_cgi_response= + +_test_cgi_check_status() { + local expected='200 OK' + local actual + actual="$( head -n 1 -- "$test_cgi_response" )" + + [ "$expected" == "$actual" ] && return 0 + + fail "Unexpected HTTP status: $actual" + fail_details "Expected: $expected" + + diff <( echo "$actual" ) <( echo "$expected" ) | cat -te >&2 + return 1 +} + +_test_cgi() { + if [ "$#" -lt 1 ]; then + log "usage: ${FUNCNAME[0]} WHAT [KEYWORD...]" + return 1 + fi + + local what="$1" + shift + + local query_string="what=$what" + log "Running CGI test for query string: $query_string" + + QUERY_STRING="$query_string" "$script_dir/../src/app.py" > "$test_cgi_response" + + _test_cgi_check_status + test_check_output "$test_cgi_response" "$@" +} + +test_run() { + # Check that app.py still works as a CGI script. + + test_setup + + test_cgi_response="$test_root_dir/response" + + # /status returns a JSON with a number of fields: + _test_cgi 'status' '"hostname":' '"thermal":' '"system":' '"user":' + # /top is `top` output: + _test_cgi 'top' 'load average:' + # /thermal is also an endpoint: + _test_cgi 'thermal' +} diff --git a/test/tst-curl.sh b/test/tst-curl.sh new file mode 100644 index 0000000..a8f9860 --- /dev/null +++ b/test/tst-curl.sh @@ -0,0 +1,80 @@ +test_curl_header= +test_curl_output= + +_curl() { + if [ "$#" -ne 1 ]; then + log "usage: ${FUNCNAME[0]} URL" + return 1 + fi + + local url="$1" + + curl \ + --silent --show-error \ + --dump-header "$test_curl_header" \ + --output "$test_curl_output" \ + --connect-timeout 3 \ + -- \ + "http://127.0.0.1:$test_server_port$url" \ + || true +} + +_test_curl_check_status() { + if [ "$#" -ne 1 ]; then + log "usage: ${FUNCNAME[0]} HTTP_STATUS" + return 1 + fi + + local expected="$1" + expected="HTTP/1.0 $expected"$'\r' + local actual + actual="$( head -n 1 -- "$test_curl_header" )" + + [ "$expected" == "$actual" ] && return 0 + + fail "Unexpected HTTP status: $actual" + fail_details "Expected: $expected" + + log 'Response headers:' + cat -- "$test_curl_header" >&2 + log 'Response data:' + cat -- "$test_curl_output" >&2 + return 1 +} + +_test_curl() { + if [ "$#" -lt 1 ]; then + log "usage: ${FUNCNAME[0]} URL [KEYWORD...]" + return 1 + fi + + local url="$1" + shift + log "Running test for URL: $url" + + _curl "$url" + _test_curl_check_status '200 OK' + test_check_output "$test_curl_output" "$@" +} + +test_run() { + test_setup + + test_curl_header="$test_root_dir/header" + test_curl_output="$test_root_dir/output" + + # / and /index.html are identical: + _test_curl '/' \ + '<link rel="stylesheet" href="css/bootstrap.min.css">' \ + 'var status_refresh_interval_seconds' + _test_curl '/index.html' \ + '<link rel="stylesheet" href="css/bootstrap.min.css">' \ + 'var status_refresh_interval_seconds' + + # /status returns a JSON with a number of fields: + _test_curl '/status' '"hostname":' '"thermal":' '"system":' '"user":' + # /top is `top` output: + _test_curl '/top' 'load average:' + # /thermal is also an endpoint: + _test_curl '/thermal' +} |
