Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ BASHUNIT_ORDER_BY= # Default: defined (or defects, random)
BASHUNIT_FAIL_ON_FLAKY= # Default: false (treat retry-passed tests as failed)
BASHUNIT_REPEAT= # Default: 1 (run each test N times)
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)
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### 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)
Expand Down
2 changes: 1 addition & 1 deletion adrs/adr-011-source-layout-and-build-pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Seventeen, in load order. The order is the dependency layering: leaves first.
| 10 | `cli/` | 5 | 445 | the `doc`/`init`/`upgrade`/`watch` subcommand implementations |
| 11 | `assert/` | 11 | 2326 | every assertion |
| 12 | `doubles/` | 4 | 505 | spies and mocks — **sourced by `assert/index.sh`, not the entrypoint** |
| 13 | `reports/` | 7 | 465 | JUnit, TAP, JSON, GHA and HTML writers |
| 13 | `reports/` | 8 | 738 | JUnit, TAP, JSON, GHA, HTML and Markdown writers |
| 14 | `runner/` | 12 | 2434 | the file loop, per-test execution, retry, result parsing, `--list` |
| 15 | `benchmark/` | 4 | 219 | the bench implementation (`runner/bench.sh` is its loop) |
| 16 | `learn/` | 14 | 1296 | the interactive tutorial (9 of those files are `learn/lessons/`) |
Expand Down
1 change: 1 addition & 0 deletions completions/_bashunit
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ _bashunit() {
'(-r --report-html)'{-r,--report-html}'[Write HTML report]:file:_files' \
'--report-tap[Write TAP version 13 report]:file:_files' \
'--report-json[Write machine-readable JSON report]:file:_files' \
'--report-md[Write a Markdown summary]:file:_files' \
'(-s --simple)'{-s,--simple}'[Simple output with dots]' \
'--detailed[Detailed output, the default]' \
'--output[Output format]:format:(tap)' \
Expand Down
2 changes: 1 addition & 1 deletion completions/bashunit.bash
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ _BASHUNIT_COMPLETIONS_TEST_OPTS="--assert --boot --changed --coverage --coverage
--no-coverage-report --no-output --no-output-on-failure --no-parallel \
--no-progress --no-snapshot-create --order-by --output --parallel --profile \
--random-order --repeat --report-html \
--report-json --report-junit --report-tap --rerun-failed --retry --run-all \
--report-json --report-junit --report-md --report-tap --rerun-failed --retry --run-all \
--seed --shard --show-incomplete --show-output --show-skipped --simple \
--skip-env-file --snapshot-report-unused --snapshot-update \
--stop-on-failure --strict --tag \
Expand Down
49 changes: 49 additions & 0 deletions docs/command-line.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ bashunit test tests/ --parallel --simple
| `-r, --report-html <file>` | Write HTML report |
| `--report-tap <file>` | Write TAP version 13 report to a file |
| `--report-json <file>` | Write machine-readable JSON report to a file |
| `--report-md <file>` | Write a Markdown summary (auto-appended to `$GITHUB_STEP_SUMMARY`) |
| `-R, --run-all` | Run all assertions (don't stop on first failure) |
| `-s, --simple` | Simple output (dots) |
| `--detailed` | Detailed output (default) |
Expand Down Expand Up @@ -458,6 +459,54 @@ bashunit test tests/ --report-json report.json
```
:::

### Markdown summary

> `bashunit test --report-md <file>`

Every other report format targets a machine. This one targets the page a
developer actually looks at first:

```markdown
## bashunit

❌ **3 failed**, 409 passed in 12.3s

| Result | Count |
|--------|-------|
| Passed | 409 |
| Failed | 3 |

## Failures

### Sums two numbers

`tests/math_test.sh:42`

```
Expected '4'
but got '5'
```
```

Inside GitHub Actions there is **nothing to configure**: with
`GITHUB_STEP_SUMMARY` set and no explicit path, the summary is appended to it
and renders on the job page. Appended, never written, because that file belongs
to the whole job and truncating it would discard the other steps' output. An
explicit `--report-md` path wins over the step summary.

The report always carries the verdict, a counts table and the failures with
their `file:line` and message. Two sections appear only when the data exists:
coverage percentage after a `--coverage` run, and the slowest tests under
[`--profile`](#profile).

Failure messages are ANSI-stripped and go inside a fence, so they render
verbatim. Test names are escaped, so a name containing `|`, `*`, `_` or a
backtick cannot break the table.

Like the annotations, only the outermost run writes the step summary: a nested
bashunit run inherits `GITHUB_STEP_SUMMARY` and would otherwise append its own
fixtures' results to the parent's job page.

### GitHub Actions annotations

Inside GitHub Actions, bashunit annotates failing tests on the pull request by
Expand Down
15 changes: 15 additions & 0 deletions src/config/env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ _BASHUNIT_DEFAULT_LOG_GHA=""
_BASHUNIT_DEFAULT_REPORT_HTML=""
_BASHUNIT_DEFAULT_REPORT_TAP=""
_BASHUNIT_DEFAULT_REPORT_JSON=""
_BASHUNIT_DEFAULT_REPORT_MD=""

# Coverage defaults (following kcov, bashcov, SimpleCov conventions)
_BASHUNIT_DEFAULT_COVERAGE="false"
Expand Down Expand Up @@ -323,6 +324,8 @@ _BASHUNIT_DEFAULT_SNAPSHOT_REPORT_UNUSED="false"
: "${BASHUNIT_FAIL_ON_FLAKY:=$_BASHUNIT_DEFAULT_FAIL_ON_FLAKY}"
: "${BASHUNIT_REPEAT:=$_BASHUNIT_DEFAULT_REPEAT}"
: "${BASHUNIT_GHA_ANNOTATIONS:=$_BASHUNIT_DEFAULT_GHA_ANNOTATIONS}"
# No bare REPORT_MD alias: the newer report flags never grew one.
: "${BASHUNIT_REPORT_MD:=$_BASHUNIT_DEFAULT_REPORT_MD}"

# GITHUB_ACTIONS is inherited by every child process, so a nested bashunit run
# (bashunit's own acceptance suite, or a user's script under test that calls
Expand Down Expand Up @@ -672,6 +675,18 @@ function bashunit::env::should_print_gha_annotations() {
! bashunit::env::is_tap_output_enabled
}

##
# Whether the Markdown summary is appended to the job's step summary. Same
# outermost-run posture as the annotations: GITHUB_STEP_SUMMARY is inherited by
# every child, so a nested run would append its own fixtures' results to the
# parent's job page.
##
function bashunit::env::should_append_step_summary() {
[ -n "${GITHUB_STEP_SUMMARY:-}" ] || return 1

[ "${_BASHUNIT_IS_OUTERMOST_RUN:-true}" = true ]
}

function bashunit::env::is_fail_on_flaky_enabled() {
[ "${BASHUNIT_FAIL_ON_FLAKY:-false}" = "true" ]
}
Expand Down
1 change: 1 addition & 0 deletions src/console/header.sh
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ Options:
-r, --report-html <file> Write HTML report
--report-tap <file> Write TAP version 13 report
--report-json <file> Write machine-readable JSON report
--report-md <file> Write a Markdown summary (auto-appended to \$GITHUB_STEP_SUMMARY)
-s, --simple Simple output (dots)
--detailed Detailed output (default)
--output <format> Output format: tap (TAP version 13)
Expand Down
39 changes: 26 additions & 13 deletions src/main/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,31 @@ function bashunit::main::exec_tests() {
bashunit::console_results::render_result
exit_code=$?

# Rows first, and the Markdown summary before print_profile_and_reset below
# removes the profile records that summary reads.
bashunit::reports::load_spooled

# Coverage data is turned into numbers here rather than with the reports
# below, because the Markdown summary quotes the same percentage and would
# otherwise read it before the hit records exist — 0% for a covered run.
if bashunit::env::is_coverage_enabled; then
# Turn captured xtrace output into hit records (no-op for the trap engine)
bashunit::coverage::finalize

# Aggregate per-process coverage data from parallel runs
if bashunit::parallel::is_enabled; then
bashunit::coverage::aggregate_parallel
fi

bashunit::coverage::precompute_file_stats
fi

if [ -n "$BASHUNIT_REPORT_MD" ]; then
bashunit::reports::generate_report_md "$BASHUNIT_REPORT_MD"
elif bashunit::env::should_append_step_summary; then
bashunit::reports::append_step_summary
fi

if bashunit::env::is_profile_enabled; then
bashunit::console_results::print_profile_and_reset
fi
Expand All @@ -158,8 +183,6 @@ function bashunit::main::exec_tests() {
bashunit::snapshot::report_unused ${test_files[@]+"${test_files[@]}"}
fi

bashunit::reports::load_spooled

# To stdout, not to a file: GitHub reads workflow commands from the job log.
# After load_spooled so a --parallel run annotates the rows its workers
# spooled, which the parent would otherwise never have seen (#1004).
Expand Down Expand Up @@ -187,18 +210,8 @@ function bashunit::main::exec_tests() {
bashunit::reports::generate_report_json "$BASHUNIT_REPORT_JSON"
fi

# Generate coverage report if enabled
# Render the coverage reports; the data behind them was computed above.
if bashunit::env::is_coverage_enabled; then
# Turn captured xtrace output into hit records (no-op for the trap engine)
bashunit::coverage::finalize

# Aggregate per-process coverage data from parallel runs
if bashunit::parallel::is_enabled; then
bashunit::coverage::aggregate_parallel
fi

bashunit::coverage::precompute_file_stats

if bashunit::coverage::is_diff_enabled; then
bashunit::coverage::report_diff
else
Expand Down
5 changes: 5 additions & 0 deletions src/main/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,11 @@ function bashunit::main::cmd_test() {
export -n BASHUNIT_REPORT_TAP
shift
;;
--report-md)
BASHUNIT_REPORT_MD="$2"
export -n BASHUNIT_REPORT_MD
shift
;;
--report-json)
BASHUNIT_REPORT_JSON="$2"
export -n BASHUNIT_REPORT_JSON
Expand Down
2 changes: 1 addition & 1 deletion src/main/validate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ function bashunit::main::validate_config_or_exit() {

local _report_var _report_path
for _report_var in BASHUNIT_LOG_JUNIT BASHUNIT_LOG_GHA BASHUNIT_REPORT_HTML \
BASHUNIT_REPORT_TAP BASHUNIT_REPORT_JSON; do
BASHUNIT_REPORT_TAP BASHUNIT_REPORT_JSON BASHUNIT_REPORT_MD; do
_report_path=${!_report_var:-}
if [ -n "$_report_path" ]; then
bashunit::main::require_writable_path_or_exit "$_report_path" "$_report_var"
Expand Down
2 changes: 2 additions & 0 deletions src/reports/collect.sh
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ function bashunit::reports::is_enabled() {
[ -n "${BASHUNIT_LOG_GHA:-}" ] ||
[ -n "${BASHUNIT_REPORT_TAP:-}" ] ||
[ -n "${BASHUNIT_REPORT_JSON:-}" ] ||
[ -n "${BASHUNIT_REPORT_MD:-}" ] ||
bashunit::env::should_append_step_summary ||
bashunit::env::should_print_gha_annotations
}

Expand Down
1 change: 1 addition & 0 deletions src/reports/index.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ source "$BASHUNIT_ROOT_DIR/src/reports/tap.sh"
source "$BASHUNIT_ROOT_DIR/src/reports/json.sh"
source "$BASHUNIT_ROOT_DIR/src/reports/gha.sh"
source "$BASHUNIT_ROOT_DIR/src/reports/html.sh"
source "$BASHUNIT_ROOT_DIR/src/reports/markdown.sh"
Loading
Loading