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 @@ -4,6 +4,7 @@

### Fixed
- Coverage propagates execution counts across multiline array literals, quoted strings, heredocs and backslash continuations, including when Bash 3.x records an array assignment on its closing `)`. Parent statement hits no longer mark commands inside command or process substitutions as covered, including within quotes or arrays (#1338)
- `--exclude-filter` treats a comma in one flag value as part of the test function name. Repeat the flag to exclude several names; the comma-separated `BASHUNIT_EXCLUDE_FILTER` cannot express a literal comma within one configured filter (#1340)

## [0.50.1](https://github.com/TypedDevs/bashunit/compare/0.50.0...0.50.1) - 2026-08-22

Expand Down
7 changes: 7 additions & 0 deletions docs/command-line.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,13 @@ Matching is identical to `--filter`, the flag is repeatable (a test is skipped
if it matches **any** value), and exclusion wins when a name matches both — the
same precedence `--exclude-tag` has over `--tag`.

Each flag value is one filter, so a comma in a function name remains literal.
Repeat the flag to exclude several names:

```bash
bashunit test tests/ --exclude-filter 'test_a,{b}' --exclude-filter test_c
```

Excluded tests are **not** reported as skipped: they are never selected, so they
do not appear in the header count either.

Expand Down
6 changes: 4 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,9 +389,11 @@ Similar as using `--order-by` option on the [command line](/command-line#order-b

## Exclude filter

> `BASHUNIT_EXCLUDE_FILTER=name`
> `BASHUNIT_EXCLUDE_FILTER=name[,name...]`

Skip tests whose name matches. Empty by default. It wins over a `--filter` match.
Skip tests whose name matches any comma-separated value. Empty by default. It
wins over a `--filter` match. Unlike the repeatable command-line option, this
setting cannot express a literal comma within one configured filter.

Similar as using `--exclude-filter` option on the [command line](/command-line#exclude-filter).

Expand Down
20 changes: 14 additions & 6 deletions src/helper/discovery.sh
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,10 @@ EOF
##
# Whether a function name matches any --exclude-filter value.
#
# The value is read from BASHUNIT_EXCLUDE_FILTER rather than passed in, so the
# header count (which reaches get_functions_to_run from a subshell) and the
# runner cannot end up applying different selections.
# Configured values come from comma-separated BASHUNIT_EXCLUDE_FILTER. Repeated
# CLI values live in a dynamically scoped array so each argument stays literal.
# Both are inherited by the header-count subshell, keeping it aligned with the
# runner without threading another argument through every call site.
#
# Locals are `__bu_`-prefixed (bash-style.md, PR #672). The only caller is
# get_functions_to_run, and this runs inside its `for fn in ...` loop, so plain
Expand All @@ -101,13 +102,21 @@ function bashunit::helper::name_matches_exclude_filter() {
local __bu_prefix=$1
local __bu_fn=$2

if [ -z "${BASHUNIT_EXCLUDE_FILTER:-}" ]; then
if [ -z "${BASHUNIT_EXCLUDE_FILTER:-}" ] &&
[ -z "${_BASHUNIT_CLI_EXCLUDE_FILTERS[*]:-}" ]; then
return 1
fi

local IFS=','
local __bu_excl
for __bu_excl in $BASHUNIT_EXCLUDE_FILTER; do
for __bu_excl in ${BASHUNIT_EXCLUDE_FILTER:-}; do
__bu_excl=${__bu_excl/test_/}
if [ -n "$__bu_excl" ]; then
case "$__bu_fn" in ${__bu_prefix}_*${__bu_excl}*) return 0 ;; esac
fi
done

for __bu_excl in ${_BASHUNIT_CLI_EXCLUDE_FILTERS[@]+"${_BASHUNIT_CLI_EXCLUDE_FILTERS[@]}"}; do
__bu_excl=${__bu_excl/test_/}
if [ -n "$__bu_excl" ]; then
case "$__bu_fn" in ${__bu_prefix}_*${__bu_excl}*) return 0 ;; esac
Expand Down Expand Up @@ -409,4 +418,3 @@ _BASHUNIT_TAGS_MAP_SCRIPT=""
_BASHUNIT_TAGS_MAP_FNS=()
_BASHUNIT_TAGS_MAP_TAGS=()
_BASHUNIT_TAGS_OUT=""

12 changes: 7 additions & 5 deletions src/main/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ function bashunit::main::cmd_test() {
local filter=""
local tag_filter=""
local exclude_tag_filter=""
local -a _BASHUNIT_CLI_EXCLUDE_FILTERS=()
local _bashunit_cli_exclude_filter_count=0
local IFS=$' \t\n'
local -a raw_args=()
local raw_args_count=0
Expand Down Expand Up @@ -147,11 +149,11 @@ function bashunit::main::cmd_test() {
shift
;;
--exclude-filter)
if [ -z "$BASHUNIT_EXCLUDE_FILTER" ]; then
BASHUNIT_EXCLUDE_FILTER="$2"
else
BASHUNIT_EXCLUDE_FILTER="$BASHUNIT_EXCLUDE_FILTER,$2"
fi
# One flag is one filter. Keep CLI values in an array so a comma in a
# function name stays literal; BASHUNIT_EXCLUDE_FILTER keeps its existing
# comma-separated configuration format (#1340).
_BASHUNIT_CLI_EXCLUDE_FILTERS[_bashunit_cli_exclude_filter_count]="$2"
_bashunit_cli_exclude_filter_count=$((_bashunit_cli_exclude_filter_count + 1))
# export -n like every other flag (#839): find_total_tests reads this
# from a plain subshell, which inherits it without exporting, and a real
# export would leak into nested ./bashunit runs.
Expand Down
15 changes: 15 additions & 0 deletions tests/acceptance/bashunit_exclude_filter_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,21 @@ function test_repeated_exclude_filters_are_or_ed() {
assert_same "$FIXTURE::test_user_list" "$output"
}

function test_a_comma_in_one_exclude_filter_stays_literal() {
local fixture
fixture="$(bashunit::temp_file comma_filter).sh"
cat >"$fixture" <<'TEST'
function test_a() { assert_same 1 1; }
function test_a,{b}() { assert_same 1 1; }
TEST

local output
output=$(./bashunit --list --filter test_a \
--exclude-filter 'test_a,{b}' "$fixture" 2>/dev/null)

assert_same "$fixture::test_a" "$output"
}

function test_exclude_filter_wins_when_a_name_matches_both() {
local output
output=$(./bashunit --list --filter admin --exclude-filter admin "$FIXTURE" 2>/dev/null)
Expand Down
Loading