Skip to content

A test killed by --test-timeout does not run tear_down #1324

Description

@Chemaclass
QA
OSmacOS 26.5.2 (arm64)
Shell & versionbash 3.2.57
bashunit version0.50.0

Summary

A test killed by --test-timeout never runs its tear_down. The watchdog signals the test's process group, the subshell dies without running its EXIT trap, and bashunit::runner::cleanup_on_exit never calls the hook.

Same class as #1318, at test scope. The file-scoped pair survives a timeout, because the loop keeps going and runs tear_down_after_script. The per-test pair does not.

This is the opposite asymmetry from #1323, where tear_down runs and tear_down_after_script does not. Both are the same missing guarantee seen from two sides.

Current behavior

The test is reported as Test timed out after 1s, the run continues, and whatever set_up acquired for that test is left behind.

How to reproduce

timeout_test.sh:

functionset_up_before_script() { :>"$CLEANUP_MARKER.setupfile"; }
functiontear_down_after_script() { :>"$CLEANUP_MARKER.RAN_FILE_TEARDOWN"; }
functionset_up() { :>"$CLEANUP_MARKER.setuptest"; }
functiontear_down() { :>"$CLEANUP_MARKER.RAN_TEST_TEARDOWN"; }
functiontest_hangs() { sleep 10; assert_true true; }
$ CLEANUP_MARKER=/tmp/bu-to.marker ./bashunit --no-parallel --test-timeout 1 timeout_test.sh
✗ Error: Hangs
Test timed out after 1s
$ ls /tmp/bu-to.marker.*
/tmp/bu-to.marker.RAN_FILE_TEARDOWN
/tmp/bu-to.marker.setupfile
/tmp/bu-to.marker.setuptest

RAN_TEST_TEARDOWN is absent. The per-test set_up ran, its tear_down did not.

Verified on bash 3.2.57. Bash's default SIGTERM handling inside a forked subshell has changed across versions, so confirm on bash 5 before choosing a fix. A closely related path behaves differently on the same machine: pkill -P $$ on Ctrl-C does let the EXIT trap run.

Expected behavior

A timed-out test runs tear_down, so a per-test resource can be released. The timeout still bounds the run.

Where it happens

  • src/runner/exec.sh:363: the test body is spawned as (bashunit::runner::execute_test_body ...) & under set -m
  • src/runner/exec.sh:399-402: the watchdog writes the marker, sends kill -TERM -"$test_pid" to the group, sleeps 0.3s, then kill -KILL -"$test_pid"
  • src/runner/hooks.sh:37: bashunit::runner::run_tear_down lives in cleanup_on_exit, the subshell's EXIT trap, which never fires
  • src/runner/hooks.sh:38-40: clear_mocks and cleanup_testcase_temp_files are skipped on the same path

Acceptance criteria

  • An acceptance test: --test-timeout 1 against a hanging test whose tear_down releases a resource, asserted gone.
  • The Test timed out after Ns message and the failure classification are unchanged. tests/acceptance timeout coverage still passes.
  • A tear_down that hangs cannot extend the run past the watchdog's KILL. The 0.3s grace, or whatever replaces it, still bounds the process.
  • Verified on bash 3.2 and bash 5. The make test-bash-3 path exists for the first.
  • --parallel runs behave the same way.
  • CHANGELOG.md gets a Fixed entry.

Notes for the implementer

  • Trapping TERM inside the test-body subshell so the EXIT trap gets a chance is the direct fix. The 0.3s between TERM and KILL is the whole budget the hook gets, so measure whether that is enough or has to grow.
  • Weigh the tradeoff and write the decision down: a teardown that never returns is worse than a teardown that never runs. A bounded best-effort hook is the useful behaviour, not an unbounded one.
  • The watchdog notes at src/runner/exec.sh:366-390 explain why it is detached and why it re-checks kill -0. Do not break that. The suites acceptance test is flaky on Bash 3.0 under load #1137 is the receipt.
  • 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)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
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;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
A test killed by `--test-timeout` does not run `tear_down` · Issue #1324 · TypedDevs/bashunit · GitHub
Skip to content

A test killed by --test-timeout does not run tear_down #1324

Description

@Chemaclass
QA
OSmacOS 26.5.2 (arm64)
Shell & versionbash 3.2.57
bashunit version0.50.0

Summary

A test killed by --test-timeout never runs its tear_down. The watchdog signals the test's process group, the subshell dies without running its EXIT trap, and bashunit::runner::cleanup_on_exit never calls the hook.

Same class as #1318, at test scope. The file-scoped pair survives a timeout, because the loop keeps going and runs tear_down_after_script. The per-test pair does not.

This is the opposite asymmetry from #1323, where tear_down runs and tear_down_after_script does not. Both are the same missing guarantee seen from two sides.

Current behavior

The test is reported as Test timed out after 1s, the run continues, and whatever set_up acquired for that test is left behind.

How to reproduce

timeout_test.sh:

functionset_up_before_script() { :>"$CLEANUP_MARKER.setupfile"; }
functiontear_down_after_script() { :>"$CLEANUP_MARKER.RAN_FILE_TEARDOWN"; }
functionset_up() { :>"$CLEANUP_MARKER.setuptest"; }
functiontear_down() { :>"$CLEANUP_MARKER.RAN_TEST_TEARDOWN"; }
functiontest_hangs() { sleep 10; assert_true true; }
$ CLEANUP_MARKER=/tmp/bu-to.marker ./bashunit --no-parallel --test-timeout 1 timeout_test.sh
✗ Error: Hangs
Test timed out after 1s
$ ls /tmp/bu-to.marker.*
/tmp/bu-to.marker.RAN_FILE_TEARDOWN
/tmp/bu-to.marker.setupfile
/tmp/bu-to.marker.setuptest

RAN_TEST_TEARDOWN is absent. The per-test set_up ran, its tear_down did not.

Verified on bash 3.2.57. Bash's default SIGTERM handling inside a forked subshell has changed across versions, so confirm on bash 5 before choosing a fix. A closely related path behaves differently on the same machine: pkill -P $$ on Ctrl-C does let the EXIT trap run.

Expected behavior

A timed-out test runs tear_down, so a per-test resource can be released. The timeout still bounds the run.

Where it happens

  • src/runner/exec.sh:363: the test body is spawned as (bashunit::runner::execute_test_body ...) & under set -m
  • src/runner/exec.sh:399-402: the watchdog writes the marker, sends kill -TERM -"$test_pid" to the group, sleeps 0.3s, then kill -KILL -"$test_pid"
  • src/runner/hooks.sh:37: bashunit::runner::run_tear_down lives in cleanup_on_exit, the subshell's EXIT trap, which never fires
  • src/runner/hooks.sh:38-40: clear_mocks and cleanup_testcase_temp_files are skipped on the same path

Acceptance criteria

  • An acceptance test: --test-timeout 1 against a hanging test whose tear_down releases a resource, asserted gone.
  • The Test timed out after Ns message and the failure classification are unchanged. tests/acceptance timeout coverage still passes.
  • A tear_down that hangs cannot extend the run past the watchdog's KILL. The 0.3s grace, or whatever replaces it, still bounds the process.
  • Verified on bash 3.2 and bash 5. The make test-bash-3 path exists for the first.
  • --parallel runs behave the same way.
  • CHANGELOG.md gets a Fixed entry.

Notes for the implementer

  • Trapping TERM inside the test-body subshell so the EXIT trap gets a chance is the direct fix. The 0.3s between TERM and KILL is the whole budget the hook gets, so measure whether that is enough or has to grow.
  • Weigh the tradeoff and write the decision down: a teardown that never returns is worse than a teardown that never runs. A bounded best-effort hook is the useful behaviour, not an unbounded one.
  • The watchdog notes at src/runner/exec.sh:366-390 explain why it is detached and why it re-checks kill -0. Do not break that. The suites acceptance test is flaky on Bash 3.0 under load #1137 is the receipt.
  • 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)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' A test killed by `--test-timeout` does not run `tear_down` · Issue #1324 · TypedDevs/bashunit · GitHub
Skip to content

A test killed by --test-timeout does not run tear_down #1324

Description

@Chemaclass
QA
OSmacOS 26.5.2 (arm64)
Shell & versionbash 3.2.57
bashunit version0.50.0

Summary

A test killed by --test-timeout never runs its tear_down. The watchdog signals the test's process group, the subshell dies without running its EXIT trap, and bashunit::runner::cleanup_on_exit never calls the hook.

Same class as #1318, at test scope. The file-scoped pair survives a timeout, because the loop keeps going and runs tear_down_after_script. The per-test pair does not.

This is the opposite asymmetry from #1323, where tear_down runs and tear_down_after_script does not. Both are the same missing guarantee seen from two sides.

Current behavior

The test is reported as Test timed out after 1s, the run continues, and whatever set_up acquired for that test is left behind.

How to reproduce

timeout_test.sh:

functionset_up_before_script() { :>"$CLEANUP_MARKER.setupfile"; }
functiontear_down_after_script() { :>"$CLEANUP_MARKER.RAN_FILE_TEARDOWN"; }
functionset_up() { :>"$CLEANUP_MARKER.setuptest"; }
functiontear_down() { :>"$CLEANUP_MARKER.RAN_TEST_TEARDOWN"; }
functiontest_hangs() { sleep 10; assert_true true; }
$ CLEANUP_MARKER=/tmp/bu-to.marker ./bashunit --no-parallel --test-timeout 1 timeout_test.sh
✗ Error: Hangs
Test timed out after 1s
$ ls /tmp/bu-to.marker.*
/tmp/bu-to.marker.RAN_FILE_TEARDOWN
/tmp/bu-to.marker.setupfile
/tmp/bu-to.marker.setuptest

RAN_TEST_TEARDOWN is absent. The per-test set_up ran, its tear_down did not.

Verified on bash 3.2.57. Bash's default SIGTERM handling inside a forked subshell has changed across versions, so confirm on bash 5 before choosing a fix. A closely related path behaves differently on the same machine: pkill -P $$ on Ctrl-C does let the EXIT trap run.

Expected behavior

A timed-out test runs tear_down, so a per-test resource can be released. The timeout still bounds the run.

Where it happens

  • src/runner/exec.sh:363: the test body is spawned as (bashunit::runner::execute_test_body ...) & under set -m
  • src/runner/exec.sh:399-402: the watchdog writes the marker, sends kill -TERM -"$test_pid" to the group, sleeps 0.3s, then kill -KILL -"$test_pid"
  • src/runner/hooks.sh:37: bashunit::runner::run_tear_down lives in cleanup_on_exit, the subshell's EXIT trap, which never fires
  • src/runner/hooks.sh:38-40: clear_mocks and cleanup_testcase_temp_files are skipped on the same path

Acceptance criteria

  • An acceptance test: --test-timeout 1 against a hanging test whose tear_down releases a resource, asserted gone.
  • The Test timed out after Ns message and the failure classification are unchanged. tests/acceptance timeout coverage still passes.
  • A tear_down that hangs cannot extend the run past the watchdog's KILL. The 0.3s grace, or whatever replaces it, still bounds the process.
  • Verified on bash 3.2 and bash 5. The make test-bash-3 path exists for the first.
  • --parallel runs behave the same way.
  • CHANGELOG.md gets a Fixed entry.

Notes for the implementer

  • Trapping TERM inside the test-body subshell so the EXIT trap gets a chance is the direct fix. The 0.3s between TERM and KILL is the whole budget the hook gets, so measure whether that is enough or has to grow.
  • Weigh the tradeoff and write the decision down: a teardown that never returns is worse than a teardown that never runs. A bounded best-effort hook is the useful behaviour, not an unbounded one.
  • The watchdog notes at src/runner/exec.sh:366-390 explain why it is detached and why it re-checks kill -0. Do not break that. The suites acceptance test is flaky on Bash 3.0 under load #1137 is the receipt.
  • 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)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' A test killed by `--test-timeout` does not run `tear_down` · Issue #1324 · TypedDevs/bashunit · GitHub
Skip to content

A test killed by --test-timeout does not run tear_down #1324

Description

@Chemaclass
QA
OSmacOS 26.5.2 (arm64)
Shell & versionbash 3.2.57
bashunit version0.50.0

Summary

A test killed by --test-timeout never runs its tear_down. The watchdog signals the test's process group, the subshell dies without running its EXIT trap, and bashunit::runner::cleanup_on_exit never calls the hook.

Same class as #1318, at test scope. The file-scoped pair survives a timeout, because the loop keeps going and runs tear_down_after_script. The per-test pair does not.

This is the opposite asymmetry from #1323, where tear_down runs and tear_down_after_script does not. Both are the same missing guarantee seen from two sides.

Current behavior

The test is reported as Test timed out after 1s, the run continues, and whatever set_up acquired for that test is left behind.

How to reproduce

timeout_test.sh:

functionset_up_before_script() { :>"$CLEANUP_MARKER.setupfile"; }
functiontear_down_after_script() { :>"$CLEANUP_MARKER.RAN_FILE_TEARDOWN"; }
functionset_up() { :>"$CLEANUP_MARKER.setuptest"; }
functiontear_down() { :>"$CLEANUP_MARKER.RAN_TEST_TEARDOWN"; }
functiontest_hangs() { sleep 10; assert_true true; }
$ CLEANUP_MARKER=/tmp/bu-to.marker ./bashunit --no-parallel --test-timeout 1 timeout_test.sh
✗ Error: Hangs
Test timed out after 1s
$ ls /tmp/bu-to.marker.*
/tmp/bu-to.marker.RAN_FILE_TEARDOWN
/tmp/bu-to.marker.setupfile
/tmp/bu-to.marker.setuptest

RAN_TEST_TEARDOWN is absent. The per-test set_up ran, its tear_down did not.

Verified on bash 3.2.57. Bash's default SIGTERM handling inside a forked subshell has changed across versions, so confirm on bash 5 before choosing a fix. A closely related path behaves differently on the same machine: pkill -P $$ on Ctrl-C does let the EXIT trap run.

Expected behavior

A timed-out test runs tear_down, so a per-test resource can be released. The timeout still bounds the run.

Where it happens

  • src/runner/exec.sh:363: the test body is spawned as (bashunit::runner::execute_test_body ...) & under set -m
  • src/runner/exec.sh:399-402: the watchdog writes the marker, sends kill -TERM -"$test_pid" to the group, sleeps 0.3s, then kill -KILL -"$test_pid"
  • src/runner/hooks.sh:37: bashunit::runner::run_tear_down lives in cleanup_on_exit, the subshell's EXIT trap, which never fires
  • src/runner/hooks.sh:38-40: clear_mocks and cleanup_testcase_temp_files are skipped on the same path

Acceptance criteria

  • An acceptance test: --test-timeout 1 against a hanging test whose tear_down releases a resource, asserted gone.
  • The Test timed out after Ns message and the failure classification are unchanged. tests/acceptance timeout coverage still passes.
  • A tear_down that hangs cannot extend the run past the watchdog's KILL. The 0.3s grace, or whatever replaces it, still bounds the process.
  • Verified on bash 3.2 and bash 5. The make test-bash-3 path exists for the first.
  • --parallel runs behave the same way.
  • CHANGELOG.md gets a Fixed entry.

Notes for the implementer

  • Trapping TERM inside the test-body subshell so the EXIT trap gets a chance is the direct fix. The 0.3s between TERM and KILL is the whole budget the hook gets, so measure whether that is enough or has to grow.
  • Weigh the tradeoff and write the decision down: a teardown that never returns is worse than a teardown that never runs. A bounded best-effort hook is the useful behaviour, not an unbounded one.
  • The watchdog notes at src/runner/exec.sh:366-390 explain why it is detached and why it re-checks kill -0. Do not break that. The suites acceptance test is flaky on Bash 3.0 under load #1137 is the receipt.
  • 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)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' A test killed by `--test-timeout` does not run `tear_down` · Issue #1324 · TypedDevs/bashunit · GitHub
Skip to content

A test killed by --test-timeout does not run tear_down #1324

Description

@Chemaclass
QA
OSmacOS 26.5.2 (arm64)
Shell & versionbash 3.2.57
bashunit version0.50.0

Summary

A test killed by --test-timeout never runs its tear_down. The watchdog signals the test's process group, the subshell dies without running its EXIT trap, and bashunit::runner::cleanup_on_exit never calls the hook.

Same class as #1318, at test scope. The file-scoped pair survives a timeout, because the loop keeps going and runs tear_down_after_script. The per-test pair does not.

This is the opposite asymmetry from #1323, where tear_down runs and tear_down_after_script does not. Both are the same missing guarantee seen from two sides.

Current behavior

The test is reported as Test timed out after 1s, the run continues, and whatever set_up acquired for that test is left behind.

How to reproduce

timeout_test.sh:

functionset_up_before_script() { :>"$CLEANUP_MARKER.setupfile"; }
functiontear_down_after_script() { :>"$CLEANUP_MARKER.RAN_FILE_TEARDOWN"; }
functionset_up() { :>"$CLEANUP_MARKER.setuptest"; }
functiontear_down() { :>"$CLEANUP_MARKER.RAN_TEST_TEARDOWN"; }
functiontest_hangs() { sleep 10; assert_true true; }
$ CLEANUP_MARKER=/tmp/bu-to.marker ./bashunit --no-parallel --test-timeout 1 timeout_test.sh
✗ Error: Hangs
Test timed out after 1s
$ ls /tmp/bu-to.marker.*
/tmp/bu-to.marker.RAN_FILE_TEARDOWN
/tmp/bu-to.marker.setupfile
/tmp/bu-to.marker.setuptest

RAN_TEST_TEARDOWN is absent. The per-test set_up ran, its tear_down did not.

Verified on bash 3.2.57. Bash's default SIGTERM handling inside a forked subshell has changed across versions, so confirm on bash 5 before choosing a fix. A closely related path behaves differently on the same machine: pkill -P $$ on Ctrl-C does let the EXIT trap run.

Expected behavior

A timed-out test runs tear_down, so a per-test resource can be released. The timeout still bounds the run.

Where it happens

  • src/runner/exec.sh:363: the test body is spawned as (bashunit::runner::execute_test_body ...) & under set -m
  • src/runner/exec.sh:399-402: the watchdog writes the marker, sends kill -TERM -"$test_pid" to the group, sleeps 0.3s, then kill -KILL -"$test_pid"
  • src/runner/hooks.sh:37: bashunit::runner::run_tear_down lives in cleanup_on_exit, the subshell's EXIT trap, which never fires
  • src/runner/hooks.sh:38-40: clear_mocks and cleanup_testcase_temp_files are skipped on the same path

Acceptance criteria

  • An acceptance test: --test-timeout 1 against a hanging test whose tear_down releases a resource, asserted gone.
  • The Test timed out after Ns message and the failure classification are unchanged. tests/acceptance timeout coverage still passes.
  • A tear_down that hangs cannot extend the run past the watchdog's KILL. The 0.3s grace, or whatever replaces it, still bounds the process.
  • Verified on bash 3.2 and bash 5. The make test-bash-3 path exists for the first.
  • --parallel runs behave the same way.
  • CHANGELOG.md gets a Fixed entry.

Notes for the implementer

  • Trapping TERM inside the test-body subshell so the EXIT trap gets a chance is the direct fix. The 0.3s between TERM and KILL is the whole budget the hook gets, so measure whether that is enough or has to grow.
  • Weigh the tradeoff and write the decision down: a teardown that never returns is worse than a teardown that never runs. A bounded best-effort hook is the useful behaviour, not an unbounded one.
  • The watchdog notes at src/runner/exec.sh:366-390 explain why it is detached and why it re-checks kill -0. Do not break that. The suites acceptance test is flaky on Bash 3.0 under load #1137 is the receipt.
  • 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)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' A test killed by `--test-timeout` does not run `tear_down` · Issue #1324 · TypedDevs/bashunit · GitHub
Skip to content

A test killed by --test-timeout does not run tear_down #1324

Description

@Chemaclass
QA
OSmacOS 26.5.2 (arm64)
Shell & versionbash 3.2.57
bashunit version0.50.0

Summary

A test killed by --test-timeout never runs its tear_down. The watchdog signals the test's process group, the subshell dies without running its EXIT trap, and bashunit::runner::cleanup_on_exit never calls the hook.

Same class as #1318, at test scope. The file-scoped pair survives a timeout, because the loop keeps going and runs tear_down_after_script. The per-test pair does not.

This is the opposite asymmetry from #1323, where tear_down runs and tear_down_after_script does not. Both are the same missing guarantee seen from two sides.

Current behavior

The test is reported as Test timed out after 1s, the run continues, and whatever set_up acquired for that test is left behind.

How to reproduce

timeout_test.sh:

functionset_up_before_script() { :>"$CLEANUP_MARKER.setupfile"; }
functiontear_down_after_script() { :>"$CLEANUP_MARKER.RAN_FILE_TEARDOWN"; }
functionset_up() { :>"$CLEANUP_MARKER.setuptest"; }
functiontear_down() { :>"$CLEANUP_MARKER.RAN_TEST_TEARDOWN"; }
functiontest_hangs() { sleep 10; assert_true true; }
$ CLEANUP_MARKER=/tmp/bu-to.marker ./bashunit --no-parallel --test-timeout 1 timeout_test.sh
✗ Error: Hangs
Test timed out after 1s
$ ls /tmp/bu-to.marker.*
/tmp/bu-to.marker.RAN_FILE_TEARDOWN
/tmp/bu-to.marker.setupfile
/tmp/bu-to.marker.setuptest

RAN_TEST_TEARDOWN is absent. The per-test set_up ran, its tear_down did not.

Verified on bash 3.2.57. Bash's default SIGTERM handling inside a forked subshell has changed across versions, so confirm on bash 5 before choosing a fix. A closely related path behaves differently on the same machine: pkill -P $$ on Ctrl-C does let the EXIT trap run.

Expected behavior

A timed-out test runs tear_down, so a per-test resource can be released. The timeout still bounds the run.

Where it happens

  • src/runner/exec.sh:363: the test body is spawned as (bashunit::runner::execute_test_body ...) & under set -m
  • src/runner/exec.sh:399-402: the watchdog writes the marker, sends kill -TERM -"$test_pid" to the group, sleeps 0.3s, then kill -KILL -"$test_pid"
  • src/runner/hooks.sh:37: bashunit::runner::run_tear_down lives in cleanup_on_exit, the subshell's EXIT trap, which never fires
  • src/runner/hooks.sh:38-40: clear_mocks and cleanup_testcase_temp_files are skipped on the same path

Acceptance criteria

  • An acceptance test: --test-timeout 1 against a hanging test whose tear_down releases a resource, asserted gone.
  • The Test timed out after Ns message and the failure classification are unchanged. tests/acceptance timeout coverage still passes.
  • A tear_down that hangs cannot extend the run past the watchdog's KILL. The 0.3s grace, or whatever replaces it, still bounds the process.
  • Verified on bash 3.2 and bash 5. The make test-bash-3 path exists for the first.
  • --parallel runs behave the same way.
  • CHANGELOG.md gets a Fixed entry.

Notes for the implementer

  • Trapping TERM inside the test-body subshell so the EXIT trap gets a chance is the direct fix. The 0.3s between TERM and KILL is the whole budget the hook gets, so measure whether that is enough or has to grow.
  • Weigh the tradeoff and write the decision down: a teardown that never returns is worse than a teardown that never runs. A bounded best-effort hook is the useful behaviour, not an unbounded one.
  • The watchdog notes at src/runner/exec.sh:366-390 explain why it is detached and why it re-checks kill -0. Do not break that. The suites acceptance test is flaky on Bash 3.0 under load #1137 is the receipt.
  • 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)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); A test killed by `--test-timeout` does not run `tear_down` · Issue #1324 · TypedDevs/bashunit · GitHub
