From 90924aaebe6440cb83523895e35905018d666bc7 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sat, 18 Apr 2026 14:29:39 +0200 Subject: [PATCH 1/7] docs(cli): document 0.34 features (watch, tags, TAP output) --- docs/command-line.md | 115 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 107 insertions(+), 8 deletions(-) diff --git a/docs/command-line.md b/docs/command-line.md index d2c3d73c..79510c09 100644 --- a/docs/command-line.md +++ b/docs/command-line.md @@ -7,6 +7,8 @@ ```bash bashunit test [path] [options] # Run tests (default) bashunit bench [path] [options] # Run benchmarks +bashunit watch [path] [options] # Watch files, re-run tests on change +bashunit assert # Run standalone assertion bashunit doc [filter] # Show assertion documentation bashunit init [dir] # Initialize test directory bashunit learn # Interactive tutorial @@ -55,6 +57,10 @@ bashunit test tests/ --parallel --simple | `-a, --assert ` | Run a standalone assert function | | `-e, --env, --boot ` | Load custom env/bootstrap file (supports args) | | `-f, --filter ` | Only run tests matching name | +| `--tag ` | Only run tests with matching `@tag` (repeatable) | +| `--exclude-tag ` | Skip tests with matching `@tag` (repeatable) | +| `--output ` | Output format (`tap` for TAP version 13) | +| `-w, --watch` | Watch files and re-run tests on change | | `--log-junit ` | Write JUnit XML report | | `-j, --jobs ` | Run tests in parallel with max N concurrent jobs | | `-p, --parallel` | Run tests in parallel | @@ -114,6 +120,66 @@ bashunit test tests/ --filter "user_login" ``` ::: +### Tags + +> `bashunit test --tag ` +> `bashunit test --exclude-tag ` + +Filter tests by `# @tag` annotations. Both flags are repeatable. `--tag` uses OR +logic across names; `--exclude-tag` wins when a test matches both. + +::: code-group +```bash [Annotate tests] +# @tag slow +function test_heavy_computation() { + ... +} + +# @tag integration +function test_api_call() { + ... +} +``` +```bash [Run by tag] +bashunit test tests/ --tag slow +bashunit test tests/ --tag slow --tag integration +bashunit test tests/ --exclude-tag integration +``` +::: + +### Output format + +> `bashunit test --output ` + +Select an alternative output format. Currently supported: + +- `tap` — [TAP version 13](https://testanything.org/tap-version-13-specification.html) for CI/CD integrations. + +::: code-group +```bash [Example] +bashunit test tests/ --output tap +``` +```[Output] +TAP version 13 +1..2 +ok 1 - Should validate input +not ok 2 - Should handle errors +``` +::: + +### Watch mode + +> `bashunit test -w|--watch` +> `bashunit watch [path]` + +Watch `.sh` files for changes and automatically re-run tests. Available as a +flag on `test` or as a dedicated [`watch`](#watch) subcommand. + +::: warning Requirements +- **Linux:** `inotifywait` (`sudo apt install inotify-tools`) +- **macOS:** `fswatch` (`brew install fswatch`) +::: + ### Environment / Bootstrap > `bashunit test -e|--env|--boot ` @@ -464,6 +530,36 @@ bashunit bench --filter "parse" | `--skip-env-file` | Skip `.env` loading, use shell environment only | | `-l, --login` | Run in login shell context | +## watch + +> `bashunit watch [path] [test-options]` + +Watch `.sh` files for changes and automatically re-run tests. Any option +accepted by `bashunit test` is also accepted here. + +::: code-group +```bash [Examples] +# Watch current directory +bashunit watch + +# Watch the tests/ directory +bashunit watch tests/ + +# Watch and filter by name +bashunit watch tests/ --filter user + +# Watch with simple output +bashunit watch tests/ --simple +``` +::: + +::: warning Requirements +- **Linux:** `inotifywait` (`sudo apt install inotify-tools`) +- **macOS:** `fswatch` (`brew install fswatch`) + +If the required tool is not installed, bashunit prints a clear installation hint. +::: + ## doc > `bashunit doc [filter]` @@ -604,16 +700,18 @@ bashunit --help Usage: bashunit [arguments] [options] Commands: - test [path] Run tests (default command) - bench [path] Run benchmarks - doc [filter] Display assertion documentation - init [dir] Initialize a new test directory - learn Start interactive tutorial - upgrade Upgrade bashunit to latest version + test [path] Run tests (default command) + bench [path] Run benchmarks + assert Run standalone assertion + doc [filter] Display assertion documentation + init [dir] Initialize a new test directory + learn Start interactive tutorial + watch [path] Watch files and re-run tests on change + upgrade Upgrade bashunit to latest version Global Options: - -h, --help Show this help message - -v, --version Display the current version + -h, --help Show this help message + -v, --version Display the current version Run 'bashunit --help' for command-specific options. ``` @@ -624,6 +722,7 @@ Each subcommand also supports `--help`: ```bash bashunit test --help bashunit bench --help +bashunit watch --help bashunit doc --help ``` From b3a6fb9de5fe6e0bc59e3d3e33f7541cfb30037a Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sat, 18 Apr 2026 14:29:50 +0200 Subject: [PATCH 2/7] docs(cli): update stale version refs to 0.34.1 --- docs/command-line.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/command-line.md b/docs/command-line.md index 79510c09..e2710fb5 100644 --- a/docs/command-line.md +++ b/docs/command-line.md @@ -392,7 +392,7 @@ This is useful for: bashunit test tests/ --no-progress ``` ```[Output] -bashunit - 0.32.0 | Tests: 10 +bashunit - 0.34.1 | Tests: 10 Tests: 10 passed, 10 total Assertions: 25 passed, 25 total @@ -663,7 +663,7 @@ bashunit upgrade ``` ```[Output] > Upgrading bashunit to latest version -> bashunit upgraded successfully to latest version 0.28.0 +> bashunit upgraded successfully to latest version 0.34.1 ``` ::: From 82531db81dba245227e9806ee6007bfbe8551b70 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sat, 18 Apr 2026 14:30:06 +0200 Subject: [PATCH 3/7] docs(globals): add missing helpers and cross-links --- docs/globals.md | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/docs/globals.md b/docs/globals.md index d410d0d2..76fca3ff 100644 --- a/docs/globals.md +++ b/docs/globals.md @@ -32,6 +32,12 @@ Internal messages from bashunit include the `[INTERNAL]` prefix so you can easil > `bashunit::caller_filename`: Gets the caller filename. +## bashunit::caller_line + +> `bashunit::caller_line`: Gets the line number of the caller. + +Useful inside custom assertions to report the line that triggered the failure. + ## bashunit::current_timestamp > `bashunit::current_timestamp`: Gets the current timestamp. @@ -54,4 +60,39 @@ The directory is automatically deleted when bashunit completes. ## bashunit::is_command_available -> `bashunit::is_command_available`: Checks if command is available +> `bashunit::is_command_available `: Checks if a command is available in `PATH`. + +Returns `0` when the command is found, `1` otherwise. + +```bash +if bashunit::is_command_available jq; then + # jq-based assertions +fi +``` + +## bashunit::print_line + +> `bashunit::print_line `: Prints a horizontal separator. + +Defaults to 70 characters of `-`. Both arguments are optional. + +```bash +bashunit::print_line # 70 dashes +bashunit::print_line 40 '=' # 40 equals signs +``` + +## Custom assertion helpers + +These helpers are intended for building [custom assertions](/custom-asserts). + +- `bashunit::assertion_passed` — Mark the current assertion as passed. +- `bashunit::assertion_failed ` — Mark the current + assertion as failed and print a failure report. +- `bashunit::fail ` — Fail the current test with an optional message. + +See [Custom asserts](/custom-asserts) for full examples. + +## Test doubles + +The `bashunit::spy`, `bashunit::mock`, and `bashunit::unmock` helpers are +documented in [Test doubles](/test-doubles). From 868681beb0cfc0568c2839676a80a3036a1ba5bc Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sat, 18 Apr 2026 14:30:16 +0200 Subject: [PATCH 4/7] docs(config): document BASHUNIT_OUTPUT_FORMAT for TAP --- docs/configuration.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index 36b01ef7..8a1cdfd8 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -203,6 +203,22 @@ BASHUNIT_SHOW_EXECUTION_TIME=false ``` ::: +## Output format + +> `BASHUNIT_OUTPUT_FORMAT=tap` + +Select an alternative output format. Currently supported: `tap` for +[TAP version 13](https://testanything.org/tap-version-13-specification.html), +useful for CI/CD integrations. + +Similar as using `--output` option on the [command line](/command-line#output-format). + +::: code-group +```bash [.env] +BASHUNIT_OUTPUT_FORMAT=tap +``` +::: + ## Log JUnit > `BASHUNIT_LOG_JUNIT=file` From b844f1fb11223e18d0a904030d6269f8f095a9d8 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sat, 18 Apr 2026 14:39:28 +0200 Subject: [PATCH 5/7] docs(cli): clarify watch modes and fix TAP output sample --- docs/command-line.md | 45 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/docs/command-line.md b/docs/command-line.md index e2710fb5..a75c1c50 100644 --- a/docs/command-line.md +++ b/docs/command-line.md @@ -155,29 +155,47 @@ Select an alternative output format. Currently supported: - `tap` — [TAP version 13](https://testanything.org/tap-version-13-specification.html) for CI/CD integrations. +The `TAP version 13` header comes first, each test file is announced via a +`# ` diagnostic line, each test emits an `ok - ` or +`not ok - ` line (failures include a YAML `--- ... ...` block with +expected/actual), and the `1..N` plan line closes the report. + ::: code-group ```bash [Example] bashunit test tests/ --output tap ``` ```[Output] TAP version 13 -1..2 +# tests/example_test.sh ok 1 - Should validate input not ok 2 - Should handle errors + --- + Expected 'foo' + but got 'bar' + ... + +1..2 ``` ::: ### Watch mode > `bashunit test -w|--watch` -> `bashunit watch [path]` -Watch `.sh` files for changes and automatically re-run tests. Available as a -flag on `test` or as a dedicated [`watch`](#watch) subcommand. +Watch the test path (plus `src/` if present) and re-run tests when files change. +The `-w`/`--watch` flag uses a lightweight **checksum polling loop** that works +on any system — no external tools required. -::: warning Requirements -- **Linux:** `inotifywait` (`sudo apt install inotify-tools`) -- **macOS:** `fswatch` (`brew install fswatch`) +::: code-group +```bash [Example] +bashunit test tests/ --watch +``` +::: + +::: tip +For file-event-driven watching (no polling), use the dedicated +[`watch`](#watch) subcommand, which relies on `inotifywait` (Linux) or +`fswatch` (macOS). ::: ### Environment / Bootstrap @@ -534,8 +552,9 @@ bashunit bench --filter "parse" > `bashunit watch [path] [test-options]` -Watch `.sh` files for changes and automatically re-run tests. Any option -accepted by `bashunit test` is also accepted here. +Dedicated watch subcommand that uses **OS file-event notifications** (no +polling) to re-run tests as soon as a `.sh` file changes. Any option accepted +by `bashunit test` is also accepted here. ::: code-group ```bash [Examples] @@ -557,7 +576,13 @@ bashunit watch tests/ --simple - **Linux:** `inotifywait` (`sudo apt install inotify-tools`) - **macOS:** `fswatch` (`brew install fswatch`) -If the required tool is not installed, bashunit prints a clear installation hint. +If the required tool is not installed, bashunit prints a clear installation hint +and exits with a non-zero code. +::: + +::: tip +If you cannot install `inotifywait` or `fswatch`, use the portable +[`-w/--watch`](#watch-mode) flag on `bashunit test` instead (uses polling). ::: ## doc From a03a9385ddff8a8b00098692d71a65fb726a5f54 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sat, 18 Apr 2026 14:42:50 +0200 Subject: [PATCH 6/7] test(acceptance): add TAP output format snapshot tests --- tests/acceptance/bashunit_tap_output_test.sh | 36 +++++++++++++++++++ ...ut_failing_tests_matches_snapshot.snapshot | 13 +++++++ ...ut_passing_tests_matches_snapshot.snapshot | 8 +++++ 3 files changed, 57 insertions(+) create mode 100644 tests/acceptance/bashunit_tap_output_test.sh create mode 100644 tests/acceptance/snapshots/bashunit_tap_output_test_sh.test_tap_output_failing_tests_matches_snapshot.snapshot create mode 100644 tests/acceptance/snapshots/bashunit_tap_output_test_sh.test_tap_output_passing_tests_matches_snapshot.snapshot diff --git a/tests/acceptance/bashunit_tap_output_test.sh b/tests/acceptance/bashunit_tap_output_test.sh new file mode 100644 index 00000000..063ac49b --- /dev/null +++ b/tests/acceptance/bashunit_tap_output_test.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +function set_up_before_script() { + TEST_ENV_FILE="tests/acceptance/fixtures/.env.default" +} + +function test_tap_output_passing_tests_matches_snapshot() { + local test_file=tests/acceptance/fixtures/test_bashunit_when_a_test_passes.sh + + assert_match_snapshot "$(./bashunit --no-parallel --env "$TEST_ENV_FILE" --output tap "$test_file")" +} + +function test_tap_output_failing_tests_matches_snapshot() { + local test_file=tests/acceptance/fixtures/test_bashunit_when_a_test_fail.sh + + assert_match_snapshot "$(./bashunit --no-parallel --env "$TEST_ENV_FILE" --output tap "$test_file" 2>&1 || true)" +} + +function test_tap_output_env_var_equivalent_to_flag() { + local test_file=tests/acceptance/fixtures/test_bashunit_when_a_test_passes.sh + local via_flag + local via_env + + via_flag=$(./bashunit --no-parallel --env "$TEST_ENV_FILE" --output tap "$test_file") + via_env=$(BASHUNIT_OUTPUT_FORMAT=tap ./bashunit --no-parallel --env "$TEST_ENV_FILE" "$test_file") + + assert_equals "$via_flag" "$via_env" +} + +function test_tap_output_exits_non_zero_on_failure() { + local test_file=tests/acceptance/fixtures/test_bashunit_when_a_test_fail.sh + + ./bashunit --no-parallel --env "$TEST_ENV_FILE" --output tap "$test_file" >/dev/null 2>&1 + assert_general_error "$?" +} diff --git a/tests/acceptance/snapshots/bashunit_tap_output_test_sh.test_tap_output_failing_tests_matches_snapshot.snapshot b/tests/acceptance/snapshots/bashunit_tap_output_test_sh.test_tap_output_failing_tests_matches_snapshot.snapshot new file mode 100644 index 00000000..8ca07169 --- /dev/null +++ b/tests/acceptance/snapshots/bashunit_tap_output_test_sh.test_tap_output_failing_tests_matches_snapshot.snapshot @@ -0,0 +1,13 @@ +TAP version 13 +# tests/acceptance/fixtures/test_bashunit_when_a_test_fail.sh +ok 1 - Assert same +ok 2 - Assert contains +not ok 3 - Assert failing + --- + Expected '1' + but got '0' + ... +ok 4 - Assert greater and less than +ok 5 - Assert empty + +1..5 diff --git a/tests/acceptance/snapshots/bashunit_tap_output_test_sh.test_tap_output_passing_tests_matches_snapshot.snapshot b/tests/acceptance/snapshots/bashunit_tap_output_test_sh.test_tap_output_passing_tests_matches_snapshot.snapshot new file mode 100644 index 00000000..0b1bbc90 --- /dev/null +++ b/tests/acceptance/snapshots/bashunit_tap_output_test_sh.test_tap_output_passing_tests_matches_snapshot.snapshot @@ -0,0 +1,8 @@ +TAP version 13 +# tests/acceptance/fixtures/test_bashunit_when_a_test_passes.sh +ok 1 - Assert same +ok 2 - Assert contains +ok 3 - Assert greater and less than +ok 4 - Assert empty + +1..4 From 99f4a07d05fd17b3352e4f6902ceebbcc00f0d3e Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sat, 18 Apr 2026 14:49:26 +0200 Subject: [PATCH 7/7] fix(test): make TAP exit-code assertion strict-mode safe --- tests/acceptance/bashunit_tap_output_test.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/acceptance/bashunit_tap_output_test.sh b/tests/acceptance/bashunit_tap_output_test.sh index 063ac49b..c72ad33f 100644 --- a/tests/acceptance/bashunit_tap_output_test.sh +++ b/tests/acceptance/bashunit_tap_output_test.sh @@ -31,6 +31,5 @@ function test_tap_output_env_var_equivalent_to_flag() { function test_tap_output_exits_non_zero_on_failure() { local test_file=tests/acceptance/fixtures/test_bashunit_when_a_test_fail.sh - ./bashunit --no-parallel --env "$TEST_ENV_FILE" --output tap "$test_file" >/dev/null 2>&1 - assert_general_error "$?" + assert_general_error "$(./bashunit --no-parallel --env "$TEST_ENV_FILE" --output tap "$test_file" 2>&1)" }