diff --git a/CHANGELOG.md b/CHANGELOG.md
index b5693793..9165ac7b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@
- Improve clock performance
- Make install.sh args more flexible
- Improve Windows detection allowing parallel tests on Git Bash, MSYS and Cygwin
+- Add progress bar to non-parallel runner
## [0.20.0](https://github.com/TypedDevs/bashunit/compare/0.19.1...0.20.0) - 2025-06-01
diff --git a/bashunit b/bashunit
index d06795f8..0091b481 100755
--- a/bashunit
+++ b/bashunit
@@ -19,6 +19,7 @@ source "$BASHUNIT_ROOT_DIR/src/parallel.sh"
source "$BASHUNIT_ROOT_DIR/src/env.sh"
source "$BASHUNIT_ROOT_DIR/src/clock.sh"
source "$BASHUNIT_ROOT_DIR/src/state.sh"
+source "$BASHUNIT_ROOT_DIR/src/progress.sh"
source "$BASHUNIT_ROOT_DIR/src/colors.sh"
source "$BASHUNIT_ROOT_DIR/src/console_header.sh"
source "$BASHUNIT_ROOT_DIR/src/console_results.sh"
@@ -63,6 +64,9 @@ while [[ $# -gt 0 ]]; do
fi
set -x
;;
+ -pb|--progress-bar)
+ export BASHUNIT_PROGRESS_BAR=true
+ ;;
-b|--bench)
_BENCH_MODE=true
export BASHUNIT_BENCH_MODE=true
diff --git a/build.sh b/build.sh
index 1dcb8665..d4e370e3 100755
--- a/build.sh
+++ b/build.sh
@@ -94,6 +94,7 @@ function build::dependencies() {
"src/env.sh"
"src/clock.sh"
"src/state.sh"
+ "src/progress.sh"
"src/colors.sh"
"src/console_header.sh"
"src/console_results.sh"
diff --git a/docs/command-line.md b/docs/command-line.md
index 5f68c40c..eb82daf5 100644
--- a/docs/command-line.md
+++ b/docs/command-line.md
@@ -340,6 +340,22 @@ Duration: 48 ms
```
:::
+## Progress bar
+
+> `bashunit -pb|--progress-bar`
+
+Enable the progress bar during test execution. By default, the progress bar is disabled.
+
+::: warning
+The progress bar cannot be used when running tests in parallel. If `--progress-bar` is combined with `--parallel`, a warning is printed and the bar is disabled.
+:::
+
+::: code-group
+```bash [Example]
+./bashunit --progress-bar
+```
+:::
+
## Version
> `bashunit --version`
diff --git a/docs/configuration.md b/docs/configuration.md
index 59819fdc..c59f9675 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -247,6 +247,22 @@ BASHUNIT_VERBOSE=true
```
:::
+## Progress bar
+
+> `BASHUNIT_PROGRESS_BAR=true|false`
+
+Controls whether the progress bar is rendered. `false` by default.
+
+::: warning
+The progress bar is disabled when tests run in parallel. Enabling `BASHUNIT_PROGRESS_BAR` while `BASHUNIT_PARALLEL_RUN` is `true` will print a warning and no bar will be shown.
+:::
+
+::: code-group
+```bash [Example]
+BASHUNIT_PROGRESS_BAR=true
+```
+:::
+
diff --git a/src/assert_snapshot.sh b/src/assert_snapshot.sh
index fe51be90..3f92c020 100644
--- a/src/assert_snapshot.sh
+++ b/src/assert_snapshot.sh
@@ -34,9 +34,17 @@ function snapshot::match_with_placeholder() {
fi
}
+# Remove progress bar output from a given string. Progress bar sequences are
+# wrapped between ESC7 and ESC8 control codes when a TTY is present.
+function snapshot::strip_progress_line() {
+ local input="$1"
+ echo -n "$input" | sed $'s/\x1b7.*\x1b8//'
+}
+
function assert_match_snapshot() {
local actual
actual=$(echo -n "$1" | tr -d '\r')
+ actual=$(snapshot::strip_progress_line "$actual")
local directory
directory="./$(dirname "${BASH_SOURCE[1]}")/snapshots"
local test_file
@@ -73,6 +81,7 @@ function assert_match_snapshot() {
function assert_match_snapshot_ignore_colors() {
local actual
actual=$(echo -n "$1" | sed -r 's/\x1B\[[0-9;]*[mK]//g' | tr -d '\r')
+ actual=$(snapshot::strip_progress_line "$actual")
local directory
directory="./$(dirname "${BASH_SOURCE[1]}")/snapshots"
diff --git a/src/benchmark.sh b/src/benchmark.sh
index 32b9eb91..d6ba623e 100644
--- a/src/benchmark.sh
+++ b/src/benchmark.sh
@@ -94,12 +94,16 @@ function benchmark::print_results() {
fi
if env::is_simple_output_enabled; then
- printf "\n"
+ progress::blank_line
fi
- printf "\nBenchmark Results (avg ms)\n"
+ progress::blank_line
+ printf "Benchmark Results (avg ms)\n"
print_line 80 "="
- printf "\n"
+ progress::blank_line
+ if env::is_simple_output_enabled; then
+ progress::refresh
+ fi
local has_threshold=false
for val in "${_BENCH_MAX_MILLIS[@]}"; do
diff --git a/src/console_header.sh b/src/console_header.sh
index 0e912e72..2e31916f 100644
--- a/src/console_header.sh
+++ b/src/console_header.sh
@@ -75,6 +75,9 @@ Options:
-p, --parallel || --no-parallel [default]
Run each test in child process, randomizing the tests execution order.
+ -pb, --progress-bar
+ Display a real-time progress bar during test execution. Requires a TTY and disables parallel mode.
+
-r, --report-html
Create a report HTML file that contains information about the test results.
diff --git a/src/console_results.sh b/src/console_results.sh
index 7e57a024..12490eb5 100644
--- a/src/console_results.sh
+++ b/src/console_results.sh
@@ -13,7 +13,7 @@ function console_results::render_result() {
fi
if env::is_simple_output_enabled; then
- printf "\n\n"
+ progress::blank_line
fi
local total_tests=0
@@ -67,36 +67,42 @@ function console_results::render_result() {
printf " %s total\n" "$total_assertions"
if [[ "$(state::get_tests_failed)" -gt 0 ]]; then
- printf "\n%s%s%s\n" "$_COLOR_RETURN_ERROR" " Some tests failed " "$_COLOR_DEFAULT"
+ progress::blank_line
+ printf "%s%s%s\n" "$_COLOR_RETURN_ERROR" " Some tests failed " "$_COLOR_DEFAULT"
console_results::print_execution_time
return 1
fi
if [[ "$(state::get_tests_incomplete)" -gt 0 ]]; then
- printf "\n%s%s%s\n" "$_COLOR_RETURN_INCOMPLETE" " Some tests incomplete " "$_COLOR_DEFAULT"
+ progress::blank_line
+ printf "%s%s%s\n" "$_COLOR_RETURN_INCOMPLETE" " Some tests incomplete " "$_COLOR_DEFAULT"
console_results::print_execution_time
return 0
fi
if [[ "$(state::get_tests_skipped)" -gt 0 ]]; then
- printf "\n%s%s%s\n" "$_COLOR_RETURN_SKIPPED" " Some tests skipped " "$_COLOR_DEFAULT"
+ progress::blank_line
+ printf "%s%s%s\n" "$_COLOR_RETURN_SKIPPED" " Some tests skipped " "$_COLOR_DEFAULT"
console_results::print_execution_time
return 0
fi
if [[ "$(state::get_tests_snapshot)" -gt 0 ]]; then
- printf "\n%s%s%s\n" "$_COLOR_RETURN_SNAPSHOT" " Some snapshots created " "$_COLOR_DEFAULT"
+ progress::blank_line
+ printf "%s%s%s\n" "$_COLOR_RETURN_SNAPSHOT" " Some snapshots created " "$_COLOR_DEFAULT"
console_results::print_execution_time
return 0
fi
if [[ $total_tests -eq 0 ]]; then
- printf "\n%s%s%s\n" "$_COLOR_RETURN_ERROR" " No tests found " "$_COLOR_DEFAULT"
+ progress::blank_line
+ printf "%s%s%s\n" "$_COLOR_RETURN_ERROR" " No tests found " "$_COLOR_DEFAULT"
console_results::print_execution_time
return 1
fi
- printf "\n%s%s%s\n" "$_COLOR_RETURN_SUCCESS" " All tests passed " "$_COLOR_DEFAULT"
+ progress::blank_line
+ printf "%s%s%s\n" "$_COLOR_RETURN_SUCCESS" " All tests passed " "$_COLOR_DEFAULT"
console_results::print_execution_time
return 0
}
@@ -143,9 +149,25 @@ function console_results::print_successful_test() {
state::print_line "successful" "$full_line"
}
+function console_results::print_failed_test_summary() {
+ local test_name=$1
+ local duration=${2:-"0"}
+
+ local line
+ line=$(printf "%s✗ Failed%s: %s" "$_COLOR_FAILED" "$_COLOR_DEFAULT" "$test_name")
+
+ local full_line=$line
+ if env::is_show_execution_time_enabled; then
+ full_line="$(printf "%s\n" "$(str::rpad "$line" "$duration ms")")"
+ fi
+
+ state::print_line "failed" "$full_line"
+}
+
function console_results::print_failure_message() {
local test_name=$1
local failure_message=$2
+ local duration=${3-}
local line
line="$(printf "\
@@ -153,7 +175,12 @@ ${_COLOR_FAILED}✗ Failed${_COLOR_DEFAULT}: %s
${_COLOR_FAINT}Message:${_COLOR_DEFAULT} ${_COLOR_BOLD}'%s'${_COLOR_DEFAULT}\n"\
"${test_name}" "${failure_message}")"
- state::print_line "failure" "$line"
+ local full_line=$line
+ if [[ -n "$duration" ]] && env::is_show_execution_time_enabled; then
+ full_line="$(printf "%s\n" "$(str::rpad "$line" "$duration ms")")"
+ fi
+
+ state::print_line "failure" "$full_line"
}
function console_results::print_failed_test() {
@@ -163,6 +190,7 @@ function console_results::print_failed_test() {
local actual=$4
local extra_key=${5-}
local extra_value=${6-}
+ local duration=${7-}
local line
line="$(printf "\
@@ -178,13 +206,19 @@ ${_COLOR_FAILED}✗ Failed${_COLOR_DEFAULT}: %s
"${extra_key}" "${extra_value}")"
fi
- state::print_line "failed" "$line"
+ local full_line=$line
+ if [[ -n "$duration" ]] && env::is_show_execution_time_enabled; then
+ full_line="$(printf "%s\n" "$(str::rpad "$line" "$duration ms")")"
+ fi
+
+ state::print_line "failed" "$full_line"
}
function console_results::print_failed_snapshot_test() {
local function_name=$1
local snapshot_file=$2
+ local duration=${3-}
local line
line="$(printf "${_COLOR_FAILED}✗ Failed${_COLOR_DEFAULT}: %s
@@ -203,12 +237,18 @@ function console_results::print_failed_snapshot_test() {
rm "$actual_file"
fi
- state::print_line "failed_snapshot" "$line"
+ local full_line=$line
+ if [[ -n "$duration" ]] && env::is_show_execution_time_enabled; then
+ full_line="$(printf "%s\n" "$(str::rpad "$line" "$duration ms")")"
+ fi
+
+ state::print_line "failed_snapshot" "$full_line"
}
function console_results::print_skipped_test() {
local function_name=$1
local reason=${2-}
+ local duration=${3-}
local line
line="$(printf "${_COLOR_SKIPPED}↷ Skipped${_COLOR_DEFAULT}: %s\n" "${function_name}")"
@@ -217,12 +257,18 @@ function console_results::print_skipped_test() {
line+="$(printf "${_COLOR_FAINT} %s${_COLOR_DEFAULT}\n" "${reason}")"
fi
- state::print_line "skipped" "$line"
+ local full_line=$line
+ if [[ -n "$duration" ]] && env::is_show_execution_time_enabled; then
+ full_line="$(printf "%s\n" "$(str::rpad "$line" "$duration ms")")"
+ fi
+
+ state::print_line "skipped" "$full_line"
}
function console_results::print_incomplete_test() {
local function_name=$1
local pending=${2-}
+ local duration=${3-}
local line
line="$(printf "${_COLOR_INCOMPLETE}✒ Incomplete${_COLOR_DEFAULT}: %s\n" "${function_name}")"
@@ -231,23 +277,35 @@ function console_results::print_incomplete_test() {
line+="$(printf "${_COLOR_FAINT} %s${_COLOR_DEFAULT}\n" "${pending}")"
fi
- state::print_line "incomplete" "$line"
+ local full_line=$line
+ if [[ -n "$duration" ]] && env::is_show_execution_time_enabled; then
+ full_line="$(printf "%s\n" "$(str::rpad "$line" "$duration ms")")"
+ fi
+
+ state::print_line "incomplete" "$full_line"
}
function console_results::print_snapshot_test() {
local function_name=$1
+ local duration=${2-}
local test_name
test_name=$(helper::normalize_test_function_name "$function_name")
local line
line="$(printf "${_COLOR_SNAPSHOT}✎ Snapshot${_COLOR_DEFAULT}: %s\n" "${test_name}")"
- state::print_line "snapshot" "$line"
+ local full_line=$line
+ if [[ -n "$duration" ]] && env::is_show_execution_time_enabled; then
+ full_line="$(printf "%s\n" "$(str::rpad "$line" "$duration ms")")"
+ fi
+
+ state::print_line "snapshot" "$full_line"
}
function console_results::print_error_test() {
local function_name=$1
local error="$2"
+ local duration=${3-}
local test_name
test_name=$(helper::normalize_test_function_name "$function_name")
@@ -256,7 +314,12 @@ function console_results::print_error_test() {
line="$(printf "${_COLOR_FAILED}✗ Error${_COLOR_DEFAULT}: %s
${_COLOR_FAINT}%s${_COLOR_DEFAULT}\n" "${test_name}" "${error}")"
- state::print_line "error" "$line"
+ local full_line=$line
+ if [[ -n "$duration" ]] && env::is_show_execution_time_enabled; then
+ full_line="$(printf "%s\n" "$(str::rpad "$line" "$duration ms")")"
+ fi
+
+ state::print_line "error" "$full_line"
}
function console_results::print_failing_tests_and_reset() {
@@ -265,7 +328,8 @@ function console_results::print_failing_tests_and_reset() {
total_failed=$(state::get_tests_failed)
if env::is_simple_output_enabled; then
- printf "\n\n"
+ progress::blank_line
+ progress::blank_line
fi
if [[ "$total_failed" -eq 1 ]]; then
@@ -277,6 +341,6 @@ function console_results::print_failing_tests_and_reset() {
sed '${/^$/d;}' "$FAILURES_OUTPUT_PATH" | sed 's/^/|/'
rm "$FAILURES_OUTPUT_PATH"
- echo ""
+ progress::blank_line
fi
}
diff --git a/src/env.sh b/src/env.sh
index 77ed9a39..2ede2472 100644
--- a/src/env.sh
+++ b/src/env.sh
@@ -28,6 +28,7 @@ _DEFAULT_STOP_ON_FAILURE="false"
_DEFAULT_SHOW_EXECUTION_TIME="true"
_DEFAULT_VERBOSE="false"
_DEFAULT_BENCH_MODE="false"
+_DEFAULT_PROGRESS_BAR="false"
: "${BASHUNIT_PARALLEL_RUN:=${PARALLEL_RUN:=$_DEFAULT_PARALLEL_RUN}}"
: "${BASHUNIT_SHOW_HEADER:=${SHOW_HEADER:=$_DEFAULT_SHOW_HEADER}}"
@@ -37,6 +38,7 @@ _DEFAULT_BENCH_MODE="false"
: "${BASHUNIT_SHOW_EXECUTION_TIME:=${SHOW_EXECUTION_TIME:=$_DEFAULT_SHOW_EXECUTION_TIME}}"
: "${BASHUNIT_VERBOSE:=${VERBOSE:=$_DEFAULT_VERBOSE}}"
: "${BASHUNIT_BENCH_MODE:=${BENCH_MODE:=$_DEFAULT_BENCH_MODE}}"
+: "${BASHUNIT_PROGRESS_BAR:=${PROGRESS_BAR:=$_DEFAULT_PROGRESS_BAR}}"
function env::is_parallel_run_enabled() {
[[ "$BASHUNIT_PARALLEL_RUN" == "true" ]]
@@ -74,6 +76,10 @@ function env::is_bench_mode_enabled() {
[[ "$BASHUNIT_BENCH_MODE" == "true" ]]
}
+function env::is_progress_bar_enabled() {
+ [[ "$BASHUNIT_PROGRESS_BAR" == "true" ]]
+}
+
function env::active_internet_connection() {
if ping -c 1 -W 3 google.com &> /dev/null; then
return 0
diff --git a/src/main.sh b/src/main.sh
index 2955b075..68e554b1 100644
--- a/src/main.sh
+++ b/src/main.sh
@@ -32,9 +32,14 @@ function main::exec_tests() {
console_header::print_version_with_env "$filter" "${test_files[@]}"
+ local total_tests
+ total_tests=$(helpers::find_total_tests "$filter" "${test_files[@]}")
+ state::set_total_tests_to_run "$total_tests"
+ progress::init "$total_tests"
+
if env::is_verbose_enabled; then
if env::is_simple_output_enabled; then
- echo ""
+ progress::blank_line
fi
printf '%*s\n' "$TERMINAL_WIDTH" '' | tr ' ' '#'
printf "%s\n" "Filter: ${filter:-None}"
@@ -44,6 +49,9 @@ function main::exec_tests() {
printf '%*s\n' "$TERMINAL_WIDTH" '' | tr ' ' '.'
env::print_verbose
printf '%*s\n' "$TERMINAL_WIDTH" '' | tr ' ' '#'
+ if env::is_simple_output_enabled; then
+ progress::refresh
+ fi
fi
runner::load_test_files "$filter" "${test_files[@]}"
@@ -59,6 +67,7 @@ function main::exec_tests() {
console_results::print_failing_tests_and_reset
console_results::render_result
exit_code=$?
+ progress::finish
if [[ -n "$BASHUNIT_LOG_JUNIT" ]]; then
reports::generate_junit_xml "$BASHUNIT_LOG_JUNIT"
@@ -105,7 +114,8 @@ function main::cleanup() {
}
function main::handle_stop_on_failure_sync() {
- printf "\n%sStop on failure enabled...%s\n" "${_COLOR_SKIPPED}" "${_COLOR_DEFAULT}"
+ progress::blank_line
+ printf "%sStop on failure enabled...%s\n" "${_COLOR_SKIPPED}" "${_COLOR_DEFAULT}"
console_results::print_failing_tests_and_reset
console_results::render_result
cleanup_temp_files
diff --git a/src/progress.sh b/src/progress.sh
new file mode 100644
index 00000000..1938075d
--- /dev/null
+++ b/src/progress.sh
@@ -0,0 +1,95 @@
+#!/usr/bin/env bash
+
+# Check if progress bar is enabled and not in parallel mode
+function progress::enabled() {
+ env::is_progress_bar_enabled && ! parallel::is_enabled
+}
+
+# Initialize progress tracking variables and optionally render the first bar
+function progress::init() {
+ export PROGRESS_BAR_TOTAL=$1
+ export PROGRESS_BAR_CURRENT=0
+
+ if env::is_progress_bar_enabled && parallel::is_enabled; then
+ printf "%sWarning: Progress bar is not supported in parallel mode.%s\n" \
+ "${_COLOR_INCOMPLETE}" "${_COLOR_DEFAULT}"
+ fi
+
+ if progress::enabled; then
+ progress::render 0 "$PROGRESS_BAR_TOTAL"
+ fi
+}
+
+# Render the progress bar line
+function progress::render() {
+ local current total width filled empty i bar line
+
+ current=$1
+ total=$2
+ PROGRESS_BAR_CURRENT=$current
+
+ if ! progress::enabled || [[ ! -t 1 ]] || [[ -z "$total" || "$total" -eq 0 ]]; then
+ return
+ fi
+
+ width=$((TERMINAL_WIDTH - 20))
+ [ "$width" -lt 10 ] && width=10
+
+ filled=$(( current * width / total ))
+ empty=$(( width - filled ))
+
+ bar=''
+ i=0
+ while [ $i -lt $filled ]; do
+ bar="${bar}#"
+ i=$((i + 1))
+ done
+ i=0
+ while [ $i -lt $empty ]; do
+ bar="${bar}-"
+ i=$((i + 1))
+ done
+
+ line=$(printf '[%s] %d/%d' "$bar" "$current" "$total")
+
+ if command -v tput >/dev/null; then
+ tput sc
+ tput cup $(( $(tput lines) - 1 )) 0
+ printf '%-*s' "$TERMINAL_WIDTH" "$line"
+ tput rc
+ else
+ printf '\r%-*s' "$TERMINAL_WIDTH" "$line"
+ fi
+}
+
+# Finish and clear the progress bar line
+function progress::finish() {
+ if ! progress::enabled; then
+ return
+ fi
+
+ if command -v tput >/dev/null; then
+ tput sc
+ tput cup $(( $(tput lines) - 1 )) 0
+ printf '%*s' "$TERMINAL_WIDTH" ''
+ tput rc
+ else
+ printf '\r%-*s' "$TERMINAL_WIDTH" ''
+ fi
+}
+
+# Redraw the progress bar using the current known state
+function progress::refresh() {
+ if progress::enabled; then
+ progress::render "${PROGRESS_BAR_CURRENT:-0}" "${PROGRESS_BAR_TOTAL:-0}"
+ fi
+}
+
+# Print an empty line, clearing any previous progress bar content
+function progress::blank_line() {
+ if [[ -t 1 ]] && command -v tput >/dev/null; then
+ printf '%*s\n' "$TERMINAL_WIDTH" ''
+ else
+ printf '\n'
+ fi
+}
diff --git a/src/runner.sh b/src/runner.sh
index e440dc44..57700238 100755
--- a/src/runner.sh
+++ b/src/runner.sh
@@ -48,7 +48,6 @@ function runner::load_bench_files() {
runner::clean_set_up_and_tear_down_after_script
done
}
-
function runner::spinner() {
if env::is_simple_output_enabled; then
printf "\n"
@@ -56,6 +55,9 @@ function runner::spinner() {
local delay=0.1
local spin_chars="|/-\\"
+
+ trap 'printf "\r \r"' EXIT
+
while true; do
for ((i=0; i<${#spin_chars}; i++)); do
printf "\r%s" "${spin_chars:$i:1}"
@@ -125,7 +127,7 @@ function runner::call_test_functions() {
done
if ! env::is_simple_output_enabled; then
- echo ""
+ progress::blank_line
fi
}
@@ -154,7 +156,7 @@ function runner::call_bench_functions() {
done
if ! env::is_simple_output_enabled; then
- echo ""
+ progress::blank_line
fi
}
@@ -164,13 +166,20 @@ function runner::render_running_file_header() {
fi
if ! env::is_simple_output_enabled; then
- if env::is_verbose_enabled; then
- printf "\n${_COLOR_BOLD}%s${_COLOR_DEFAULT}\n" "Running $script"
- else
- printf "${_COLOR_BOLD}%s${_COLOR_DEFAULT}\n" "Running $script"
+ if env::is_verbose_enabled; then
+ progress::blank_line
+ printf "${_COLOR_BOLD}%s${_COLOR_DEFAULT}\n" "Running $script"
+ else
+ printf "${_COLOR_BOLD}%s${_COLOR_DEFAULT}\n" "Running $script"
+ fi
+ elif env::is_verbose_enabled; then
+ progress::blank_line
+ progress::blank_line
+ printf "${_COLOR_BOLD}%s${_COLOR_DEFAULT}" "Running $script"
fi
- elif env::is_verbose_enabled; then
- printf "\n\n${_COLOR_BOLD}%s${_COLOR_DEFAULT}" "Running $script"
+
+ if env::is_simple_output_enabled && env::is_verbose_enabled; then
+ progress::refresh
fi
}
@@ -230,7 +239,7 @@ function runner::run_test() {
if env::is_verbose_enabled; then
if env::is_simple_output_enabled; then
- echo ""
+ progress::blank_line
fi
printf '%*s\n' "$TERMINAL_WIDTH" '' | tr ' ' '='
@@ -283,7 +292,8 @@ function runner::run_test() {
if [[ -n $runtime_error || $test_exit_code -ne 0 ]]; then
state::add_tests_failed
- console_results::print_error_test "$fn_name" "$runtime_error"
+ console_results::print_error_test "$fn_name" "$runtime_error" "$duration"
+ console_results::print_failed_test_summary "$fn_name" "$duration"
reports::add_test_failed "$test_file" "$fn_name" "$duration" "$total_assertions"
runner::write_failure_result_output "$test_file" "$runtime_error"
return
@@ -291,6 +301,7 @@ function runner::run_test() {
if [[ "$current_assertions_failed" != "$(state::get_assertions_failed)" ]]; then
state::add_tests_failed
+ console_results::print_failed_test_summary "$fn_name" "$duration"
reports::add_test_failed "$test_file" "$fn_name" "$duration" "$total_assertions"
runner::write_failure_result_output "$test_file" "$subshell_output"
@@ -306,19 +317,21 @@ function runner::run_test() {
if [[ "$current_assertions_snapshot" != "$(state::get_assertions_snapshot)" ]]; then
state::add_tests_snapshot
- console_results::print_snapshot_test "$fn_name"
+ console_results::print_snapshot_test "$fn_name" "$duration"
reports::add_test_snapshot "$test_file" "$fn_name" "$duration" "$total_assertions"
return
fi
if [[ "$current_assertions_incomplete" != "$(state::get_assertions_incomplete)" ]]; then
state::add_tests_incomplete
+ console_results::print_incomplete_test "$fn_name" "" "$duration"
reports::add_test_incomplete "$test_file" "$fn_name" "$duration" "$total_assertions"
return
fi
if [[ "$current_assertions_skipped" != "$(state::get_assertions_skipped)" ]]; then
state::add_tests_skipped
+ console_results::print_skipped_test "$fn_name" "" "$duration"
reports::add_test_skipped "$test_file" "$fn_name" "$duration" "$total_assertions"
return
fi
diff --git a/src/state.sh b/src/state.sh
index 8d8fc7ed..8841560c 100644
--- a/src/state.sh
+++ b/src/state.sh
@@ -15,6 +15,15 @@ _FILE_WITH_DUPLICATED_FUNCTION_NAMES=""
_DUPLICATED_TEST_FUNCTIONS_FOUND=false
_TEST_OUTPUT=""
_TEST_EXIT_CODE=0
+_TOTAL_TESTS_TO_RUN=0
+
+function state::set_total_tests_to_run() {
+ _TOTAL_TESTS_TO_RUN=$1
+}
+
+function state::get_total_tests_to_run() {
+ echo "$_TOTAL_TESTS_TO_RUN"
+}
function state::get_tests_passed() {
echo "$_TESTS_PASSED"
@@ -195,6 +204,7 @@ function state::print_line() {
if ! env::is_simple_output_enabled; then
printf "%s\n" "$line"
+ progress::render "$_TOTAL_TESTS_COUNT" "$(state::get_total_tests_to_run)"
return
fi
@@ -220,4 +230,6 @@ function state::print_line() {
printf "%s" "$char"
fi
fi
+
+ progress::render "$_TOTAL_TESTS_COUNT" "$(state::get_total_tests_to_run)"
}
diff --git a/tests/acceptance/snapshots/bashunit_benchmark_test_sh.test_bashunit_functional_benchmark.snapshot b/tests/acceptance/snapshots/bashunit_benchmark_test_sh.test_bashunit_functional_benchmark.snapshot
deleted file mode 100644
index ebdd70f1..00000000
--- a/tests/acceptance/snapshots/bashunit_benchmark_test_sh.test_bashunit_functional_benchmark.snapshot
+++ /dev/null
@@ -1,5 +0,0 @@
-.
-
-Benchmark Results (avg ms)
-Name Revs Its Avg(ms)
-bench_run_bashunit_functional 2 1 ::ignore::
diff --git a/tests/acceptance/snapshots/bashunit_benchmark_test_sh.test_bashunit_runs_benchmark_file.snapshot b/tests/acceptance/snapshots/bashunit_benchmark_test_sh.test_bashunit_runs_benchmark_file.snapshot
deleted file mode 100644
index fcecaece..00000000
--- a/tests/acceptance/snapshots/bashunit_benchmark_test_sh.test_bashunit_runs_benchmark_file.snapshot
+++ /dev/null
@@ -1,7 +0,0 @@
-bench bench_sleep [1/2] ::ignore::
-bench bench_sleep [2/2] ::ignore::
-
-
-Benchmark Results (avg ms)
-Name Revs Its Avg(ms)
-bench_sleep 5 2 ::ignore::
diff --git a/tests/acceptance/snapshots/bashunit_exit_code_test_sh.test_bashunit_when_a_test_returns_non_zero.snapshot b/tests/acceptance/snapshots/bashunit_exit_code_test_sh.test_bashunit_when_a_test_returns_non_zero.snapshot
index 4b3dbae2..59887a6a 100644
--- a/tests/acceptance/snapshots/bashunit_exit_code_test_sh.test_bashunit_when_a_test_returns_non_zero.snapshot
+++ b/tests/acceptance/snapshots/bashunit_exit_code_test_sh.test_bashunit_when_a_test_returns_non_zero.snapshot
@@ -1,6 +1,7 @@
[1mRunning tests/acceptance/fixtures/test_bashunit_when_a_test_returns_non_zero.sh[0m
[31m✗ Error[0m: Returns non zero
[2m[0m
+[31m✗ Failed[0m: test_returns_non_zero
[1mThere was 1 failure:[0m
diff --git a/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_when_a_test_fail_simple_output_env.snapshot b/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_when_a_test_fail_simple_output_env.snapshot
index f1c6b334..8fdad5fb 100644
--- a/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_when_a_test_fail_simple_output_env.snapshot
+++ b/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_when_a_test_fail_simple_output_env.snapshot
@@ -1,4 +1,4 @@
-..[31mF[0m..
+..[31mF[0m[31mF[0m..
[1mThere was 1 failure:[0m
@@ -8,7 +8,6 @@
| [2mbut got [0m [1m'0'[0m
-
[2mTests: [0m [32m4 passed[0m, [31m1 failed[0m, 5 total
[2mAssertions:[0m [32m6 passed[0m, [31m1 failed[0m, 7 total
diff --git a/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_when_a_test_fail_simple_output_option.snapshot b/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_when_a_test_fail_simple_output_option.snapshot
index f1c6b334..8fdad5fb 100644
--- a/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_when_a_test_fail_simple_output_option.snapshot
+++ b/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_when_a_test_fail_simple_output_option.snapshot
@@ -1,4 +1,4 @@
-..[31mF[0m..
+..[31mF[0m[31mF[0m..
[1mThere was 1 failure:[0m
@@ -8,7 +8,6 @@
| [2mbut got [0m [1m'0'[0m
-
[2mTests: [0m [32m4 passed[0m, [31m1 failed[0m, 5 total
[2mAssertions:[0m [32m6 passed[0m, [31m1 failed[0m, 7 total
diff --git a/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_when_a_test_fail_verbose_output_env.snapshot b/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_when_a_test_fail_verbose_output_env.snapshot
index 3b2767ac..a7fa80bd 100644
--- a/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_when_a_test_fail_verbose_output_env.snapshot
+++ b/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_when_a_test_fail_verbose_output_env.snapshot
@@ -4,6 +4,7 @@
[31m✗ Failed[0m: Assert failing
[2mExpected[0m [1m'1'[0m
[2mbut got [0m [1m'0'[0m
+[31m✗ Failed[0m: test_assert_failing
[32m✓ Passed[0m: Assert greater and less than
[32m✓ Passed[0m: Assert empty
diff --git a/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_when_a_test_fail_verbose_output_option.snapshot b/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_when_a_test_fail_verbose_output_option.snapshot
index 3b2767ac..a7fa80bd 100644
--- a/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_when_a_test_fail_verbose_output_option.snapshot
+++ b/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_when_a_test_fail_verbose_output_option.snapshot
@@ -4,6 +4,7 @@
[31m✗ Failed[0m: Assert failing
[2mExpected[0m [1m'1'[0m
[2mbut got [0m [1m'0'[0m
+[31m✗ Failed[0m: test_assert_failing
[32m✓ Passed[0m: Assert greater and less than
[32m✓ Passed[0m: Assert empty
diff --git a/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_with_a_test_fail_and_exit_immediately.snapshot b/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_with_a_test_fail_and_exit_immediately.snapshot
index 44ecfe42..2b209317 100644
--- a/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_with_a_test_fail_and_exit_immediately.snapshot
+++ b/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_with_a_test_fail_and_exit_immediately.snapshot
@@ -1,6 +1,7 @@
[1mRunning ./tests/acceptance/fixtures/test_bashunit_when_exit_immediately_after_execution_error.sh[0m
[31m✗ Error[0m: Error
[2m[0m
+[31m✗ Failed[0m: test_error
[1mThere was 1 failure:[0m
diff --git a/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_with_multiple_failing_tests.snapshot b/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_with_multiple_failing_tests.snapshot
index 16777156..a9351f59 100644
--- a/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_with_multiple_failing_tests.snapshot
+++ b/tests/acceptance/snapshots/bashunit_fail_test_sh.test_bashunit_with_multiple_failing_tests.snapshot
@@ -6,10 +6,13 @@
[31m✗ Failed[0m: Assert failing
[2mExpected[0m [1m'3'[0m
[2mbut got [0m [1m'4'[0m
+[31m✗ Failed[0m: test_assert_failing
[36m✒ Incomplete[0m: Assert todo and skip[2m foo[0m
[33m↷ Skipped[0m: Assert todo and skip[2m bar[0m
+[36m✒ Incomplete[0m: test_assert_todo_and_skip
[33m↷ Skipped[0m: Assert skip and todo[2m baz[0m
[36m✒ Incomplete[0m: Assert skip and todo[2m yei[0m
+[36m✒ Incomplete[0m: test_assert_skip_and_todo
[1mThere was 1 failure:[0m
diff --git a/tests/acceptance/snapshots/bashunit_log_junit_test_sh.test_bashunit_when_log_junit_env.snapshot b/tests/acceptance/snapshots/bashunit_log_junit_test_sh.test_bashunit_when_log_junit_env.snapshot
index c5a8817d..7e475a7b 100644
--- a/tests/acceptance/snapshots/bashunit_log_junit_test_sh.test_bashunit_when_log_junit_env.snapshot
+++ b/tests/acceptance/snapshots/bashunit_log_junit_test_sh.test_bashunit_when_log_junit_env.snapshot
@@ -3,6 +3,7 @@
[31m✗ Failed[0m: Failure
[2mExpected[0m [1m'2'[0m
[2mbut got [0m [1m'3'[0m
+[31m✗ Failed[0m: test_failure
[1mThere was 1 failure:[0m
diff --git a/tests/acceptance/snapshots/bashunit_log_junit_test_sh.test_bashunit_when_log_junit_option.snapshot b/tests/acceptance/snapshots/bashunit_log_junit_test_sh.test_bashunit_when_log_junit_option.snapshot
index c5a8817d..7e475a7b 100644
--- a/tests/acceptance/snapshots/bashunit_log_junit_test_sh.test_bashunit_when_log_junit_option.snapshot
+++ b/tests/acceptance/snapshots/bashunit_log_junit_test_sh.test_bashunit_when_log_junit_option.snapshot
@@ -3,6 +3,7 @@
[31m✗ Failed[0m: Failure
[2mExpected[0m [1m'2'[0m
[2mbut got [0m [1m'3'[0m
+[31m✗ Failed[0m: test_failure
[1mThere was 1 failure:[0m
diff --git a/tests/acceptance/snapshots/bashunit_pass_test_sh.test_bashunit_when_a_test_passes_simple_output_env.snapshot b/tests/acceptance/snapshots/bashunit_pass_test_sh.test_bashunit_when_a_test_passes_simple_output_env.snapshot
index da114271..3d4daf1d 100644
--- a/tests/acceptance/snapshots/bashunit_pass_test_sh.test_bashunit_when_a_test_passes_simple_output_env.snapshot
+++ b/tests/acceptance/snapshots/bashunit_pass_test_sh.test_bashunit_when_a_test_passes_simple_output_env.snapshot
@@ -1,5 +1,4 @@
....
-
[2mTests: [0m [32m4 passed[0m, 4 total
[2mAssertions:[0m [32m6 passed[0m, 6 total
diff --git a/tests/acceptance/snapshots/bashunit_pass_test_sh.test_bashunit_when_a_test_passes_simple_output_option.snapshot b/tests/acceptance/snapshots/bashunit_pass_test_sh.test_bashunit_when_a_test_passes_simple_output_option.snapshot
index da114271..3d4daf1d 100644
--- a/tests/acceptance/snapshots/bashunit_pass_test_sh.test_bashunit_when_a_test_passes_simple_output_option.snapshot
+++ b/tests/acceptance/snapshots/bashunit_pass_test_sh.test_bashunit_when_a_test_passes_simple_output_option.snapshot
@@ -1,5 +1,4 @@
....
-
[2mTests: [0m [32m4 passed[0m, 4 total
[2mAssertions:[0m [32m6 passed[0m, 6 total
diff --git a/tests/acceptance/snapshots/bashunit_path_test_sh.test_bashunit_without_path_env_nor_argument.snapshot b/tests/acceptance/snapshots/bashunit_path_test_sh.test_bashunit_without_path_env_nor_argument.snapshot
index b43fb469..c325f738 100644
--- a/tests/acceptance/snapshots/bashunit_path_test_sh.test_bashunit_without_path_env_nor_argument.snapshot
+++ b/tests/acceptance/snapshots/bashunit_path_test_sh.test_bashunit_without_path_env_nor_argument.snapshot
@@ -22,6 +22,9 @@ Options:
-p, --parallel || --no-parallel [default]
Run each test in child process, randomizing the tests execution order.
+ -pb, --progress-bar
+ Display a real-time progress bar during test execution. Requires a TTY and disables parallel mode.
+
-r, --report-html
Create a report HTML file that contains information about the test results.
diff --git a/tests/acceptance/snapshots/bashunit_report_html_test_sh.test_bashunit_when_report_html_env.snapshot b/tests/acceptance/snapshots/bashunit_report_html_test_sh.test_bashunit_when_report_html_env.snapshot
index deab502d..09b522a8 100644
--- a/tests/acceptance/snapshots/bashunit_report_html_test_sh.test_bashunit_when_report_html_env.snapshot
+++ b/tests/acceptance/snapshots/bashunit_report_html_test_sh.test_bashunit_when_report_html_env.snapshot
@@ -3,8 +3,11 @@
[31m✗ Failed[0m: Fail
[2mExpected[0m [1m'to be empty'[0m
[2mbut got [0m [1m'non empty'[0m
+[31m✗ Failed[0m: test_fail
[33m↷ Skipped[0m: Skipped
+[33m↷ Skipped[0m: test_skipped
[36m✒ Incomplete[0m: Todo
+[36m✒ Incomplete[0m: test_todo
[1mThere was 1 failure:[0m
diff --git a/tests/acceptance/snapshots/bashunit_report_html_test_sh.test_bashunit_when_report_html_option.snapshot b/tests/acceptance/snapshots/bashunit_report_html_test_sh.test_bashunit_when_report_html_option.snapshot
index deab502d..09b522a8 100644
--- a/tests/acceptance/snapshots/bashunit_report_html_test_sh.test_bashunit_when_report_html_option.snapshot
+++ b/tests/acceptance/snapshots/bashunit_report_html_test_sh.test_bashunit_when_report_html_option.snapshot
@@ -3,8 +3,11 @@
[31m✗ Failed[0m: Fail
[2mExpected[0m [1m'to be empty'[0m
[2mbut got [0m [1m'non empty'[0m
+[31m✗ Failed[0m: test_fail
[33m↷ Skipped[0m: Skipped
+[33m↷ Skipped[0m: test_skipped
[36m✒ Incomplete[0m: Todo
+[36m✒ Incomplete[0m: test_todo
[1mThere was 1 failure:[0m
diff --git a/tests/acceptance/snapshots/bashunit_stop_on_failure_test_sh.test_bashunit_when_stop_on_failure_env.snapshot b/tests/acceptance/snapshots/bashunit_stop_on_failure_test_sh.test_bashunit_when_stop_on_failure_env.snapshot
index 4b7f2bd4..f7924fcc 100644
--- a/tests/acceptance/snapshots/bashunit_stop_on_failure_test_sh.test_bashunit_when_stop_on_failure_env.snapshot
+++ b/tests/acceptance/snapshots/bashunit_stop_on_failure_test_sh.test_bashunit_when_stop_on_failure_env.snapshot
@@ -3,6 +3,7 @@
[31m✗ Failed[0m: B error
[2mExpected[0m [1m'1'[0m
[2mbut got [0m [1m'2'[0m
+[31m✗ Failed[0m: test_b_error
[33mStop on failure enabled...[0m
[1mThere was 1 failure:[0m
diff --git a/tests/acceptance/snapshots/bashunit_stop_on_failure_test_sh.test_bashunit_when_stop_on_failure_env_simple_output.snapshot b/tests/acceptance/snapshots/bashunit_stop_on_failure_test_sh.test_bashunit_when_stop_on_failure_env_simple_output.snapshot
index bccd2609..5b218515 100644
--- a/tests/acceptance/snapshots/bashunit_stop_on_failure_test_sh.test_bashunit_when_stop_on_failure_env_simple_output.snapshot
+++ b/tests/acceptance/snapshots/bashunit_stop_on_failure_test_sh.test_bashunit_when_stop_on_failure_env_simple_output.snapshot
@@ -1,4 +1,4 @@
-.[31mF[0m
+.[31mF[0m[31mF[0m
[33mStop on failure enabled...[0m
@@ -10,7 +10,6 @@
| [2mbut got [0m [1m'2'[0m
-
[2mTests: [0m [32m1 passed[0m, [31m1 failed[0m, 2 total
[2mAssertions:[0m [32m3 passed[0m, [31m1 failed[0m, 4 total
diff --git a/tests/acceptance/snapshots/bashunit_stop_on_failure_test_sh.test_bashunit_when_stop_on_failure_option.snapshot b/tests/acceptance/snapshots/bashunit_stop_on_failure_test_sh.test_bashunit_when_stop_on_failure_option.snapshot
index 4b7f2bd4..f7924fcc 100644
--- a/tests/acceptance/snapshots/bashunit_stop_on_failure_test_sh.test_bashunit_when_stop_on_failure_option.snapshot
+++ b/tests/acceptance/snapshots/bashunit_stop_on_failure_test_sh.test_bashunit_when_stop_on_failure_option.snapshot
@@ -3,6 +3,7 @@
[31m✗ Failed[0m: B error
[2mExpected[0m [1m'1'[0m
[2mbut got [0m [1m'2'[0m
+[31m✗ Failed[0m: test_b_error
[33mStop on failure enabled...[0m
[1mThere was 1 failure:[0m
diff --git a/tests/acceptance/snapshots/bashunit_test_sh.test_bashunit_should_display_help.snapshot b/tests/acceptance/snapshots/bashunit_test_sh.test_bashunit_should_display_help.snapshot
index 43dd8ec2..de03850c 100644
--- a/tests/acceptance/snapshots/bashunit_test_sh.test_bashunit_should_display_help.snapshot
+++ b/tests/acceptance/snapshots/bashunit_test_sh.test_bashunit_should_display_help.snapshot
@@ -21,6 +21,9 @@ Options:
-p, --parallel || --no-parallel [default]
Run each test in child process, randomizing the tests execution order.
+ -pb, --progress-bar
+ Display a real-time progress bar during test execution. Requires a TTY and disables parallel mode.
+
-r, --report-html
Create a report HTML file that contains information about the test results.
diff --git a/tests/unit/directory_test.sh b/tests/unit/directory_test.sh
index eeb26046..b5424f1b 100644
--- a/tests/unit/directory_test.sh
+++ b/tests/unit/directory_test.sh
@@ -114,6 +114,10 @@ function test_unsuccessful_assert_is_directory_readable_without_execution_permis
if [[ "$_OS" == "Windows" || $_DISTRO = "Alpine" ]]; then
return
fi
+ if [[ $(id -u) -eq 0 ]]; then
+ skip "Running as root"
+ return
+ fi
local a_directory=$(mktemp -d)
chmod a-x "$a_directory"
@@ -129,6 +133,10 @@ function test_unsuccessful_assert_is_directory_readable_without_read_permission(
if [[ "$_OS" == "Windows" || $_DISTRO = "Alpine" ]]; then
return
fi
+ if [[ $(id -u) -eq 0 ]]; then
+ skip "Running as root"
+ return
+ fi
local a_directory=$(mktemp -d)
chmod a-r "$a_directory"
@@ -144,6 +152,10 @@ function test_successful_assert_is_directory_not_readable_without_read_permissio
if [[ "$_OS" == "Windows" || $_DISTRO = "Alpine" ]]; then
return
fi
+ if [[ $(id -u) -eq 0 ]]; then
+ skip "Running as root"
+ return
+ fi
local a_directory=$(mktemp -d)
chmod a-r "$a_directory"
@@ -155,6 +167,10 @@ function test_successful_assert_is_directory_not_readable_without_execution_perm
if [[ "$_OS" == "Windows" || $_DISTRO = "Alpine" ]]; then
return
fi
+ if [[ $(id -u) -eq 0 ]]; then
+ skip "Running as root"
+ return
+ fi
local a_directory=$(mktemp -d)
chmod a-x "$a_directory"
@@ -181,6 +197,10 @@ function test_unsuccessful_assert_is_directory_writable() {
if [[ "$_OS" == "Windows" || $_DISTRO = "Alpine" ]]; then
return
fi
+ if [[ $(id -u) -eq 0 ]]; then
+ skip "Running as root"
+ return
+ fi
local a_directory=$(mktemp -d)
chmod a-w "$a_directory"
@@ -205,6 +225,10 @@ function test_successful_assert_is_directory_not_writable() {
if [[ "$_OS" == "Windows" || $_DISTRO = "Alpine" ]]; then
return
fi
+ if [[ $(id -u) -eq 0 ]]; then
+ skip "Running as root"
+ return
+ fi
local a_directory=$(mktemp -d)
chmod a-w "$a_directory"