Skip to content

A malformed @timeout or @retry does not fail a --parallel run alongside a passing file #1335

Description

@Chemaclass
Q A
OS macOS 26.5.2 (arm64)
Shell & version bash 3.2.57
bashunit version 0.50.0 (main @ 40560c2)

Summary

Under --parallel, a malformed # @timeout or # @retry does not fail the run when another file passes. The abort happens inside the file's worker, the parent never learns, and the run exits 0 with "All tests passed". The same input exits 1 sequentially.

Found while fixing #1329, kept separate: that issue is the teardown the abort skipped, this is the exit code the abort never reaches.

Current behavior

The worker prints the annotation error to its captured stderr, which the parent replays, and stops running that file. Nothing else happens. The summary counts the other files' tests, finds no failure, and the run succeeds.

A single-file run does exit 1, but not for this reason: no test ran, so "No tests found" fails the run on its own. Add one passing file and the error goes silent.

How to reproduce

annot_test.sh:

# @timeout abc
function test_bad_annotation() { assert_true true; }

ok_test.sh:

function test_fine() { assert_true true; }
$ ./bashunit --parallel .
✓ Passed: Fine

Stderr from ./annot_test.sh
|Error: @timeout 'abc' above test_bad_annotation in ./annot_test.sh is not a non-negative integer.
Tests:      1 passed, 1 total

 All tests passed
$ echo $?
0

Sequentially the same directory exits 1.

Expected behavior

A malformed annotation fails the run under --parallel as it does sequentially, whatever else is in the selection. The error message is already right; only the exit code is wrong.

Where it happens

  • src/runner/discovery.sh:252: the worker drops call_test_functions's status. Propagating it as the worker's exit status does not help, because the parent's wait ignores it
  • src/runner/result.sh:82: publish_file_hook_failure is the channel a worker already uses to publish a file-level failure into the aggregate. A malformed annotation is not a hook failure, so it needs its own shape or a shared one
  • src/runner/discovery.sh:281: the sequential path exits with the file's status, which is where the difference comes from

Acceptance criteria

  • An acceptance test: a malformed @timeout next to a passing file exits non-zero under --parallel.
  • The error message text is unchanged, and it still reaches stderr once.
  • The failure is not double-counted. --parallel reports count a file-level hook failure twice; the console says one #1301 fixed a file-level hook failure showing up as two failed tests in the reports and one in the console summary; whatever carries this must not reintroduce that.
  • Sequential behaviour is unchanged.
  • CHANGELOG.md gets a Fixed entry.

Notes for the implementer

  • The worker cannot exit non-zero to say this. The parent's wait at src/runner/discovery.sh:290 discards worker statuses, and wait_for_job_slot breaks its poll loop on the first non-zero wait -n, so a non-zero worker would change scheduling instead of the exit code.
  • Decide whether this counts as a failed test or only as a non-zero exit. The console summary and the json, junit, html and markdown reports all read the same aggregate, so the answer has to be the same in all five.
  • Bash 3.0+ only. See .claude/rules/bash-style.md.
  • make sa, make lint, ./bashunit tests/, ./bashunit --parallel tests/.

Activity

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

  • Status
    Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions