Skip to content

Commit 4c30149

Browse files
authored
Merge branch 'main' into codex/add-assert_match_snapshot_ignore_colors
2 parents 2dfdd95 + 5acce04 commit 4c30149

10 files changed

Lines changed: 155 additions & 51 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Unreleased
44

55
- Fix asserts on test doubles in subshell
6+
- Allow interpolating arguments in data providers output
67
- Added `assert_match_snapshot_ignore_colors`
78

89
## [0.19.1](https://github.com/TypedDevs/bashunit/compare/0.19.0...0.19.1) - 2025-05-23

docs/command-line.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@ Creates a report XML file that follows the JUnit XML format and contains informa
129129
130130
bashunit provides an option to run each test in a separate child process, allowing you to parallelize the test execution and potentially speed up the testing process. When running in parallel mode, the execution order of tests is randomized.
131131

132+
::: warning
133+
Parallel mode is currently only supported on **macOS** and **Ubuntu**. On other
134+
systems (like Alpine Linux or Windows) the option is automatically disabled due
135+
to inconsistent results. In those environments consider using `--no-parallel`.
136+
:::
137+
132138
::: code-group
133139
```bash [Example]
134140
./bashunit ./tests --parallel

docs/configuration.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ BASHUNIT_SIMPLE_OUTPUT=false
6868
6969
Runs the tests in child processes with randomized execution, which may improve overall testing speed, especially for larger test suites.
7070

71+
::: warning
72+
Parallel execution is supported only on **macOS** and **Ubuntu**. On other
73+
systems bashunit forces sequential execution to avoid inconsistent results.
74+
:::
75+
7176
Similar as using `-p|--parallel` option on the [command line](/command-line#parallel).
7277

7378

docs/data-providers.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,31 @@ function test_my_test_case() {
2424

2525
The provider function can echo a single value like `"one"` or multiple values `"one" "two" "three"`.
2626

27+
## Interpolating arguments in test names
28+
29+
You can reference the values provided by a data provider directly in the test
30+
function name using placeholders like `::1::`, `::2::`, ... matching the
31+
argument position.
32+
33+
::: code-group
34+
```bash [example_test.sh]
35+
# data_provider fizz_numbers
36+
function test_returns_fizz_when_multiple_of_::1::_like_::2::_given() {
37+
# ...
38+
}
39+
40+
function fizz_numbers() {
41+
echo 3 4
42+
echo 3 6
43+
}
44+
```
45+
```[Output]
46+
Running example_test.sh
47+
✓ Passed: Returns fizz when multiple of '3' like '4' given
48+
✓ Passed: Returns fizz when multiple of '3' like '6' given
49+
```
50+
:::
51+
2752
## Multiple args in one call
2853

2954
::: code-group

docs/test-doubles.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,22 @@ EOF
3737
assert_same "13525 pts/7 00:00:01 bash" "$(code)"
3838
}
3939
```
40+
41+
:::
42+
Mocked functions are also available inside subshells:
43+
44+
::: code-group
45+
```bash [Example]
46+
function test_example() {
47+
mock date echo "2024-05-01"
48+
49+
function run() {
50+
date
51+
}
52+
53+
assert_same "2024-05-01" "$(run)"
54+
}
55+
```
4056
:::
4157

4258
## spy

