From af2ea0bd8749a20e085f2d1c7483e1ed7829ba76 Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Wed, 14 Oct 2026 17:18:39 +0500 Subject: import main files from tag-release I'm using an identical pattern in a number of projects now, so I'm factoring this out into a separate repo. --- lib/log.sh | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/test.sh | 21 +++++++++ main.sh | 75 ++++++++++++++++++++++++++++++++ 3 files changed, 235 insertions(+) create mode 100644 lib/log.sh create mode 100644 lib/test.sh create mode 100755 main.sh diff --git a/lib/log.sh b/lib/log.sh new file mode 100644 index 0000000..74d4cc7 --- /dev/null +++ b/lib/log.sh @@ -0,0 +1,139 @@ +# Copyright (c) 2026 Egor Tensin +# This file is part of the "test-harness" project. +# For details, see https://github.com/egor-tensin/test-harness +# 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 <&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 <&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 <&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 <&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 <&2 +FAIL: $test_file +EOF + done + + cat <&2 +---------------------------------------------------------------------- +EOF + + local ret=0 + [ "$log_numof_tests_failed" -gt 0 ] && ret=1 + + return "$ret" +} diff --git a/lib/test.sh b/lib/test.sh new file mode 100644 index 0000000..fc4f1a6 --- /dev/null +++ b/lib/test.sh @@ -0,0 +1,21 @@ +# Copyright (c) 2026 Egor Tensin +# This file is part of the "test-harness" project. +# For details, see https://github.com/egor-tensin/test-harness +# Distributed under the MIT License. + +test_should_fail= +test_root_dir= + +test_setup_global() { + test_root_dir="$( mktemp -d )" +} + +test_cleanup_global() { + if [ "$( type -t test_cleanup )" == function ]; then + test_cleanup || true + fi + if [ -n "$test_root_dir" ]; then + log "Removing test's root directory: $test_root_dir" + rm -rf -- "$test_root_dir" + fi +} diff --git a/main.sh b/main.sh new file mode 100755 index 0000000..8b7e482 --- /dev/null +++ b/main.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +# Copyright (c) 2026 Egor Tensin +# This file is part of the "test-harness" project. +# For details, see https://github.com/egor-tensin/test-harness +# 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 tests_dir="$script_dir/.." + +source "$script_dir/lib/log.sh" + +run_test_file() { + local test_file + for test_file; do + source "$script_dir/lib/test.sh" + source "$tests_dir/$test_file" + + log_test_start "$test_file" + + set +e + ( + set -o errexit -o nounset -o pipefail + shopt -s inherit_errexit lastpipe + + trap test_cleanup_global EXIT + test_setup_global + 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 "$tests_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 "$@" -- cgit v1.3