Skip to content

fix(coverage): the DEBUG-trap buffer loses hits on Bash 5 and costs more than it saves #1101

Description

@Chemaclass

Problem

The trap engine collects hits into a 100-entry in-memory buffer
(_BASHUNIT_COVERAGE_BUFFER, _BASHUNIT_COVERAGE_BUFFER_LIMIT=100) and
flushes when it fills. A buffer that lives in a variable dies with the subshell
that filled it, so anything recorded inside a short-lived command substitution
never reaches the data file.

Running tests/unit/assert/basic_test.sh against --coverage-paths src:

Bash 5hitswall
trap, buffer=100 (today)196/114384612 ms
trap, write-through236/114384258 ms
xtrace238/114382147 ms

The 40 missing lines are real code: bashunit::temp_dir and friends, executed
inside $( ). Deterministic across runs.

On Bash 3.2 there is no loss (234 either way) — so the same project reports
different coverage depending on the Bash version
, which is worse than being
uniformly wrong.

The buffer also costs more than it saves

buffer=100write-through
Bash 3.2 (macOS, trap is the only engine)8896 ms7080 ms
Bash 54612 ms4258 ms

Appending to a growing shell string is not cheaper than an >> append per
line, so the buffer is pure downside on both counts.

Proposal

Write through, and delete the buffer and its flush bookkeeping. Points to
verify before landing:

  • --parallel: workers already write to their own data files, so concurrent
    appends stay per-worker; confirm no shared-target case remains
  • the fork budget: >> per line is a syscall, not a fork, so
    bashunit_coverage_forks_test.sh should be unchanged

Related

xtrace is 2x faster than trap on Bash 5 and auto already selects it
there, so this is mainly about the Bash 3.x/macOS path — where trap is the only
option.

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" + '
fix(coverage): the DEBUG-trap buffer loses hits on Bash 5 and costs more than it saves · Issue #1101 · TypedDevs/bashunit · GitHub
Skip to content

fix(coverage): the DEBUG-trap buffer loses hits on Bash 5 and costs more than it saves #1101

Description

@Chemaclass

Problem

The trap engine collects hits into a 100-entry in-memory buffer
(_BASHUNIT_COVERAGE_BUFFER, _BASHUNIT_COVERAGE_BUFFER_LIMIT=100) and
flushes when it fills. A buffer that lives in a variable dies with the subshell
that filled it, so anything recorded inside a short-lived command substitution
never reaches the data file.

Running tests/unit/assert/basic_test.sh against --coverage-paths src:

Bash 5hitswall
trap, buffer=100 (today)196/114384612 ms
trap, write-through236/114384258 ms
xtrace238/114382147 ms

The 40 missing lines are real code: bashunit::temp_dir and friends, executed
inside $( ). Deterministic across runs.

On Bash 3.2 there is no loss (234 either way) — so the same project reports
different coverage depending on the Bash version
, which is worse than being
uniformly wrong.

The buffer also costs more than it saves

buffer=100write-through
Bash 3.2 (macOS, trap is the only engine)8896 ms7080 ms
Bash 54612 ms4258 ms

Appending to a growing shell string is not cheaper than an >> append per
line, so the buffer is pure downside on both counts.

Proposal

Write through, and delete the buffer and its flush bookkeeping. Points to
verify before landing:

  • --parallel: workers already write to their own data files, so concurrent
    appends stay per-worker; confirm no shared-target case remains
  • the fork budget: >> per line is a syscall, not a fork, so
    bashunit_coverage_forks_test.sh should be unchanged

Related

xtrace is 2x faster than trap on Bash 5 and auto already selects it
there, so this is mainly about the Bash 3.x/macOS path — where trap is the only
option.

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('^' + ".*" + ' fix(coverage): the DEBUG-trap buffer loses hits on Bash 5 and costs more than it saves · Issue #1101 · TypedDevs/bashunit · GitHub
Skip to content

fix(coverage): the DEBUG-trap buffer loses hits on Bash 5 and costs more than it saves #1101

Description

@Chemaclass

Problem

The trap engine collects hits into a 100-entry in-memory buffer
(_BASHUNIT_COVERAGE_BUFFER, _BASHUNIT_COVERAGE_BUFFER_LIMIT=100) and
flushes when it fills. A buffer that lives in a variable dies with the subshell
that filled it, so anything recorded inside a short-lived command substitution
never reaches the data file.

Running tests/unit/assert/basic_test.sh against --coverage-paths src:

Bash 5hitswall
trap, buffer=100 (today)196/114384612 ms
trap, write-through236/114384258 ms
xtrace238/114382147 ms

The 40 missing lines are real code: bashunit::temp_dir and friends, executed
inside $( ). Deterministic across runs.

On Bash 3.2 there is no loss (234 either way) — so the same project reports
different coverage depending on the Bash version
, which is worse than being
uniformly wrong.

The buffer also costs more than it saves

buffer=100write-through
Bash 3.2 (macOS, trap is the only engine)8896 ms7080 ms
Bash 54612 ms4258 ms

Appending to a growing shell string is not cheaper than an >> append per
line, so the buffer is pure downside on both counts.

Proposal

Write through, and delete the buffer and its flush bookkeeping. Points to
verify before landing:

  • --parallel: workers already write to their own data files, so concurrent
    appends stay per-worker; confirm no shared-target case remains
  • the fork budget: >> per line is a syscall, not a fork, so
    bashunit_coverage_forks_test.sh should be unchanged

Related

xtrace is 2x faster than trap on Bash 5 and auto already selects it
there, so this is mainly about the Bash 3.x/macOS path — where trap is the only
option.

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('^' + ".*" + ' fix(coverage): the DEBUG-trap buffer loses hits on Bash 5 and costs more than it saves · Issue #1101 · TypedDevs/bashunit · GitHub
Skip to content

fix(coverage): the DEBUG-trap buffer loses hits on Bash 5 and costs more than it saves #1101

Description

@Chemaclass

Problem

The trap engine collects hits into a 100-entry in-memory buffer
(_BASHUNIT_COVERAGE_BUFFER, _BASHUNIT_COVERAGE_BUFFER_LIMIT=100) and
flushes when it fills. A buffer that lives in a variable dies with the subshell
that filled it, so anything recorded inside a short-lived command substitution
never reaches the data file.

Running tests/unit/assert/basic_test.sh against --coverage-paths src:

Bash 5hitswall
trap, buffer=100 (today)196/114384612 ms
trap, write-through236/114384258 ms
xtrace238/114382147 ms

The 40 missing lines are real code: bashunit::temp_dir and friends, executed
inside $( ). Deterministic across runs.

On Bash 3.2 there is no loss (234 either way) — so the same project reports
different coverage depending on the Bash version
, which is worse than being
uniformly wrong.

The buffer also costs more than it saves

buffer=100write-through
Bash 3.2 (macOS, trap is the only engine)8896 ms7080 ms
Bash 54612 ms4258 ms

Appending to a growing shell string is not cheaper than an >> append per
line, so the buffer is pure downside on both counts.

Proposal

Write through, and delete the buffer and its flush bookkeeping. Points to
verify before landing:

  • --parallel: workers already write to their own data files, so concurrent
    appends stay per-worker; confirm no shared-target case remains
  • the fork budget: >> per line is a syscall, not a fork, so
    bashunit_coverage_forks_test.sh should be unchanged

Related

xtrace is 2x faster than trap on Bash 5 and auto already selects it
there, so this is mainly about the Bash 3.x/macOS path — where trap is the only
option.

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" + ' fix(coverage): the DEBUG-trap buffer loses hits on Bash 5 and costs more than it saves · Issue #1101 · TypedDevs/bashunit · GitHub
Skip to content

fix(coverage): the DEBUG-trap buffer loses hits on Bash 5 and costs more than it saves #1101

Description

@Chemaclass

Problem

The trap engine collects hits into a 100-entry in-memory buffer
(_BASHUNIT_COVERAGE_BUFFER, _BASHUNIT_COVERAGE_BUFFER_LIMIT=100) and
flushes when it fills. A buffer that lives in a variable dies with the subshell
that filled it, so anything recorded inside a short-lived command substitution
never reaches the data file.

Running tests/unit/assert/basic_test.sh against --coverage-paths src:

Bash 5hitswall
trap, buffer=100 (today)196/114384612 ms
trap, write-through236/114384258 ms
xtrace238/114382147 ms

The 40 missing lines are real code: bashunit::temp_dir and friends, executed
inside $( ). Deterministic across runs.

On Bash 3.2 there is no loss (234 either way) — so the same project reports
different coverage depending on the Bash version
, which is worse than being
uniformly wrong.

The buffer also costs more than it saves

buffer=100write-through
Bash 3.2 (macOS, trap is the only engine)8896 ms7080 ms
Bash 54612 ms4258 ms

Appending to a growing shell string is not cheaper than an >> append per
line, so the buffer is pure downside on both counts.

Proposal

Write through, and delete the buffer and its flush bookkeeping. Points to
verify before landing:

  • --parallel: workers already write to their own data files, so concurrent
    appends stay per-worker; confirm no shared-target case remains
  • the fork budget: >> per line is a syscall, not a fork, so
    bashunit_coverage_forks_test.sh should be unchanged

Related

xtrace is 2x faster than trap on Bash 5 and auto already selects it
there, so this is mainly about the Bash 3.x/macOS path — where trap is the only
option.

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('^' + ".*" + ' fix(coverage): the DEBUG-trap buffer loses hits on Bash 5 and costs more than it saves · Issue #1101 · TypedDevs/bashunit · GitHub
Skip to content

fix(coverage): the DEBUG-trap buffer loses hits on Bash 5 and costs more than it saves #1101

Description

@Chemaclass

Problem

The trap engine collects hits into a 100-entry in-memory buffer
(_BASHUNIT_COVERAGE_BUFFER, _BASHUNIT_COVERAGE_BUFFER_LIMIT=100) and
flushes when it fills. A buffer that lives in a variable dies with the subshell
that filled it, so anything recorded inside a short-lived command substitution
never reaches the data file.

Running tests/unit/assert/basic_test.sh against --coverage-paths src:

Bash 5hitswall
trap, buffer=100 (today)196/114384612 ms
trap, write-through236/114384258 ms
xtrace238/114382147 ms

The 40 missing lines are real code: bashunit::temp_dir and friends, executed
inside $( ). Deterministic across runs.

On Bash 3.2 there is no loss (234 either way) — so the same project reports
different coverage depending on the Bash version
, which is worse than being
uniformly wrong.

The buffer also costs more than it saves

buffer=100write-through
Bash 3.2 (macOS, trap is the only engine)8896 ms7080 ms
Bash 54612 ms4258 ms

Appending to a growing shell string is not cheaper than an >> append per
line, so the buffer is pure downside on both counts.

Proposal

Write through, and delete the buffer and its flush bookkeeping. Points to
verify before landing:

  • --parallel: workers already write to their own data files, so concurrent
    appends stay per-worker; confirm no shared-target case remains
  • the fork budget: >> per line is a syscall, not a fork, so
    bashunit_coverage_forks_test.sh should be unchanged

Related

xtrace is 2x faster than trap on Bash 5 and auto already selects it
there, so this is mainly about the Bash 3.x/macOS path — where trap is the only
option.

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); } })(); })(); fix(coverage): the DEBUG-trap buffer loses hits on Bash 5 and costs more than it saves · Issue #1101 · TypedDevs/bashunit · GitHub
Skip to content

fix(coverage): the DEBUG-trap buffer loses hits on Bash 5 and costs more than it saves #1101

Description

@Chemaclass

Problem

The trap engine collects hits into a 100-entry in-memory buffer
(_BASHUNIT_COVERAGE_BUFFER, _BASHUNIT_COVERAGE_BUFFER_LIMIT=100) and
flushes when it fills. A buffer that lives in a variable dies with the subshell
that filled it, so anything recorded inside a short-lived command substitution
never reaches the data file.

Running tests/unit/assert/basic_test.sh against --coverage-paths src:

Bash 5hitswall
trap, buffer=100 (today)196/114384612 ms
trap, write-through236/114384258 ms
xtrace238/114382147 ms

The 40 missing lines are real code: bashunit::temp_dir and friends, executed
inside $( ). Deterministic across runs.

On Bash 3.2 there is no loss (234 either way) — so the same project reports
different coverage depending on the Bash version
, which is worse than being
uniformly wrong.

The buffer also costs more than it saves

buffer=100write-through
Bash 3.2 (macOS, trap is the only engine)8896 ms7080 ms
Bash 54612 ms4258 ms

Appending to a growing shell string is not cheaper than an >> append per
line, so the buffer is pure downside on both counts.

Proposal

Write through, and delete the buffer and its flush bookkeeping. Points to
verify before landing:

  • --parallel: workers already write to their own data files, so concurrent
    appends stay per-worker; confirm no shared-target case remains
  • the fork budget: >> per line is a syscall, not a fork, so
    bashunit_coverage_forks_test.sh should be unchanged

Related

xtrace is 2x faster than trap on Bash 5 and auto already selects it
there, so this is mainly about the Bash 3.x/macOS path — where trap is the only
option.

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