src/helpers.sh

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,20 @@ declare -r BASHUNIT_GIT_REPO="https://github.com/TypedDevs/bashunit"
88
# @return string Eg: "Some logic camelCase"
99
#
1010
function helper::normalize_test_function_name() {
11-
local original_function_name="${1-}"
11+
local original_fn_name="${1-}"
12+
local interpolated_fn_name="${2-}"
13+
14+
if [[ -n "${interpolated_fn_name-}" ]]; then
15+
original_fn_name="$interpolated_fn_name"
16+
fi
17+
1218
local result
1319

1420
# Remove the first "test_" prefix, if present
15-
result="${original_function_name#test_}"
21+
result="${original_fn_name#test_}"
1622
# If no "test_" was removed (e.g., "testFoo"), remove the "test" prefix
17-
if [[ "$result" == "$original_function_name" ]]; then
18-
result="${original_function_name#test}"
23+
if [[ "$result" == "$original_fn_name" ]]; then
24+
result="${original_fn_name#test}"
1925
fi
2026
# Replace underscores with spaces
2127
result="${result//_/ }"
@@ -25,6 +31,29 @@ function helper::normalize_test_function_name() {
2531
echo "$result"
2632
}
2733

34+
function helper::escape_single_quotes() {
35+
local value="$1"
36+
# shellcheck disable=SC1003
37+
echo "${value//\'/'\'\\''\'}"
38+
}
39+
40+
function helper::interpolate_function_name() {
41+
local function_name="$1"
42+
shift
43+
local args=("$@")
44+
local result="$function_name"
45+
46+
for ((i=0; i<${#args[@]}; i++)); do
47+
local placeholder="::$((i+1))::"
48+
# shellcheck disable=SC2155
49+
local value="$(helper::escape_single_quotes "${args[$i]}")"
50+
value="'$value'"
51+
result="${result//${placeholder}/${value}}"
52+
done
53+
54+
echo "$result"
55+
}
56+
2857
function helper::check_duplicate_functions() {
2958
local script="$1"
3059

src/runner.sh

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,11 @@ function runner::spinner() {
5050

5151
function runner::functions_for_script() {
5252
local script="$1"
53-
local all_function_names="$2"
53+
local all_fn_names="$2"
5454

5555
# Filter the names down to the ones defined in the script, sort them by line number
5656
shopt -s extdebug
57-
for f in $all_function_names; do
57+
for f in $all_fn_names; do
5858
declare -F "$f" | grep "$script"
5959
done | sort -k2 -n | awk '{print $1}'
6060
shopt -u extdebug
@@ -65,8 +65,8 @@ function runner::call_test_functions() {
6565
local filter="$2"
6666
local prefix="test"
6767
# Use declare -F to list all function names
68-
local all_function_names=$(declare -F | awk '{print $3}')
69-
local filtered_functions=$(helper::get_functions_to_run "$prefix" "$filter" "$all_function_names")
68+
local all_fn_names=$(declare -F | awk '{print $3}')
69+
local filtered_functions=$(helper::get_functions_to_run "$prefix" "$filter" "$all_fn_names")
7070
# shellcheck disable=SC2207
7171
local functions_to_run=($(runner::functions_for_script "$script" "$filtered_functions"))
7272

@@ -77,33 +77,33 @@ function runner::call_test_functions() {
7777
runner::render_running_file_header
7878
helper::check_duplicate_functions "$script" || true
7979

80-
for function_name in "${functions_to_run[@]}"; do
80+
for fn_name in "${functions_to_run[@]}"; do
8181
if parallel::is_enabled && parallel::must_stop_on_failure; then
8282
break
8383
fi
8484

8585
local provider_data=()
8686
while IFS=" " read -r line; do
8787
provider_data+=("$line")
88-
done <<< "$(helper::get_provider_data "$function_name" "$script")"
88+
done <<< "$(helper::get_provider_data "$fn_name" "$script")"
8989

9090
# No data provider found
9191
if [[ "${#provider_data[@]}" -eq 0 ]]; then
92-
runner::run_test "$script" "$function_name"
93-
unset function_name
92+
runner::run_test "$script" "$fn_name"
93+
unset fn_name
9494
continue
9595
fi
9696

9797
# Execute the test function for each line of data
9898
for data in "${provider_data[@]}"; do
9999
IFS=" " read -r -a args <<< "$data"
100100
if [ "${#args[@]}" -gt 1 ]; then
101-
runner::run_test "$script" "$function_name" "${args[@]}"
101+
runner::run_test "$script" "$fn_name" "${args[@]}"
102102
else
103-
runner::run_test "$script" "$function_name" "$data"
103+
runner::run_test "$script" "$fn_name" "$data"
104104
fi
105105
done
106-
unset function_name
106+
unset fn_name
107107
done
108108

109109
if ! env::is_simple_output_enabled; then
@@ -133,8 +133,10 @@ function runner::run_test() {
133133

134134
local test_file="$1"
135135
shift
136-
local function_name="$1"
136+
local fn_name="$1"
137137
shift
138+
139+
local interpolated_fn_name="$(helper::interpolate_function_name "$fn_name" "$@")"
138140
local current_assertions_failed="$(state::get_assertions_failed)"
139141
local current_assertions_snapshot="$(state::get_assertions_snapshot)"
140142
local current_assertions_incomplete="$(state::get_assertions_incomplete)"
@@ -157,7 +159,7 @@ function runner::run_test() {
157159
158160
# 2>&1: Redirects the std-error (FD 2) to the std-output (FD 1).
159161
# points to the original std-output.
160-
"$function_name" "$@" 2>&1
162+
"$fn_name" "$@" 2>&1
161163
162164
)
163165

@@ -175,7 +177,7 @@ function runner::run_test() {
175177

176178
printf '%*s\n' "$TERMINAL_WIDTH" '' | tr ' ' '='
177179
printf "%s\n" "File: $test_file"
178-
printf "%s\n" "Function: $function_name"
180+
printf "%s\n" "Function: $fn_name"
179181
printf "%s\n" "Duration: $duration ms"
180182
local raw_text=${test_execution_result%%##ASSERTIONS_*}
181183
[[ -n $raw_text ]] && printf "%s" "Raw text: ${test_execution_result%%##ASSERTIONS_*}"
@@ -216,22 +218,22 @@ function runner::run_test() {
216218
fi
217219
done
218220

219-
runner::parse_result "$function_name" "$test_execution_result" "$@"
221+
runner::parse_result "$fn_name" "$test_execution_result" "$@"
220222

221223
local total_assertions="$(state::calculate_total_assertions "$test_execution_result")"
222224
local test_exit_code="$(state::get_test_exit_code)"
223225

224226
if [[ -n $runtime_error || $test_exit_code -ne 0 ]]; then
225227
state::add_tests_failed
226-
console_results::print_error_test "$function_name" "$runtime_error"
227-
reports::add_test_failed "$test_file" "$function_name" "$duration" "$total_assertions"
228+
console_results::print_error_test "$fn_name" "$runtime_error"
229+
reports::add_test_failed "$test_file" "$fn_name" "$duration" "$total_assertions"
228230
runner::write_failure_result_output "$test_file" "$runtime_error"
229231
return
230232
fi
231233

232234
if [[ "$current_assertions_failed" != "$(state::get_assertions_failed)" ]]; then
233235
state::add_tests_failed
234-
reports::add_test_failed "$test_file" "$function_name" "$duration" "$total_assertions"
236+
reports::add_test_failed "$test_file" "$fn_name" "$duration" "$total_assertions"
235237
runner::write_failure_result_output "$test_file" "$subshell_output"
236238

237239
if env::is_stop_on_failure_enabled; then
@@ -246,28 +248,32 @@ function runner::run_test() {
246248

247249
if [[ "$current_assertions_snapshot" != "$(state::get_assertions_snapshot)" ]]; then
248250
state::add_tests_snapshot
249-
console_results::print_snapshot_test "$function_name"
250-
reports::add_test_snapshot "$test_file" "$function_name" "$duration" "$total_assertions"
251+
console_results::print_snapshot_test "$fn_name"
252+
reports::add_test_snapshot "$test_file" "$fn_name" "$duration" "$total_assertions"
251253
return
252254
fi
253255

254256
if [[ "$current_assertions_incomplete" != "$(state::get_assertions_incomplete)" ]]; then
255257
state::add_tests_incomplete
256-
reports::add_test_incomplete "$test_file" "$function_name" "$duration" "$total_assertions"
258+
reports::add_test_incomplete "$test_file" "$fn_name" "$duration" "$total_assertions"
257259
return
258260
fi
259261

260262
if [[ "$current_assertions_skipped" != "$(state::get_assertions_skipped)" ]]; then
261263
state::add_tests_skipped
262-
reports::add_test_skipped "$test_file" "$function_name" "$duration" "$total_assertions"
264+
reports::add_test_skipped "$test_file" "$fn_name" "$duration" "$total_assertions"
263265
return
264266
fi
265267

266-
local label="$(helper::normalize_test_function_name "$function_name")"
268+
local label="$(helper::normalize_test_function_name "$fn_name" "$interpolated_fn_name")"
267269

268-
console_results::print_successful_test "${label}" "$duration" "$@"
270+
if [[ "$fn_name" == "$interpolated_fn_name" ]]; then
271+
console_results::print_successful_test "${label}" "$duration" "$@"
272+
else
273+
console_results::print_successful_test "${label}" "$duration"
274+
fi
269275
state::add_tests_passed
270-
reports::add_test_passed "$test_file" "$function_name" "$duration" "$total_assertions"
276+
reports::add_test_passed "$test_file" "$fn_name" "$duration" "$total_assertions"
271277
}
272278

273279
function runner::decode_subshell_output() {
@@ -285,21 +291,21 @@ function runner::decode_subshell_output() {
285291
}
286292

287293
function runner::parse_result() {
288-
local function_name=$1
294+
local fn_name=$1
289295
shift
290296
local execution_result=$1
291297
shift
292298
local args=("$@")
293299

294300
if parallel::is_enabled; then
295-
runner::parse_result_parallel "$function_name" "$execution_result" "${args[@]}"
301+
runner::parse_result_parallel "$fn_name" "$execution_result" "${args[@]}"
296302
else
297-
runner::parse_result_sync "$function_name" "$execution_result"
303+
runner::parse_result_sync "$fn_name" "$execution_result"
298304
fi
299305
}
300306

301307
function runner::parse_result_parallel() {
302-
local function_name=$1
308+
local fn_name=$1
303309
shift
304310
local execution_result=$1
305311
shift
@@ -310,9 +316,9 @@ function runner::parse_result_parallel() {
310316

311317
local test_result_file=$(echo "${args[@]}" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-|-$//')
312318
if [[ -z "$test_result_file" ]]; then
313-
test_result_file="${function_name}.$$.result"
319+
test_result_file="${fn_name}.$$.result"
314320
else
315-
test_result_file="${function_name}-${test_result_file}.$$.result"
321+
test_result_file="${fn_name}-${test_result_file}.$$.result"
316322
fi
317323

318324
local unique_test_result_file="${test_suite_dir}/${test_result_file}"
@@ -323,15 +329,15 @@ function runner::parse_result_parallel() {
323329
count=$((count + 1))
324330
done
325331

326-
log "debug" "[PARA]" "function_name:$function_name" "execution_result:$execution_result"
332+
log "debug" "[PARA]" "fn_name:$fn_name" "execution_result:$execution_result"
327333

328-
runner::parse_result_sync "$function_name" "$execution_result"
334+
runner::parse_result_sync "$fn_name" "$execution_result"
329335

330336
echo "$execution_result" > "$unique_test_result_file"
331337
}
332338

333339
function runner::parse_result_sync() {
334-
local function_name=$1
340+
local fn_name=$1
335341
local execution_result=$2
336342

337343
local assertions_failed=$(\
@@ -370,7 +376,7 @@ function runner::parse_result_sync() {
370376
sed -E -e 's/.*##TEST_EXIT_CODE=([0-9]*)##.*/\1/g'\
371377
)
372378

373-
log "debug" "[SYNC]" "function_name:$function_name" "execution_result:$execution_result"
379+
log "debug" "[SYNC]" "fn_name:$fn_name" "execution_result:$execution_result"
374380

375381
((_ASSERTIONS_PASSED += assertions_passed)) || true
376382
((_ASSERTIONS_FAILED += assertions_failed)) || true

tests/functional/doubles_test.sh

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,6 @@ function test_mock_ps_when_executing_a_sourced_function() {
1313
assert_match_snapshot "$(top_mem)"
1414
}
1515

16-
function test_spy_commands_called_once_when_executing_a_script() {
17-
spy ps
18-
spy awk
19-
spy head
20-
21-
bash ./tests/functional/fixtures/doubles_script.sh
22-
23-
assert_have_been_called_times 1 ps
24-
assert_have_been_called_times 1 awk
25-
assert_have_been_called_times 1 head
26-
}
27-
2816
function test_spy_commands_called_once_when_executing_a_sourced_function() {
2917

3018
source ./tests/functional/fixtures/doubles_function.sh

0 commit comments

Comments
 (0)