Skip to content

feat(cli): --rerun-failed to replay only the last failing tests #776

Description

@Chemaclass

Why

The fastest edit-run loop after a red suite is "run only what just failed". bashunit has rich selection (--filter, --tag, file:line, shard) but no failure memory — after a failing run you must hand-craft a --filter or re-run everything. PHPUnit (--cache-result + --order-by=defects), pytest (--lf), and Jest (--onlyFailures) all ship this.

Proposal

Add --rerun-failed (env: BASHUNIT_RERUN_FAILED):

  1. Record: every run writes failing tests as <test_file>:<function_name> lines to a cache file, e.g. .bashunit/last-failed under the working directory (create dir lazily; document adding .bashunit/ to .gitignore in the docs page). Write it on normal exit: sequential failures are known in bashunit::runner::parse_result / state layer; parallel failures are known after aggregation in src/parallel.sh (aggregate_test_results) — hook the common point where _BASHUNIT_TESTS_FAILED is finalized, not each mode separately if possible. A fully green run truncates the cache.
  2. Replay: with --rerun-failed, if the cache exists and is non-empty, restrict discovery to the files listed and filter to the recorded function names (reuse the existing per-file function filtering used by file:line selection — see bashunit::helper::find_function_at_line wiring at src/main.sh:327 for how an inline filter is applied). If the cache is missing or empty, print a short notice and run the full suite.
  3. Flag parsing in src/main.sh (test subcommand options block), .env.example entry, and --help text.

Edge cases to cover

  • Data-provider tests: record the base function name once; replaying runs all its data rows.
  • Interpolated/parameterized display names must not leak into the cache — store the raw fn_name.
  • Cache entries pointing at deleted files/functions: skip with a warning, don't crash.
  • --rerun-failed combined with --filter/--tag: intersect (both filters apply).
  • Parallel mode writes the same cache format.

Tests first (TDD)

Acceptance tests in tests/acceptance/ (pattern: existing CLI tests spawning ./bashunit as subprocess): failing run populates cache, --rerun-failed runs only those tests, green run clears cache, empty-cache fallback message. Unit tests for the cache read/write helpers.

Constraints

  • Bash 3.0+ (no mapfile, no associative arrays).
  • No behavior change when the flag is absent, except writing the cache file. Write it on every run (one write at end of run, cheap) so "fail, then --rerun-failed" works without pre-planning; document this. If a read-only CWD makes the write fail, ignore silently.
  • Update docs/ and CHANGELOG.md (user-visible feature).

Acceptance criteria

  • ./bashunit tests/ and ./bashunit --parallel tests/ pass.
  • make sa, make lint pass; shfmt -w . produces no diff.
  • --help documents the flag; docs page updated.

Line references valid at commit 1ae3e82.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

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" + '
feat(cli): --rerun-failed to replay only the last failing tests · Issue #776 · TypedDevs/bashunit · GitHub
Skip to content

feat(cli): --rerun-failed to replay only the last failing tests #776

Description

@Chemaclass

Why

The fastest edit-run loop after a red suite is "run only what just failed". bashunit has rich selection (--filter, --tag, file:line, shard) but no failure memory — after a failing run you must hand-craft a --filter or re-run everything. PHPUnit (--cache-result + --order-by=defects), pytest (--lf), and Jest (--onlyFailures) all ship this.

Proposal

Add --rerun-failed (env: BASHUNIT_RERUN_FAILED):

  1. Record: every run writes failing tests as <test_file>:<function_name> lines to a cache file, e.g. .bashunit/last-failed under the working directory (create dir lazily; document adding .bashunit/ to .gitignore in the docs page). Write it on normal exit: sequential failures are known in bashunit::runner::parse_result / state layer; parallel failures are known after aggregation in src/parallel.sh (aggregate_test_results) — hook the common point where _BASHUNIT_TESTS_FAILED is finalized, not each mode separately if possible. A fully green run truncates the cache.
  2. Replay: with --rerun-failed, if the cache exists and is non-empty, restrict discovery to the files listed and filter to the recorded function names (reuse the existing per-file function filtering used by file:line selection — see bashunit::helper::find_function_at_line wiring at src/main.sh:327 for how an inline filter is applied). If the cache is missing or empty, print a short notice and run the full suite.
  3. Flag parsing in src/main.sh (test subcommand options block), .env.example entry, and --help text.

Edge cases to cover

  • Data-provider tests: record the base function name once; replaying runs all its data rows.
  • Interpolated/parameterized display names must not leak into the cache — store the raw fn_name.
  • Cache entries pointing at deleted files/functions: skip with a warning, don't crash.
  • --rerun-failed combined with --filter/--tag: intersect (both filters apply).
  • Parallel mode writes the same cache format.

Tests first (TDD)

Acceptance tests in tests/acceptance/ (pattern: existing CLI tests spawning ./bashunit as subprocess): failing run populates cache, --rerun-failed runs only those tests, green run clears cache, empty-cache fallback message. Unit tests for the cache read/write helpers.

Constraints

  • Bash 3.0+ (no mapfile, no associative arrays).
  • No behavior change when the flag is absent, except writing the cache file. Write it on every run (one write at end of run, cheap) so "fail, then --rerun-failed" works without pre-planning; document this. If a read-only CWD makes the write fail, ignore silently.
  • Update docs/ and CHANGELOG.md (user-visible feature).

Acceptance criteria

  • ./bashunit tests/ and ./bashunit --parallel tests/ pass.
  • make sa, make lint pass; shfmt -w . produces no diff.
  • --help documents the flag; docs page updated.

Line references valid at commit 1ae3e82.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

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('^' + ".*" + ' feat(cli): --rerun-failed to replay only the last failing tests · Issue #776 · TypedDevs/bashunit · GitHub
Skip to content

feat(cli): --rerun-failed to replay only the last failing tests #776

Description

@Chemaclass

Why

The fastest edit-run loop after a red suite is "run only what just failed". bashunit has rich selection (--filter, --tag, file:line, shard) but no failure memory — after a failing run you must hand-craft a --filter or re-run everything. PHPUnit (--cache-result + --order-by=defects), pytest (--lf), and Jest (--onlyFailures) all ship this.

Proposal

Add --rerun-failed (env: BASHUNIT_RERUN_FAILED):

  1. Record: every run writes failing tests as <test_file>:<function_name> lines to a cache file, e.g. .bashunit/last-failed under the working directory (create dir lazily; document adding .bashunit/ to .gitignore in the docs page). Write it on normal exit: sequential failures are known in bashunit::runner::parse_result / state layer; parallel failures are known after aggregation in src/parallel.sh (aggregate_test_results) — hook the common point where _BASHUNIT_TESTS_FAILED is finalized, not each mode separately if possible. A fully green run truncates the cache.
  2. Replay: with --rerun-failed, if the cache exists and is non-empty, restrict discovery to the files listed and filter to the recorded function names (reuse the existing per-file function filtering used by file:line selection — see bashunit::helper::find_function_at_line wiring at src/main.sh:327 for how an inline filter is applied). If the cache is missing or empty, print a short notice and run the full suite.
  3. Flag parsing in src/main.sh (test subcommand options block), .env.example entry, and --help text.

Edge cases to cover

  • Data-provider tests: record the base function name once; replaying runs all its data rows.
  • Interpolated/parameterized display names must not leak into the cache — store the raw fn_name.
  • Cache entries pointing at deleted files/functions: skip with a warning, don't crash.
  • --rerun-failed combined with --filter/--tag: intersect (both filters apply).
  • Parallel mode writes the same cache format.

Tests first (TDD)

Acceptance tests in tests/acceptance/ (pattern: existing CLI tests spawning ./bashunit as subprocess): failing run populates cache, --rerun-failed runs only those tests, green run clears cache, empty-cache fallback message. Unit tests for the cache read/write helpers.

Constraints

  • Bash 3.0+ (no mapfile, no associative arrays).
  • No behavior change when the flag is absent, except writing the cache file. Write it on every run (one write at end of run, cheap) so "fail, then --rerun-failed" works without pre-planning; document this. If a read-only CWD makes the write fail, ignore silently.
  • Update docs/ and CHANGELOG.md (user-visible feature).

Acceptance criteria

  • ./bashunit tests/ and ./bashunit --parallel tests/ pass.
  • make sa, make lint pass; shfmt -w . produces no diff.
  • --help documents the flag; docs page updated.

Line references valid at commit 1ae3e82.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

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('^' + ".*" + ' feat(cli): --rerun-failed to replay only the last failing tests · Issue #776 · TypedDevs/bashunit · GitHub
Skip to content

feat(cli): --rerun-failed to replay only the last failing tests #776

Description

@Chemaclass

Why

The fastest edit-run loop after a red suite is "run only what just failed". bashunit has rich selection (--filter, --tag, file:line, shard) but no failure memory — after a failing run you must hand-craft a --filter or re-run everything. PHPUnit (--cache-result + --order-by=defects), pytest (--lf), and Jest (--onlyFailures) all ship this.

Proposal

Add --rerun-failed (env: BASHUNIT_RERUN_FAILED):

  1. Record: every run writes failing tests as <test_file>:<function_name> lines to a cache file, e.g. .bashunit/last-failed under the working directory (create dir lazily; document adding .bashunit/ to .gitignore in the docs page). Write it on normal exit: sequential failures are known in bashunit::runner::parse_result / state layer; parallel failures are known after aggregation in src/parallel.sh (aggregate_test_results) — hook the common point where _BASHUNIT_TESTS_FAILED is finalized, not each mode separately if possible. A fully green run truncates the cache.
  2. Replay: with --rerun-failed, if the cache exists and is non-empty, restrict discovery to the files listed and filter to the recorded function names (reuse the existing per-file function filtering used by file:line selection — see bashunit::helper::find_function_at_line wiring at src/main.sh:327 for how an inline filter is applied). If the cache is missing or empty, print a short notice and run the full suite.
  3. Flag parsing in src/main.sh (test subcommand options block), .env.example entry, and --help text.

Edge cases to cover

  • Data-provider tests: record the base function name once; replaying runs all its data rows.
  • Interpolated/parameterized display names must not leak into the cache — store the raw fn_name.
  • Cache entries pointing at deleted files/functions: skip with a warning, don't crash.
  • --rerun-failed combined with --filter/--tag: intersect (both filters apply).
  • Parallel mode writes the same cache format.

Tests first (TDD)

Acceptance tests in tests/acceptance/ (pattern: existing CLI tests spawning ./bashunit as subprocess): failing run populates cache, --rerun-failed runs only those tests, green run clears cache, empty-cache fallback message. Unit tests for the cache read/write helpers.

Constraints

  • Bash 3.0+ (no mapfile, no associative arrays).
  • No behavior change when the flag is absent, except writing the cache file. Write it on every run (one write at end of run, cheap) so "fail, then --rerun-failed" works without pre-planning; document this. If a read-only CWD makes the write fail, ignore silently.
  • Update docs/ and CHANGELOG.md (user-visible feature).

Acceptance criteria

  • ./bashunit tests/ and ./bashunit --parallel tests/ pass.
  • make sa, make lint pass; shfmt -w . produces no diff.
  • --help documents the flag; docs page updated.

Line references valid at commit 1ae3e82.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

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" + ' feat(cli): --rerun-failed to replay only the last failing tests · Issue #776 · TypedDevs/bashunit · GitHub
Skip to content

feat(cli): --rerun-failed to replay only the last failing tests #776

Description

@Chemaclass

Why

The fastest edit-run loop after a red suite is "run only what just failed". bashunit has rich selection (--filter, --tag, file:line, shard) but no failure memory — after a failing run you must hand-craft a --filter or re-run everything. PHPUnit (--cache-result + --order-by=defects), pytest (--lf), and Jest (--onlyFailures) all ship this.

Proposal

Add --rerun-failed (env: BASHUNIT_RERUN_FAILED):

  1. Record: every run writes failing tests as <test_file>:<function_name> lines to a cache file, e.g. .bashunit/last-failed under the working directory (create dir lazily; document adding .bashunit/ to .gitignore in the docs page). Write it on normal exit: sequential failures are known in bashunit::runner::parse_result / state layer; parallel failures are known after aggregation in src/parallel.sh (aggregate_test_results) — hook the common point where _BASHUNIT_TESTS_FAILED is finalized, not each mode separately if possible. A fully green run truncates the cache.
  2. Replay: with --rerun-failed, if the cache exists and is non-empty, restrict discovery to the files listed and filter to the recorded function names (reuse the existing per-file function filtering used by file:line selection — see bashunit::helper::find_function_at_line wiring at src/main.sh:327 for how an inline filter is applied). If the cache is missing or empty, print a short notice and run the full suite.
  3. Flag parsing in src/main.sh (test subcommand options block), .env.example entry, and --help text.

Edge cases to cover

  • Data-provider tests: record the base function name once; replaying runs all its data rows.
  • Interpolated/parameterized display names must not leak into the cache — store the raw fn_name.
  • Cache entries pointing at deleted files/functions: skip with a warning, don't crash.
  • --rerun-failed combined with --filter/--tag: intersect (both filters apply).
  • Parallel mode writes the same cache format.

Tests first (TDD)

Acceptance tests in tests/acceptance/ (pattern: existing CLI tests spawning ./bashunit as subprocess): failing run populates cache, --rerun-failed runs only those tests, green run clears cache, empty-cache fallback message. Unit tests for the cache read/write helpers.

Constraints

  • Bash 3.0+ (no mapfile, no associative arrays).
  • No behavior change when the flag is absent, except writing the cache file. Write it on every run (one write at end of run, cheap) so "fail, then --rerun-failed" works without pre-planning; document this. If a read-only CWD makes the write fail, ignore silently.
  • Update docs/ and CHANGELOG.md (user-visible feature).

Acceptance criteria

  • ./bashunit tests/ and ./bashunit --parallel tests/ pass.
  • make sa, make lint pass; shfmt -w . produces no diff.
  • --help documents the flag; docs page updated.

Line references valid at commit 1ae3e82.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

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('^' + ".*" + ' feat(cli): --rerun-failed to replay only the last failing tests · Issue #776 · TypedDevs/bashunit · GitHub
Skip to content

feat(cli): --rerun-failed to replay only the last failing tests #776

Description

@Chemaclass

Why

The fastest edit-run loop after a red suite is "run only what just failed". bashunit has rich selection (--filter, --tag, file:line, shard) but no failure memory — after a failing run you must hand-craft a --filter or re-run everything. PHPUnit (--cache-result + --order-by=defects), pytest (--lf), and Jest (--onlyFailures) all ship this.

Proposal

Add --rerun-failed (env: BASHUNIT_RERUN_FAILED):

  1. Record: every run writes failing tests as <test_file>:<function_name> lines to a cache file, e.g. .bashunit/last-failed under the working directory (create dir lazily; document adding .bashunit/ to .gitignore in the docs page). Write it on normal exit: sequential failures are known in bashunit::runner::parse_result / state layer; parallel failures are known after aggregation in src/parallel.sh (aggregate_test_results) — hook the common point where _BASHUNIT_TESTS_FAILED is finalized, not each mode separately if possible. A fully green run truncates the cache.
  2. Replay: with --rerun-failed, if the cache exists and is non-empty, restrict discovery to the files listed and filter to the recorded function names (reuse the existing per-file function filtering used by file:line selection — see bashunit::helper::find_function_at_line wiring at src/main.sh:327 for how an inline filter is applied). If the cache is missing or empty, print a short notice and run the full suite.
  3. Flag parsing in src/main.sh (test subcommand options block), .env.example entry, and --help text.

Edge cases to cover

  • Data-provider tests: record the base function name once; replaying runs all its data rows.
  • Interpolated/parameterized display names must not leak into the cache — store the raw fn_name.
  • Cache entries pointing at deleted files/functions: skip with a warning, don't crash.
  • --rerun-failed combined with --filter/--tag: intersect (both filters apply).
  • Parallel mode writes the same cache format.

Tests first (TDD)

Acceptance tests in tests/acceptance/ (pattern: existing CLI tests spawning ./bashunit as subprocess): failing run populates cache, --rerun-failed runs only those tests, green run clears cache, empty-cache fallback message. Unit tests for the cache read/write helpers.

Constraints

  • Bash 3.0+ (no mapfile, no associative arrays).
  • No behavior change when the flag is absent, except writing the cache file. Write it on every run (one write at end of run, cheap) so "fail, then --rerun-failed" works without pre-planning; document this. If a read-only CWD makes the write fail, ignore silently.
  • Update docs/ and CHANGELOG.md (user-visible feature).

Acceptance criteria

  • ./bashunit tests/ and ./bashunit --parallel tests/ pass.
  • make sa, make lint pass; shfmt -w . produces no diff.
  • --help documents the flag; docs page updated.

Line references valid at commit 1ae3e82.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

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); } })(); })(); feat(cli): --rerun-failed to replay only the last failing tests · Issue #776 · TypedDevs/bashunit · GitHub
Skip to content

