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 @@ -8,6 +8,7 @@
- Per-line execution hit counts in the text report: `BASHUNIT_COVERAGE_SHOW_LINE_HITS=true` prints a `Line Hits` block listing each covered line as `<lineno>:<count>` per file. The LCOV report already carried the same counts in its `DA:<line>,<count>` records; those are now pinned by tests (#856)

### Changed
- Deprecated forms now warn at runtime instead of only being marked deprecated in a help string. When an unprefixed setting (`VERBOSE`, `COVERAGE`, …) is what supplied a value, or `bashunit test --assert` is used, bashunit prints `Deprecated: … Use … instead.` on **stderr**, so it never corrupts a report on stdout. Silence with `BASHUNIT_NO_DEPRECATION_WARNINGS=true`. The policy is now documented: a deprecated form warns for at least one minor release before being removed in a major one (#866)
- Docs: [Command line](https://bashunit.com/command-line#invalid-input) now documents how bashunit rejects invalid input — unknown options, out-of-range values and unusable bootstrap/report paths — which had been shipped across several releases without a page describing it. `BASHUNIT_REPORT_TAP` and `BASHUNIT_REPORT_JSON` are documented in [Configuration](https://bashunit.com/configuration) alongside the JUnit/HTML/GHA reports they sit next to, and [Standalone](https://bashunit.com/standalone) documents the malformed-invocation exit code
- Docs: `snapshots.md` omitted the optional `["snapshot_file"]` argument that both snapshot assertions accept; the README claimed "hundreds of assertions" against an actual 71
- `bashunit doc` and the [Assertions](https://bashunit.com/assertions) reference now cover all 71 assertions instead of 64. The two snapshot assertions and all five spy assertions were documented only on their own pages, so `bashunit doc` — the command the docs tell agents to use so they do not invent names — listed no spy assertion at all
Expand Down
32 changes: 32 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,38 @@ BASHUNIT_COVERAGE_THRESHOLD_HIGH=90
```
:::

## Deprecations

Every setting also answers to an **unprefixed** name — `VERBOSE` as well as
`BASHUNIT_VERBOSE`. Those unprefixed aliases predate the `BASHUNIT_` prefix
introduced in 0.15.0 and are deprecated: the names are generic enough that an
unrelated tool exporting `COVERAGE=true` or `VERBOSE=true` silently
reconfigures bashunit. Always use the prefixed name.

When a deprecated form is what supplied a value, bashunit says so on stderr:

```
Deprecated: the unprefixed `VERBOSE`. Use `BASHUNIT_VERBOSE` instead.
```

The warning goes to stderr, so it never corrupts a report on stdout. Silence it
with:

> `BASHUNIT_NO_DEPRECATION_WARNINGS=true`

::: tip Deprecation policy
A form that still works but is on its way out warns for at least one minor
release before it is removed in a major one. If you see one of these warnings,
you have time to migrate — but the form will not be there forever.
:::

Currently deprecated:

| Deprecated | Use instead |
|------------|-------------|
| Unprefixed settings (`VERBOSE`, `COVERAGE`, …) | The `BASHUNIT_`-prefixed name |
| `bashunit test --assert <fn>` | [`bashunit assert <fn>`](/standalone) |

## Related

- [Command line](/command-line) — flags that mirror these settings
Expand Down
57 changes: 57 additions & 0 deletions src/env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,24 @@ function bashunit::env::load_config_file() {
done <"$file"
}

##
# Reports a deprecated form once per run, on stderr so it cannot corrupt a
# report on stdout. Silence with BASHUNIT_NO_DEPRECATION_WARNINGS=true.
#
# The deprecation policy: a form that still works but is on its way out warns
# here for at least one minor release before it is removed in a major one. That
# gives the warning somewhere to live instead of the deprecation sitting
# indefinitely in a help string nobody reads.
#
# Arguments: $1 the deprecated form, $2 what to use instead
##
function bashunit::env::warn_deprecated() {
[ "${BASHUNIT_NO_DEPRECATION_WARNINGS:-false}" = "true" ] && return 0

printf "%sDeprecated: %s. Use %s instead.%s\n" \
"${_BASHUNIT_COLOR_SKIPPED:-}" "$1" "$2" "${_BASHUNIT_COLOR_DEFAULT:-}" >&2
}

##
# Echoes $1 when it is a positive integer, otherwise echoes the default $2.
# Arguments: $1 candidate value, $2 fallback default
Expand Down Expand Up @@ -99,6 +117,45 @@ if [ "${BASHUNIT_SKIP_ENV_FILE:-false}" != "true" ]; then
fi
fi

# Settings that still answer to an unprefixed name (`VERBOSE`, `COVERAGE`, …).
# The prefix landed in 0.15.0; these remain only for back-compat and are an
# active hazard, because the names are generic enough that an unrelated tool
# exporting COVERAGE=true silently reconfigures bashunit (#866).
#
# Kept in sync with the `: "${BASHUNIT_X:=${X:=…}}"` lines below by
# tests/unit/env_deprecated_aliases_test.sh, which fails if the two drift.
_BASHUNIT_DEPRECATED_ALIASES="DEFAULT_PATH DEV_LOG BOOTSTRAP BOOTSTRAP_ARGS
LOG_JUNIT LOG_GHA REPORT_HTML REPORT_TAP REPORT_JSON WATCH_INTERVAL COVERAGE
COVERAGE_PATHS COVERAGE_EXCLUDE COVERAGE_REPORT COVERAGE_REPORT_HTML
COVERAGE_MIN COVERAGE_THRESHOLD_LOW COVERAGE_THRESHOLD_HIGH PARALLEL_RUN
SHOW_HEADER HEADER_ASCII_ART SIMPLE_OUTPUT STOP_ON_FAILURE SHOW_EXECUTION_TIME
VERBOSE BENCH_MODE NO_OUTPUT INTERNAL_LOG SHOW_SKIPPED SHOW_INCOMPLETE
STRICT_MODE STOP_ON_ASSERTION_FAILURE SKIP_ENV_FILE LOGIN_SHELL FAILURES_ONLY
SHOW_OUTPUT_ON_FAILURE NO_DIFF NO_PROGRESS OUTPUT_FORMAT FAIL_ON_RISKY PROFILE
PROFILE_COUNT TEST_TIMEOUT"

# Record which unprefixed names are about to supply a value. Runs BEFORE the
# `:=` block below, because after it the prefixed name is set either way and the
# two cases are indistinguishable. Pure bash: a `compgen -v` capture would cost
# a fork on the cold-start path, which is budgeted (#801).
_bashunit_deprecated_in_use=""
for _bashunit_alias in $_BASHUNIT_DEPRECATED_ALIASES; do
eval "_bashunit_alias_prefixed=\${BASHUNIT_$_bashunit_alias+set}"
[ -n "$_bashunit_alias_prefixed" ] && continue
eval "_bashunit_alias_value=\${$_bashunit_alias:-}"
[ -n "$_bashunit_alias_value" ] &&
_bashunit_deprecated_in_use="$_bashunit_deprecated_in_use $_bashunit_alias"
done
unset _bashunit_alias _bashunit_alias_prefixed _bashunit_alias_value

if [ -n "$_bashunit_deprecated_in_use" ]; then
for _bashunit_alias in $_bashunit_deprecated_in_use; do
bashunit::env::warn_deprecated \
"the unprefixed \`$_bashunit_alias\`" "\`BASHUNIT_$_bashunit_alias\`"
done
unset _bashunit_alias
fi

_BASHUNIT_DEFAULT_DEFAULT_PATH="tests"
_BASHUNIT_DEFAULT_BOOTSTRAP="tests/bootstrap.sh"
_BASHUNIT_DEFAULT_DEV_LOG=""
Expand Down
9 changes: 9 additions & 0 deletions src/main.sh
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,15 @@ function bashunit::main::cmd_test() {
while [ $# -gt 0 ]; do
case "$1" in
-a | --assert)
# Superseded by the `bashunit assert` subcommand. The help text has said
# "deprecated" since that subcommand landed without ever warning at
# runtime, which is how a deprecation stays put forever.
#
# Not a pure rename: this path goes through cmd_test's parser, so it also
# accepts test-level flags (`--env`, `--no-parallel`). `bashunit assert`
# forwards everything to the assertion instead. Removing this form needs
# that gap closed first.
bashunit::env::warn_deprecated "\`bashunit test --assert\`" "\`bashunit assert\`"
assert_fn="$2"
shift
;;
Expand Down
19 changes: 18 additions & 1 deletion tests/acceptance/bashunit_assert_errors_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ function test_bashunit_assert_subcommand_failure() {
assert_general_error "" "" "$exit_code"
}

# Test backward compatibility with --assert option
# Backward compatibility with the deprecated --assert option. These are the only
# tests that should still use it: everything else exercises `bashunit assert`.
# The form keeps working and warns; it is removed no earlier than the next major.
function test_bashunit_old_assert_option_still_works() {
local output
output=$(./bashunit -a equals "foo" "foo" 2>&1)
Expand All @@ -69,6 +71,21 @@ function test_bashunit_old_assert_option_long_form() {
assert_successful_code "$output"
}

function test_bashunit_old_assert_option_warns_that_it_is_deprecated() {
local warnings
warnings=$(./bashunit -a equals "foo" "foo" 2>&1 >/dev/null)

assert_contains "Deprecated" "$warnings"
assert_contains "bashunit assert" "$warnings"
}

function test_bashunit_new_assert_subcommand_does_not_warn() {
local warnings
warnings=$(./bashunit assert equals "foo" "foo" 2>&1 >/dev/null)

assert_empty "$warnings"
}

# Test deprecation notice in help
function test_bashunit_test_help_shows_deprecation() {
local output
Expand Down
4 changes: 4 additions & 0 deletions tests/acceptance/bashunit_direct_fn_call_advanced_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ function set_up_before_script() {

function set_up() {
export BASHUNIT_SIMPLE_OUTPUT=false
# These exercise the deprecated `-a` form itself, so silence its warning to
# keep the captured output stable. The warning is covered by
# bashunit_assert_errors_test.sh instead.
export BASHUNIT_NO_DEPRECATION_WARNINGS=true
}

function test_bashunit_direct_fn_call_failure() {
Expand Down
4 changes: 4 additions & 0 deletions tests/acceptance/bashunit_direct_fn_call_basic_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ find me with \n a regular expression"

function set_up() {
export BASHUNIT_SIMPLE_OUTPUT=false
# These exercise the deprecated `-a` form itself, so silence its warning to
# keep the captured output stable. The warning is covered by
# bashunit_assert_errors_test.sh instead.
export BASHUNIT_NO_DEPRECATION_WARNINGS=true
}

function test_bashunit_direct_fn_call_passes() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
✗ Failed: assert same
Expected 'foo'
but got  'bar'
at tests/acceptance/bashunit_direct_fn_call_advanced_test.sh:12
at tests/acceptance/bashunit_direct_fn_call_advanced_test.sh:16
61 changes: 61 additions & 0 deletions tests/unit/env_deprecated_aliases_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env bash

# _BASHUNIT_DEPRECATED_ALIASES is hand-maintained: deriving it at runtime would
# need a `compgen -v` capture, and that costs a fork on the cold-start path,
# which is budgeted (#801). These tests are what keeps a hand-maintained list
# honest -- they fail if a setting gains or loses its unprefixed alias without
# the list being updated.

# Every `: "${BASHUNIT_X:=${X:=...}}"` line in env.sh declares an unprefixed
# alias for X. That is the source of truth.
function bashunit::test::aliases_declared_in_env_sh() {
grep -oE '^: "\$\{BASHUNIT_[A-Z0-9_]+:=\$\{[A-Z0-9_]+:=' src/env.sh |
sed -E 's/^: "\$\{BASHUNIT_[A-Z0-9_]+:=\$\{([A-Z0-9_]+):=$/\1/' |
sort
}

function bashunit::test::aliases_listed_in_the_constant() {
# Unquoted on purpose: the constant is a whitespace-separated list and the
# word splitting is what turns it into one name per line.
# shellcheck disable=SC2086
printf '%s\n' $_BASHUNIT_DEPRECATED_ALIASES | sort
}

function test_the_alias_list_matches_the_declarations_in_env_sh() {
local declared listed
declared=$(bashunit::test::aliases_declared_in_env_sh)
listed=$(bashunit::test::aliases_listed_in_the_constant)

assert_same "$declared" "$listed"
}

function test_the_alias_list_is_not_empty() {
assert_not_empty "$_BASHUNIT_DEPRECATED_ALIASES"
}

# The warning must name the offending variable and point at the replacement, so
# the message is actionable without consulting the docs.
function test_warning_names_the_variable_and_its_replacement() {
local output
output=$(bashunit::env::warn_deprecated "the unprefixed \`VERBOSE\`" "\`BASHUNIT_VERBOSE\`" 2>&1)

assert_contains "VERBOSE" "$output"
assert_contains "BASHUNIT_VERBOSE" "$output"
assert_contains "Deprecated" "$output"
}

function test_warnings_can_be_silenced() {
local output
output=$(BASHUNIT_NO_DEPRECATION_WARNINGS=true \
bashunit::env::warn_deprecated "anything" "anything else" 2>&1)

assert_empty "$output"
}

# stdout carries reports (TAP, JSON); a warning there would corrupt them.
function test_warning_goes_to_stderr_not_stdout() {
local on_stdout
on_stdout=$(bashunit::env::warn_deprecated "the unprefixed \`VERBOSE\`" "\`BASHUNIT_VERBOSE\`" 2>/dev/null)

assert_empty "$on_stdout"
}
Loading