diff --git a/docs/command-line.md b/docs/command-line.md index d2c3d73c..a75c1c50 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,84 @@ 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. + +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 +# 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` + +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. + +::: 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 > `bashunit test -e|--env|--boot ` @@ -326,7 +410,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 @@ -464,6 +548,43 @@ 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]` + +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] +# 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 +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 > `bashunit doc [filter]` @@ -567,7 +688,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 ``` ::: @@ -604,16 +725,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 +747,7 @@ Each subcommand also supports `--help`: ```bash bashunit test --help bashunit bench --help +bashunit watch --help bashunit doc --help ``` 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` 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). diff --git a/tests/acceptance/bashunit_tap_output_test.sh b/tests/acceptance/bashunit_tap_output_test.sh new file mode 100644 index 00000000..c72ad33f --- /dev/null +++ b/tests/acceptance/bashunit_tap_output_test.sh @@ -0,0 +1,35 @@ +#!/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 + + assert_general_error "$(./bashunit --no-parallel --env "$TEST_ENV_FILE" --output tap "$test_file" 2>&1)" +} 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