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
30 changes: 28 additions & 2 deletions .env.example
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,9 @@ BASHUNIT_PROFILE= # Default: false (report slowest tests after
BASHUNIT_PROFILE_COUNT= # Default: 10 (how many slowest tests to report)
BASHUNIT_NO_COLOR= # Default: false (disable colors)
BASHUNIT_NO_DIFF= # Default: false (disable unified diff on multiline assert failures)
BASHUNIT_NO_PROGRESS= # Default: false (suppress real-time progress, final results only)
BASHUNIT_SHOW_OUTPUT_ON_FAILURE= # Default: true (show captured test output on failure)
BASHUNIT_OUTPUT_FORMAT= # Default: empty (tap = TAP version 13 on stdout)

#───────────────────────────────────────────────────────────────────────────────
# Test Execution
Expand All@@ -49,20 +52,38 @@ BASHUNIT_GHA_ANNOTATIONS= # Default: auto (or always, never)
BASHUNIT_REPORT_MD= # Default: empty (Markdown summary path)
BASHUNIT_CHANGED= # Default: false (run only test files changed since a git ref)
BASHUNIT_CHANGED_REF= # Default: empty (--changed ref: origin/HEAD, then HEAD)
BASHUNIT_COVERAGE_DIFF= # Default: empty (restrict coverage to lines changed since this ref)
BASHUNIT_COVERAGE_DIFF= # Default: empty (restrict coverage to lines changed since this ref)
BASHUNIT_EXCLUDE_FILTER= # Default: empty (skip tests whose name matches)
BASHUNIT_LIST_TESTS= # Default: false (print the tests that would run, run none)
BASHUNIT_LIST_FORMAT= # Default: text (--list rendering: text or json)
BASHUNIT_STOP_ON_ASSERTION_FAILURE= # Default: true (stop test on first assertion fail)
BASHUNIT_STRICT_MODE= # Default: false (enable set -euo pipefail)
BASHUNIT_LOGIN_SHELL= # Default: false (source login shell profiles)
BASHUNIT_PARALLEL_JOBS= # Default: 0 (unlimited; N or auto caps concurrency)
BASHUNIT_RANDOM_ORDER= # Default: false (shuffle files and tests)
BASHUNIT_SEED= # Default: empty (pins --random-order for a replay)
BASHUNIT_RETRY= # Default: 0 (re-run a failed test up to N extra times)
BASHUNIT_TEST_TIMEOUT= # Default: 0 (off; fail a test running longer than N seconds)
BASHUNIT_SHARD_INDEX= # Default: empty (run shard i of BASHUNIT_SHARD_TOTAL)
BASHUNIT_SHARD_TOTAL= # Default: empty (how many shards to split the suite into)
BASHUNIT_SKIP_ENV_FILE= # Default: false (skip .env and .bashunitrc)
BASHUNIT_WATCH_INTERVAL= # Default: 3 (seconds between polls in the watch fallback)

#───────────────────────────────────────────────────────────────────────────────
# Reports
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_LOG_JUNIT= # JUnit XML report path (e.g., report.xml)
BASHUNIT_LOG_GHA= # GitHub Actions workflow-commands log path (e.g., gha.log)
BASHUNIT_REPORT_HTML= # HTML test report path (e.g., report.html)
BASHUNIT_REPORT_TAP= # TAP version 13 report path (e.g., report.tap)
BASHUNIT_REPORT_JSON= # JSON report path (e.g., report.json)

#───────────────────────────────────────────────────────────────────────────────
# Snapshots
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_SNAPSHOT_UPDATE= # Default: false (rewrite existing snapshots)
BASHUNIT_SNAPSHOT_CREATE= # Default: true (record a missing snapshot instead of failing)
BASHUNIT_SNAPSHOT_REPORT_UNUSED= # Default: false (list snapshots no test resolved)

#───────────────────────────────────────────────────────────────────────────────
# Code Coverage
Expand All@@ -74,11 +95,16 @@ BASHUNIT_COVERAGE_REPORT= # Default: coverage/lcov.info
BASHUNIT_COVERAGE_REPORT_HTML= # HTML coverage report directory (e.g., coverage/html)
BASHUNIT_COVERAGE_MIN= # Minimum coverage % (fails if below)
BASHUNIT_COVERAGE_THRESHOLD_LOW= # Default: 50 (red below this)
BASHUNIT_COVERAGE_THRESHOLD_HIGH= # Default: 80 (green above this)
BASHUNIT_COVERAGE_THRESHOLD_HIGH= # Default: 80 (green at or above this)
BASHUNIT_COVERAGE_ENGINE= # Default: auto (or xtrace, trap)
BASHUNIT_COVERAGE_SHOW_LINE_HITS= # Default: false (print per-line execution counts)
BASHUNIT_COVERAGE_SHOW_FUNCTIONS= # Default: false (print per-function coverage)
BASHUNIT_COVERAGE_SHOW_UNCOVERED= # Default: false (print missed line ranges per file)

#───────────────────────────────────────────────────────────────────────────────
# Advanced / Debug
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_DEV_LOG= # Developer log file path
BASHUNIT_BENCH_MODE= # Default: false (benchmark mode)
BASHUNIT_INTERNAL_LOG= # Default: false (internal debug logging)
BASHUNIT_NO_DEPRECATION_WARNINGS= # Default: false (silence unprefixed-alias warnings)
33 changes: 19 additions & 14 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,28 +3,33 @@
## Unreleased

### Added
- `--coverage-diff <ref>` restricts the coverage console report to lines changed since a base ref, and `--coverage-min` then gates on that diff percentage (#1032)
- `--report-md <file>` writes a Markdown run summary: verdict, counts table, failures with their message, plus coverage and slowest tests when those ran. Inside GitHub Actions it is appended to `$GITHUB_STEP_SUMMARY` automatically, so failures render on the job page (#1015)
- `--gha-annotations <auto|always|never>` controls GitHub Actions annotations on stdout; `auto` turns them on inside GitHub Actions and stays quiet everywhere else (#1014)
- `--repeat <n>` runs each selected test n times so flakiness can be hunted before it reaches CI. The test is reported once with the aggregate outcome, a failure names the iteration it happened on, and repeat wraps `--retry` rather than the other way round (#1013)
- Flaky is a first-class outcome: a test that only passed after a retry is counted separately, kept inside the pass total so the exit code is unchanged, and carried into JUnit (`<flakyFailure>`), TAP, JSON, HTML and GitHub Actions along with the first attempt's failure message. `--fail-on-flaky` turns such a run red (#1012)
- `--order-by <mode>` picks the execution order: `defined` (default), `defects` (last run's failures first, whole suite still runs) or `random`. `--random-order` and `--seed` keep working unchanged (#1011)
- `--changed [<ref>]` runs only the test files git reports as touched since `<ref>` (default `origin/HEAD`, then `HEAD`), covering committed, staged, unstaged and untracked changes. Deletions are dropped, a rename selects its new path, and a missing work tree or unresolvable ref fails the run instead of selecting nothing (#1010)
- `--coverage-diff <ref>` limits the coverage console report to lines changed since a base ref; `--coverage-min` then gates on that diff percentage (#1032)
- `assert_command_available <command>` asserts a command, shell builtin or function resolves through `command -v` (#1027)
- `assert_between <min> <max> <actual>` and `assert_not_between` add inclusive numeric-range assertions for integers and decimals (#1026)
- `--list` (alias `--dry-run`) prints the tests a run would execute, without running them; `--list-format json` emits file, function, name, line and tags. Honours every selection flag, including `--shard` and `--random-order --seed` ordering (#1007)
- `--report-md <file>` writes a Markdown run summary: verdict, counts table, failures with their message, plus coverage and slowest tests when those ran. Inside GitHub Actions it is also appended to `$GITHUB_STEP_SUMMARY` (#1015)
- `--gha-annotations <auto|always|never>` controls GitHub Actions annotations on stdout; `auto` enables them only inside GitHub Actions (#1014)
- `--repeat <n>` runs each selected test n times to hunt flakiness before CI does. One report line with the aggregate outcome, a failure names its iteration, and repeat wraps `--retry` (#1013)
- Flaky is a first-class outcome: a test that only passed after a retry is counted separately, stays inside the pass total so the exit code is unchanged, and is carried into JUnit (`<flakyFailure>`), TAP, JSON, HTML and GitHub Actions with the first attempt's failure message. `--fail-on-flaky` turns such a run red (#1012)
- `--order-by <mode>` picks the execution order: `defined` (default), `defects` (last run's failures first, whole suite still runs) or `random`. `--random-order` and `--seed` are unchanged (#1011)
- `--changed [<ref>]` runs only the test files git reports as touched since `<ref>` (default `origin/HEAD`, then `HEAD`), covering committed, staged, unstaged and untracked changes. Deletions are dropped, a rename selects its new path, and a missing work tree or unresolvable ref fails the run (#1010)
- `--exclude-filter <name>` skips tests by name, the counterpart of `--exclude-tag`. Repeatable, OR'd, and wins over `--filter` (#1009)
- `# @tags a b` above any top-level line applies those tags to every test in the file, unioned with per-function `# @tag` (#1008)
- `--tag` accepts expressions: `'a&&b'` (AND) and `'!a'` (NOT), combinable as `'a&&!b'`. Repeated `--tag` flags keep OR semantics, and `--exclude-tag` still wins (#1008)
- `assert_command_available <command>` asserts that an external command, shell builtin or function resolves through `command -v` (#1027)
- The coverage engine in use is reported by `--verbose`, and an explicit `BASHUNIT_COVERAGE_ENGINE=xtrace` that the running Bash cannot honour now warns instead of being silently ignored (#1005)
- `# @tags a b` above any top-level line tags every test in the file, unioned with per-function `# @tag` (#1008)
- `--list` (alias `--dry-run`) prints the tests a run would execute without running them; `--list-format json` emits file, function, name, line and tags. Honours every selection flag, including `--shard` and `--random-order --seed` ordering (#1007)
- `--verbose` reports the coverage engine in use, and an explicit `BASHUNIT_COVERAGE_ENGINE=xtrace` the running Bash cannot honour now warns instead of being silently ignored (#1005)

### Changed
- The JUnit XML shape changed: one `<testsuite>` per test file (with its own counts, time and timestamp) instead of a single flat suite, `classname` on every `<testcase>`, `<failure message="...">` carrying the first informative line of the real message with `type="AssertionFailed"`, `<system-out>` with the test's captured output, and aggregate totals on `<testsuites>`. Consumers that group by suite or classname (Jenkins, GitLab, dorny/test-reporter) now get real groupings (#1016)
- Performance: `--coverage` is about 1.6x to 2.3x faster. Executable-line classification no longer forks `grep` per source line, which was roughly half of a coverage run's wall time and affected both engines equally (#1005)
- `bashunit test --help` lists `--show-skipped` and `--show-incomplete`, both accepted by the parser but never advertised. `BASHUNIT_COVERAGE_SHOW_FUNCTIONS` and `BASHUNIT_COVERAGE_SHOW_UNCOVERED` are registered in `src/config/env.sh` like every other setting, and `.env.example` now lists all 66 settings, 19 of which were missing
- Docs: a full audit of the reference pages against the code. `docs/configuration.md` gained the 17 settings it never documented, `docs/command-line.md` gained the `assert` subcommand section and had every stale example output replaced with a real one, the `assert_equals` examples in `docs/assertions.md` were inverted, and the coverage settings and diff-coverage narrative now live in one place instead of two that had drifted
- JUnit XML: one `<testsuite>` per test file with its own counts, time and timestamp instead of a single flat suite, `classname` on every `<testcase>`, `<failure message="...">` carrying the first informative line of the real message with `type="AssertionFailed"`, `<system-out>` with the test's captured output, and aggregate totals on `<testsuites>`. Consumers that group by suite or classname (Jenkins, GitLab, dorny/test-reporter) now get real groupings (#1016)
- Performance: `--coverage` is about 1.6x to 2.3x faster. Executable-line classification no longer forks `grep` per source line, roughly half of a coverage run's wall time on both engines (#1005)

### Fixed
- A failed assertion is no longer reported twice. `bashunit::assert_that` returns 1 on failure by design, so a custom assertion ending with it made the test body exit non-zero and the runner printed a spurious `✗ Error` on top of the `✗ Failed`. Custom assertions no longer need a trailing `return 0`; a real runtime error is still reported as an error
- `--coverage-report` with no value uses `coverage/lcov.info` instead of aborting the run with `$2: unbound variable`, and no longer consumes a following flag as its filename. Write the test path before it, since an optional value cannot be told apart from a path
- `--list --list-format json` reports each tag as its own array element. The emitter split the tag list on whitespace while every other consumer splits it on commas, so a test with two tags rendered as `["slow,fileTag"]` and a tag containing spaces was split into one element per word
- Build: the standalone binary size budget is 544 KiB, raised from 500 KiB after ordinary feature growth crossed it; the artifact keeps its indentation rather than being minified (#1045)
- `assert_within_delta` rejects malformed numbers such as `1.2.3` or `5-3` as non-numeric instead of leaking a raw `bc` parse error or silently evaluating them as an expression (#1026)
- `assert_within_delta` rejects malformed numbers such as `1.2.3` or `5-3` as non-numeric instead of leaking a raw `bc` parse error or evaluating them as an expression (#1026)
- Report formats are no longer empty under `--parallel`. `--report-junit`, `--report-tap`, `--report-json`, `--report-html` and `--log-junit` all recorded zero tests, because the rows were collected inside the per-test worker and nothing rebuilt them in the parent (#1004)

## [0.45.0](https://github.com/TypedDevs/bashunit/compare/0.44.0...0.45.0) - 2026-08-09
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,7 @@
## Why bashunit

A lightweight, fast testing framework for **Bash 3.0+**, focused on developer experience.
It ships 77 assertions plus spies, mocks, data providers, snapshots and more.
It ships 84 assertions plus spies, mocks, data providers, snapshots and more.

## Quick start

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
docs: bring the reference pages back to parity with the code by Chemaclass · Pull Request #1063 · TypedDevs/bashunit · GitHub
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
30 changes: 28 additions & 2 deletions .env.example
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,9 @@ BASHUNIT_PROFILE= # Default: false (report slowest tests after
BASHUNIT_PROFILE_COUNT= # Default: 10 (how many slowest tests to report)
BASHUNIT_NO_COLOR= # Default: false (disable colors)
BASHUNIT_NO_DIFF= # Default: false (disable unified diff on multiline assert failures)
BASHUNIT_NO_PROGRESS= # Default: false (suppress real-time progress, final results only)
BASHUNIT_SHOW_OUTPUT_ON_FAILURE= # Default: true (show captured test output on failure)
BASHUNIT_OUTPUT_FORMAT= # Default: empty (tap = TAP version 13 on stdout)

#───────────────────────────────────────────────────────────────────────────────
# Test Execution
Expand All@@ -49,20 +52,38 @@ BASHUNIT_GHA_ANNOTATIONS= # Default: auto (or always, never)
BASHUNIT_REPORT_MD= # Default: empty (Markdown summary path)
BASHUNIT_CHANGED= # Default: false (run only test files changed since a git ref)
BASHUNIT_CHANGED_REF= # Default: empty (--changed ref: origin/HEAD, then HEAD)
BASHUNIT_COVERAGE_DIFF= # Default: empty (restrict coverage to lines changed since this ref)
BASHUNIT_COVERAGE_DIFF= # Default: empty (restrict coverage to lines changed since this ref)
BASHUNIT_EXCLUDE_FILTER= # Default: empty (skip tests whose name matches)
BASHUNIT_LIST_TESTS= # Default: false (print the tests that would run, run none)
BASHUNIT_LIST_FORMAT= # Default: text (--list rendering: text or json)
BASHUNIT_STOP_ON_ASSERTION_FAILURE= # Default: true (stop test on first assertion fail)
BASHUNIT_STRICT_MODE= # Default: false (enable set -euo pipefail)
BASHUNIT_LOGIN_SHELL= # Default: false (source login shell profiles)
BASHUNIT_PARALLEL_JOBS= # Default: 0 (unlimited; N or auto caps concurrency)
BASHUNIT_RANDOM_ORDER= # Default: false (shuffle files and tests)
BASHUNIT_SEED= # Default: empty (pins --random-order for a replay)
BASHUNIT_RETRY= # Default: 0 (re-run a failed test up to N extra times)
BASHUNIT_TEST_TIMEOUT= # Default: 0 (off; fail a test running longer than N seconds)
BASHUNIT_SHARD_INDEX= # Default: empty (run shard i of BASHUNIT_SHARD_TOTAL)
BASHUNIT_SHARD_TOTAL= # Default: empty (how many shards to split the suite into)
BASHUNIT_SKIP_ENV_FILE= # Default: false (skip .env and .bashunitrc)
BASHUNIT_WATCH_INTERVAL= # Default: 3 (seconds between polls in the watch fallback)

#───────────────────────────────────────────────────────────────────────────────
# Reports
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_LOG_JUNIT= # JUnit XML report path (e.g., report.xml)
BASHUNIT_LOG_GHA= # GitHub Actions workflow-commands log path (e.g., gha.log)
BASHUNIT_REPORT_HTML= # HTML test report path (e.g., report.html)
BASHUNIT_REPORT_TAP= # TAP version 13 report path (e.g., report.tap)
BASHUNIT_REPORT_JSON= # JSON report path (e.g., report.json)

#───────────────────────────────────────────────────────────────────────────────
# Snapshots
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_SNAPSHOT_UPDATE= # Default: false (rewrite existing snapshots)
BASHUNIT_SNAPSHOT_CREATE= # Default: true (record a missing snapshot instead of failing)
BASHUNIT_SNAPSHOT_REPORT_UNUSED= # Default: false (list snapshots no test resolved)

#───────────────────────────────────────────────────────────────────────────────
# Code Coverage
Expand All@@ -74,11 +95,16 @@ BASHUNIT_COVERAGE_REPORT= # Default: coverage/lcov.info
BASHUNIT_COVERAGE_REPORT_HTML= # HTML coverage report directory (e.g., coverage/html)
BASHUNIT_COVERAGE_MIN= # Minimum coverage % (fails if below)
BASHUNIT_COVERAGE_THRESHOLD_LOW= # Default: 50 (red below this)
BASHUNIT_COVERAGE_THRESHOLD_HIGH= # Default: 80 (green above this)
BASHUNIT_COVERAGE_THRESHOLD_HIGH= # Default: 80 (green at or above this)
BASHUNIT_COVERAGE_ENGINE= # Default: auto (or xtrace, trap)
BASHUNIT_COVERAGE_SHOW_LINE_HITS= # Default: false (print per-line execution counts)
BASHUNIT_COVERAGE_SHOW_FUNCTIONS= # Default: false (print per-function coverage)
BASHUNIT_COVERAGE_SHOW_UNCOVERED= # Default: false (print missed line ranges per file)

#───────────────────────────────────────────────────────────────────────────────
# Advanced / Debug
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_DEV_LOG= # Developer log file path
BASHUNIT_BENCH_MODE= # Default: false (benchmark mode)
BASHUNIT_INTERNAL_LOG= # Default: false (internal debug logging)
BASHUNIT_NO_DEPRECATION_WARNINGS= # Default: false (silence unprefixed-alias warnings)
33 changes: 19 additions & 14 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,28 +3,33 @@
## Unreleased

### Added
- `--coverage-diff <ref>` restricts the coverage console report to lines changed since a base ref, and `--coverage-min` then gates on that diff percentage (#1032)
- `--report-md <file>` writes a Markdown run summary: verdict, counts table, failures with their message, plus coverage and slowest tests when those ran. Inside GitHub Actions it is appended to `$GITHUB_STEP_SUMMARY` automatically, so failures render on the job page (#1015)
- `--gha-annotations <auto|always|never>` controls GitHub Actions annotations on stdout; `auto` turns them on inside GitHub Actions and stays quiet everywhere else (#1014)
- `--repeat <n>` runs each selected test n times so flakiness can be hunted before it reaches CI. The test is reported once with the aggregate outcome, a failure names the iteration it happened on, and repeat wraps `--retry` rather than the other way round (#1013)
- Flaky is a first-class outcome: a test that only passed after a retry is counted separately, kept inside the pass total so the exit code is unchanged, and carried into JUnit (`<flakyFailure>`), TAP, JSON, HTML and GitHub Actions along with the first attempt's failure message. `--fail-on-flaky` turns such a run red (#1012)
- `--order-by <mode>` picks the execution order: `defined` (default), `defects` (last run's failures first, whole suite still runs) or `random`. `--random-order` and `--seed` keep working unchanged (#1011)
- `--changed [<ref>]` runs only the test files git reports as touched since `<ref>` (default `origin/HEAD`, then `HEAD`), covering committed, staged, unstaged and untracked changes. Deletions are dropped, a rename selects its new path, and a missing work tree or unresolvable ref fails the run instead of selecting nothing (#1010)
- `--coverage-diff <ref>` limits the coverage console report to lines changed since a base ref; `--coverage-min` then gates on that diff percentage (#1032)
- `assert_command_available <command>` asserts a command, shell builtin or function resolves through `command -v` (#1027)
- `assert_between <min> <max> <actual>` and `assert_not_between` add inclusive numeric-range assertions for integers and decimals (#1026)
- `--list` (alias `--dry-run`) prints the tests a run would execute, without running them; `--list-format json` emits file, function, name, line and tags. Honours every selection flag, including `--shard` and `--random-order --seed` ordering (#1007)
- `--report-md <file>` writes a Markdown run summary: verdict, counts table, failures with their message, plus coverage and slowest tests when those ran. Inside GitHub Actions it is also appended to `$GITHUB_STEP_SUMMARY` (#1015)
- `--gha-annotations <auto|always|never>` controls GitHub Actions annotations on stdout; `auto` enables them only inside GitHub Actions (#1014)
- `--repeat <n>` runs each selected test n times to hunt flakiness before CI does. One report line with the aggregate outcome, a failure names its iteration, and repeat wraps `--retry` (#1013)
- Flaky is a first-class outcome: a test that only passed after a retry is counted separately, stays inside the pass total so the exit code is unchanged, and is carried into JUnit (`<flakyFailure>`), TAP, JSON, HTML and GitHub Actions with the first attempt's failure message. `--fail-on-flaky` turns such a run red (#1012)
- `--order-by <mode>` picks the execution order: `defined` (default), `defects` (last run's failures first, whole suite still runs) or `random`. `--random-order` and `--seed` are unchanged (#1011)
- `--changed [<ref>]` runs only the test files git reports as touched since `<ref>` (default `origin/HEAD`, then `HEAD`), covering committed, staged, unstaged and untracked changes. Deletions are dropped, a rename selects its new path, and a missing work tree or unresolvable ref fails the run (#1010)
- `--exclude-filter <name>` skips tests by name, the counterpart of `--exclude-tag`. Repeatable, OR'd, and wins over `--filter` (#1009)
- `# @tags a b` above any top-level line applies those tags to every test in the file, unioned with per-function `# @tag` (#1008)
- `--tag` accepts expressions: `'a&&b'` (AND) and `'!a'` (NOT), combinable as `'a&&!b'`. Repeated `--tag` flags keep OR semantics, and `--exclude-tag` still wins (#1008)
- `assert_command_available <command>` asserts that an external command, shell builtin or function resolves through `command -v` (#1027)
- The coverage engine in use is reported by `--verbose`, and an explicit `BASHUNIT_COVERAGE_ENGINE=xtrace` that the running Bash cannot honour now warns instead of being silently ignored (#1005)
- `# @tags a b` above any top-level line tags every test in the file, unioned with per-function `# @tag` (#1008)
- `--list` (alias `--dry-run`) prints the tests a run would execute without running them; `--list-format json` emits file, function, name, line and tags. Honours every selection flag, including `--shard` and `--random-order --seed` ordering (#1007)
- `--verbose` reports the coverage engine in use, and an explicit `BASHUNIT_COVERAGE_ENGINE=xtrace` the running Bash cannot honour now warns instead of being silently ignored (#1005)

### Changed
- The JUnit XML shape changed: one `<testsuite>` per test file (with its own counts, time and timestamp) instead of a single flat suite, `classname` on every `<testcase>`, `<failure message="...">` carrying the first informative line of the real message with `type="AssertionFailed"`, `<system-out>` with the test's captured output, and aggregate totals on `<testsuites>`. Consumers that group by suite or classname (Jenkins, GitLab, dorny/test-reporter) now get real groupings (#1016)
- Performance: `--coverage` is about 1.6x to 2.3x faster. Executable-line classification no longer forks `grep` per source line, which was roughly half of a coverage run's wall time and affected both engines equally (#1005)
- `bashunit test --help` lists `--show-skipped` and `--show-incomplete`, both accepted by the parser but never advertised. `BASHUNIT_COVERAGE_SHOW_FUNCTIONS` and `BASHUNIT_COVERAGE_SHOW_UNCOVERED` are registered in `src/config/env.sh` like every other setting, and `.env.example` now lists all 66 settings, 19 of which were missing
- Docs: a full audit of the reference pages against the code. `docs/configuration.md` gained the 17 settings it never documented, `docs/command-line.md` gained the `assert` subcommand section and had every stale example output replaced with a real one, the `assert_equals` examples in `docs/assertions.md` were inverted, and the coverage settings and diff-coverage narrative now live in one place instead of two that had drifted
- JUnit XML: one `<testsuite>` per test file with its own counts, time and timestamp instead of a single flat suite, `classname` on every `<testcase>`, `<failure message="...">` carrying the first informative line of the real message with `type="AssertionFailed"`, `<system-out>` with the test's captured output, and aggregate totals on `<testsuites>`. Consumers that group by suite or classname (Jenkins, GitLab, dorny/test-reporter) now get real groupings (#1016)
- Performance: `--coverage` is about 1.6x to 2.3x faster. Executable-line classification no longer forks `grep` per source line, roughly half of a coverage run's wall time on both engines (#1005)

### Fixed
- A failed assertion is no longer reported twice. `bashunit::assert_that` returns 1 on failure by design, so a custom assertion ending with it made the test body exit non-zero and the runner printed a spurious `✗ Error` on top of the `✗ Failed`. Custom assertions no longer need a trailing `return 0`; a real runtime error is still reported as an error
- `--coverage-report` with no value uses `coverage/lcov.info` instead of aborting the run with `$2: unbound variable`, and no longer consumes a following flag as its filename. Write the test path before it, since an optional value cannot be told apart from a path
- `--list --list-format json` reports each tag as its own array element. The emitter split the tag list on whitespace while every other consumer splits it on commas, so a test with two tags rendered as `["slow,fileTag"]` and a tag containing spaces was split into one element per word
- Build: the standalone binary size budget is 544 KiB, raised from 500 KiB after ordinary feature growth crossed it; the artifact keeps its indentation rather than being minified (#1045)
- `assert_within_delta` rejects malformed numbers such as `1.2.3` or `5-3` as non-numeric instead of leaking a raw `bc` parse error or silently evaluating them as an expression (#1026)
- `assert_within_delta` rejects malformed numbers such as `1.2.3` or `5-3` as non-numeric instead of leaking a raw `bc` parse error or evaluating them as an expression (#1026)
- Report formats are no longer empty under `--parallel`. `--report-junit`, `--report-tap`, `--report-json`, `--report-html` and `--log-junit` all recorded zero tests, because the rows were collected inside the per-test worker and nothing rebuilt them in the parent (#1004)

## [0.45.0](https://github.com/TypedDevs/bashunit/compare/0.44.0...0.45.0) - 2026-08-09
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,7 @@
## Why bashunit

A lightweight, fast testing framework for **Bash 3.0+**, focused on developer experience.
It ships 77 assertions plus spies, mocks, data providers, snapshots and more.
It ships 84 assertions plus spies, mocks, data providers, snapshots and more.

## Quick start

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' docs: bring the reference pages back to parity with the code by Chemaclass · Pull Request #1063 · TypedDevs/bashunit · GitHub
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
30 changes: 28 additions & 2 deletions .env.example
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,9 @@ BASHUNIT_PROFILE= # Default: false (report slowest tests after
BASHUNIT_PROFILE_COUNT= # Default: 10 (how many slowest tests to report)
BASHUNIT_NO_COLOR= # Default: false (disable colors)
BASHUNIT_NO_DIFF= # Default: false (disable unified diff on multiline assert failures)
BASHUNIT_NO_PROGRESS= # Default: false (suppress real-time progress, final results only)
BASHUNIT_SHOW_OUTPUT_ON_FAILURE= # Default: true (show captured test output on failure)
BASHUNIT_OUTPUT_FORMAT= # Default: empty (tap = TAP version 13 on stdout)

#───────────────────────────────────────────────────────────────────────────────
# Test Execution
Expand All@@ -49,20 +52,38 @@ BASHUNIT_GHA_ANNOTATIONS= # Default: auto (or always, never)
BASHUNIT_REPORT_MD= # Default: empty (Markdown summary path)
BASHUNIT_CHANGED= # Default: false (run only test files changed since a git ref)
BASHUNIT_CHANGED_REF= # Default: empty (--changed ref: origin/HEAD, then HEAD)
BASHUNIT_COVERAGE_DIFF= # Default: empty (restrict coverage to lines changed since this ref)
BASHUNIT_COVERAGE_DIFF= # Default: empty (restrict coverage to lines changed since this ref)
BASHUNIT_EXCLUDE_FILTER= # Default: empty (skip tests whose name matches)
BASHUNIT_LIST_TESTS= # Default: false (print the tests that would run, run none)
BASHUNIT_LIST_FORMAT= # Default: text (--list rendering: text or json)
BASHUNIT_STOP_ON_ASSERTION_FAILURE= # Default: true (stop test on first assertion fail)
BASHUNIT_STRICT_MODE= # Default: false (enable set -euo pipefail)
BASHUNIT_LOGIN_SHELL= # Default: false (source login shell profiles)
BASHUNIT_PARALLEL_JOBS= # Default: 0 (unlimited; N or auto caps concurrency)
BASHUNIT_RANDOM_ORDER= # Default: false (shuffle files and tests)
BASHUNIT_SEED= # Default: empty (pins --random-order for a replay)
BASHUNIT_RETRY= # Default: 0 (re-run a failed test up to N extra times)
BASHUNIT_TEST_TIMEOUT= # Default: 0 (off; fail a test running longer than N seconds)
BASHUNIT_SHARD_INDEX= # Default: empty (run shard i of BASHUNIT_SHARD_TOTAL)
BASHUNIT_SHARD_TOTAL= # Default: empty (how many shards to split the suite into)
BASHUNIT_SKIP_ENV_FILE= # Default: false (skip .env and .bashunitrc)
BASHUNIT_WATCH_INTERVAL= # Default: 3 (seconds between polls in the watch fallback)

#───────────────────────────────────────────────────────────────────────────────
# Reports
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_LOG_JUNIT= # JUnit XML report path (e.g., report.xml)
BASHUNIT_LOG_GHA= # GitHub Actions workflow-commands log path (e.g., gha.log)
BASHUNIT_REPORT_HTML= # HTML test report path (e.g., report.html)
BASHUNIT_REPORT_TAP= # TAP version 13 report path (e.g., report.tap)
BASHUNIT_REPORT_JSON= # JSON report path (e.g., report.json)

#───────────────────────────────────────────────────────────────────────────────
# Snapshots
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_SNAPSHOT_UPDATE= # Default: false (rewrite existing snapshots)
BASHUNIT_SNAPSHOT_CREATE= # Default: true (record a missing snapshot instead of failing)
BASHUNIT_SNAPSHOT_REPORT_UNUSED= # Default: false (list snapshots no test resolved)

#───────────────────────────────────────────────────────────────────────────────
# Code Coverage
Expand All@@ -74,11 +95,16 @@ BASHUNIT_COVERAGE_REPORT= # Default: coverage/lcov.info
BASHUNIT_COVERAGE_REPORT_HTML= # HTML coverage report directory (e.g., coverage/html)
BASHUNIT_COVERAGE_MIN= # Minimum coverage % (fails if below)
BASHUNIT_COVERAGE_THRESHOLD_LOW= # Default: 50 (red below this)
BASHUNIT_COVERAGE_THRESHOLD_HIGH= # Default: 80 (green above this)
BASHUNIT_COVERAGE_THRESHOLD_HIGH= # Default: 80 (green at or above this)
BASHUNIT_COVERAGE_ENGINE= # Default: auto (or xtrace, trap)
BASHUNIT_COVERAGE_SHOW_LINE_HITS= # Default: false (print per-line execution counts)
BASHUNIT_COVERAGE_SHOW_FUNCTIONS= # Default: false (print per-function coverage)
BASHUNIT_COVERAGE_SHOW_UNCOVERED= # Default: false (print missed line ranges per file)

#───────────────────────────────────────────────────────────────────────────────
# Advanced / Debug
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_DEV_LOG= # Developer log file path
BASHUNIT_BENCH_MODE= # Default: false (benchmark mode)
BASHUNIT_INTERNAL_LOG= # Default: false (internal debug logging)
BASHUNIT_NO_DEPRECATION_WARNINGS= # Default: false (silence unprefixed-alias warnings)
33 changes: 19 additions & 14 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,28 +3,33 @@
## Unreleased

### Added
- `--coverage-diff <ref>` restricts the coverage console report to lines changed since a base ref, and `--coverage-min` then gates on that diff percentage (#1032)
- `--report-md <file>` writes a Markdown run summary: verdict, counts table, failures with their message, plus coverage and slowest tests when those ran. Inside GitHub Actions it is appended to `$GITHUB_STEP_SUMMARY` automatically, so failures render on the job page (#1015)
- `--gha-annotations <auto|always|never>` controls GitHub Actions annotations on stdout; `auto` turns them on inside GitHub Actions and stays quiet everywhere else (#1014)
- `--repeat <n>` runs each selected test n times so flakiness can be hunted before it reaches CI. The test is reported once with the aggregate outcome, a failure names the iteration it happened on, and repeat wraps `--retry` rather than the other way round (#1013)
- Flaky is a first-class outcome: a test that only passed after a retry is counted separately, kept inside the pass total so the exit code is unchanged, and carried into JUnit (`<flakyFailure>`), TAP, JSON, HTML and GitHub Actions along with the first attempt's failure message. `--fail-on-flaky` turns such a run red (#1012)
- `--order-by <mode>` picks the execution order: `defined` (default), `defects` (last run's failures first, whole suite still runs) or `random`. `--random-order` and `--seed` keep working unchanged (#1011)
- `--changed [<ref>]` runs only the test files git reports as touched since `<ref>` (default `origin/HEAD`, then `HEAD`), covering committed, staged, unstaged and untracked changes. Deletions are dropped, a rename selects its new path, and a missing work tree or unresolvable ref fails the run instead of selecting nothing (#1010)
- `--coverage-diff <ref>` limits the coverage console report to lines changed since a base ref; `--coverage-min` then gates on that diff percentage (#1032)
- `assert_command_available <command>` asserts a command, shell builtin or function resolves through `command -v` (#1027)
- `assert_between <min> <max> <actual>` and `assert_not_between` add inclusive numeric-range assertions for integers and decimals (#1026)
- `--list` (alias `--dry-run`) prints the tests a run would execute, without running them; `--list-format json` emits file, function, name, line and tags. Honours every selection flag, including `--shard` and `--random-order --seed` ordering (#1007)
- `--report-md <file>` writes a Markdown run summary: verdict, counts table, failures with their message, plus coverage and slowest tests when those ran. Inside GitHub Actions it is also appended to `$GITHUB_STEP_SUMMARY` (#1015)
- `--gha-annotations <auto|always|never>` controls GitHub Actions annotations on stdout; `auto` enables them only inside GitHub Actions (#1014)
- `--repeat <n>` runs each selected test n times to hunt flakiness before CI does. One report line with the aggregate outcome, a failure names its iteration, and repeat wraps `--retry` (#1013)
- Flaky is a first-class outcome: a test that only passed after a retry is counted separately, stays inside the pass total so the exit code is unchanged, and is carried into JUnit (`<flakyFailure>`), TAP, JSON, HTML and GitHub Actions with the first attempt's failure message. `--fail-on-flaky` turns such a run red (#1012)
- `--order-by <mode>` picks the execution order: `defined` (default), `defects` (last run's failures first, whole suite still runs) or `random`. `--random-order` and `--seed` are unchanged (#1011)
- `--changed [<ref>]` runs only the test files git reports as touched since `<ref>` (default `origin/HEAD`, then `HEAD`), covering committed, staged, unstaged and untracked changes. Deletions are dropped, a rename selects its new path, and a missing work tree or unresolvable ref fails the run (#1010)
- `--exclude-filter <name>` skips tests by name, the counterpart of `--exclude-tag`. Repeatable, OR'd, and wins over `--filter` (#1009)
- `# @tags a b` above any top-level line applies those tags to every test in the file, unioned with per-function `# @tag` (#1008)
- `--tag` accepts expressions: `'a&&b'` (AND) and `'!a'` (NOT), combinable as `'a&&!b'`. Repeated `--tag` flags keep OR semantics, and `--exclude-tag` still wins (#1008)
- `assert_command_available <command>` asserts that an external command, shell builtin or function resolves through `command -v` (#1027)
- The coverage engine in use is reported by `--verbose`, and an explicit `BASHUNIT_COVERAGE_ENGINE=xtrace` that the running Bash cannot honour now warns instead of being silently ignored (#1005)
- `# @tags a b` above any top-level line tags every test in the file, unioned with per-function `# @tag` (#1008)
- `--list` (alias `--dry-run`) prints the tests a run would execute without running them; `--list-format json` emits file, function, name, line and tags. Honours every selection flag, including `--shard` and `--random-order --seed` ordering (#1007)
- `--verbose` reports the coverage engine in use, and an explicit `BASHUNIT_COVERAGE_ENGINE=xtrace` the running Bash cannot honour now warns instead of being silently ignored (#1005)

### Changed
- The JUnit XML shape changed: one `<testsuite>` per test file (with its own counts, time and timestamp) instead of a single flat suite, `classname` on every `<testcase>`, `<failure message="...">` carrying the first informative line of the real message with `type="AssertionFailed"`, `<system-out>` with the test's captured output, and aggregate totals on `<testsuites>`. Consumers that group by suite or classname (Jenkins, GitLab, dorny/test-reporter) now get real groupings (#1016)
- Performance: `--coverage` is about 1.6x to 2.3x faster. Executable-line classification no longer forks `grep` per source line, which was roughly half of a coverage run's wall time and affected both engines equally (#1005)
- `bashunit test --help` lists `--show-skipped` and `--show-incomplete`, both accepted by the parser but never advertised. `BASHUNIT_COVERAGE_SHOW_FUNCTIONS` and `BASHUNIT_COVERAGE_SHOW_UNCOVERED` are registered in `src/config/env.sh` like every other setting, and `.env.example` now lists all 66 settings, 19 of which were missing
- Docs: a full audit of the reference pages against the code. `docs/configuration.md` gained the 17 settings it never documented, `docs/command-line.md` gained the `assert` subcommand section and had every stale example output replaced with a real one, the `assert_equals` examples in `docs/assertions.md` were inverted, and the coverage settings and diff-coverage narrative now live in one place instead of two that had drifted
- JUnit XML: one `<testsuite>` per test file with its own counts, time and timestamp instead of a single flat suite, `classname` on every `<testcase>`, `<failure message="...">` carrying the first informative line of the real message with `type="AssertionFailed"`, `<system-out>` with the test's captured output, and aggregate totals on `<testsuites>`. Consumers that group by suite or classname (Jenkins, GitLab, dorny/test-reporter) now get real groupings (#1016)
- Performance: `--coverage` is about 1.6x to 2.3x faster. Executable-line classification no longer forks `grep` per source line, roughly half of a coverage run's wall time on both engines (#1005)

### Fixed
- A failed assertion is no longer reported twice. `bashunit::assert_that` returns 1 on failure by design, so a custom assertion ending with it made the test body exit non-zero and the runner printed a spurious `✗ Error` on top of the `✗ Failed`. Custom assertions no longer need a trailing `return 0`; a real runtime error is still reported as an error
- `--coverage-report` with no value uses `coverage/lcov.info` instead of aborting the run with `$2: unbound variable`, and no longer consumes a following flag as its filename. Write the test path before it, since an optional value cannot be told apart from a path
- `--list --list-format json` reports each tag as its own array element. The emitter split the tag list on whitespace while every other consumer splits it on commas, so a test with two tags rendered as `["slow,fileTag"]` and a tag containing spaces was split into one element per word
- Build: the standalone binary size budget is 544 KiB, raised from 500 KiB after ordinary feature growth crossed it; the artifact keeps its indentation rather than being minified (#1045)
- `assert_within_delta` rejects malformed numbers such as `1.2.3` or `5-3` as non-numeric instead of leaking a raw `bc` parse error or silently evaluating them as an expression (#1026)
- `assert_within_delta` rejects malformed numbers such as `1.2.3` or `5-3` as non-numeric instead of leaking a raw `bc` parse error or evaluating them as an expression (#1026)
- Report formats are no longer empty under `--parallel`. `--report-junit`, `--report-tap`, `--report-json`, `--report-html` and `--log-junit` all recorded zero tests, because the rows were collected inside the per-test worker and nothing rebuilt them in the parent (#1004)

## [0.45.0](https://github.com/TypedDevs/bashunit/compare/0.44.0...0.45.0) - 2026-08-09
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,7 @@
## Why bashunit

A lightweight, fast testing framework for **Bash 3.0+**, focused on developer experience.
It ships 77 assertions plus spies, mocks, data providers, snapshots and more.
It ships 84 assertions plus spies, mocks, data providers, snapshots and more.

## Quick start

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' docs: bring the reference pages back to parity with the code by Chemaclass · Pull Request #1063 · TypedDevs/bashunit · GitHub
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
30 changes: 28 additions & 2 deletions .env.example
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,9 @@ BASHUNIT_PROFILE= # Default: false (report slowest tests after
BASHUNIT_PROFILE_COUNT= # Default: 10 (how many slowest tests to report)
BASHUNIT_NO_COLOR= # Default: false (disable colors)
BASHUNIT_NO_DIFF= # Default: false (disable unified diff on multiline assert failures)
BASHUNIT_NO_PROGRESS= # Default: false (suppress real-time progress, final results only)
BASHUNIT_SHOW_OUTPUT_ON_FAILURE= # Default: true (show captured test output on failure)
BASHUNIT_OUTPUT_FORMAT= # Default: empty (tap = TAP version 13 on stdout)

#───────────────────────────────────────────────────────────────────────────────
# Test Execution
Expand All@@ -49,20 +52,38 @@ BASHUNIT_GHA_ANNOTATIONS= # Default: auto (or always, never)
BASHUNIT_REPORT_MD= # Default: empty (Markdown summary path)
BASHUNIT_CHANGED= # Default: false (run only test files changed since a git ref)
BASHUNIT_CHANGED_REF= # Default: empty (--changed ref: origin/HEAD, then HEAD)
BASHUNIT_COVERAGE_DIFF= # Default: empty (restrict coverage to lines changed since this ref)
BASHUNIT_COVERAGE_DIFF= # Default: empty (restrict coverage to lines changed since this ref)
BASHUNIT_EXCLUDE_FILTER= # Default: empty (skip tests whose name matches)
BASHUNIT_LIST_TESTS= # Default: false (print the tests that would run, run none)
BASHUNIT_LIST_FORMAT= # Default: text (--list rendering: text or json)
BASHUNIT_STOP_ON_ASSERTION_FAILURE= # Default: true (stop test on first assertion fail)
BASHUNIT_STRICT_MODE= # Default: false (enable set -euo pipefail)
BASHUNIT_LOGIN_SHELL= # Default: false (source login shell profiles)
BASHUNIT_PARALLEL_JOBS= # Default: 0 (unlimited; N or auto caps concurrency)
BASHUNIT_RANDOM_ORDER= # Default: false (shuffle files and tests)
BASHUNIT_SEED= # Default: empty (pins --random-order for a replay)
BASHUNIT_RETRY= # Default: 0 (re-run a failed test up to N extra times)
BASHUNIT_TEST_TIMEOUT= # Default: 0 (off; fail a test running longer than N seconds)
BASHUNIT_SHARD_INDEX= # Default: empty (run shard i of BASHUNIT_SHARD_TOTAL)
BASHUNIT_SHARD_TOTAL= # Default: empty (how many shards to split the suite into)
BASHUNIT_SKIP_ENV_FILE= # Default: false (skip .env and .bashunitrc)
BASHUNIT_WATCH_INTERVAL= # Default: 3 (seconds between polls in the watch fallback)

#───────────────────────────────────────────────────────────────────────────────
# Reports
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_LOG_JUNIT= # JUnit XML report path (e.g., report.xml)
BASHUNIT_LOG_GHA= # GitHub Actions workflow-commands log path (e.g., gha.log)
BASHUNIT_REPORT_HTML= # HTML test report path (e.g., report.html)
BASHUNIT_REPORT_TAP= # TAP version 13 report path (e.g., report.tap)
BASHUNIT_REPORT_JSON= # JSON report path (e.g., report.json)

#───────────────────────────────────────────────────────────────────────────────
# Snapshots
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_SNAPSHOT_UPDATE= # Default: false (rewrite existing snapshots)
BASHUNIT_SNAPSHOT_CREATE= # Default: true (record a missing snapshot instead of failing)
BASHUNIT_SNAPSHOT_REPORT_UNUSED= # Default: false (list snapshots no test resolved)

#───────────────────────────────────────────────────────────────────────────────
# Code Coverage
Expand All@@ -74,11 +95,16 @@ BASHUNIT_COVERAGE_REPORT= # Default: coverage/lcov.info
BASHUNIT_COVERAGE_REPORT_HTML= # HTML coverage report directory (e.g., coverage/html)
BASHUNIT_COVERAGE_MIN= # Minimum coverage % (fails if below)
BASHUNIT_COVERAGE_THRESHOLD_LOW= # Default: 50 (red below this)
BASHUNIT_COVERAGE_THRESHOLD_HIGH= # Default: 80 (green above this)
BASHUNIT_COVERAGE_THRESHOLD_HIGH= # Default: 80 (green at or above this)
BASHUNIT_COVERAGE_ENGINE= # Default: auto (or xtrace, trap)
BASHUNIT_COVERAGE_SHOW_LINE_HITS= # Default: false (print per-line execution counts)
BASHUNIT_COVERAGE_SHOW_FUNCTIONS= # Default: false (print per-function coverage)
BASHUNIT_COVERAGE_SHOW_UNCOVERED= # Default: false (print missed line ranges per file)

#───────────────────────────────────────────────────────────────────────────────
# Advanced / Debug
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_DEV_LOG= # Developer log file path
BASHUNIT_BENCH_MODE= # Default: false (benchmark mode)
BASHUNIT_INTERNAL_LOG= # Default: false (internal debug logging)
BASHUNIT_NO_DEPRECATION_WARNINGS= # Default: false (silence unprefixed-alias warnings)
33 changes: 19 additions & 14 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,28 +3,33 @@
## Unreleased

### Added
- `--coverage-diff <ref>` restricts the coverage console report to lines changed since a base ref, and `--coverage-min` then gates on that diff percentage (#1032)
- `--report-md <file>` writes a Markdown run summary: verdict, counts table, failures with their message, plus coverage and slowest tests when those ran. Inside GitHub Actions it is appended to `$GITHUB_STEP_SUMMARY` automatically, so failures render on the job page (#1015)
- `--gha-annotations <auto|always|never>` controls GitHub Actions annotations on stdout; `auto` turns them on inside GitHub Actions and stays quiet everywhere else (#1014)
- `--repeat <n>` runs each selected test n times so flakiness can be hunted before it reaches CI. The test is reported once with the aggregate outcome, a failure names the iteration it happened on, and repeat wraps `--retry` rather than the other way round (#1013)
- Flaky is a first-class outcome: a test that only passed after a retry is counted separately, kept inside the pass total so the exit code is unchanged, and carried into JUnit (`<flakyFailure>`), TAP, JSON, HTML and GitHub Actions along with the first attempt's failure message. `--fail-on-flaky` turns such a run red (#1012)
- `--order-by <mode>` picks the execution order: `defined` (default), `defects` (last run's failures first, whole suite still runs) or `random`. `--random-order` and `--seed` keep working unchanged (#1011)
- `--changed [<ref>]` runs only the test files git reports as touched since `<ref>` (default `origin/HEAD`, then `HEAD`), covering committed, staged, unstaged and untracked changes. Deletions are dropped, a rename selects its new path, and a missing work tree or unresolvable ref fails the run instead of selecting nothing (#1010)
- `--coverage-diff <ref>` limits the coverage console report to lines changed since a base ref; `--coverage-min` then gates on that diff percentage (#1032)
- `assert_command_available <command>` asserts a command, shell builtin or function resolves through `command -v` (#1027)
- `assert_between <min> <max> <actual>` and `assert_not_between` add inclusive numeric-range assertions for integers and decimals (#1026)
- `--list` (alias `--dry-run`) prints the tests a run would execute, without running them; `--list-format json` emits file, function, name, line and tags. Honours every selection flag, including `--shard` and `--random-order --seed` ordering (#1007)
- `--report-md <file>` writes a Markdown run summary: verdict, counts table, failures with their message, plus coverage and slowest tests when those ran. Inside GitHub Actions it is also appended to `$GITHUB_STEP_SUMMARY` (#1015)
- `--gha-annotations <auto|always|never>` controls GitHub Actions annotations on stdout; `auto` enables them only inside GitHub Actions (#1014)
- `--repeat <n>` runs each selected test n times to hunt flakiness before CI does. One report line with the aggregate outcome, a failure names its iteration, and repeat wraps `--retry` (#1013)
- Flaky is a first-class outcome: a test that only passed after a retry is counted separately, stays inside the pass total so the exit code is unchanged, and is carried into JUnit (`<flakyFailure>`), TAP, JSON, HTML and GitHub Actions with the first attempt's failure message. `--fail-on-flaky` turns such a run red (#1012)
- `--order-by <mode>` picks the execution order: `defined` (default), `defects` (last run's failures first, whole suite still runs) or `random`. `--random-order` and `--seed` are unchanged (#1011)
- `--changed [<ref>]` runs only the test files git reports as touched since `<ref>` (default `origin/HEAD`, then `HEAD`), covering committed, staged, unstaged and untracked changes. Deletions are dropped, a rename selects its new path, and a missing work tree or unresolvable ref fails the run (#1010)
- `--exclude-filter <name>` skips tests by name, the counterpart of `--exclude-tag`. Repeatable, OR'd, and wins over `--filter` (#1009)
- `# @tags a b` above any top-level line applies those tags to every test in the file, unioned with per-function `# @tag` (#1008)
- `--tag` accepts expressions: `'a&&b'` (AND) and `'!a'` (NOT), combinable as `'a&&!b'`. Repeated `--tag` flags keep OR semantics, and `--exclude-tag` still wins (#1008)
- `assert_command_available <command>` asserts that an external command, shell builtin or function resolves through `command -v` (#1027)
- The coverage engine in use is reported by `--verbose`, and an explicit `BASHUNIT_COVERAGE_ENGINE=xtrace` that the running Bash cannot honour now warns instead of being silently ignored (#1005)
- `# @tags a b` above any top-level line tags every test in the file, unioned with per-function `# @tag` (#1008)
- `--list` (alias `--dry-run`) prints the tests a run would execute without running them; `--list-format json` emits file, function, name, line and tags. Honours every selection flag, including `--shard` and `--random-order --seed` ordering (#1007)
- `--verbose` reports the coverage engine in use, and an explicit `BASHUNIT_COVERAGE_ENGINE=xtrace` the running Bash cannot honour now warns instead of being silently ignored (#1005)

### Changed
- The JUnit XML shape changed: one `<testsuite>` per test file (with its own counts, time and timestamp) instead of a single flat suite, `classname` on every `<testcase>`, `<failure message="...">` carrying the first informative line of the real message with `type="AssertionFailed"`, `<system-out>` with the test's captured output, and aggregate totals on `<testsuites>`. Consumers that group by suite or classname (Jenkins, GitLab, dorny/test-reporter) now get real groupings (#1016)
- Performance: `--coverage` is about 1.6x to 2.3x faster. Executable-line classification no longer forks `grep` per source line, which was roughly half of a coverage run's wall time and affected both engines equally (#1005)
- `bashunit test --help` lists `--show-skipped` and `--show-incomplete`, both accepted by the parser but never advertised. `BASHUNIT_COVERAGE_SHOW_FUNCTIONS` and `BASHUNIT_COVERAGE_SHOW_UNCOVERED` are registered in `src/config/env.sh` like every other setting, and `.env.example` now lists all 66 settings, 19 of which were missing
- Docs: a full audit of the reference pages against the code. `docs/configuration.md` gained the 17 settings it never documented, `docs/command-line.md` gained the `assert` subcommand section and had every stale example output replaced with a real one, the `assert_equals` examples in `docs/assertions.md` were inverted, and the coverage settings and diff-coverage narrative now live in one place instead of two that had drifted
- JUnit XML: one `<testsuite>` per test file with its own counts, time and timestamp instead of a single flat suite, `classname` on every `<testcase>`, `<failure message="...">` carrying the first informative line of the real message with `type="AssertionFailed"`, `<system-out>` with the test's captured output, and aggregate totals on `<testsuites>`. Consumers that group by suite or classname (Jenkins, GitLab, dorny/test-reporter) now get real groupings (#1016)
- Performance: `--coverage` is about 1.6x to 2.3x faster. Executable-line classification no longer forks `grep` per source line, roughly half of a coverage run's wall time on both engines (#1005)

### Fixed
- A failed assertion is no longer reported twice. `bashunit::assert_that` returns 1 on failure by design, so a custom assertion ending with it made the test body exit non-zero and the runner printed a spurious `✗ Error` on top of the `✗ Failed`. Custom assertions no longer need a trailing `return 0`; a real runtime error is still reported as an error
- `--coverage-report` with no value uses `coverage/lcov.info` instead of aborting the run with `$2: unbound variable`, and no longer consumes a following flag as its filename. Write the test path before it, since an optional value cannot be told apart from a path
- `--list --list-format json` reports each tag as its own array element. The emitter split the tag list on whitespace while every other consumer splits it on commas, so a test with two tags rendered as `["slow,fileTag"]` and a tag containing spaces was split into one element per word
- Build: the standalone binary size budget is 544 KiB, raised from 500 KiB after ordinary feature growth crossed it; the artifact keeps its indentation rather than being minified (#1045)
- `assert_within_delta` rejects malformed numbers such as `1.2.3` or `5-3` as non-numeric instead of leaking a raw `bc` parse error or silently evaluating them as an expression (#1026)
- `assert_within_delta` rejects malformed numbers such as `1.2.3` or `5-3` as non-numeric instead of leaking a raw `bc` parse error or evaluating them as an expression (#1026)
- Report formats are no longer empty under `--parallel`. `--report-junit`, `--report-tap`, `--report-json`, `--report-html` and `--log-junit` all recorded zero tests, because the rows were collected inside the per-test worker and nothing rebuilt them in the parent (#1004)

## [0.45.0](https://github.com/TypedDevs/bashunit/compare/0.44.0...0.45.0) - 2026-08-09
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,7 @@
## Why bashunit

A lightweight, fast testing framework for **Bash 3.0+**, focused on developer experience.
It ships 77 assertions plus spies, mocks, data providers, snapshots and more.
It ships 84 assertions plus spies, mocks, data providers, snapshots and more.

## Quick start

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' docs: bring the reference pages back to parity with the code by Chemaclass · Pull Request #1063 · TypedDevs/bashunit · GitHub
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
30 changes: 28 additions & 2 deletions .env.example
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,9 @@ BASHUNIT_PROFILE= # Default: false (report slowest tests after
BASHUNIT_PROFILE_COUNT= # Default: 10 (how many slowest tests to report)
BASHUNIT_NO_COLOR= # Default: false (disable colors)
BASHUNIT_NO_DIFF= # Default: false (disable unified diff on multiline assert failures)
BASHUNIT_NO_PROGRESS= # Default: false (suppress real-time progress, final results only)
BASHUNIT_SHOW_OUTPUT_ON_FAILURE= # Default: true (show captured test output on failure)
BASHUNIT_OUTPUT_FORMAT= # Default: empty (tap = TAP version 13 on stdout)

#───────────────────────────────────────────────────────────────────────────────
# Test Execution
Expand All@@ -49,20 +52,38 @@ BASHUNIT_GHA_ANNOTATIONS= # Default: auto (or always, never)
BASHUNIT_REPORT_MD= # Default: empty (Markdown summary path)
BASHUNIT_CHANGED= # Default: false (run only test files changed since a git ref)
BASHUNIT_CHANGED_REF= # Default: empty (--changed ref: origin/HEAD, then HEAD)
BASHUNIT_COVERAGE_DIFF= # Default: empty (restrict coverage to lines changed since this ref)
BASHUNIT_COVERAGE_DIFF= # Default: empty (restrict coverage to lines changed since this ref)
BASHUNIT_EXCLUDE_FILTER= # Default: empty (skip tests whose name matches)
BASHUNIT_LIST_TESTS= # Default: false (print the tests that would run, run none)
BASHUNIT_LIST_FORMAT= # Default: text (--list rendering: text or json)
BASHUNIT_STOP_ON_ASSERTION_FAILURE= # Default: true (stop test on first assertion fail)
BASHUNIT_STRICT_MODE= # Default: false (enable set -euo pipefail)
BASHUNIT_LOGIN_SHELL= # Default: false (source login shell profiles)
BASHUNIT_PARALLEL_JOBS= # Default: 0 (unlimited; N or auto caps concurrency)
BASHUNIT_RANDOM_ORDER= # Default: false (shuffle files and tests)
BASHUNIT_SEED= # Default: empty (pins --random-order for a replay)
BASHUNIT_RETRY= # Default: 0 (re-run a failed test up to N extra times)
BASHUNIT_TEST_TIMEOUT= # Default: 0 (off; fail a test running longer than N seconds)
BASHUNIT_SHARD_INDEX= # Default: empty (run shard i of BASHUNIT_SHARD_TOTAL)
BASHUNIT_SHARD_TOTAL= # Default: empty (how many shards to split the suite into)
BASHUNIT_SKIP_ENV_FILE= # Default: false (skip .env and .bashunitrc)
BASHUNIT_WATCH_INTERVAL= # Default: 3 (seconds between polls in the watch fallback)

#───────────────────────────────────────────────────────────────────────────────
# Reports
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_LOG_JUNIT= # JUnit XML report path (e.g., report.xml)
BASHUNIT_LOG_GHA= # GitHub Actions workflow-commands log path (e.g., gha.log)
BASHUNIT_REPORT_HTML= # HTML test report path (e.g., report.html)
BASHUNIT_REPORT_TAP= # TAP version 13 report path (e.g., report.tap)
BASHUNIT_REPORT_JSON= # JSON report path (e.g., report.json)

#───────────────────────────────────────────────────────────────────────────────
# Snapshots
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_SNAPSHOT_UPDATE= # Default: false (rewrite existing snapshots)
BASHUNIT_SNAPSHOT_CREATE= # Default: true (record a missing snapshot instead of failing)
BASHUNIT_SNAPSHOT_REPORT_UNUSED= # Default: false (list snapshots no test resolved)

#───────────────────────────────────────────────────────────────────────────────
# Code Coverage
Expand All@@ -74,11 +95,16 @@ BASHUNIT_COVERAGE_REPORT= # Default: coverage/lcov.info
BASHUNIT_COVERAGE_REPORT_HTML= # HTML coverage report directory (e.g., coverage/html)
BASHUNIT_COVERAGE_MIN= # Minimum coverage % (fails if below)
BASHUNIT_COVERAGE_THRESHOLD_LOW= # Default: 50 (red below this)
BASHUNIT_COVERAGE_THRESHOLD_HIGH= # Default: 80 (green above this)
BASHUNIT_COVERAGE_THRESHOLD_HIGH= # Default: 80 (green at or above this)
BASHUNIT_COVERAGE_ENGINE= # Default: auto (or xtrace, trap)
BASHUNIT_COVERAGE_SHOW_LINE_HITS= # Default: false (print per-line execution counts)
BASHUNIT_COVERAGE_SHOW_FUNCTIONS= # Default: false (print per-function coverage)
BASHUNIT_COVERAGE_SHOW_UNCOVERED= # Default: false (print missed line ranges per file)

#───────────────────────────────────────────────────────────────────────────────
# Advanced / Debug
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_DEV_LOG= # Developer log file path
BASHUNIT_BENCH_MODE= # Default: false (benchmark mode)
BASHUNIT_INTERNAL_LOG= # Default: false (internal debug logging)
BASHUNIT_NO_DEPRECATION_WARNINGS= # Default: false (silence unprefixed-alias warnings)
33 changes: 19 additions & 14 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,28 +3,33 @@
## Unreleased

### Added
- `--coverage-diff <ref>` restricts the coverage console report to lines changed since a base ref, and `--coverage-min` then gates on that diff percentage (#1032)
- `--report-md <file>` writes a Markdown run summary: verdict, counts table, failures with their message, plus coverage and slowest tests when those ran. Inside GitHub Actions it is appended to `$GITHUB_STEP_SUMMARY` automatically, so failures render on the job page (#1015)
- `--gha-annotations <auto|always|never>` controls GitHub Actions annotations on stdout; `auto` turns them on inside GitHub Actions and stays quiet everywhere else (#1014)
- `--repeat <n>` runs each selected test n times so flakiness can be hunted before it reaches CI. The test is reported once with the aggregate outcome, a failure names the iteration it happened on, and repeat wraps `--retry` rather than the other way round (#1013)
- Flaky is a first-class outcome: a test that only passed after a retry is counted separately, kept inside the pass total so the exit code is unchanged, and carried into JUnit (`<flakyFailure>`), TAP, JSON, HTML and GitHub Actions along with the first attempt's failure message. `--fail-on-flaky` turns such a run red (#1012)
- `--order-by <mode>` picks the execution order: `defined` (default), `defects` (last run's failures first, whole suite still runs) or `random`. `--random-order` and `--seed` keep working unchanged (#1011)
- `--changed [<ref>]` runs only the test files git reports as touched since `<ref>` (default `origin/HEAD`, then `HEAD`), covering committed, staged, unstaged and untracked changes. Deletions are dropped, a rename selects its new path, and a missing work tree or unresolvable ref fails the run instead of selecting nothing (#1010)
- `--coverage-diff <ref>` limits the coverage console report to lines changed since a base ref; `--coverage-min` then gates on that diff percentage (#1032)
- `assert_command_available <command>` asserts a command, shell builtin or function resolves through `command -v` (#1027)
- `assert_between <min> <max> <actual>` and `assert_not_between` add inclusive numeric-range assertions for integers and decimals (#1026)
- `--list` (alias `--dry-run`) prints the tests a run would execute, without running them; `--list-format json` emits file, function, name, line and tags. Honours every selection flag, including `--shard` and `--random-order --seed` ordering (#1007)
- `--report-md <file>` writes a Markdown run summary: verdict, counts table, failures with their message, plus coverage and slowest tests when those ran. Inside GitHub Actions it is also appended to `$GITHUB_STEP_SUMMARY` (#1015)
- `--gha-annotations <auto|always|never>` controls GitHub Actions annotations on stdout; `auto` enables them only inside GitHub Actions (#1014)
- `--repeat <n>` runs each selected test n times to hunt flakiness before CI does. One report line with the aggregate outcome, a failure names its iteration, and repeat wraps `--retry` (#1013)
- Flaky is a first-class outcome: a test that only passed after a retry is counted separately, stays inside the pass total so the exit code is unchanged, and is carried into JUnit (`<flakyFailure>`), TAP, JSON, HTML and GitHub Actions with the first attempt's failure message. `--fail-on-flaky` turns such a run red (#1012)
- `--order-by <mode>` picks the execution order: `defined` (default), `defects` (last run's failures first, whole suite still runs) or `random`. `--random-order` and `--seed` are unchanged (#1011)
- `--changed [<ref>]` runs only the test files git reports as touched since `<ref>` (default `origin/HEAD`, then `HEAD`), covering committed, staged, unstaged and untracked changes. Deletions are dropped, a rename selects its new path, and a missing work tree or unresolvable ref fails the run (#1010)
- `--exclude-filter <name>` skips tests by name, the counterpart of `--exclude-tag`. Repeatable, OR'd, and wins over `--filter` (#1009)
- `# @tags a b` above any top-level line applies those tags to every test in the file, unioned with per-function `# @tag` (#1008)
- `--tag` accepts expressions: `'a&&b'` (AND) and `'!a'` (NOT), combinable as `'a&&!b'`. Repeated `--tag` flags keep OR semantics, and `--exclude-tag` still wins (#1008)
- `assert_command_available <command>` asserts that an external command, shell builtin or function resolves through `command -v` (#1027)
- The coverage engine in use is reported by `--verbose`, and an explicit `BASHUNIT_COVERAGE_ENGINE=xtrace` that the running Bash cannot honour now warns instead of being silently ignored (#1005)
- `# @tags a b` above any top-level line tags every test in the file, unioned with per-function `# @tag` (#1008)
- `--list` (alias `--dry-run`) prints the tests a run would execute without running them; `--list-format json` emits file, function, name, line and tags. Honours every selection flag, including `--shard` and `--random-order --seed` ordering (#1007)
- `--verbose` reports the coverage engine in use, and an explicit `BASHUNIT_COVERAGE_ENGINE=xtrace` the running Bash cannot honour now warns instead of being silently ignored (#1005)

### Changed
- The JUnit XML shape changed: one `<testsuite>` per test file (with its own counts, time and timestamp) instead of a single flat suite, `classname` on every `<testcase>`, `<failure message="...">` carrying the first informative line of the real message with `type="AssertionFailed"`, `<system-out>` with the test's captured output, and aggregate totals on `<testsuites>`. Consumers that group by suite or classname (Jenkins, GitLab, dorny/test-reporter) now get real groupings (#1016)
- Performance: `--coverage` is about 1.6x to 2.3x faster. Executable-line classification no longer forks `grep` per source line, which was roughly half of a coverage run's wall time and affected both engines equally (#1005)
- `bashunit test --help` lists `--show-skipped` and `--show-incomplete`, both accepted by the parser but never advertised. `BASHUNIT_COVERAGE_SHOW_FUNCTIONS` and `BASHUNIT_COVERAGE_SHOW_UNCOVERED` are registered in `src/config/env.sh` like every other setting, and `.env.example` now lists all 66 settings, 19 of which were missing
- Docs: a full audit of the reference pages against the code. `docs/configuration.md` gained the 17 settings it never documented, `docs/command-line.md` gained the `assert` subcommand section and had every stale example output replaced with a real one, the `assert_equals` examples in `docs/assertions.md` were inverted, and the coverage settings and diff-coverage narrative now live in one place instead of two that had drifted
- JUnit XML: one `<testsuite>` per test file with its own counts, time and timestamp instead of a single flat suite, `classname` on every `<testcase>`, `<failure message="...">` carrying the first informative line of the real message with `type="AssertionFailed"`, `<system-out>` with the test's captured output, and aggregate totals on `<testsuites>`. Consumers that group by suite or classname (Jenkins, GitLab, dorny/test-reporter) now get real groupings (#1016)
- Performance: `--coverage` is about 1.6x to 2.3x faster. Executable-line classification no longer forks `grep` per source line, roughly half of a coverage run's wall time on both engines (#1005)

### Fixed
- A failed assertion is no longer reported twice. `bashunit::assert_that` returns 1 on failure by design, so a custom assertion ending with it made the test body exit non-zero and the runner printed a spurious `✗ Error` on top of the `✗ Failed`. Custom assertions no longer need a trailing `return 0`; a real runtime error is still reported as an error
- `--coverage-report` with no value uses `coverage/lcov.info` instead of aborting the run with `$2: unbound variable`, and no longer consumes a following flag as its filename. Write the test path before it, since an optional value cannot be told apart from a path
- `--list --list-format json` reports each tag as its own array element. The emitter split the tag list on whitespace while every other consumer splits it on commas, so a test with two tags rendered as `["slow,fileTag"]` and a tag containing spaces was split into one element per word
- Build: the standalone binary size budget is 544 KiB, raised from 500 KiB after ordinary feature growth crossed it; the artifact keeps its indentation rather than being minified (#1045)
- `assert_within_delta` rejects malformed numbers such as `1.2.3` or `5-3` as non-numeric instead of leaking a raw `bc` parse error or silently evaluating them as an expression (#1026)
- `assert_within_delta` rejects malformed numbers such as `1.2.3` or `5-3` as non-numeric instead of leaking a raw `bc` parse error or evaluating them as an expression (#1026)
- Report formats are no longer empty under `--parallel`. `--report-junit`, `--report-tap`, `--report-json`, `--report-html` and `--log-junit` all recorded zero tests, because the rows were collected inside the per-test worker and nothing rebuilt them in the parent (#1004)

## [0.45.0](https://github.com/TypedDevs/bashunit/compare/0.44.0...0.45.0) - 2026-08-09
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,7 @@
## Why bashunit

A lightweight, fast testing framework for **Bash 3.0+**, focused on developer experience.
It ships 77 assertions plus spies, mocks, data providers, snapshots and more.
It ships 84 assertions plus spies, mocks, data providers, snapshots and more.

## Quick start

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' docs: bring the reference pages back to parity with the code by Chemaclass · Pull Request #1063 · TypedDevs/bashunit · GitHub
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
30 changes: 28 additions & 2 deletions .env.example
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,9 @@ BASHUNIT_PROFILE= # Default: false (report slowest tests after
BASHUNIT_PROFILE_COUNT= # Default: 10 (how many slowest tests to report)
BASHUNIT_NO_COLOR= # Default: false (disable colors)
BASHUNIT_NO_DIFF= # Default: false (disable unified diff on multiline assert failures)
BASHUNIT_NO_PROGRESS= # Default: false (suppress real-time progress, final results only)
BASHUNIT_SHOW_OUTPUT_ON_FAILURE= # Default: true (show captured test output on failure)
BASHUNIT_OUTPUT_FORMAT= # Default: empty (tap = TAP version 13 on stdout)

#───────────────────────────────────────────────────────────────────────────────
# Test Execution
Expand All@@ -49,20 +52,38 @@ BASHUNIT_GHA_ANNOTATIONS= # Default: auto (or always, never)
BASHUNIT_REPORT_MD= # Default: empty (Markdown summary path)
BASHUNIT_CHANGED= # Default: false (run only test files changed since a git ref)
BASHUNIT_CHANGED_REF= # Default: empty (--changed ref: origin/HEAD, then HEAD)
BASHUNIT_COVERAGE_DIFF= # Default: empty (restrict coverage to lines changed since this ref)
BASHUNIT_COVERAGE_DIFF= # Default: empty (restrict coverage to lines changed since this ref)
BASHUNIT_EXCLUDE_FILTER= # Default: empty (skip tests whose name matches)
BASHUNIT_LIST_TESTS= # Default: false (print the tests that would run, run none)
BASHUNIT_LIST_FORMAT= # Default: text (--list rendering: text or json)
BASHUNIT_STOP_ON_ASSERTION_FAILURE= # Default: true (stop test on first assertion fail)
BASHUNIT_STRICT_MODE= # Default: false (enable set -euo pipefail)
BASHUNIT_LOGIN_SHELL= # Default: false (source login shell profiles)
BASHUNIT_PARALLEL_JOBS= # Default: 0 (unlimited; N or auto caps concurrency)
BASHUNIT_RANDOM_ORDER= # Default: false (shuffle files and tests)
BASHUNIT_SEED= # Default: empty (pins --random-order for a replay)
BASHUNIT_RETRY= # Default: 0 (re-run a failed test up to N extra times)
BASHUNIT_TEST_TIMEOUT= # Default: 0 (off; fail a test running longer than N seconds)
BASHUNIT_SHARD_INDEX= # Default: empty (run shard i of BASHUNIT_SHARD_TOTAL)
BASHUNIT_SHARD_TOTAL= # Default: empty (how many shards to split the suite into)
BASHUNIT_SKIP_ENV_FILE= # Default: false (skip .env and .bashunitrc)
BASHUNIT_WATCH_INTERVAL= # Default: 3 (seconds between polls in the watch fallback)

#───────────────────────────────────────────────────────────────────────────────
# Reports
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_LOG_JUNIT= # JUnit XML report path (e.g., report.xml)
BASHUNIT_LOG_GHA= # GitHub Actions workflow-commands log path (e.g., gha.log)
BASHUNIT_REPORT_HTML= # HTML test report path (e.g., report.html)
BASHUNIT_REPORT_TAP= # TAP version 13 report path (e.g., report.tap)
BASHUNIT_REPORT_JSON= # JSON report path (e.g., report.json)

#───────────────────────────────────────────────────────────────────────────────
# Snapshots
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_SNAPSHOT_UPDATE= # Default: false (rewrite existing snapshots)
BASHUNIT_SNAPSHOT_CREATE= # Default: true (record a missing snapshot instead of failing)
BASHUNIT_SNAPSHOT_REPORT_UNUSED= # Default: false (list snapshots no test resolved)

#───────────────────────────────────────────────────────────────────────────────
# Code Coverage
Expand All@@ -74,11 +95,16 @@ BASHUNIT_COVERAGE_REPORT= # Default: coverage/lcov.info
BASHUNIT_COVERAGE_REPORT_HTML= # HTML coverage report directory (e.g., coverage/html)
BASHUNIT_COVERAGE_MIN= # Minimum coverage % (fails if below)
BASHUNIT_COVERAGE_THRESHOLD_LOW= # Default: 50 (red below this)
BASHUNIT_COVERAGE_THRESHOLD_HIGH= # Default: 80 (green above this)
BASHUNIT_COVERAGE_THRESHOLD_HIGH= # Default: 80 (green at or above this)
BASHUNIT_COVERAGE_ENGINE= # Default: auto (or xtrace, trap)
BASHUNIT_COVERAGE_SHOW_LINE_HITS= # Default: false (print per-line execution counts)
BASHUNIT_COVERAGE_SHOW_FUNCTIONS= # Default: false (print per-function coverage)
BASHUNIT_COVERAGE_SHOW_UNCOVERED= # Default: false (print missed line ranges per file)

#───────────────────────────────────────────────────────────────────────────────
# Advanced / Debug
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_DEV_LOG= # Developer log file path
BASHUNIT_BENCH_MODE= # Default: false (benchmark mode)
BASHUNIT_INTERNAL_LOG= # Default: false (internal debug logging)
BASHUNIT_NO_DEPRECATION_WARNINGS= # Default: false (silence unprefixed-alias warnings)
33 changes: 19 additions & 14 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,28 +3,33 @@
## Unreleased

### Added
- `--coverage-diff <ref>` restricts the coverage console report to lines changed since a base ref, and `--coverage-min` then gates on that diff percentage (#1032)
- `--report-md <file>` writes a Markdown run summary: verdict, counts table, failures with their message, plus coverage and slowest tests when those ran. Inside GitHub Actions it is appended to `$GITHUB_STEP_SUMMARY` automatically, so failures render on the job page (#1015)
- `--gha-annotations <auto|always|never>` controls GitHub Actions annotations on stdout; `auto` turns them on inside GitHub Actions and stays quiet everywhere else (#1014)
- `--repeat <n>` runs each selected test n times so flakiness can be hunted before it reaches CI. The test is reported once with the aggregate outcome, a failure names the iteration it happened on, and repeat wraps `--retry` rather than the other way round (#1013)
- Flaky is a first-class outcome: a test that only passed after a retry is counted separately, kept inside the pass total so the exit code is unchanged, and carried into JUnit (`<flakyFailure>`), TAP, JSON, HTML and GitHub Actions along with the first attempt's failure message. `--fail-on-flaky` turns such a run red (#1012)
- `--order-by <mode>` picks the execution order: `defined` (default), `defects` (last run's failures first, whole suite still runs) or `random`. `--random-order` and `--seed` keep working unchanged (#1011)
- `--changed [<ref>]` runs only the test files git reports as touched since `<ref>` (default `origin/HEAD`, then `HEAD`), covering committed, staged, unstaged and untracked changes. Deletions are dropped, a rename selects its new path, and a missing work tree or unresolvable ref fails the run instead of selecting nothing (#1010)
- `--coverage-diff <ref>` limits the coverage console report to lines changed since a base ref; `--coverage-min` then gates on that diff percentage (#1032)
- `assert_command_available <command>` asserts a command, shell builtin or function resolves through `command -v` (#1027)
- `assert_between <min> <max> <actual>` and `assert_not_between` add inclusive numeric-range assertions for integers and decimals (#1026)
- `--list` (alias `--dry-run`) prints the tests a run would execute, without running them; `--list-format json` emits file, function, name, line and tags. Honours every selection flag, including `--shard` and `--random-order --seed` ordering (#1007)
- `--report-md <file>` writes a Markdown run summary: verdict, counts table, failures with their message, plus coverage and slowest tests when those ran. Inside GitHub Actions it is also appended to `$GITHUB_STEP_SUMMARY` (#1015)
- `--gha-annotations <auto|always|never>` controls GitHub Actions annotations on stdout; `auto` enables them only inside GitHub Actions (#1014)
- `--repeat <n>` runs each selected test n times to hunt flakiness before CI does. One report line with the aggregate outcome, a failure names its iteration, and repeat wraps `--retry` (#1013)
- Flaky is a first-class outcome: a test that only passed after a retry is counted separately, stays inside the pass total so the exit code is unchanged, and is carried into JUnit (`<flakyFailure>`), TAP, JSON, HTML and GitHub Actions with the first attempt's failure message. `--fail-on-flaky` turns such a run red (#1012)
- `--order-by <mode>` picks the execution order: `defined` (default), `defects` (last run's failures first, whole suite still runs) or `random`. `--random-order` and `--seed` are unchanged (#1011)
- `--changed [<ref>]` runs only the test files git reports as touched since `<ref>` (default `origin/HEAD`, then `HEAD`), covering committed, staged, unstaged and untracked changes. Deletions are dropped, a rename selects its new path, and a missing work tree or unresolvable ref fails the run (#1010)
- `--exclude-filter <name>` skips tests by name, the counterpart of `--exclude-tag`. Repeatable, OR'd, and wins over `--filter` (#1009)
- `# @tags a b` above any top-level line applies those tags to every test in the file, unioned with per-function `# @tag` (#1008)
- `--tag` accepts expressions: `'a&&b'` (AND) and `'!a'` (NOT), combinable as `'a&&!b'`. Repeated `--tag` flags keep OR semantics, and `--exclude-tag` still wins (#1008)
- `assert_command_available <command>` asserts that an external command, shell builtin or function resolves through `command -v` (#1027)
- The coverage engine in use is reported by `--verbose`, and an explicit `BASHUNIT_COVERAGE_ENGINE=xtrace` that the running Bash cannot honour now warns instead of being silently ignored (#1005)
- `# @tags a b` above any top-level line tags every test in the file, unioned with per-function `# @tag` (#1008)
- `--list` (alias `--dry-run`) prints the tests a run would execute without running them; `--list-format json` emits file, function, name, line and tags. Honours every selection flag, including `--shard` and `--random-order --seed` ordering (#1007)
- `--verbose` reports the coverage engine in use, and an explicit `BASHUNIT_COVERAGE_ENGINE=xtrace` the running Bash cannot honour now warns instead of being silently ignored (#1005)

### Changed
- The JUnit XML shape changed: one `<testsuite>` per test file (with its own counts, time and timestamp) instead of a single flat suite, `classname` on every `<testcase>`, `<failure message="...">` carrying the first informative line of the real message with `type="AssertionFailed"`, `<system-out>` with the test's captured output, and aggregate totals on `<testsuites>`. Consumers that group by suite or classname (Jenkins, GitLab, dorny/test-reporter) now get real groupings (#1016)
- Performance: `--coverage` is about 1.6x to 2.3x faster. Executable-line classification no longer forks `grep` per source line, which was roughly half of a coverage run's wall time and affected both engines equally (#1005)
- `bashunit test --help` lists `--show-skipped` and `--show-incomplete`, both accepted by the parser but never advertised. `BASHUNIT_COVERAGE_SHOW_FUNCTIONS` and `BASHUNIT_COVERAGE_SHOW_UNCOVERED` are registered in `src/config/env.sh` like every other setting, and `.env.example` now lists all 66 settings, 19 of which were missing
- Docs: a full audit of the reference pages against the code. `docs/configuration.md` gained the 17 settings it never documented, `docs/command-line.md` gained the `assert` subcommand section and had every stale example output replaced with a real one, the `assert_equals` examples in `docs/assertions.md` were inverted, and the coverage settings and diff-coverage narrative now live in one place instead of two that had drifted
- JUnit XML: one `<testsuite>` per test file with its own counts, time and timestamp instead of a single flat suite, `classname` on every `<testcase>`, `<failure message="...">` carrying the first informative line of the real message with `type="AssertionFailed"`, `<system-out>` with the test's captured output, and aggregate totals on `<testsuites>`. Consumers that group by suite or classname (Jenkins, GitLab, dorny/test-reporter) now get real groupings (#1016)
- Performance: `--coverage` is about 1.6x to 2.3x faster. Executable-line classification no longer forks `grep` per source line, roughly half of a coverage run's wall time on both engines (#1005)

### Fixed
- A failed assertion is no longer reported twice. `bashunit::assert_that` returns 1 on failure by design, so a custom assertion ending with it made the test body exit non-zero and the runner printed a spurious `✗ Error` on top of the `✗ Failed`. Custom assertions no longer need a trailing `return 0`; a real runtime error is still reported as an error
- `--coverage-report` with no value uses `coverage/lcov.info` instead of aborting the run with `$2: unbound variable`, and no longer consumes a following flag as its filename. Write the test path before it, since an optional value cannot be told apart from a path
- `--list --list-format json` reports each tag as its own array element. The emitter split the tag list on whitespace while every other consumer splits it on commas, so a test with two tags rendered as `["slow,fileTag"]` and a tag containing spaces was split into one element per word
- Build: the standalone binary size budget is 544 KiB, raised from 500 KiB after ordinary feature growth crossed it; the artifact keeps its indentation rather than being minified (#1045)
- `assert_within_delta` rejects malformed numbers such as `1.2.3` or `5-3` as non-numeric instead of leaking a raw `bc` parse error or silently evaluating them as an expression (#1026)
- `assert_within_delta` rejects malformed numbers such as `1.2.3` or `5-3` as non-numeric instead of leaking a raw `bc` parse error or evaluating them as an expression (#1026)
- Report formats are no longer empty under `--parallel`. `--report-junit`, `--report-tap`, `--report-json`, `--report-html` and `--log-junit` all recorded zero tests, because the rows were collected inside the per-test worker and nothing rebuilt them in the parent (#1004)

## [0.45.0](https://github.com/TypedDevs/bashunit/compare/0.44.0...0.45.0) - 2026-08-09
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,7 @@
## Why bashunit

A lightweight, fast testing framework for **Bash 3.0+**, focused on developer experience.
It ships 77 assertions plus spies, mocks, data providers, snapshots and more.
It ships 84 assertions plus spies, mocks, data providers, snapshots and more.

## Quick start

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' docs: bring the reference pages back to parity with the code by Chemaclass · Pull Request #1063 · TypedDevs/bashunit · GitHub
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
30 changes: 28 additions & 2 deletions .env.example
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,9 @@ BASHUNIT_PROFILE= # Default: false (report slowest tests after
BASHUNIT_PROFILE_COUNT= # Default: 10 (how many slowest tests to report)
BASHUNIT_NO_COLOR= # Default: false (disable colors)
BASHUNIT_NO_DIFF= # Default: false (disable unified diff on multiline assert failures)
BASHUNIT_NO_PROGRESS= # Default: false (suppress real-time progress, final results only)
BASHUNIT_SHOW_OUTPUT_ON_FAILURE= # Default: true (show captured test output on failure)
BASHUNIT_OUTPUT_FORMAT= # Default: empty (tap = TAP version 13 on stdout)

#───────────────────────────────────────────────────────────────────────────────
# Test Execution
Expand All@@ -49,20 +52,38 @@ BASHUNIT_GHA_ANNOTATIONS= # Default: auto (or always, never)
BASHUNIT_REPORT_MD= # Default: empty (Markdown summary path)
BASHUNIT_CHANGED= # Default: false (run only test files changed since a git ref)
BASHUNIT_CHANGED_REF= # Default: empty (--changed ref: origin/HEAD, then HEAD)
BASHUNIT_COVERAGE_DIFF= # Default: empty (restrict coverage to lines changed since this ref)
BASHUNIT_COVERAGE_DIFF= # Default: empty (restrict coverage to lines changed since this ref)
BASHUNIT_EXCLUDE_FILTER= # Default: empty (skip tests whose name matches)
BASHUNIT_LIST_TESTS= # Default: false (print the tests that would run, run none)
BASHUNIT_LIST_FORMAT= # Default: text (--list rendering: text or json)
BASHUNIT_STOP_ON_ASSERTION_FAILURE= # Default: true (stop test on first assertion fail)
BASHUNIT_STRICT_MODE= # Default: false (enable set -euo pipefail)
BASHUNIT_LOGIN_SHELL= # Default: false (source login shell profiles)
BASHUNIT_PARALLEL_JOBS= # Default: 0 (unlimited; N or auto caps concurrency)
BASHUNIT_RANDOM_ORDER= # Default: false (shuffle files and tests)
BASHUNIT_SEED= # Default: empty (pins --random-order for a replay)
BASHUNIT_RETRY= # Default: 0 (re-run a failed test up to N extra times)
BASHUNIT_TEST_TIMEOUT= # Default: 0 (off; fail a test running longer than N seconds)
BASHUNIT_SHARD_INDEX= # Default: empty (run shard i of BASHUNIT_SHARD_TOTAL)
BASHUNIT_SHARD_TOTAL= # Default: empty (how many shards to split the suite into)
BASHUNIT_SKIP_ENV_FILE= # Default: false (skip .env and .bashunitrc)
BASHUNIT_WATCH_INTERVAL= # Default: 3 (seconds between polls in the watch fallback)

#───────────────────────────────────────────────────────────────────────────────
# Reports
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_LOG_JUNIT= # JUnit XML report path (e.g., report.xml)
BASHUNIT_LOG_GHA= # GitHub Actions workflow-commands log path (e.g., gha.log)
BASHUNIT_REPORT_HTML= # HTML test report path (e.g., report.html)
BASHUNIT_REPORT_TAP= # TAP version 13 report path (e.g., report.tap)
BASHUNIT_REPORT_JSON= # JSON report path (e.g., report.json)

#───────────────────────────────────────────────────────────────────────────────
# Snapshots
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_SNAPSHOT_UPDATE= # Default: false (rewrite existing snapshots)
BASHUNIT_SNAPSHOT_CREATE= # Default: true (record a missing snapshot instead of failing)
BASHUNIT_SNAPSHOT_REPORT_UNUSED= # Default: false (list snapshots no test resolved)

#───────────────────────────────────────────────────────────────────────────────
# Code Coverage
Expand All@@ -74,11 +95,16 @@ BASHUNIT_COVERAGE_REPORT= # Default: coverage/lcov.info
BASHUNIT_COVERAGE_REPORT_HTML= # HTML coverage report directory (e.g., coverage/html)
BASHUNIT_COVERAGE_MIN= # Minimum coverage % (fails if below)
BASHUNIT_COVERAGE_THRESHOLD_LOW= # Default: 50 (red below this)
BASHUNIT_COVERAGE_THRESHOLD_HIGH= # Default: 80 (green above this)
BASHUNIT_COVERAGE_THRESHOLD_HIGH= # Default: 80 (green at or above this)
BASHUNIT_COVERAGE_ENGINE= # Default: auto (or xtrace, trap)
BASHUNIT_COVERAGE_SHOW_LINE_HITS= # Default: false (print per-line execution counts)
BASHUNIT_COVERAGE_SHOW_FUNCTIONS= # Default: false (print per-function coverage)
BASHUNIT_COVERAGE_SHOW_UNCOVERED= # Default: false (print missed line ranges per file)

#───────────────────────────────────────────────────────────────────────────────
# Advanced / Debug
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_DEV_LOG= # Developer log file path
BASHUNIT_BENCH_MODE= # Default: false (benchmark mode)
BASHUNIT_INTERNAL_LOG= # Default: false (internal debug logging)
BASHUNIT_NO_DEPRECATION_WARNINGS= # Default: false (silence unprefixed-alias warnings)
33 changes: 19 additions & 14 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,28 +3,33 @@
## Unreleased

### Added
- `--coverage-diff <ref>` restricts the coverage console report to lines changed since a base ref, and `--coverage-min` then gates on that diff percentage (#1032)
- `--report-md <file>` writes a Markdown run summary: verdict, counts table, failures with their message, plus coverage and slowest tests when those ran. Inside GitHub Actions it is appended to `$GITHUB_STEP_SUMMARY` automatically, so failures render on the job page (#1015)
- `--gha-annotations <auto|always|never>` controls GitHub Actions annotations on stdout; `auto` turns them on inside GitHub Actions and stays quiet everywhere else (#1014)
- `--repeat <n>` runs each selected test n times so flakiness can be hunted before it reaches CI. The test is reported once with the aggregate outcome, a failure names the iteration it happened on, and repeat wraps `--retry` rather than the other way round (#1013)
- Flaky is a first-class outcome: a test that only passed after a retry is counted separately, kept inside the pass total so the exit code is unchanged, and carried into JUnit (`<flakyFailure>`), TAP, JSON, HTML and GitHub Actions along with the first attempt's failure message. `--fail-on-flaky` turns such a run red (#1012)
- `--order-by <mode>` picks the execution order: `defined` (default), `defects` (last run's failures first, whole suite still runs) or `random`. `--random-order` and `--seed` keep working unchanged (#1011)
- `--changed [<ref>]` runs only the test files git reports as touched since `<ref>` (default `origin/HEAD`, then `HEAD`), covering committed, staged, unstaged and untracked changes. Deletions are dropped, a rename selects its new path, and a missing work tree or unresolvable ref fails the run instead of selecting nothing (#1010)
- `--coverage-diff <ref>` limits the coverage console report to lines changed since a base ref; `--coverage-min` then gates on that diff percentage (#1032)
- `assert_command_available <command>` asserts a command, shell builtin or function resolves through `command -v` (#1027)
- `assert_between <min> <max> <actual>` and `assert_not_between` add inclusive numeric-range assertions for integers and decimals (#1026)
- `--list` (alias `--dry-run`) prints the tests a run would execute, without running them; `--list-format json` emits file, function, name, line and tags. Honours every selection flag, including `--shard` and `--random-order --seed` ordering (#1007)
- `--report-md <file>` writes a Markdown run summary: verdict, counts table, failures with their message, plus coverage and slowest tests when those ran. Inside GitHub Actions it is also appended to `$GITHUB_STEP_SUMMARY` (#1015)
- `--gha-annotations <auto|always|never>` controls GitHub Actions annotations on stdout; `auto` enables them only inside GitHub Actions (#1014)
- `--repeat <n>` runs each selected test n times to hunt flakiness before CI does. One report line with the aggregate outcome, a failure names its iteration, and repeat wraps `--retry` (#1013)
- Flaky is a first-class outcome: a test that only passed after a retry is counted separately, stays inside the pass total so the exit code is unchanged, and is carried into JUnit (`<flakyFailure>`), TAP, JSON, HTML and GitHub Actions with the first attempt's failure message. `--fail-on-flaky` turns such a run red (#1012)
- `--order-by <mode>` picks the execution order: `defined` (default), `defects` (last run's failures first, whole suite still runs) or `random`. `--random-order` and `--seed` are unchanged (#1011)
- `--changed [<ref>]` runs only the test files git reports as touched since `<ref>` (default `origin/HEAD`, then `HEAD`), covering committed, staged, unstaged and untracked changes. Deletions are dropped, a rename selects its new path, and a missing work tree or unresolvable ref fails the run (#1010)
- `--exclude-filter <name>` skips tests by name, the counterpart of `--exclude-tag`. Repeatable, OR'd, and wins over `--filter` (#1009)
- `# @tags a b` above any top-level line applies those tags to every test in the file, unioned with per-function `# @tag` (#1008)
- `--tag` accepts expressions: `'a&&b'` (AND) and `'!a'` (NOT), combinable as `'a&&!b'`. Repeated `--tag` flags keep OR semantics, and `--exclude-tag` still wins (#1008)
- `assert_command_available <command>` asserts that an external command, shell builtin or function resolves through `command -v` (#1027)
- The coverage engine in use is reported by `--verbose`, and an explicit `BASHUNIT_COVERAGE_ENGINE=xtrace` that the running Bash cannot honour now warns instead of being silently ignored (#1005)
- `# @tags a b` above any top-level line tags every test in the file, unioned with per-function `# @tag` (#1008)
- `--list` (alias `--dry-run`) prints the tests a run would execute without running them; `--list-format json` emits file, function, name, line and tags. Honours every selection flag, including `--shard` and `--random-order --seed` ordering (#1007)
- `--verbose` reports the coverage engine in use, and an explicit `BASHUNIT_COVERAGE_ENGINE=xtrace` the running Bash cannot honour now warns instead of being silently ignored (#1005)

### Changed
- The JUnit XML shape changed: one `<testsuite>` per test file (with its own counts, time and timestamp) instead of a single flat suite, `classname` on every `<testcase>`, `<failure message="...">` carrying the first informative line of the real message with `type="AssertionFailed"`, `<system-out>` with the test's captured output, and aggregate totals on `<testsuites>`. Consumers that group by suite or classname (Jenkins, GitLab, dorny/test-reporter) now get real groupings (#1016)
- Performance: `--coverage` is about 1.6x to 2.3x faster. Executable-line classification no longer forks `grep` per source line, which was roughly half of a coverage run's wall time and affected both engines equally (#1005)
- `bashunit test --help` lists `--show-skipped` and `--show-incomplete`, both accepted by the parser but never advertised. `BASHUNIT_COVERAGE_SHOW_FUNCTIONS` and `BASHUNIT_COVERAGE_SHOW_UNCOVERED` are registered in `src/config/env.sh` like every other setting, and `.env.example` now lists all 66 settings, 19 of which were missing
- Docs: a full audit of the reference pages against the code. `docs/configuration.md` gained the 17 settings it never documented, `docs/command-line.md` gained the `assert` subcommand section and had every stale example output replaced with a real one, the `assert_equals` examples in `docs/assertions.md` were inverted, and the coverage settings and diff-coverage narrative now live in one place instead of two that had drifted
- JUnit XML: one `<testsuite>` per test file with its own counts, time and timestamp instead of a single flat suite, `classname` on every `<testcase>`, `<failure message="...">` carrying the first informative line of the real message with `type="AssertionFailed"`, `<system-out>` with the test's captured output, and aggregate totals on `<testsuites>`. Consumers that group by suite or classname (Jenkins, GitLab, dorny/test-reporter) now get real groupings (#1016)
- Performance: `--coverage` is about 1.6x to 2.3x faster. Executable-line classification no longer forks `grep` per source line, roughly half of a coverage run's wall time on both engines (#1005)

### Fixed
- A failed assertion is no longer reported twice. `bashunit::assert_that` returns 1 on failure by design, so a custom assertion ending with it made the test body exit non-zero and the runner printed a spurious `✗ Error` on top of the `✗ Failed`. Custom assertions no longer need a trailing `return 0`; a real runtime error is still reported as an error
- `--coverage-report` with no value uses `coverage/lcov.info` instead of aborting the run with `$2: unbound variable`, and no longer consumes a following flag as its filename. Write the test path before it, since an optional value cannot be told apart from a path
- `--list --list-format json` reports each tag as its own array element. The emitter split the tag list on whitespace while every other consumer splits it on commas, so a test with two tags rendered as `["slow,fileTag"]` and a tag containing spaces was split into one element per word
- Build: the standalone binary size budget is 544 KiB, raised from 500 KiB after ordinary feature growth crossed it; the artifact keeps its indentation rather than being minified (#1045)
- `assert_within_delta` rejects malformed numbers such as `1.2.3` or `5-3` as non-numeric instead of leaking a raw `bc` parse error or silently evaluating them as an expression (#1026)
- `assert_within_delta` rejects malformed numbers such as `1.2.3` or `5-3` as non-numeric instead of leaking a raw `bc` parse error or evaluating them as an expression (#1026)
- Report formats are no longer empty under `--parallel`. `--report-junit`, `--report-tap`, `--report-json`, `--report-html` and `--log-junit` all recorded zero tests, because the rows were collected inside the per-test worker and nothing rebuilt them in the parent (#1004)

## [0.45.0](https://github.com/TypedDevs/bashunit/compare/0.44.0...0.45.0) - 2026-08-09
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,7 @@
## Why bashunit

A lightweight, fast testing framework for **Bash 3.0+**, focused on developer experience.
It ships 77 assertions plus spies, mocks, data providers, snapshots and more.
It ships 84 assertions plus spies, mocks, data providers, snapshots and more.

## Quick start

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); docs: bring the reference pages back to parity with the code by Chemaclass · Pull Request #1063 · TypedDevs/bashunit · GitHub
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
30 changes: 28 additions & 2 deletions .env.example
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,9 @@ BASHUNIT_PROFILE= # Default: false (report slowest tests after
BASHUNIT_PROFILE_COUNT= # Default: 10 (how many slowest tests to report)
BASHUNIT_NO_COLOR= # Default: false (disable colors)
BASHUNIT_NO_DIFF= # Default: false (disable unified diff on multiline assert failures)
BASHUNIT_NO_PROGRESS= # Default: false (suppress real-time progress, final results only)
BASHUNIT_SHOW_OUTPUT_ON_FAILURE= # Default: true (show captured test output on failure)
BASHUNIT_OUTPUT_FORMAT= # Default: empty (tap = TAP version 13 on stdout)

#───────────────────────────────────────────────────────────────────────────────
# Test Execution
Expand All@@ -49,20 +52,38 @@ BASHUNIT_GHA_ANNOTATIONS= # Default: auto (or always, never)
BASHUNIT_REPORT_MD= # Default: empty (Markdown summary path)
BASHUNIT_CHANGED= # Default: false (run only test files changed since a git ref)
BASHUNIT_CHANGED_REF= # Default: empty (--changed ref: origin/HEAD, then HEAD)
BASHUNIT_COVERAGE_DIFF= # Default: empty (restrict coverage to lines changed since this ref)
BASHUNIT_COVERAGE_DIFF= # Default: empty (restrict coverage to lines changed since this ref)
BASHUNIT_EXCLUDE_FILTER= # Default: empty (skip tests whose name matches)
BASHUNIT_LIST_TESTS= # Default: false (print the tests that would run, run none)
BASHUNIT_LIST_FORMAT= # Default: text (--list rendering: text or json)
BASHUNIT_STOP_ON_ASSERTION_FAILURE= # Default: true (stop test on first assertion fail)
BASHUNIT_STRICT_MODE= # Default: false (enable set -euo pipefail)
BASHUNIT_LOGIN_SHELL= # Default: false (source login shell profiles)
BASHUNIT_PARALLEL_JOBS= # Default: 0 (unlimited; N or auto caps concurrency)
BASHUNIT_RANDOM_ORDER= # Default: false (shuffle files and tests)
BASHUNIT_SEED= # Default: empty (pins --random-order for a replay)
BASHUNIT_RETRY= # Default: 0 (re-run a failed test up to N extra times)
BASHUNIT_TEST_TIMEOUT= # Default: 0 (off; fail a test running longer than N seconds)
BASHUNIT_SHARD_INDEX= # Default: empty (run shard i of BASHUNIT_SHARD_TOTAL)
BASHUNIT_SHARD_TOTAL= # Default: empty (how many shards to split the suite into)
BASHUNIT_SKIP_ENV_FILE= # Default: false (skip .env and .bashunitrc)
BASHUNIT_WATCH_INTERVAL= # Default: 3 (seconds between polls in the watch fallback)

#───────────────────────────────────────────────────────────────────────────────
# Reports
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_LOG_JUNIT= # JUnit XML report path (e.g., report.xml)
BASHUNIT_LOG_GHA= # GitHub Actions workflow-commands log path (e.g., gha.log)
BASHUNIT_REPORT_HTML= # HTML test report path (e.g., report.html)
BASHUNIT_REPORT_TAP= # TAP version 13 report path (e.g., report.tap)
BASHUNIT_REPORT_JSON= # JSON report path (e.g., report.json)

#───────────────────────────────────────────────────────────────────────────────
# Snapshots
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_SNAPSHOT_UPDATE= # Default: false (rewrite existing snapshots)
BASHUNIT_SNAPSHOT_CREATE= # Default: true (record a missing snapshot instead of failing)
BASHUNIT_SNAPSHOT_REPORT_UNUSED= # Default: false (list snapshots no test resolved)

#───────────────────────────────────────────────────────────────────────────────
# Code Coverage
Expand All@@ -74,11 +95,16 @@ BASHUNIT_COVERAGE_REPORT= # Default: coverage/lcov.info
BASHUNIT_COVERAGE_REPORT_HTML= # HTML coverage report directory (e.g., coverage/html)
BASHUNIT_COVERAGE_MIN= # Minimum coverage % (fails if below)
BASHUNIT_COVERAGE_THRESHOLD_LOW= # Default: 50 (red below this)
BASHUNIT_COVERAGE_THRESHOLD_HIGH= # Default: 80 (green above this)
BASHUNIT_COVERAGE_THRESHOLD_HIGH= # Default: 80 (green at or above this)
BASHUNIT_COVERAGE_ENGINE= # Default: auto (or xtrace, trap)
BASHUNIT_COVERAGE_SHOW_LINE_HITS= # Default: false (print per-line execution counts)
BASHUNIT_COVERAGE_SHOW_FUNCTIONS= # Default: false (print per-function coverage)
BASHUNIT_COVERAGE_SHOW_UNCOVERED= # Default: false (print missed line ranges per file)

#───────────────────────────────────────────────────────────────────────────────
# Advanced / Debug
#───────────────────────────────────────────────────────────────────────────────
BASHUNIT_DEV_LOG= # Developer log file path
BASHUNIT_BENCH_MODE= # Default: false (benchmark mode)
BASHUNIT_INTERNAL_LOG= # Default: false (internal debug logging)
BASHUNIT_NO_DEPRECATION_WARNINGS= # Default: false (silence unprefixed-alias warnings)
33 changes: 19 additions & 14 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,28 +3,33 @@
## Unreleased

### Added
- `--coverage-diff <ref>` restricts the coverage console report to lines changed since a base ref, and `--coverage-min` then gates on that diff percentage (#1032)
- `--report-md <file>` writes a Markdown run summary: verdict, counts table, failures with their message, plus coverage and slowest tests when those ran. Inside GitHub Actions it is appended to `$GITHUB_STEP_SUMMARY` automatically, so failures render on the job page (#1015)
- `--gha-annotations <auto|always|never>` controls GitHub Actions annotations on stdout; `auto` turns them on inside GitHub Actions and stays quiet everywhere else (#1014)
- `--repeat <n>` runs each selected test n times so flakiness can be hunted before it reaches CI. The test is reported once with the aggregate outcome, a failure names the iteration it happened on, and repeat wraps `--retry` rather than the other way round (#1013)
- Flaky is a first-class outcome: a test that only passed after a retry is counted separately, kept inside the pass total so the exit code is unchanged, and carried into JUnit (`<flakyFailure>`), TAP, JSON, HTML and GitHub Actions along with the first attempt's failure message. `--fail-on-flaky` turns such a run red (#1012)
- `--order-by <mode>` picks the execution order: `defined` (default), `defects` (last run's failures first, whole suite still runs) or `random`. `--random-order` and `--seed` keep working unchanged (#1011)
- `--changed [<ref>]` runs only the test files git reports as touched since `<ref>` (default `origin/HEAD`, then `HEAD`), covering committed, staged, unstaged and untracked changes. Deletions are dropped, a rename selects its new path, and a missing work tree or unresolvable ref fails the run instead of selecting nothing (#1010)
- `--coverage-diff <ref>` limits the coverage console report to lines changed since a base ref; `--coverage-min` then gates on that diff percentage (#1032)
- `assert_command_available <command>` asserts a command, shell builtin or function resolves through `command -v` (#1027)
- `assert_between <min> <max> <actual>` and `assert_not_between` add inclusive numeric-range assertions for integers and decimals (#1026)
- `--list` (alias `--dry-run`) prints the tests a run would execute, without running them; `--list-format json` emits file, function, name, line and tags. Honours every selection flag, including `--shard` and `--random-order --seed` ordering (#1007)
- `--report-md <file>` writes a Markdown run summary: verdict, counts table, failures with their message, plus coverage and slowest tests when those ran. Inside GitHub Actions it is also appended to `$GITHUB_STEP_SUMMARY` (#1015)
- `--gha-annotations <auto|always|never>` controls GitHub Actions annotations on stdout; `auto` enables them only inside GitHub Actions (#1014)
- `--repeat <n>` runs each selected test n times to hunt flakiness before CI does. One report line with the aggregate outcome, a failure names its iteration, and repeat wraps `--retry` (#1013)
- Flaky is a first-class outcome: a test that only passed after a retry is counted separately, stays inside the pass total so the exit code is unchanged, and is carried into JUnit (`<flakyFailure>`), TAP, JSON, HTML and GitHub Actions with the first attempt's failure message. `--fail-on-flaky` turns such a run red (#1012)
- `--order-by <mode>` picks the execution order: `defined` (default), `defects` (last run's failures first, whole suite still runs) or `random`. `--random-order` and `--seed` are unchanged (#1011)
- `--changed [<ref>]` runs only the test files git reports as touched since `<ref>` (default `origin/HEAD`, then `HEAD`), covering committed, staged, unstaged and untracked changes. Deletions are dropped, a rename selects its new path, and a missing work tree or unresolvable ref fails the run (#1010)
- `--exclude-filter <name>` skips tests by name, the counterpart of `--exclude-tag`. Repeatable, OR'd, and wins over `--filter` (#1009)
- `# @tags a b` above any top-level line applies those tags to every test in the file, unioned with per-function `# @tag` (#1008)
- `--tag` accepts expressions: `'a&&b'` (AND) and `'!a'` (NOT), combinable as `'a&&!b'`. Repeated `--tag` flags keep OR semantics, and `--exclude-tag` still wins (#1008)
- `assert_command_available <command>` asserts that an external command, shell builtin or function resolves through `command -v` (#1027)
- The coverage engine in use is reported by `--verbose`, and an explicit `BASHUNIT_COVERAGE_ENGINE=xtrace` that the running Bash cannot honour now warns instead of being silently ignored (#1005)
- `# @tags a b` above any top-level line tags every test in the file, unioned with per-function `# @tag` (#1008)
- `--list` (alias `--dry-run`) prints the tests a run would execute without running them; `--list-format json` emits file, function, name, line and tags. Honours every selection flag, including `--shard` and `--random-order --seed` ordering (#1007)
- `--verbose` reports the coverage engine in use, and an explicit `BASHUNIT_COVERAGE_ENGINE=xtrace` the running Bash cannot honour now warns instead of being silently ignored (#1005)

### Changed
- The JUnit XML shape changed: one `<testsuite>` per test file (with its own counts, time and timestamp) instead of a single flat suite, `classname` on every `<testcase>`, `<failure message="...">` carrying the first informative line of the real message with `type="AssertionFailed"`, `<system-out>` with the test's captured output, and aggregate totals on `<testsuites>`. Consumers that group by suite or classname (Jenkins, GitLab, dorny/test-reporter) now get real groupings (#1016)
- Performance: `--coverage` is about 1.6x to 2.3x faster. Executable-line classification no longer forks `grep` per source line, which was roughly half of a coverage run's wall time and affected both engines equally (#1005)
- `bashunit test --help` lists `--show-skipped` and `--show-incomplete`, both accepted by the parser but never advertised. `BASHUNIT_COVERAGE_SHOW_FUNCTIONS` and `BASHUNIT_COVERAGE_SHOW_UNCOVERED` are registered in `src/config/env.sh` like every other setting, and `.env.example` now lists all 66 settings, 19 of which were missing
- Docs: a full audit of the reference pages against the code. `docs/configuration.md` gained the 17 settings it never documented, `docs/command-line.md` gained the `assert` subcommand section and had every stale example output replaced with a real one, the `assert_equals` examples in `docs/assertions.md` were inverted, and the coverage settings and diff-coverage narrative now live in one place instead of two that had drifted
- JUnit XML: one `<testsuite>` per test file with its own counts, time and timestamp instead of a single flat suite, `classname` on every `<testcase>`, `<failure message="...">` carrying the first informative line of the real message with `type="AssertionFailed"`, `<system-out>` with the test's captured output, and aggregate totals on `<testsuites>`. Consumers that group by suite or classname (Jenkins, GitLab, dorny/test-reporter) now get real groupings (#1016)
- Performance: `--coverage` is about 1.6x to 2.3x faster. Executable-line classification no longer forks `grep` per source line, roughly half of a coverage run's wall time on both engines (#1005)

### Fixed
- A failed assertion is no longer reported twice. `bashunit::assert_that` returns 1 on failure by design, so a custom assertion ending with it made the test body exit non-zero and the runner printed a spurious `✗ Error` on top of the `✗ Failed`. Custom assertions no longer need a trailing `return 0`; a real runtime error is still reported as an error
- `--coverage-report` with no value uses `coverage/lcov.info` instead of aborting the run with `$2: unbound variable`, and no longer consumes a following flag as its filename. Write the test path before it, since an optional value cannot be told apart from a path
- `--list --list-format json` reports each tag as its own array element. The emitter split the tag list on whitespace while every other consumer splits it on commas, so a test with two tags rendered as `["slow,fileTag"]` and a tag containing spaces was split into one element per word
- Build: the standalone binary size budget is 544 KiB, raised from 500 KiB after ordinary feature growth crossed it; the artifact keeps its indentation rather than being minified (#1045)
- `assert_within_delta` rejects malformed numbers such as `1.2.3` or `5-3` as non-numeric instead of leaking a raw `bc` parse error or silently evaluating them as an expression (#1026)
- `assert_within_delta` rejects malformed numbers such as `1.2.3` or `5-3` as non-numeric instead of leaking a raw `bc` parse error or evaluating them as an expression (#1026)
- Report formats are no longer empty under `--parallel`. `--report-junit`, `--report-tap`, `--report-json`, `--report-html` and `--log-junit` all recorded zero tests, because the rows were collected inside the per-test worker and nothing rebuilt them in the parent (#1004)

## [0.45.0](https://github.com/TypedDevs/bashunit/compare/0.44.0...0.45.0) - 2026-08-09
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,7 @@
## Why bashunit

A lightweight, fast testing framework for **Bash 3.0+**, focused on developer experience.
It ships 77 assertions plus spies, mocks, data providers, snapshots and more.
It ships 84 assertions plus spies, mocks, data providers, snapshots and more.

## Quick start

Expand Down
Loading
Loading