aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/log.sh139
-rwxr-xr-xsrc/main.sh143
-rw-r--r--src/test.sh23
3 files changed, 305 insertions, 0 deletions
diff --git a/src/log.sh b/src/log.sh
new file mode 100644
index 0000000..db2c498
--- /dev/null
+++ b/src/log.sh
@@ -0,0 +1,139 @@
+# Copyright (c) 2026 Egor Tensin <egor@tensin.name>
+# 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 <<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/src/main.sh b/src/main.sh
new file mode 100755
index 0000000..ff5aeac
--- /dev/null
+++ b/src/main.sh
@@ -0,0 +1,143 @@
+#!/usr/bin/env bash
+
+# Copyright (c) 2026 Egor Tensin <egor@tensin.name>
+# 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 2> /dev/null || true
+shopt -s lastpipe
+
+_script_name="$( basename -- "${BASH_SOURCE[0]}" )"
+readonly _script_name
+_script_path="$( realpath -e -- "${BASH_SOURCE[0]}" )"
+readonly _script_path
+_script_dir="$( dirname -- "$_script_path" )"
+_script_dir="$( cd -- "$_script_dir" && pwd )"
+readonly _script_dir
+
+source "$_script_dir/log.sh"
+
+_test_run_file() {
+ local test_file
+ for test_file; do
+ source "$_script_dir/test.sh"
+
+ local lib_file
+ find "$tests_dir/lib" \
+ -mindepth 1 \
+ -maxdepth 1 \
+ -type f \
+ -name '*.sh' \
+ -print0 \
+ | sort -z \
+ | \
+ while IFS= read -d '' -r lib_file; do
+ source "$lib_file"
+ done
+
+ source "$tests_dir/$test_file"
+
+ _log_test_start "$test_file"
+
+ set +e
+ (
+ set -o errexit -o nounset -o pipefail
+ shopt -s inherit_errexit 2> /dev/null || true
+ shopt -s lastpipe
+
+ trap _test_cleanup_global EXIT
+ _test_setup_global
+ test_run
+ )
+ local ret="$?"
+ set -e
+
+ _log_test_finish "$test_file" "$ret"
+ done
+}
+
+_test_usage() {
+ local msg
+ for msg; do
+ echo "$_script_name: $msg"
+ done
+
+ echo "usage: $_script_name [-h|--help] [-d|--dir DIR] [-f|--filter NAME_PATTERN]
+ -h,--help show this message and exit
+ -d,--dir directory with tests
+ -f,--filter only run tests with names matching the pattern (uses fnmatch(3))"
+}
+
+_test_parse_args() {
+ tests_dir="$_script_dir/../.."
+ _test_pattern='*'
+
+ while [ "$#" -gt 0 ]; do
+ local key="$1"
+ shift
+
+ case "$key" in
+ -h|--help)
+ _test_usage
+ exit 0
+ ;;
+ -d|--dir|-f|--filter)
+ ;;
+ *)
+ _test_usage "unrecognized parameter: $key" >&2
+ return 1
+ ;;
+ esac
+
+ if [ "$#" -eq 0 ]; then
+ _test_usage "missing argument for parameter: $key" >&2
+ return 1
+ fi
+
+ local value="$1"
+ shift
+
+ case "$key" in
+ -d|--dir)
+ tests_dir="$value"
+ ;;
+ -f|--filter)
+ _test_pattern="$value"
+ ;;
+ *)
+ _test_usage "unrecognized parameter: $key" >&2
+ return 1
+ ;;
+ esac
+ done
+
+ tests_dir="$( realpath -e -- "$tests_dir" )"
+}
+
+_test_main() {
+ _test_parse_args "$@"
+
+ local -a test_files=()
+ local test_file
+
+ find "$tests_dir" \
+ -mindepth 1 \
+ -maxdepth 1 \
+ -type f \
+ -regex '.*/tst-.*\.sh$' \
+ -regextype posix-basic \
+ -name "$_test_pattern" \
+ -printf '%P\0' \
+ | sort -z \
+ | \
+ while IFS= read -d '' -r test_file; do
+ test_files+=("$test_file")
+ done
+
+ _test_run_file ${test_files[@]+"${test_files[@]}"}
+ _log_tests_summary
+}
+
+_test_main "$@"
diff --git a/src/test.sh b/src/test.sh
new file mode 100644
index 0000000..7c5844b
--- /dev/null
+++ b/src/test.sh
@@ -0,0 +1,23 @@
+# Copyright (c) 2026 Egor Tensin <egor@tensin.name>
+# 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 )"
+ # mktemp returns /var/..., which is actually in /private/var/... on macOS.
+ test_root_dir="$( readlink -e -- "$test_root_dir" )"
+}
+
+_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
+}