feat(cli): --rerun-failed to replay only the last failing tests #776

Description

@Chemaclass

Why

The fastest edit-run loop after a red suite is "run only what just failed". bashunit has rich selection (--filter, --tag, file:line, shard) but no failure memory — after a failing run you must hand-craft a --filter or re-run everything. PHPUnit (--cache-result + --order-by=defects), pytest (--lf), and Jest (--onlyFailures) all ship this.

Proposal

Add --rerun-failed (env: BASHUNIT_RERUN_FAILED):

  1. Record: every run writes failing tests as <test_file>:<function_name> lines to a cache file, e.g. .bashunit/last-failed under the working directory (create dir lazily; document adding .bashunit/ to .gitignore in the docs page). Write it on normal exit: sequential failures are known in bashunit::runner::parse_result / state layer; parallel failures are known after aggregation in src/parallel.sh (aggregate_test_results) — hook the common point where _BASHUNIT_TESTS_FAILED is finalized, not each mode separately if possible. A fully green run truncates the cache.
  2. Replay: with --rerun-failed, if the cache exists and is non-empty, restrict discovery to the files listed and filter to the recorded function names (reuse the existing per-file function filtering used by file:line selection — see bashunit::helper::find_function_at_line wiring at src/main.sh:327 for how an inline filter is applied). If the cache is missing or empty, print a short notice and run the full suite.
  3. Flag parsing in src/main.sh (test subcommand options block), .env.example entry, and --help text.

Edge cases to cover

  • Data-provider tests: record the base function name once; replaying runs all its data rows.
  • Interpolated/parameterized display names must not leak into the cache — store the raw fn_name.
  • Cache entries pointing at deleted files/functions: skip with a warning, don't crash.
  • --rerun-failed combined with --filter/--tag: intersect (both filters apply).
  • Parallel mode writes the same cache format.

Tests first (TDD)

Acceptance tests in tests/acceptance/ (pattern: existing CLI tests spawning ./bashunit as subprocess): failing run populates cache, --rerun-failed runs only those tests, green run clears cache, empty-cache fallback message. Unit tests for the cache read/write helpers.

Constraints

  • Bash 3.0+ (no mapfile, no associative arrays).
  • No behavior change when the flag is absent, except writing the cache file. Write it on every run (one write at end of run, cheap) so "fail, then --rerun-failed" works without pre-planning; document this. If a read-only CWD makes the write fail, ignore silently.
  • Update docs/ and CHANGELOG.md (user-visible feature).

Acceptance criteria

  • ./bashunit tests/ and ./bashunit --parallel tests/ pass.
  • make sa, make lint pass; shfmt -w . produces no diff.
  • --help documents the flag; docs page updated.

Line references valid at commit 1ae3e82.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions