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

Description

@Chemaclass
QA
OSmacOS 26.5.2 (arm64)
Shell & versionbash 3.2.57
bashunit version0.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 abcfunctiontest_bad_annotation() { assert_true true; }

ok_test.sh:

functiontest_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/.

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

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n 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;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

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

Description

@Chemaclass
QA
OSmacOS 26.5.2 (arm64)
Shell & versionbash 3.2.57
bashunit version0.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 abcfunctiontest_bad_annotation() { assert_true true; }

ok_test.sh:

functiontest_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/.

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

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Description

@Chemaclass
QA
OSmacOS 26.5.2 (arm64)
Shell & versionbash 3.2.57
bashunit version0.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 abcfunctiontest_bad_annotation() { assert_true true; }

ok_test.sh:

functiontest_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/.

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

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Description

@Chemaclass
QA
OSmacOS 26.5.2 (arm64)
Shell & versionbash 3.2.57
bashunit version0.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 abcfunctiontest_bad_annotation() { assert_true true; }

ok_test.sh:

functiontest_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/.

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

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

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

Description

@Chemaclass
QA
OSmacOS 26.5.2 (arm64)
Shell & versionbash 3.2.57
bashunit version0.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 abcfunctiontest_bad_annotation() { assert_true true; }

ok_test.sh:

functiontest_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/.

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

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Description

@Chemaclass
QA
OSmacOS 26.5.2 (arm64)
Shell & versionbash 3.2.57
bashunit version0.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 abcfunctiontest_bad_annotation() { assert_true true; }

ok_test.sh:

functiontest_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/.

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

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Description

@Chemaclass
QA
OSmacOS 26.5.2 (arm64)
Shell & versionbash 3.2.57
bashunit version0.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 abcfunctiontest_bad_annotation() { assert_true true; }

ok_test.sh:

functiontest_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/.

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

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

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

Description

@Chemaclass
QA
OSmacOS 26.5.2 (arm64)
Shell & versionbash 3.2.57
bashunit version0.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 abcfunctiontest_bad_annotation() { assert_true true; }

ok_test.sh:

functiontest_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/.

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