Skip to content

fix(coverage): files that never ran are missing from the report, so 0% code is invisible #1053

Description

@Chemaclass

Problem

A source file that no test ever executed is absent from the coverage report, so it cannot lower the percentage. Coverage is measured over the files that ran, not over the files the user asked about.

should_track (src/coverage/paths.sh:29) only runs from inside the capture path, so a file enters files.dat the first time one of its lines executes. get_tracked_files then feeds every report section. A 0% file is structurally impossible to print.

Reproduced on Bash 3.2 arm64, macOS:

./bashunit --coverage --coverage-paths src --coverage-report /tmp/cov.info \
tests/unit/assert/basic_test.sh
  • files in the LCOV report: 11
  • src/*.sh files tracked by git: 121
  • reported denominator: 2,200 executable lines
  • real src/ executable lines: 9,285

The reported total was 171/2200 (7%). Measured against the source the user pointed at, it is 171/9285 (1%). The nightly badge (.github/workflows/coverage.yml) inherits this: any src/ file the unit suite never reaches is invisible rather than red.

docs/coverage.md:46 describes the filtering step as "only files matching your coverage paths (and not excluded) are tracked", which reads as path-based selection. The implementation is execution-based.

Proposal

Seed the tracked-file list from the coverage paths at init, with zero hits.

  • In bashunit::coverage::init, enumerate the files under BASHUNIT_COVERAGE_PATHS once, apply BASHUNIT_COVERAGE_EXCLUDE, and write them into files.dat.
  • The capture path keeps adding files as it sees them, so nothing about tracking changes for executed code. get_tracked_files already sorts and dedupes.
  • Cost is one find at startup, which is bounded by the project, not by the test count.
  • Fix docs/coverage.md so the described behaviour matches: with this change, "files matching your coverage paths" becomes true.

This raises report input by roughly 11x for this repo, so land the report-phase performance work first. Otherwise the honest number arrives with a report phase measured at 16.4s for 121 files.

Where to change

  • src/coverage/config.sh:61-112init
  • src/coverage/paths.sh:22-27get_tracked_files
  • docs/coverage.md:45-55 the pipeline description
  • .github/workflows/coverage.yml no code change needed, but expect the badge to drop, which is the point

Acceptance criteria

  • A tracked file that no test executed appears in the text report as 0/N (0%)
  • The same file appears in LCOV with DA records at 0, LH:0, and in the HTML report
  • The total denominator equals the executable lines of every non-excluded file under BASHUNIT_COVERAGE_PATHS
  • BASHUNIT_COVERAGE_EXCLUDE still removes files from the seeded set, tested with the default tests/*,vendor/*,*_test.sh,*Test.sh
  • A file created after the run started is not required to appear (define the behaviour and test it)
  • --coverage-min gates on the new, larger denominator
  • Empty or unset BASHUNIT_COVERAGE_PATHS keeps its current behaviour rather than seeding the whole repo
  • auto_discover_paths (src/coverage/config.sh:26) still behaves as documented
  • Works under --parallel, where the seed happens once in the parent, not per worker
  • Both engines produce the same numbers (trap and xtrace)
  • docs/coverage.md updated

Repo checklist (agent)

  • TDD: RED then GREEN then REFACTOR. The RED test is a fixture project with one executed file and one never-executed file, asserting the second appears at 0%.
  • Bash 3.0+ only: no printf -v, no += append, no declare -A, no [[ ]], no ${var,,}, no &>>, no ${arr[-1]}. Expanding a possibly-empty array under set -u needs ${arr[@]+"${arr[@]}"}.
  • Fixtures under tests/acceptance/fixtures/ must not end in *test.sh.
  • Gates: make sa, make lint, ./bashunit tests/, ./bashunit --parallel tests/. Never run shfmt -w.
  • CHANGELOG.md: one line under ## Unreleased. This changes reported numbers for every user, so say so plainly.
  • One issue = one PR.

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): files that never ran are missing from the report, so 0% code is invisible · Issue #1053 · TypedDevs/bashunit · GitHub
Skip to content

fix(coverage): files that never ran are missing from the report, so 0% code is invisible #1053

Description

@Chemaclass

Problem

A source file that no test ever executed is absent from the coverage report, so it cannot lower the percentage. Coverage is measured over the files that ran, not over the files the user asked about.

should_track (src/coverage/paths.sh:29) only runs from inside the capture path, so a file enters files.dat the first time one of its lines executes. get_tracked_files then feeds every report section. A 0% file is structurally impossible to print.

Reproduced on Bash 3.2 arm64, macOS:

./bashunit --coverage --coverage-paths src --coverage-report /tmp/cov.info \
tests/unit/assert/basic_test.sh
  • files in the LCOV report: 11
  • src/*.sh files tracked by git: 121
  • reported denominator: 2,200 executable lines
  • real src/ executable lines: 9,285

The reported total was 171/2200 (7%). Measured against the source the user pointed at, it is 171/9285 (1%). The nightly badge (.github/workflows/coverage.yml) inherits this: any src/ file the unit suite never reaches is invisible rather than red.

docs/coverage.md:46 describes the filtering step as "only files matching your coverage paths (and not excluded) are tracked", which reads as path-based selection. The implementation is execution-based.

Proposal

Seed the tracked-file list from the coverage paths at init, with zero hits.

  • In bashunit::coverage::init, enumerate the files under BASHUNIT_COVERAGE_PATHS once, apply BASHUNIT_COVERAGE_EXCLUDE, and write them into files.dat.
  • The capture path keeps adding files as it sees them, so nothing about tracking changes for executed code. get_tracked_files already sorts and dedupes.
  • Cost is one find at startup, which is bounded by the project, not by the test count.
  • Fix docs/coverage.md so the described behaviour matches: with this change, "files matching your coverage paths" becomes true.

This raises report input by roughly 11x for this repo, so land the report-phase performance work first. Otherwise the honest number arrives with a report phase measured at 16.4s for 121 files.

Where to change

  • src/coverage/config.sh:61-112init
  • src/coverage/paths.sh:22-27get_tracked_files
  • docs/coverage.md:45-55 the pipeline description
  • .github/workflows/coverage.yml no code change needed, but expect the badge to drop, which is the point

Acceptance criteria

  • A tracked file that no test executed appears in the text report as 0/N (0%)
  • The same file appears in LCOV with DA records at 0, LH:0, and in the HTML report
  • The total denominator equals the executable lines of every non-excluded file under BASHUNIT_COVERAGE_PATHS
  • BASHUNIT_COVERAGE_EXCLUDE still removes files from the seeded set, tested with the default tests/*,vendor/*,*_test.sh,*Test.sh
  • A file created after the run started is not required to appear (define the behaviour and test it)
  • --coverage-min gates on the new, larger denominator
  • Empty or unset BASHUNIT_COVERAGE_PATHS keeps its current behaviour rather than seeding the whole repo
  • auto_discover_paths (src/coverage/config.sh:26) still behaves as documented
  • Works under --parallel, where the seed happens once in the parent, not per worker
  • Both engines produce the same numbers (trap and xtrace)
  • docs/coverage.md updated

Repo checklist (agent)

  • TDD: RED then GREEN then REFACTOR. The RED test is a fixture project with one executed file and one never-executed file, asserting the second appears at 0%.
  • Bash 3.0+ only: no printf -v, no += append, no declare -A, no [[ ]], no ${var,,}, no &>>, no ${arr[-1]}. Expanding a possibly-empty array under set -u needs ${arr[@]+"${arr[@]}"}.
  • Fixtures under tests/acceptance/fixtures/ must not end in *test.sh.
  • Gates: make sa, make lint, ./bashunit tests/, ./bashunit --parallel tests/. Never run shfmt -w.
  • CHANGELOG.md: one line under ## Unreleased. This changes reported numbers for every user, so say so plainly.
  • One issue = one PR.

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): files that never ran are missing from the report, so 0% code is invisible · Issue #1053 · TypedDevs/bashunit · GitHub
Skip to content

fix(coverage): files that never ran are missing from the report, so 0% code is invisible #1053

Description

@Chemaclass

Problem

A source file that no test ever executed is absent from the coverage report, so it cannot lower the percentage. Coverage is measured over the files that ran, not over the files the user asked about.

should_track (src/coverage/paths.sh:29) only runs from inside the capture path, so a file enters files.dat the first time one of its lines executes. get_tracked_files then feeds every report section. A 0% file is structurally impossible to print.

Reproduced on Bash 3.2 arm64, macOS:

./bashunit --coverage --coverage-paths src --coverage-report /tmp/cov.info \
tests/unit/assert/basic_test.sh
  • files in the LCOV report: 11
  • src/*.sh files tracked by git: 121
  • reported denominator: 2,200 executable lines
  • real src/ executable lines: 9,285

The reported total was 171/2200 (7%). Measured against the source the user pointed at, it is 171/9285 (1%). The nightly badge (.github/workflows/coverage.yml) inherits this: any src/ file the unit suite never reaches is invisible rather than red.

docs/coverage.md:46 describes the filtering step as "only files matching your coverage paths (and not excluded) are tracked", which reads as path-based selection. The implementation is execution-based.

Proposal

Seed the tracked-file list from the coverage paths at init, with zero hits.

  • In bashunit::coverage::init, enumerate the files under BASHUNIT_COVERAGE_PATHS once, apply BASHUNIT_COVERAGE_EXCLUDE, and write them into files.dat.
  • The capture path keeps adding files as it sees them, so nothing about tracking changes for executed code. get_tracked_files already sorts and dedupes.
  • Cost is one find at startup, which is bounded by the project, not by the test count.
  • Fix docs/coverage.md so the described behaviour matches: with this change, "files matching your coverage paths" becomes true.

This raises report input by roughly 11x for this repo, so land the report-phase performance work first. Otherwise the honest number arrives with a report phase measured at 16.4s for 121 files.

Where to change

  • src/coverage/config.sh:61-112init
  • src/coverage/paths.sh:22-27get_tracked_files
  • docs/coverage.md:45-55 the pipeline description
  • .github/workflows/coverage.yml no code change needed, but expect the badge to drop, which is the point

Acceptance criteria

  • A tracked file that no test executed appears in the text report as 0/N (0%)
  • The same file appears in LCOV with DA records at 0, LH:0, and in the HTML report
  • The total denominator equals the executable lines of every non-excluded file under BASHUNIT_COVERAGE_PATHS
  • BASHUNIT_COVERAGE_EXCLUDE still removes files from the seeded set, tested with the default tests/*,vendor/*,*_test.sh,*Test.sh
  • A file created after the run started is not required to appear (define the behaviour and test it)
  • --coverage-min gates on the new, larger denominator
  • Empty or unset BASHUNIT_COVERAGE_PATHS keeps its current behaviour rather than seeding the whole repo
  • auto_discover_paths (src/coverage/config.sh:26) still behaves as documented
  • Works under --parallel, where the seed happens once in the parent, not per worker
  • Both engines produce the same numbers (trap and xtrace)
  • docs/coverage.md updated

Repo checklist (agent)

  • TDD: RED then GREEN then REFACTOR. The RED test is a fixture project with one executed file and one never-executed file, asserting the second appears at 0%.
  • Bash 3.0+ only: no printf -v, no += append, no declare -A, no [[ ]], no ${var,,}, no &>>, no ${arr[-1]}. Expanding a possibly-empty array under set -u needs ${arr[@]+"${arr[@]}"}.
  • Fixtures under tests/acceptance/fixtures/ must not end in *test.sh.
  • Gates: make sa, make lint, ./bashunit tests/, ./bashunit --parallel tests/. Never run shfmt -w.
  • CHANGELOG.md: one line under ## Unreleased. This changes reported numbers for every user, so say so plainly.
  • One issue = one PR.

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): files that never ran are missing from the report, so 0% code is invisible · Issue #1053 · TypedDevs/bashunit · GitHub
Skip to content

fix(coverage): files that never ran are missing from the report, so 0% code is invisible #1053

Description

@Chemaclass

Problem

A source file that no test ever executed is absent from the coverage report, so it cannot lower the percentage. Coverage is measured over the files that ran, not over the files the user asked about.

should_track (src/coverage/paths.sh:29) only runs from inside the capture path, so a file enters files.dat the first time one of its lines executes. get_tracked_files then feeds every report section. A 0% file is structurally impossible to print.

Reproduced on Bash 3.2 arm64, macOS:

./bashunit --coverage --coverage-paths src --coverage-report /tmp/cov.info \
tests/unit/assert/basic_test.sh
  • files in the LCOV report: 11
  • src/*.sh files tracked by git: 121
  • reported denominator: 2,200 executable lines
  • real src/ executable lines: 9,285

The reported total was 171/2200 (7%). Measured against the source the user pointed at, it is 171/9285 (1%). The nightly badge (.github/workflows/coverage.yml) inherits this: any src/ file the unit suite never reaches is invisible rather than red.

docs/coverage.md:46 describes the filtering step as "only files matching your coverage paths (and not excluded) are tracked", which reads as path-based selection. The implementation is execution-based.

Proposal

Seed the tracked-file list from the coverage paths at init, with zero hits.

  • In bashunit::coverage::init, enumerate the files under BASHUNIT_COVERAGE_PATHS once, apply BASHUNIT_COVERAGE_EXCLUDE, and write them into files.dat.
  • The capture path keeps adding files as it sees them, so nothing about tracking changes for executed code. get_tracked_files already sorts and dedupes.
  • Cost is one find at startup, which is bounded by the project, not by the test count.
  • Fix docs/coverage.md so the described behaviour matches: with this change, "files matching your coverage paths" becomes true.

This raises report input by roughly 11x for this repo, so land the report-phase performance work first. Otherwise the honest number arrives with a report phase measured at 16.4s for 121 files.

Where to change

  • src/coverage/config.sh:61-112init
  • src/coverage/paths.sh:22-27get_tracked_files
  • docs/coverage.md:45-55 the pipeline description
  • .github/workflows/coverage.yml no code change needed, but expect the badge to drop, which is the point

Acceptance criteria

  • A tracked file that no test executed appears in the text report as 0/N (0%)
  • The same file appears in LCOV with DA records at 0, LH:0, and in the HTML report
  • The total denominator equals the executable lines of every non-excluded file under BASHUNIT_COVERAGE_PATHS
  • BASHUNIT_COVERAGE_EXCLUDE still removes files from the seeded set, tested with the default tests/*,vendor/*,*_test.sh,*Test.sh
  • A file created after the run started is not required to appear (define the behaviour and test it)
  • --coverage-min gates on the new, larger denominator
  • Empty or unset BASHUNIT_COVERAGE_PATHS keeps its current behaviour rather than seeding the whole repo
  • auto_discover_paths (src/coverage/config.sh:26) still behaves as documented
  • Works under --parallel, where the seed happens once in the parent, not per worker
  • Both engines produce the same numbers (trap and xtrace)
  • docs/coverage.md updated

Repo checklist (agent)

  • TDD: RED then GREEN then REFACTOR. The RED test is a fixture project with one executed file and one never-executed file, asserting the second appears at 0%.
  • Bash 3.0+ only: no printf -v, no += append, no declare -A, no [[ ]], no ${var,,}, no &>>, no ${arr[-1]}. Expanding a possibly-empty array under set -u needs ${arr[@]+"${arr[@]}"}.
  • Fixtures under tests/acceptance/fixtures/ must not end in *test.sh.
  • Gates: make sa, make lint, ./bashunit tests/, ./bashunit --parallel tests/. Never run shfmt -w.
  • CHANGELOG.md: one line under ## Unreleased. This changes reported numbers for every user, so say so plainly.
  • One issue = one PR.

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): files that never ran are missing from the report, so 0% code is invisible · Issue #1053 · TypedDevs/bashunit · GitHub
Skip to content

fix(coverage): files that never ran are missing from the report, so 0% code is invisible #1053

Description

@Chemaclass

Problem

A source file that no test ever executed is absent from the coverage report, so it cannot lower the percentage. Coverage is measured over the files that ran, not over the files the user asked about.

should_track (src/coverage/paths.sh:29) only runs from inside the capture path, so a file enters files.dat the first time one of its lines executes. get_tracked_files then feeds every report section. A 0% file is structurally impossible to print.

Reproduced on Bash 3.2 arm64, macOS:

./bashunit --coverage --coverage-paths src --coverage-report /tmp/cov.info \
tests/unit/assert/basic_test.sh
  • files in the LCOV report: 11
  • src/*.sh files tracked by git: 121
  • reported denominator: 2,200 executable lines
  • real src/ executable lines: 9,285

The reported total was 171/2200 (7%). Measured against the source the user pointed at, it is 171/9285 (1%). The nightly badge (.github/workflows/coverage.yml) inherits this: any src/ file the unit suite never reaches is invisible rather than red.

docs/coverage.md:46 describes the filtering step as "only files matching your coverage paths (and not excluded) are tracked", which reads as path-based selection. The implementation is execution-based.

Proposal

Seed the tracked-file list from the coverage paths at init, with zero hits.

  • In bashunit::coverage::init, enumerate the files under BASHUNIT_COVERAGE_PATHS once, apply BASHUNIT_COVERAGE_EXCLUDE, and write them into files.dat.
  • The capture path keeps adding files as it sees them, so nothing about tracking changes for executed code. get_tracked_files already sorts and dedupes.
  • Cost is one find at startup, which is bounded by the project, not by the test count.
  • Fix docs/coverage.md so the described behaviour matches: with this change, "files matching your coverage paths" becomes true.

This raises report input by roughly 11x for this repo, so land the report-phase performance work first. Otherwise the honest number arrives with a report phase measured at 16.4s for 121 files.

Where to change

  • src/coverage/config.sh:61-112init
  • src/coverage/paths.sh:22-27get_tracked_files
  • docs/coverage.md:45-55 the pipeline description
  • .github/workflows/coverage.yml no code change needed, but expect the badge to drop, which is the point

Acceptance criteria

  • A tracked file that no test executed appears in the text report as 0/N (0%)
  • The same file appears in LCOV with DA records at 0, LH:0, and in the HTML report
  • The total denominator equals the executable lines of every non-excluded file under BASHUNIT_COVERAGE_PATHS
  • BASHUNIT_COVERAGE_EXCLUDE still removes files from the seeded set, tested with the default tests/*,vendor/*,*_test.sh,*Test.sh
  • A file created after the run started is not required to appear (define the behaviour and test it)
  • --coverage-min gates on the new, larger denominator
  • Empty or unset BASHUNIT_COVERAGE_PATHS keeps its current behaviour rather than seeding the whole repo
  • auto_discover_paths (src/coverage/config.sh:26) still behaves as documented
  • Works under --parallel, where the seed happens once in the parent, not per worker
  • Both engines produce the same numbers (trap and xtrace)
  • docs/coverage.md updated

Repo checklist (agent)

  • TDD: RED then GREEN then REFACTOR. The RED test is a fixture project with one executed file and one never-executed file, asserting the second appears at 0%.
  • Bash 3.0+ only: no printf -v, no += append, no declare -A, no [[ ]], no ${var,,}, no &>>, no ${arr[-1]}. Expanding a possibly-empty array under set -u needs ${arr[@]+"${arr[@]}"}.
  • Fixtures under tests/acceptance/fixtures/ must not end in *test.sh.
  • Gates: make sa, make lint, ./bashunit tests/, ./bashunit --parallel tests/. Never run shfmt -w.
  • CHANGELOG.md: one line under ## Unreleased. This changes reported numbers for every user, so say so plainly.
  • One issue = one PR.

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): files that never ran are missing from the report, so 0% code is invisible · Issue #1053 · TypedDevs/bashunit · GitHub
Skip to content

fix(coverage): files that never ran are missing from the report, so 0% code is invisible #1053

Description

@Chemaclass

Problem

A source file that no test ever executed is absent from the coverage report, so it cannot lower the percentage. Coverage is measured over the files that ran, not over the files the user asked about.

should_track (src/coverage/paths.sh:29) only runs from inside the capture path, so a file enters files.dat the first time one of its lines executes. get_tracked_files then feeds every report section. A 0% file is structurally impossible to print.

Reproduced on Bash 3.2 arm64, macOS:

./bashunit --coverage --coverage-paths src --coverage-report /tmp/cov.info \
tests/unit/assert/basic_test.sh
  • files in the LCOV report: 11
  • src/*.sh files tracked by git: 121
  • reported denominator: 2,200 executable lines
  • real src/ executable lines: 9,285

The reported total was 171/2200 (7%). Measured against the source the user pointed at, it is 171/9285 (1%). The nightly badge (.github/workflows/coverage.yml) inherits this: any src/ file the unit suite never reaches is invisible rather than red.

docs/coverage.md:46 describes the filtering step as "only files matching your coverage paths (and not excluded) are tracked", which reads as path-based selection. The implementation is execution-based.

Proposal

Seed the tracked-file list from the coverage paths at init, with zero hits.

  • In bashunit::coverage::init, enumerate the files under BASHUNIT_COVERAGE_PATHS once, apply BASHUNIT_COVERAGE_EXCLUDE, and write them into files.dat.
  • The capture path keeps adding files as it sees them, so nothing about tracking changes for executed code. get_tracked_files already sorts and dedupes.
  • Cost is one find at startup, which is bounded by the project, not by the test count.
  • Fix docs/coverage.md so the described behaviour matches: with this change, "files matching your coverage paths" becomes true.

This raises report input by roughly 11x for this repo, so land the report-phase performance work first. Otherwise the honest number arrives with a report phase measured at 16.4s for 121 files.

Where to change

  • src/coverage/config.sh:61-112init
  • src/coverage/paths.sh:22-27get_tracked_files
  • docs/coverage.md:45-55 the pipeline description
  • .github/workflows/coverage.yml no code change needed, but expect the badge to drop, which is the point

Acceptance criteria

  • A tracked file that no test executed appears in the text report as 0/N (0%)
  • The same file appears in LCOV with DA records at 0, LH:0, and in the HTML report
  • The total denominator equals the executable lines of every non-excluded file under BASHUNIT_COVERAGE_PATHS
  • BASHUNIT_COVERAGE_EXCLUDE still removes files from the seeded set, tested with the default tests/*,vendor/*,*_test.sh,*Test.sh
  • A file created after the run started is not required to appear (define the behaviour and test it)
  • --coverage-min gates on the new, larger denominator
  • Empty or unset BASHUNIT_COVERAGE_PATHS keeps its current behaviour rather than seeding the whole repo
  • auto_discover_paths (src/coverage/config.sh:26) still behaves as documented
  • Works under --parallel, where the seed happens once in the parent, not per worker
  • Both engines produce the same numbers (trap and xtrace)
  • docs/coverage.md updated

Repo checklist (agent)

  • TDD: RED then GREEN then REFACTOR. The RED test is a fixture project with one executed file and one never-executed file, asserting the second appears at 0%.
  • Bash 3.0+ only: no printf -v, no += append, no declare -A, no [[ ]], no ${var,,}, no &>>, no ${arr[-1]}. Expanding a possibly-empty array under set -u needs ${arr[@]+"${arr[@]}"}.
  • Fixtures under tests/acceptance/fixtures/ must not end in *test.sh.
  • Gates: make sa, make lint, ./bashunit tests/, ./bashunit --parallel tests/. Never run shfmt -w.
  • CHANGELOG.md: one line under ## Unreleased. This changes reported numbers for every user, so say so plainly.
  • One issue = one PR.

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); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(coverage): files that never ran are missing from the report, so 0% code is invisible · Issue #1053 · TypedDevs/bashunit · GitHub
Skip to content

fix(coverage): files that never ran are missing from the report, so 0% code is invisible #1053

Description

@Chemaclass

Problem

A source file that no test ever executed is absent from the coverage report, so it cannot lower the percentage. Coverage is measured over the files that ran, not over the files the user asked about.

should_track (src/coverage/paths.sh:29) only runs from inside the capture path, so a file enters files.dat the first time one of its lines executes. get_tracked_files then feeds every report section. A 0% file is structurally impossible to print.

Reproduced on Bash 3.2 arm64, macOS:

./bashunit --coverage --coverage-paths src --coverage-report /tmp/cov.info \
tests/unit/assert/basic_test.sh
  • files in the LCOV report: 11
  • src/*.sh files tracked by git: 121
  • reported denominator: 2,200 executable lines
  • real src/ executable lines: 9,285

The reported total was 171/2200 (7%). Measured against the source the user pointed at, it is 171/9285 (1%). The nightly badge (.github/workflows/coverage.yml) inherits this: any src/ file the unit suite never reaches is invisible rather than red.

docs/coverage.md:46 describes the filtering step as "only files matching your coverage paths (and not excluded) are tracked", which reads as path-based selection. The implementation is execution-based.

Proposal

Seed the tracked-file list from the coverage paths at init, with zero hits.

  • In bashunit::coverage::init, enumerate the files under BASHUNIT_COVERAGE_PATHS once, apply BASHUNIT_COVERAGE_EXCLUDE, and write them into files.dat.
  • The capture path keeps adding files as it sees them, so nothing about tracking changes for executed code. get_tracked_files already sorts and dedupes.
  • Cost is one find at startup, which is bounded by the project, not by the test count.
  • Fix docs/coverage.md so the described behaviour matches: with this change, "files matching your coverage paths" becomes true.

This raises report input by roughly 11x for this repo, so land the report-phase performance work first. Otherwise the honest number arrives with a report phase measured at 16.4s for 121 files.

Where to change

  • src/coverage/config.sh:61-112init
  • src/coverage/paths.sh:22-27get_tracked_files
  • docs/coverage.md:45-55 the pipeline description
  • .github/workflows/coverage.yml no code change needed, but expect the badge to drop, which is the point

Acceptance criteria

  • A tracked file that no test executed appears in the text report as 0/N (0%)
  • The same file appears in LCOV with DA records at 0, LH:0, and in the HTML report
  • The total denominator equals the executable lines of every non-excluded file under BASHUNIT_COVERAGE_PATHS
  • BASHUNIT_COVERAGE_EXCLUDE still removes files from the seeded set, tested with the default tests/*,vendor/*,*_test.sh,*Test.sh
  • A file created after the run started is not required to appear (define the behaviour and test it)
  • --coverage-min gates on the new, larger denominator
  • Empty or unset BASHUNIT_COVERAGE_PATHS keeps its current behaviour rather than seeding the whole repo
  • auto_discover_paths (src/coverage/config.sh:26) still behaves as documented
  • Works under --parallel, where the seed happens once in the parent, not per worker
  • Both engines produce the same numbers (trap and xtrace)
  • docs/coverage.md updated

Repo checklist (agent)

  • TDD: RED then GREEN then REFACTOR. The RED test is a fixture project with one executed file and one never-executed file, asserting the second appears at 0%.
  • Bash 3.0+ only: no printf -v, no += append, no declare -A, no [[ ]], no ${var,,}, no &>>, no ${arr[-1]}. Expanding a possibly-empty array under set -u needs ${arr[@]+"${arr[@]}"}.
  • Fixtures under tests/acceptance/fixtures/ must not end in *test.sh.
  • Gates: make sa, make lint, ./bashunit tests/, ./bashunit --parallel tests/. Never run shfmt -w.
  • CHANGELOG.md: one line under ## Unreleased. This changes reported numbers for every user, so say so plainly.
  • One issue = one PR.

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

fix(coverage): files that never ran are missing from the report, so 0% code is invisible #1053

Description

@Chemaclass

Problem

A source file that no test ever executed is absent from the coverage report, so it cannot lower the percentage. Coverage is measured over the files that ran, not over the files the user asked about.

should_track (src/coverage/paths.sh:29) only runs from inside the capture path, so a file enters files.dat the first time one of its lines executes. get_tracked_files then feeds every report section. A 0% file is structurally impossible to print.

Reproduced on Bash 3.2 arm64, macOS:

./bashunit --coverage --coverage-paths src --coverage-report /tmp/cov.info \
tests/unit/assert/basic_test.sh
  • files in the LCOV report: 11
  • src/*.sh files tracked by git: 121
  • reported denominator: 2,200 executable lines
  • real src/ executable lines: 9,285

The reported total was 171/2200 (7%). Measured against the source the user pointed at, it is 171/9285 (1%). The nightly badge (.github/workflows/coverage.yml) inherits this: any src/ file the unit suite never reaches is invisible rather than red.

docs/coverage.md:46 describes the filtering step as "only files matching your coverage paths (and not excluded) are tracked", which reads as path-based selection. The implementation is execution-based.

Proposal

Seed the tracked-file list from the coverage paths at init, with zero hits.

  • In bashunit::coverage::init, enumerate the files under BASHUNIT_COVERAGE_PATHS once, apply BASHUNIT_COVERAGE_EXCLUDE, and write them into files.dat.
  • The capture path keeps adding files as it sees them, so nothing about tracking changes for executed code. get_tracked_files already sorts and dedupes.
  • Cost is one find at startup, which is bounded by the project, not by the test count.
  • Fix docs/coverage.md so the described behaviour matches: with this change, "files matching your coverage paths" becomes true.

This raises report input by roughly 11x for this repo, so land the report-phase performance work first. Otherwise the honest number arrives with a report phase measured at 16.4s for 121 files.

Where to change

  • src/coverage/config.sh:61-112init
  • src/coverage/paths.sh:22-27get_tracked_files
  • docs/coverage.md:45-55 the pipeline description
  • .github/workflows/coverage.yml no code change needed, but expect the badge to drop, which is the point

Acceptance criteria

  • A tracked file that no test executed appears in the text report as 0/N (0%)
  • The same file appears in LCOV with DA records at 0, LH:0, and in the HTML report
  • The total denominator equals the executable lines of every non-excluded file under BASHUNIT_COVERAGE_PATHS
  • BASHUNIT_COVERAGE_EXCLUDE still removes files from the seeded set, tested with the default tests/*,vendor/*,*_test.sh,*Test.sh
  • A file created after the run started is not required to appear (define the behaviour and test it)
  • --coverage-min gates on the new, larger denominator
  • Empty or unset BASHUNIT_COVERAGE_PATHS keeps its current behaviour rather than seeding the whole repo
  • auto_discover_paths (src/coverage/config.sh:26) still behaves as documented
  • Works under --parallel, where the seed happens once in the parent, not per worker
  • Both engines produce the same numbers (trap and xtrace)
  • docs/coverage.md updated

Repo checklist (agent)

  • TDD: RED then GREEN then REFACTOR. The RED test is a fixture project with one executed file and one never-executed file, asserting the second appears at 0%.
  • Bash 3.0+ only: no printf -v, no += append, no declare -A, no [[ ]], no ${var,,}, no &>>, no ${arr[-1]}. Expanding a possibly-empty array under set -u needs ${arr[@]+"${arr[@]}"}.
  • Fixtures under tests/acceptance/fixtures/ must not end in *test.sh.
  • Gates: make sa, make lint, ./bashunit tests/, ./bashunit --parallel tests/. Never run shfmt -w.
  • CHANGELOG.md: one line under ## Unreleased. This changes reported numbers for every user, so say so plainly.
  • One issue = one PR.

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