Problem
Every existing report format targets a machine: JUnit XML, TAP, JSON, LCOV, plus an HTML file that has to be uploaded as an artifact and downloaded to be read. There is no format aimed at the place a developer actually looks first — the pull request page.
GitHub Actions renders anything appended to $GITHUB_STEP_SUMMARY as Markdown directly on the job page. GitLab and Azure have equivalent surfaces that consume Markdown.
Proposal
--report-md <file> Write a Markdown summary
Plus: when GITHUB_STEP_SUMMARY is set and no explicit path is given, append the summary there automatically (same auto-detect posture as the annotations issue).
Content, in order:
- One-line verdict:
✅ 412 passed / ❌ 3 failed, 409 passed with total duration.
- A counts table (passed / failed / skipped / incomplete / risky / snapshot, and flaky if that lands).
- A failures section: test name,
file:line, and the failure message in a fenced block — this is the part that saves a click into the raw log.
- Coverage percentage when
--coverage ran.
- Slowest tests when
--profile is on (BASHUNIT_PROFILE_COUNT, default 10).
ANSI must be stripped (bashunit::reports::__strip_ansi, src/reports/collect.sh:9) and Markdown special characters escaped in test names.
Where to change
Acceptance criteria
Repo checklist (agent)
- TDD: RED → GREEN → REFACTOR. Write the failing test first.
- Bash 3.0+ only: no
printf -v, no += append, no declare -A, no [[ ]], no ${var,,}, no &>>, no ${arr[-1]}. Expanding a possibly-empty array under set -u needs ${arr[@]+"${arr[@]}"}.
- A new CLI flag must be wired in all of these or a parity test fails:
- Gates:
make sa, make lint, ./bashunit tests/, ./bashunit --parallel tests/. Never run shfmt -w.
- Docs: update
docs/command-line.md. Editing docs/assertions.md invalidates the bashunit doc acceptance snapshot — regenerate it.
- CHANGELOG.md: add one line under
## Unreleased.
- Fixtures under
tests/acceptance/fixtures/ must not end in *test.sh.
- One issue = one PR.
Problem
Every existing report format targets a machine: JUnit XML, TAP, JSON, LCOV, plus an HTML file that has to be uploaded as an artifact and downloaded to be read. There is no format aimed at the place a developer actually looks first — the pull request page.
GitHub Actions renders anything appended to
$GITHUB_STEP_SUMMARYas Markdown directly on the job page. GitLab and Azure have equivalent surfaces that consume Markdown.Proposal
Plus: when
GITHUB_STEP_SUMMARYis set and no explicit path is given, append the summary there automatically (same auto-detect posture as the annotations issue).Content, in order:
✅ 412 passed/❌ 3 failed, 409 passedwith total duration.file:line, and the failure message in a fenced block — this is the part that saves a click into the raw log.--coverageran.--profileis on (BASHUNIT_PROFILE_COUNT, default 10).ANSI must be stripped (
bashunit::reports::__strip_ansi,src/reports/collect.sh:9) and Markdown special characters escaped in test names.Where to change
src/reports/markdown.sh, registered insrc/reports/index.sh(the build derives its dependency list from the entrypoint, so a new file must be sourced from anindex.shor it will be missing from the standalone binary — that is exactly how Watch subcommand fails with "bashunit::watch::run: command not found" (src/watch.sh missing from release build) #735 shipped a brokenwatch).src/main/run.sh:132-150— the report dispatch block.src/main/test.sh,src/config/env.sh(BASHUNIT_REPORT_MD),.env.example,src/main/validate.sh:110(the writable-path check list).Acceptance criteria
--report-md out.mdwrites a valid Markdown fileGITHUB_STEP_SUMMARYset and no flag, the summary is appended (not truncated) to that file|,*,_or backticks do not break the table--profile--parallel(regression guard for fix(reports): every report format is empty under --parallel #1004)--report-md /unwritable/pathfails fast with a clear error (A missing --env bootstrap or an unwritable report path fails silently and exits 0 #875)tests/unit/reports/plus an acceptance testRepo checklist (agent)
printf -v, no+=append, nodeclare -A, no[[ ]], no${var,,}, no&>>, no${arr[-1]}. Expanding a possibly-empty array underset -uneeds${arr[@]+"${arr[@]}"}.src/main/test.sh(report-style flags needexport -n, seesrc/main/test.sh:188-196for why)bashunit::main::validate_config_or_exit(src/main/validate.sh:60) — unvalidated input used to run the wrong thing and exit 0 (Unknown options are silently ignored: a typo'd flag runs a different suite and exits 0 #871, --jobs with a non-integer value hangs on Bash 3.x and is silently ignored on Bash 4.3+ #873)src/config/env.shand a documented line in.env.example--helptext in the same block it belongs tocompletions/bashunit.bashandcompletions/_bashunit(anti-drift test feat(cli): bash and zsh completion scripts with an anti-drift test #778 fails otherwise)make sa,make lint,./bashunit tests/,./bashunit --parallel tests/. Never runshfmt -w.docs/command-line.md. Editingdocs/assertions.mdinvalidates thebashunit docacceptance snapshot — regenerate it.## Unreleased.tests/acceptance/fixtures/must not end in*test.sh.