Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- Performance: `--coverage` is roughly 5x faster and `--coverage-report-html` roughly 19x — a run over this repo went from 16.2s to 2.9s, and a 128-file HTML report from 58.7s to 3.1s. The report phase emits each format in one awk invocation per run instead of Bash loops and forks per file and per row, and the capture path writes records straight to disk, normalizes a path with one fork instead of four, and reads each cache once (#1092, #1096, #1098, #1099, #1102, #1104, #1110, #1117)

### Fixed
- A bootstrap file that fails to load now reports it and exits non-zero, instead of leaving the run with no tests, no summary and exit 0. `source` is a POSIX special builtin, so a syntax error or a bare `exit` in the file ends the shell before anything after the call can run — an absent bootstrap is still ignored, since the default path is one most projects do not have (#1179)
- A report path that is a directory (`--report-junit build/` with the filename forgotten) fails fast with `is a directory, not a file` instead of passing validation, failing inside the writer with a raw bash message naming a bashunit source file, and exiting 0 with no report written. Affects every report flag and the `bench` equivalents (#1177)
- `bashunit init` no longer adds a dead `BASHUNIT_BOOTSTRAP` line to `.env` on every run: it commented out the existing setting and appended a fresh copy even when the value was unchanged, so a third run left three lines, two of them inert. It also now reports what it did to `.env`, the one file it wrote without saying so (#1175)
- A coverage run that tracked no executable line at all no longer reports it as `Coverage 0% is below minimum N%`: that 0% is arithmetic rather than a measurement, and the message sent the reader to their tests when the cause is almost always a `--coverage-paths` that matched nothing. The gate still fails, now naming the real problem (#1171)
Expand Down
19 changes: 18 additions & 1 deletion src/config/env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -948,7 +948,24 @@ function bashunit::env::cleanup_run_output_dir() {
# replaces this trap in main.sh and calls the cleanup explicitly instead; child
# subshells never inherit EXIT traps, so a parallel worker cannot remove the
# directory mid-run.
trap 'bashunit::env::cleanup_run_output_dir' EXIT
# Set while a bootstrap file is being sourced, cleared immediately after. A
# `source` that never returns -- special-builtin semantics on a syntax error or
# a bare `exit` -- leaves it set, which is the only trace such a failure leaves
# (#1179).
_BASHUNIT_LOADING_BOOTSTRAP=""

function bashunit::env::report_unfinished_bootstrap() {
if [ -n "${_BASHUNIT_LOADING_BOOTSTRAP:-}" ]; then
printf "%sError: the bootstrap file did not load: '%s'.%s\n" \
"${_BASHUNIT_COLOR_FAILED}" "$_BASHUNIT_LOADING_BOOTSTRAP" \
"${_BASHUNIT_COLOR_DEFAULT}" >&2
printf "%s\n" "It ended the shell before any test ran (a syntax error, or an 'exit')." >&2
_BASHUNIT_LOADING_BOOTSTRAP=""
exit 1
fi
}

trap 'bashunit::env::report_unfinished_bootstrap; bashunit::env::cleanup_run_output_dir' EXIT

if bashunit::env::is_dev_mode_enabled; then
bashunit::internal_log "info" "Dev log enabled" "file:$BASHUNIT_DEV_LOG"
Expand Down
20 changes: 17 additions & 3 deletions src/main/bench.sh
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,23 @@ function bashunit::main::cmd_bench() {
done
fi

# Optional bootstrap
# shellcheck disable=SC1090,SC2086
[ -f "${BASHUNIT_BOOTSTRAP:-}" ] && source "$BASHUNIT_BOOTSTRAP" ${BASHUNIT_BOOTSTRAP_ARGS:-}
# Optional bootstrap. Absent is fine -- BASHUNIT_BOOTSTRAP defaults to
# tests/bootstrap.sh, which most projects do not have. Present but broken is
# not: `[ -f ] && source` swallowed the failure, so a bootstrap with a syntax
# error left the run with no tests, no summary and exit 0, and CI passed
# having executed nothing (#1179).
if [ -f "${BASHUNIT_BOOTSTRAP:-}" ]; then
# `source` is a POSIX special builtin: a syntax error in the file, or a
# bare `exit`, terminates this shell right here -- so nothing after the
# call can report it, and the run ended with no tests, no summary and
# exit 0 (#1179). Pre-validating with `bash -n` would cost an interpreter
# fork on every invocation, which the cold-start budget forbids, so leave
# a marker instead and let the EXIT trap notice it was never cleared.
_BASHUNIT_LOADING_BOOTSTRAP="$BASHUNIT_BOOTSTRAP"
# shellcheck disable=SC1090,SC2086
source "$BASHUNIT_BOOTSTRAP" ${BASHUNIT_BOOTSTRAP_ARGS:-}
_BASHUNIT_LOADING_BOOTSTRAP=""
fi

set +euo pipefail

Expand Down
20 changes: 17 additions & 3 deletions src/main/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -655,9 +655,23 @@ function bashunit::main::cmd_test() {
fi
fi

# Optional bootstrap
# shellcheck disable=SC1090,SC2086
[ -f "${BASHUNIT_BOOTSTRAP:-}" ] && source "$BASHUNIT_BOOTSTRAP" ${BASHUNIT_BOOTSTRAP_ARGS:-}
# Optional bootstrap. Absent is fine -- BASHUNIT_BOOTSTRAP defaults to
# tests/bootstrap.sh, which most projects do not have. Present but broken is
# not: `[ -f ] && source` swallowed the failure, so a bootstrap with a syntax
# error left the run with no tests, no summary and exit 0, and CI passed
# having executed nothing (#1179).
if [ -f "${BASHUNIT_BOOTSTRAP:-}" ]; then
# `source` is a POSIX special builtin: a syntax error in the file, or a
# bare `exit`, terminates this shell right here -- so nothing after the
# call can report it, and the run ended with no tests, no summary and
# exit 0 (#1179). Pre-validating with `bash -n` would cost an interpreter
# fork on every invocation, which the cold-start budget forbids, so leave
# a marker instead and let the EXIT trap notice it was never cleared.
_BASHUNIT_LOADING_BOOTSTRAP="$BASHUNIT_BOOTSTRAP"
# shellcheck disable=SC1090,SC2086
source "$BASHUNIT_BOOTSTRAP" ${BASHUNIT_BOOTSTRAP_ARGS:-}
_BASHUNIT_LOADING_BOOTSTRAP=""
fi

if [ "${BASHUNIT_NO_OUTPUT:-false}" = true ]; then
exec >/dev/null 2>&1
Expand Down
48 changes: 48 additions & 0 deletions tests/acceptance/bashunit_init_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,51 @@ function test_bashunit_init_reports_the_env_file_it_writes() {

assert_file_contains "$TMP_DIR/init.log" ".env"
}

# A bootstrap that fails to load left the run with nothing: no tests, no
# summary, and exit 0 -- so CI passed having executed nothing. The file is
# optional (BASHUNIT_BOOTSTRAP defaults to tests/bootstrap.sh, which most
# projects do not have), but a file that exists and is broken is a different
# thing from one that is absent (#1179).
function test_a_bootstrap_with_a_syntax_error_fails_the_run() {
pushd "$TMP_DIR" >/dev/null
printf 'function broken( {\n' >boot.sh
printf 'function test_ok() { assert_same 1 1; }\n' >t_test.sh

local ec=0
local output
output=$(BASHUNIT_BOOTSTRAP=boot.sh "$BASHUNIT_PATH" --no-parallel t_test.sh 2>&1) || ec=$?
popd >/dev/null

assert_general_error "" "" "$ec"
assert_contains "bootstrap" "$output"
}

function test_a_bootstrap_that_exits_non_zero_says_so() {
pushd "$TMP_DIR" >/dev/null
printf 'exit 3\n' >boot.sh
printf 'function test_ok() { assert_same 1 1; }\n' >t_test.sh

local ec=0
local output
output=$(BASHUNIT_BOOTSTRAP=boot.sh "$BASHUNIT_PATH" --no-parallel t_test.sh 2>&1) || ec=$?
popd >/dev/null

assert_not_same 0 "$ec"
assert_contains "bootstrap" "$output"
}

# An absent bootstrap stays silent: the default path is one most projects do
# not have, and failing on it would break every one of them.
function test_an_absent_bootstrap_is_still_ignored() {
pushd "$TMP_DIR" >/dev/null
printf 'function test_ok() { assert_same 1 1; }\n' >t_test.sh

local ec=0
local output
output=$(BASHUNIT_BOOTSTRAP=tests/bootstrap.sh "$BASHUNIT_PATH" --no-parallel t_test.sh 2>&1) || ec=$?
popd >/dev/null

assert_same 0 "$ec"
assert_not_contains "bootstrap" "$output"
}
Loading