Skip to content

A test killed by --test-timeout does not run tear_down #1324

Description

@Chemaclass
QA
OSmacOS 26.5.2 (arm64)
Shell & versionbash 3.2.57
bashunit version0.50.0

Summary

A test killed by --test-timeout never runs its tear_down. The watchdog signals the test's process group, the subshell dies without running its EXIT trap, and bashunit::runner::cleanup_on_exit never calls the hook.

Same class as #1318, at test scope. The file-scoped pair survives a timeout, because the loop keeps going and runs tear_down_after_script. The per-test pair does not.

This is the opposite asymmetry from #1323, where tear_down runs and tear_down_after_script does not. Both are the same missing guarantee seen from two sides.

Current behavior

The test is reported as Test timed out after 1s, the run continues, and whatever set_up acquired for that test is left behind.

How to reproduce

timeout_test.sh:

functionset_up_before_script() { :>"$CLEANUP_MARKER.setupfile"; }
functiontear_down_after_script() { :>"$CLEANUP_MARKER.RAN_FILE_TEARDOWN"; }
functionset_up() { :>"$CLEANUP_MARKER.setuptest"; }
functiontear_down() { :>"$CLEANUP_MARKER.RAN_TEST_TEARDOWN"; }
functiontest_hangs() { sleep 10; assert_true true; }
$ CLEANUP_MARKER=/tmp/bu-to.marker ./bashunit --no-parallel --test-timeout 1 timeout_test.sh
✗ Error: Hangs
Test timed out after 1s
$ ls /tmp/bu-to.marker.*
/tmp/bu-to.marker.RAN_FILE_TEARDOWN
/tmp/bu-to.marker.setupfile
/tmp/bu-to.marker.setuptest

RAN_TEST_TEARDOWN is absent. The per-test set_up ran, its tear_down did not.

Verified on bash 3.2.57. Bash's default SIGTERM handling inside a forked subshell has changed across versions, so confirm on bash 5 before choosing a fix. A closely related path behaves differently on the same machine: pkill -P $$ on Ctrl-C does let the EXIT trap run.

Expected behavior

A timed-out test runs tear_down, so a per-test resource can be released. The timeout still bounds the run.

Where it happens

  • src/runner/exec.sh:363: the test body is spawned as (bashunit::runner::execute_test_body ...) & under set -m
  • src/runner/exec.sh:399-402: the watchdog writes the marker, sends kill -TERM -"$test_pid" to the group, sleeps 0.3s, then kill -KILL -"$test_pid"
  • src/runner/hooks.sh:37: bashunit::runner::run_tear_down lives in cleanup_on_exit, the subshell's EXIT trap, which never fires
  • src/runner/hooks.sh:38-40: clear_mocks and cleanup_testcase_temp_files are skipped on the same path

Acceptance criteria

  • An acceptance test: --test-timeout 1 against a hanging test whose tear_down releases a resource, asserted gone.
  • The Test timed out after Ns message and the failure classification are unchanged. tests/acceptance timeout coverage still passes.
  • A tear_down that hangs cannot extend the run past the watchdog's KILL. The 0.3s grace, or whatever replaces it, still bounds the process.
  • Verified on bash 3.2 and bash 5. The make test-bash-3 path exists for the first.
  • --parallel runs behave the same way.
  • CHANGELOG.md gets a Fixed entry.

Notes for the implementer

  • Trapping TERM inside the test-body subshell so the EXIT trap gets a chance is the direct fix. The 0.3s between TERM and KILL is the whole budget the hook gets, so measure whether that is enough or has to grow.
  • Weigh the tradeoff and write the decision down: a teardown that never returns is worse than a teardown that never runs. A bounded best-effort hook is the useful behaviour, not an unbounded one.
  • The watchdog notes at src/runner/exec.sh:366-390 explain why it is detached and why it re-checks kill -0. Do not break that. The suites acceptance test is flaky on Bash 3.0 under load #1137 is the receipt.
  • 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