Skip to content

feat(runner): flaky is not a status — a test that only passed on retry reports as a plain pass #1012

Closed

Description

@Chemaclass

Problem

--retry <n> hides flakiness instead of surfacing it.

src/runner/exec.sh:312-345 retries execution only, then commits a single result. When a test fails and later passes, the run records a plain pass. The only trace is a console suffix built at src/runner/exec.sh:524-527:

# A test that only passed after retrying is annotated so flakiness stays visible.
_BASHUNIT_RETRY_NOTE=""
if [ "$retries_used" -gt 0 ]; then
  _BASHUNIT_RETRY_NOTE=" (retry $retries_used/$retry_max)"
fi

That suffix exists only in the terminal. It is absent from the summary counters (src/state/counters.sh has passed/failed/skipped/incomplete/snapshot/risky — no flaky), from JUnit, TAP, JSON and HTML (src/reports/collect.sh:21-41 has no add_test_flaky), and from the GitHub Actions annotations. A CI job that retries a flaky test is indistinguishable from a clean run, so flakiness never gets triaged.

Proposal

Make flaky a first-class outcome: passed, but not on the first attempt.

  • New counter bashunit::state::add_tests_flaky / get_tests_flaky, alongside the existing six.
  • Summary line shows Flaky: N when non-zero (still counted inside the pass total, so exit code does not change by default).
  • src/reports/collect.sh gains add_test_flaky carrying the retry count and the failure message from the first attempt (currently discarded — that message is the whole diagnostic value).
  • Reporters:
    • JUnit: <flakyFailure> inside the <testcase> (Jenkins/GitLab render this natively)
    • TAP: ok N - name # TODO flaky (retried 2/3)
    • JSON: "status": "flaky", "retries": 2, "first_failure": "..."
    • HTML: distinct row styling
    • GHA: ::warning annotation (src/reports/gha.sh:31 case "$status")
  • New flag --fail-on-flaky — treat flaky as failure for the exit code, mirroring the existing --fail-on-risky.

See also #1013 (--repeat), which finds flaky tests proactively.

Where to change

  • src/runner/exec.sh:298-345 (capture the first attempt's failure message) and :518-530 (classification).
  • src/state/counters.sh, src/state/index.sh, src/state/parallel.sh (the parallel aggregation must carry the new counter — see fix(reports): every report format is empty under --parallel #1004, where per-worker rows were dropped in the parent).
  • src/reports/collect.sh, src/reports/junit.sh, src/reports/tap.sh, src/reports/json.sh, src/reports/html.sh, src/reports/gha.sh.
  • src/console/summary.sh:5 render_result.

Acceptance criteria

  • A test that fails once then passes under --retry 2 is reported as flaky, not as a plain pass
  • Summary prints a flaky count; zero flaky tests print nothing new
  • Default exit code is unchanged (flaky counts as a pass)
  • --fail-on-flaky makes the run exit non-zero when any test is flaky
  • The first attempt's failure message is preserved and shown in the reports
  • JUnit emits <flakyFailure>; validate the XML parses
  • TAP, JSON, HTML and GHA all carry the flaky status
  • Counters are correct under --parallel (regression guard for fix(reports): every report format is empty under --parallel #1004)
  • --retry 0 (default) can never produce a flaky result
  • Unit tests in tests/unit/runner/ and tests/unit/reports/, plus an acceptance test

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.

Activity

  1. self-assigned this
    on Aug 10, 2026
  2. Chemaclass commented on Aug 10, 2026

    @Chemaclass
    MemberAuthor

    Done in #1040 (merged).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

  • Status
    Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions