tear_down_after_script is not run when set_up_before_script fails #1318

Description

@carlfriedrich
QA
OSLinux (openSUSE Tumbleweed, WSL2)
Shell & versionbash 5.3.9
bashunit version0.50.0

Summary

tear_down_after_script is not run when set_up_before_script fails, so anything the setup hook acquired before a failing statement is leaked, with no framework-supported way to release it.

The per-test pair behaves the opposite way: tear_downis run when set_up fails. Both pairs are documented as symmetric counterparts, so the same code shape leaks at file scope and cleans up at test scope.

This matters because set_up_before_script is the only place to acquire a file-scoped resource needed for all tests in the file, and tear_down_after_script is the only place to release it. A setup hook that does more than one thing leaks whatever it already acquired as soon as any later step fails. Every author then has to duplicate the teardown logic into the setup hook's error paths, which is precisely the duplication the hook pair exists to avoid.

Current behavior

When set_up_before_script fails, bashunit reports every test in the file as failed and moves on to the next file. tear_down_after_script is not called, and nothing in the output says that it was skipped — so whatever the hook had already acquired is left behind silently.

How to reproduce

Two files, each acquiring a resource in its setup hook and then failing.

file_hooks.test.sh:

#!/usr/bin/env bash
RESOURCE=""functionset_up_before_script() {
RESOURCE="$(mktemp /tmp/bu-repro/file-scoped.XXXXXX)"echo"acquired ${RESOURCE}"false
}
functiontear_down_after_script() {
echo"tear_down_after_script ran"
rm -f "${RESOURCE}"
}
functiontest_anything() {
assert_same 1 1
}

test_hooks.test.sh:

#!/usr/bin/env bash
RESOURCE=""functionset_up() {
RESOURCE="$(mktemp /tmp/bu-repro/test-scoped.XXXXXX)"echo"acquired ${RESOURCE}"false
}
functiontear_down() {
echo"tear_down ran"
rm -f "${RESOURCE}"
}
functiontest_anything() {
assert_same 1 1
}

File scope — no tear_down_after_script ran in the output, and the resource survives:

$ bashunit /tmp/bu-repro/file_hooks.test.sh
Running /tmp/bu-repro/file_hooks.test.sh
✗ Error: Set up before script
acquired /tmp/bu-repro/file-scoped.WdPcUO
Tests: 1 failed, 1 total
$ ls /tmp/bu-repro/file-scoped.*
/tmp/bu-repro/file-scoped.WdPcUO

Test scope — tear_down ran, and the resource is gone:

$ bashunit /tmp/bu-repro/test_hooks.test.sh
Running /tmp/bu-repro/test_hooks.test.sh
✗ Error: Set up
acquired /tmp/bu-repro/test-scoped.oRcGOd Output: acquired /tmp/bu-repro/test-scoped.oRcGOdtear_down ran
Tests: 1 failed, 1 total
$ ls /tmp/bu-repro/test-scoped.*
ls: cannot access '/tmp/bu-repro/test-scoped.*': No such file or directory

Expected behavior

tear_down_after_script runs after a failing set_up_before_script, matching what the per-test pair already does, so a resource acquired before the failure can be released in the one place meant for it.

docs/test-files.md currently describes the hook as running "only once when all the test functions in the test file have been executed", so the current behaviour seems deliberate. I still find it surprising, though:

  • The asymmetry with set_up/tear_down is invisible at the call site. The same code shape behaves differently depending only on which of the two hook pairs it sits in.
  • tear_down_after_script already has to tolerate partially-built state today, because it runs after tests that potentially failed halfway through. Running it after a failed setup adds no new requirement on it.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

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

tear_down_after_script is not run when set_up_before_script fails #1318

Description

@carlfriedrich
QA
OSLinux (openSUSE Tumbleweed, WSL2)
Shell & versionbash 5.3.9
bashunit version0.50.0

Summary

tear_down_after_script is not run when set_up_before_script fails, so anything the setup hook acquired before a failing statement is leaked, with no framework-supported way to release it.

The per-test pair behaves the opposite way: tear_downis run when set_up fails. Both pairs are documented as symmetric counterparts, so the same code shape leaks at file scope and cleans up at test scope.

This matters because set_up_before_script is the only place to acquire a file-scoped resource needed for all tests in the file, and tear_down_after_script is the only place to release it. A setup hook that does more than one thing leaks whatever it already acquired as soon as any later step fails. Every author then has to duplicate the teardown logic into the setup hook's error paths, which is precisely the duplication the hook pair exists to avoid.

Current behavior

When set_up_before_script fails, bashunit reports every test in the file as failed and moves on to the next file. tear_down_after_script is not called, and nothing in the output says that it was skipped — so whatever the hook had already acquired is left behind silently.

How to reproduce

Two files, each acquiring a resource in its setup hook and then failing.

file_hooks.test.sh:

#!/usr/bin/env bash
RESOURCE=""functionset_up_before_script() {
RESOURCE="$(mktemp /tmp/bu-repro/file-scoped.XXXXXX)"echo"acquired ${RESOURCE}"false
}
functiontear_down_after_script() {
echo"tear_down_after_script ran"
rm -f "${RESOURCE}"
}
functiontest_anything() {
assert_same 1 1
}

test_hooks.test.sh:

#!/usr/bin/env bash
RESOURCE=""functionset_up() {
RESOURCE="$(mktemp /tmp/bu-repro/test-scoped.XXXXXX)"echo"acquired ${RESOURCE}"false
}
functiontear_down() {
echo"tear_down ran"
rm -f "${RESOURCE}"
}
functiontest_anything() {
assert_same 1 1
}

File scope — no tear_down_after_script ran in the output, and the resource survives:

$ bashunit /tmp/bu-repro/file_hooks.test.sh
Running /tmp/bu-repro/file_hooks.test.sh
✗ Error: Set up before script
acquired /tmp/bu-repro/file-scoped.WdPcUO
Tests: 1 failed, 1 total
$ ls /tmp/bu-repro/file-scoped.*
/tmp/bu-repro/file-scoped.WdPcUO

Test scope — tear_down ran, and the resource is gone:

$ bashunit /tmp/bu-repro/test_hooks.test.sh
Running /tmp/bu-repro/test_hooks.test.sh
✗ Error: Set up
acquired /tmp/bu-repro/test-scoped.oRcGOd Output: acquired /tmp/bu-repro/test-scoped.oRcGOdtear_down ran
Tests: 1 failed, 1 total
$ ls /tmp/bu-repro/test-scoped.*
ls: cannot access '/tmp/bu-repro/test-scoped.*': No such file or directory

Expected behavior

tear_down_after_script runs after a failing set_up_before_script, matching what the per-test pair already does, so a resource acquired before the failure can be released in the one place meant for it.

docs/test-files.md currently describes the hook as running "only once when all the test functions in the test file have been executed", so the current behaviour seems deliberate. I still find it surprising, though:

  • The asymmetry with set_up/tear_down is invisible at the call site. The same code shape behaves differently depending only on which of the two hook pairs it sits in.
  • tear_down_after_script already has to tolerate partially-built state today, because it runs after tests that potentially failed halfway through. Running it after a failed setup adds no new requirement on it.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

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

tear_down_after_script is not run when set_up_before_script fails #1318

Description

@carlfriedrich
QA
OSLinux (openSUSE Tumbleweed, WSL2)
Shell & versionbash 5.3.9
bashunit version0.50.0

Summary

tear_down_after_script is not run when set_up_before_script fails, so anything the setup hook acquired before a failing statement is leaked, with no framework-supported way to release it.

The per-test pair behaves the opposite way: tear_downis run when set_up fails. Both pairs are documented as symmetric counterparts, so the same code shape leaks at file scope and cleans up at test scope.

This matters because set_up_before_script is the only place to acquire a file-scoped resource needed for all tests in the file, and tear_down_after_script is the only place to release it. A setup hook that does more than one thing leaks whatever it already acquired as soon as any later step fails. Every author then has to duplicate the teardown logic into the setup hook's error paths, which is precisely the duplication the hook pair exists to avoid.

Current behavior

When set_up_before_script fails, bashunit reports every test in the file as failed and moves on to the next file. tear_down_after_script is not called, and nothing in the output says that it was skipped — so whatever the hook had already acquired is left behind silently.

How to reproduce

Two files, each acquiring a resource in its setup hook and then failing.

file_hooks.test.sh:

#!/usr/bin/env bash
RESOURCE=""functionset_up_before_script() {
RESOURCE="$(mktemp /tmp/bu-repro/file-scoped.XXXXXX)"echo"acquired ${RESOURCE}"false
}
functiontear_down_after_script() {
echo"tear_down_after_script ran"
rm -f "${RESOURCE}"
}
functiontest_anything() {
assert_same 1 1
}

test_hooks.test.sh:

#!/usr/bin/env bash
RESOURCE=""functionset_up() {
RESOURCE="$(mktemp /tmp/bu-repro/test-scoped.XXXXXX)"echo"acquired ${RESOURCE}"false
}
functiontear_down() {
echo"tear_down ran"
rm -f "${RESOURCE}"
}
functiontest_anything() {
assert_same 1 1
}

File scope — no tear_down_after_script ran in the output, and the resource survives:

$ bashunit /tmp/bu-repro/file_hooks.test.sh
Running /tmp/bu-repro/file_hooks.test.sh
✗ Error: Set up before script
acquired /tmp/bu-repro/file-scoped.WdPcUO
Tests: 1 failed, 1 total
$ ls /tmp/bu-repro/file-scoped.*
/tmp/bu-repro/file-scoped.WdPcUO

Test scope — tear_down ran, and the resource is gone:

$ bashunit /tmp/bu-repro/test_hooks.test.sh
Running /tmp/bu-repro/test_hooks.test.sh
✗ Error: Set up
acquired /tmp/bu-repro/test-scoped.oRcGOd Output: acquired /tmp/bu-repro/test-scoped.oRcGOdtear_down ran
Tests: 1 failed, 1 total
$ ls /tmp/bu-repro/test-scoped.*
ls: cannot access '/tmp/bu-repro/test-scoped.*': No such file or directory

Expected behavior

tear_down_after_script runs after a failing set_up_before_script, matching what the per-test pair already does, so a resource acquired before the failure can be released in the one place meant for it.

docs/test-files.md currently describes the hook as running "only once when all the test functions in the test file have been executed", so the current behaviour seems deliberate. I still find it surprising, though:

  • The asymmetry with set_up/tear_down is invisible at the call site. The same code shape behaves differently depending only on which of the two hook pairs it sits in.
  • tear_down_after_script already has to tolerate partially-built state today, because it runs after tests that potentially failed halfway through. Running it after a failed setup adds no new requirement on it.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

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

tear_down_after_script is not run when set_up_before_script fails #1318

Description

@carlfriedrich
QA
OSLinux (openSUSE Tumbleweed, WSL2)
Shell & versionbash 5.3.9
bashunit version0.50.0

Summary

tear_down_after_script is not run when set_up_before_script fails, so anything the setup hook acquired before a failing statement is leaked, with no framework-supported way to release it.

The per-test pair behaves the opposite way: tear_downis run when set_up fails. Both pairs are documented as symmetric counterparts, so the same code shape leaks at file scope and cleans up at test scope.

This matters because set_up_before_script is the only place to acquire a file-scoped resource needed for all tests in the file, and tear_down_after_script is the only place to release it. A setup hook that does more than one thing leaks whatever it already acquired as soon as any later step fails. Every author then has to duplicate the teardown logic into the setup hook's error paths, which is precisely the duplication the hook pair exists to avoid.

Current behavior

When set_up_before_script fails, bashunit reports every test in the file as failed and moves on to the next file. tear_down_after_script is not called, and nothing in the output says that it was skipped — so whatever the hook had already acquired is left behind silently.

How to reproduce

Two files, each acquiring a resource in its setup hook and then failing.

file_hooks.test.sh:

#!/usr/bin/env bash
RESOURCE=""functionset_up_before_script() {
RESOURCE="$(mktemp /tmp/bu-repro/file-scoped.XXXXXX)"echo"acquired ${RESOURCE}"false
}
functiontear_down_after_script() {
echo"tear_down_after_script ran"
rm -f "${RESOURCE}"
}
functiontest_anything() {
assert_same 1 1
}

test_hooks.test.sh:

#!/usr/bin/env bash
RESOURCE=""functionset_up() {
RESOURCE="$(mktemp /tmp/bu-repro/test-scoped.XXXXXX)"echo"acquired ${RESOURCE}"false
}
functiontear_down() {
echo"tear_down ran"
rm -f "${RESOURCE}"
}
functiontest_anything() {
assert_same 1 1
}

File scope — no tear_down_after_script ran in the output, and the resource survives:

$ bashunit /tmp/bu-repro/file_hooks.test.sh
Running /tmp/bu-repro/file_hooks.test.sh
✗ Error: Set up before script
acquired /tmp/bu-repro/file-scoped.WdPcUO
Tests: 1 failed, 1 total
$ ls /tmp/bu-repro/file-scoped.*
/tmp/bu-repro/file-scoped.WdPcUO

Test scope — tear_down ran, and the resource is gone:

$ bashunit /tmp/bu-repro/test_hooks.test.sh
Running /tmp/bu-repro/test_hooks.test.sh
✗ Error: Set up
acquired /tmp/bu-repro/test-scoped.oRcGOd Output: acquired /tmp/bu-repro/test-scoped.oRcGOdtear_down ran
Tests: 1 failed, 1 total
$ ls /tmp/bu-repro/test-scoped.*
ls: cannot access '/tmp/bu-repro/test-scoped.*': No such file or directory

Expected behavior

tear_down_after_script runs after a failing set_up_before_script, matching what the per-test pair already does, so a resource acquired before the failure can be released in the one place meant for it.

docs/test-files.md currently describes the hook as running "only once when all the test functions in the test file have been executed", so the current behaviour seems deliberate. I still find it surprising, though:

  • The asymmetry with set_up/tear_down is invisible at the call site. The same code shape behaves differently depending only on which of the two hook pairs it sits in.
  • tear_down_after_script already has to tolerate partially-built state today, because it runs after tests that potentially failed halfway through. Running it after a failed setup adds no new requirement on it.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

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

tear_down_after_script is not run when set_up_before_script fails #1318

Description

@carlfriedrich
QA
OSLinux (openSUSE Tumbleweed, WSL2)
Shell & versionbash 5.3.9
bashunit version0.50.0

Summary

tear_down_after_script is not run when set_up_before_script fails, so anything the setup hook acquired before a failing statement is leaked, with no framework-supported way to release it.

The per-test pair behaves the opposite way: tear_downis run when set_up fails. Both pairs are documented as symmetric counterparts, so the same code shape leaks at file scope and cleans up at test scope.

This matters because set_up_before_script is the only place to acquire a file-scoped resource needed for all tests in the file, and tear_down_after_script is the only place to release it. A setup hook that does more than one thing leaks whatever it already acquired as soon as any later step fails. Every author then has to duplicate the teardown logic into the setup hook's error paths, which is precisely the duplication the hook pair exists to avoid.

Current behavior

When set_up_before_script fails, bashunit reports every test in the file as failed and moves on to the next file. tear_down_after_script is not called, and nothing in the output says that it was skipped — so whatever the hook had already acquired is left behind silently.

How to reproduce

Two files, each acquiring a resource in its setup hook and then failing.

file_hooks.test.sh:

#!/usr/bin/env bash
RESOURCE=""functionset_up_before_script() {
RESOURCE="$(mktemp /tmp/bu-repro/file-scoped.XXXXXX)"echo"acquired ${RESOURCE}"false
}
functiontear_down_after_script() {
echo"tear_down_after_script ran"
rm -f "${RESOURCE}"
}
functiontest_anything() {
assert_same 1 1
}

test_hooks.test.sh:

#!/usr/bin/env bash
RESOURCE=""functionset_up() {
RESOURCE="$(mktemp /tmp/bu-repro/test-scoped.XXXXXX)"echo"acquired ${RESOURCE}"false
}
functiontear_down() {
echo"tear_down ran"
rm -f "${RESOURCE}"
}
functiontest_anything() {
assert_same 1 1
}

File scope — no tear_down_after_script ran in the output, and the resource survives:

$ bashunit /tmp/bu-repro/file_hooks.test.sh
Running /tmp/bu-repro/file_hooks.test.sh
✗ Error: Set up before script
acquired /tmp/bu-repro/file-scoped.WdPcUO
Tests: 1 failed, 1 total
$ ls /tmp/bu-repro/file-scoped.*
/tmp/bu-repro/file-scoped.WdPcUO

Test scope — tear_down ran, and the resource is gone:

$ bashunit /tmp/bu-repro/test_hooks.test.sh
Running /tmp/bu-repro/test_hooks.test.sh
✗ Error: Set up
acquired /tmp/bu-repro/test-scoped.oRcGOd Output: acquired /tmp/bu-repro/test-scoped.oRcGOdtear_down ran
Tests: 1 failed, 1 total
$ ls /tmp/bu-repro/test-scoped.*
ls: cannot access '/tmp/bu-repro/test-scoped.*': No such file or directory

Expected behavior

tear_down_after_script runs after a failing set_up_before_script, matching what the per-test pair already does, so a resource acquired before the failure can be released in the one place meant for it.

docs/test-files.md currently describes the hook as running "only once when all the test functions in the test file have been executed", so the current behaviour seems deliberate. I still find it surprising, though:

  • The asymmetry with set_up/tear_down is invisible at the call site. The same code shape behaves differently depending only on which of the two hook pairs it sits in.
  • tear_down_after_script already has to tolerate partially-built state today, because it runs after tests that potentially failed halfway through. Running it after a failed setup adds no new requirement on it.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

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

tear_down_after_script is not run when set_up_before_script fails #1318

Description

@carlfriedrich
QA
OSLinux (openSUSE Tumbleweed, WSL2)
Shell & versionbash 5.3.9
bashunit version0.50.0

Summary

tear_down_after_script is not run when set_up_before_script fails, so anything the setup hook acquired before a failing statement is leaked, with no framework-supported way to release it.

The per-test pair behaves the opposite way: tear_downis run when set_up fails. Both pairs are documented as symmetric counterparts, so the same code shape leaks at file scope and cleans up at test scope.

This matters because set_up_before_script is the only place to acquire a file-scoped resource needed for all tests in the file, and tear_down_after_script is the only place to release it. A setup hook that does more than one thing leaks whatever it already acquired as soon as any later step fails. Every author then has to duplicate the teardown logic into the setup hook's error paths, which is precisely the duplication the hook pair exists to avoid.

Current behavior

When set_up_before_script fails, bashunit reports every test in the file as failed and moves on to the next file. tear_down_after_script is not called, and nothing in the output says that it was skipped — so whatever the hook had already acquired is left behind silently.

How to reproduce

Two files, each acquiring a resource in its setup hook and then failing.

file_hooks.test.sh:

#!/usr/bin/env bash
RESOURCE=""functionset_up_before_script() {
RESOURCE="$(mktemp /tmp/bu-repro/file-scoped.XXXXXX)"echo"acquired ${RESOURCE}"false
}
functiontear_down_after_script() {
echo"tear_down_after_script ran"
rm -f "${RESOURCE}"
}
functiontest_anything() {
assert_same 1 1
}

test_hooks.test.sh:

#!/usr/bin/env bash
RESOURCE=""functionset_up() {
RESOURCE="$(mktemp /tmp/bu-repro/test-scoped.XXXXXX)"echo"acquired ${RESOURCE}"false
}
functiontear_down() {
echo"tear_down ran"
rm -f "${RESOURCE}"
}
functiontest_anything() {
assert_same 1 1
}

File scope — no tear_down_after_script ran in the output, and the resource survives:

$ bashunit /tmp/bu-repro/file_hooks.test.sh
Running /tmp/bu-repro/file_hooks.test.sh
✗ Error: Set up before script
acquired /tmp/bu-repro/file-scoped.WdPcUO
Tests: 1 failed, 1 total
$ ls /tmp/bu-repro/file-scoped.*
/tmp/bu-repro/file-scoped.WdPcUO

Test scope — tear_down ran, and the resource is gone:

$ bashunit /tmp/bu-repro/test_hooks.test.sh
Running /tmp/bu-repro/test_hooks.test.sh
✗ Error: Set up
acquired /tmp/bu-repro/test-scoped.oRcGOd Output: acquired /tmp/bu-repro/test-scoped.oRcGOdtear_down ran
Tests: 1 failed, 1 total
$ ls /tmp/bu-repro/test-scoped.*
ls: cannot access '/tmp/bu-repro/test-scoped.*': No such file or directory

Expected behavior

tear_down_after_script runs after a failing set_up_before_script, matching what the per-test pair already does, so a resource acquired before the failure can be released in the one place meant for it.

docs/test-files.md currently describes the hook as running "only once when all the test functions in the test file have been executed", so the current behaviour seems deliberate. I still find it surprising, though:

  • The asymmetry with set_up/tear_down is invisible at the call site. The same code shape behaves differently depending only on which of the two hook pairs it sits in.
  • tear_down_after_script already has to tolerate partially-built state today, because it runs after tests that potentially failed halfway through. Running it after a failed setup adds no new requirement on it.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

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

tear_down_after_script is not run when set_up_before_script fails #1318

Description

@carlfriedrich
QA
OSLinux (openSUSE Tumbleweed, WSL2)
Shell & versionbash 5.3.9
bashunit version0.50.0

Summary

tear_down_after_script is not run when set_up_before_script fails, so anything the setup hook acquired before a failing statement is leaked, with no framework-supported way to release it.

The per-test pair behaves the opposite way: tear_downis run when set_up fails. Both pairs are documented as symmetric counterparts, so the same code shape leaks at file scope and cleans up at test scope.

This matters because set_up_before_script is the only place to acquire a file-scoped resource needed for all tests in the file, and tear_down_after_script is the only place to release it. A setup hook that does more than one thing leaks whatever it already acquired as soon as any later step fails. Every author then has to duplicate the teardown logic into the setup hook's error paths, which is precisely the duplication the hook pair exists to avoid.

Current behavior

When set_up_before_script fails, bashunit reports every test in the file as failed and moves on to the next file. tear_down_after_script is not called, and nothing in the output says that it was skipped — so whatever the hook had already acquired is left behind silently.

How to reproduce

Two files, each acquiring a resource in its setup hook and then failing.

file_hooks.test.sh:

#!/usr/bin/env bash
RESOURCE=""functionset_up_before_script() {
RESOURCE="$(mktemp /tmp/bu-repro/file-scoped.XXXXXX)"echo"acquired ${RESOURCE}"false
}
functiontear_down_after_script() {
echo"tear_down_after_script ran"
rm -f "${RESOURCE}"
}
functiontest_anything() {
assert_same 1 1
}

test_hooks.test.sh:

#!/usr/bin/env bash
RESOURCE=""functionset_up() {
RESOURCE="$(mktemp /tmp/bu-repro/test-scoped.XXXXXX)"echo"acquired ${RESOURCE}"false
}
functiontear_down() {
echo"tear_down ran"
rm -f "${RESOURCE}"
}
functiontest_anything() {
assert_same 1 1
}

File scope — no tear_down_after_script ran in the output, and the resource survives:

$ bashunit /tmp/bu-repro/file_hooks.test.sh
Running /tmp/bu-repro/file_hooks.test.sh
✗ Error: Set up before script
acquired /tmp/bu-repro/file-scoped.WdPcUO
Tests: 1 failed, 1 total
$ ls /tmp/bu-repro/file-scoped.*
/tmp/bu-repro/file-scoped.WdPcUO

Test scope — tear_down ran, and the resource is gone:

$ bashunit /tmp/bu-repro/test_hooks.test.sh
Running /tmp/bu-repro/test_hooks.test.sh
✗ Error: Set up
acquired /tmp/bu-repro/test-scoped.oRcGOd Output: acquired /tmp/bu-repro/test-scoped.oRcGOdtear_down ran
Tests: 1 failed, 1 total
$ ls /tmp/bu-repro/test-scoped.*
ls: cannot access '/tmp/bu-repro/test-scoped.*': No such file or directory

Expected behavior

tear_down_after_script runs after a failing set_up_before_script, matching what the per-test pair already does, so a resource acquired before the failure can be released in the one place meant for it.

docs/test-files.md currently describes the hook as running "only once when all the test functions in the test file have been executed", so the current behaviour seems deliberate. I still find it surprising, though:

  • The asymmetry with set_up/tear_down is invisible at the call site. The same code shape behaves differently depending only on which of the two hook pairs it sits in.
  • tear_down_after_script already has to tolerate partially-built state today, because it runs after tests that potentially failed halfway through. Running it after a failed setup adds no new requirement on it.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

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

tear_down_after_script is not run when set_up_before_script fails #1318

Description

@carlfriedrich
QA
OSLinux (openSUSE Tumbleweed, WSL2)
Shell & versionbash 5.3.9
bashunit version0.50.0

Summary

tear_down_after_script is not run when set_up_before_script fails, so anything the setup hook acquired before a failing statement is leaked, with no framework-supported way to release it.

The per-test pair behaves the opposite way: tear_downis run when set_up fails. Both pairs are documented as symmetric counterparts, so the same code shape leaks at file scope and cleans up at test scope.

This matters because set_up_before_script is the only place to acquire a file-scoped resource needed for all tests in the file, and tear_down_after_script is the only place to release it. A setup hook that does more than one thing leaks whatever it already acquired as soon as any later step fails. Every author then has to duplicate the teardown logic into the setup hook's error paths, which is precisely the duplication the hook pair exists to avoid.

Current behavior

When set_up_before_script fails, bashunit reports every test in the file as failed and moves on to the next file. tear_down_after_script is not called, and nothing in the output says that it was skipped — so whatever the hook had already acquired is left behind silently.

How to reproduce

Two files, each acquiring a resource in its setup hook and then failing.

file_hooks.test.sh:

#!/usr/bin/env bash
RESOURCE=""functionset_up_before_script() {
RESOURCE="$(mktemp /tmp/bu-repro/file-scoped.XXXXXX)"echo"acquired ${RESOURCE}"false
}
functiontear_down_after_script() {
echo"tear_down_after_script ran"
rm -f "${RESOURCE}"
}
functiontest_anything() {
assert_same 1 1
}

test_hooks.test.sh:

#!/usr/bin/env bash
RESOURCE=""functionset_up() {
RESOURCE="$(mktemp /tmp/bu-repro/test-scoped.XXXXXX)"echo"acquired ${RESOURCE}"false
}
functiontear_down() {
echo"tear_down ran"
rm -f "${RESOURCE}"
}
functiontest_anything() {
assert_same 1 1
}

File scope — no tear_down_after_script ran in the output, and the resource survives:

$ bashunit /tmp/bu-repro/file_hooks.test.sh
Running /tmp/bu-repro/file_hooks.test.sh
✗ Error: Set up before script
acquired /tmp/bu-repro/file-scoped.WdPcUO
Tests: 1 failed, 1 total
$ ls /tmp/bu-repro/file-scoped.*
/tmp/bu-repro/file-scoped.WdPcUO

Test scope — tear_down ran, and the resource is gone:

$ bashunit /tmp/bu-repro/test_hooks.test.sh
Running /tmp/bu-repro/test_hooks.test.sh
✗ Error: Set up
acquired /tmp/bu-repro/test-scoped.oRcGOd Output: acquired /tmp/bu-repro/test-scoped.oRcGOdtear_down ran
Tests: 1 failed, 1 total
$ ls /tmp/bu-repro/test-scoped.*
ls: cannot access '/tmp/bu-repro/test-scoped.*': No such file or directory

Expected behavior

tear_down_after_script runs after a failing set_up_before_script, matching what the per-test pair already does, so a resource acquired before the failure can be released in the one place meant for it.

docs/test-files.md currently describes the hook as running "only once when all the test functions in the test file have been executed", so the current behaviour seems deliberate. I still find it surprising, though:

  • The asymmetry with set_up/tear_down is invisible at the call site. The same code shape behaves differently depending only on which of the two hook pairs it sits in.
  • tear_down_after_script already has to tolerate partially-built state today, because it runs after tests that potentially failed halfway through. Running it after a failed setup adds no new requirement on it.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

Projects

  • Status
    Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions