Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
- Internal: `src/runner.sh` and `src/coverage.sh` are split into `src/runner/` and `src/coverage/` modules of single-responsibility files, each behind a `source`-only `index.sh` aggregator. A pure relocation, no behavior change; see [ADR-010](adrs/adr-010-src-module-directories.md) (#924, #925)

### Fixed
- `assert_json_equals` no longer reports two invalid (unparseable) JSON strings as equal. It sorted both sides with `jq -S` but never checked jq's exit code, so two differently-invalid inputs both silently sorted to an empty string and compared equal instead of failing
- Parallel runs no longer lose results when two test files in different directories share a filename. Per-test results were bucketed by basename, so the second file overwrote the first — silently, with the run still green. A tests/ tree mirroring a src/ tree makes that layout ordinary (#959)
- Coverage no longer counts variable assignments as functions. A line like `URL="https://${host}/api"` was reported as a function, inflating `FNF`/`FNH` in the LCOV report (17 phantom entries in bashunit's own run); when the value also contained a `|`, the malformed record aborted the LCOV writer with a raw bash arithmetic error (#936)
- `build.sh` dedupes embedded files by repo-relative path. The previous basename key compared the top-level loop's relative paths against the recursion's absolute ones, so a file reached from two places could be bundled twice in the released binary; it also collided for same-named files in different directories (#923)
Expand Down
13 changes: 9 additions & 4 deletions src/assert/json.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,12 +53,17 @@ function assert_json_equals() {
local expected="$1"
local actual="$2"

local expected_sorted
expected_sorted=$(printf '%s' "$expected" | jq -S '.' 2>/dev/null)
# jq -S prints nothing (not an error message) on invalid JSON, so its exit
# code -- not just its output -- has to gate the comparison: two inputs that
# both fail to parse would otherwise both sort to "" and compare equal,
# reporting unparseable input as matching JSON instead of failing.
local expected_sorted actual_valid=true expected_valid=true
expected_sorted=$(printf '%s' "$expected" | jq -S '.' 2>/dev/null) || expected_valid=false
local actual_sorted
actual_sorted=$(printf '%s' "$actual" | jq -S '.' 2>/dev/null)
actual_sorted=$(printf '%s' "$actual" | jq -S '.' 2>/dev/null) || actual_valid=false

if [ "$expected_sorted" != "$actual_sorted" ]; then
if [ "$expected_valid" = false ] || [ "$actual_valid" = false ] ||
[ "$expected_sorted" != "$actual_sorted" ]; then
bashunit::assert::fail_with "" "${expected}" "but got " "${actual}"
return
fi
Expand Down
28 changes: 28 additions & 0 deletions tests/unit/assert/json_test.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,3 +75,31 @@ function test_unsuccessful_assert_json_equals() {
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}

# jq -S silently produces empty output (not a parse-error message) on invalid
# JSON; without checking its exit code, two differently-invalid or identically
# unparseable inputs both sort to "" and compare equal, turning "not JSON at
# all" into a false pass.
function test_unsuccessful_assert_json_equals_when_expected_is_invalid_json() {
if [ "$_JQ_AVAILABLE" = false ]; then bashunit::skip "jq required"; return; fi
local expected='not json'
local actual='{"a":1}'

assert_same \
"$(bashunit::console_results::print_failed_test \
"Unsuccessful assert json equals when expected is invalid json" \
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}

function test_unsuccessful_assert_json_equals_when_both_sides_are_invalid_json() {
if [ "$_JQ_AVAILABLE" = false ]; then bashunit::skip "jq required"; return; fi
local expected='not json'
local actual='also not json'

assert_same \
"$(bashunit::console_results::print_failed_test \
"Unsuccessful assert json equals when both sides are invalid json" \
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}
Loading
, '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(assert): assert_json_equals no longer passes on unparseable JSON by Chemaclass · Pull Request #967 · TypedDevs/bashunit · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
- Internal: `src/runner.sh` and `src/coverage.sh` are split into `src/runner/` and `src/coverage/` modules of single-responsibility files, each behind a `source`-only `index.sh` aggregator. A pure relocation, no behavior change; see [ADR-010](adrs/adr-010-src-module-directories.md) (#924, #925)

### Fixed
- `assert_json_equals` no longer reports two invalid (unparseable) JSON strings as equal. It sorted both sides with `jq -S` but never checked jq's exit code, so two differently-invalid inputs both silently sorted to an empty string and compared equal instead of failing
- Parallel runs no longer lose results when two test files in different directories share a filename. Per-test results were bucketed by basename, so the second file overwrote the first — silently, with the run still green. A tests/ tree mirroring a src/ tree makes that layout ordinary (#959)
- Coverage no longer counts variable assignments as functions. A line like `URL="https://${host}/api"` was reported as a function, inflating `FNF`/`FNH` in the LCOV report (17 phantom entries in bashunit's own run); when the value also contained a `|`, the malformed record aborted the LCOV writer with a raw bash arithmetic error (#936)
- `build.sh` dedupes embedded files by repo-relative path. The previous basename key compared the top-level loop's relative paths against the recursion's absolute ones, so a file reached from two places could be bundled twice in the released binary; it also collided for same-named files in different directories (#923)
Expand Down
13 changes: 9 additions & 4 deletions src/assert/json.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,12 +53,17 @@ function assert_json_equals() {
local expected="$1"
local actual="$2"

local expected_sorted
expected_sorted=$(printf '%s' "$expected" | jq -S '.' 2>/dev/null)
# jq -S prints nothing (not an error message) on invalid JSON, so its exit
# code -- not just its output -- has to gate the comparison: two inputs that
# both fail to parse would otherwise both sort to "" and compare equal,
# reporting unparseable input as matching JSON instead of failing.
local expected_sorted actual_valid=true expected_valid=true
expected_sorted=$(printf '%s' "$expected" | jq -S '.' 2>/dev/null) || expected_valid=false
local actual_sorted
actual_sorted=$(printf '%s' "$actual" | jq -S '.' 2>/dev/null)
actual_sorted=$(printf '%s' "$actual" | jq -S '.' 2>/dev/null) || actual_valid=false

if [ "$expected_sorted" != "$actual_sorted" ]; then
if [ "$expected_valid" = false ] || [ "$actual_valid" = false ] ||
[ "$expected_sorted" != "$actual_sorted" ]; then
bashunit::assert::fail_with "" "${expected}" "but got " "${actual}"
return
fi
Expand Down
28 changes: 28 additions & 0 deletions tests/unit/assert/json_test.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,3 +75,31 @@ function test_unsuccessful_assert_json_equals() {
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}

# jq -S silently produces empty output (not a parse-error message) on invalid
# JSON; without checking its exit code, two differently-invalid or identically
# unparseable inputs both sort to "" and compare equal, turning "not JSON at
# all" into a false pass.
function test_unsuccessful_assert_json_equals_when_expected_is_invalid_json() {
if [ "$_JQ_AVAILABLE" = false ]; then bashunit::skip "jq required"; return; fi
local expected='not json'
local actual='{"a":1}'

assert_same \
"$(bashunit::console_results::print_failed_test \
"Unsuccessful assert json equals when expected is invalid json" \
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}

function test_unsuccessful_assert_json_equals_when_both_sides_are_invalid_json() {
if [ "$_JQ_AVAILABLE" = false ]; then bashunit::skip "jq required"; return; fi
local expected='not json'
local actual='also not json'

assert_same \
"$(bashunit::console_results::print_failed_test \
"Unsuccessful assert json equals when both sides are invalid json" \
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}
Loading
, '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(assert): assert_json_equals no longer passes on unparseable JSON by Chemaclass · Pull Request #967 · TypedDevs/bashunit · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
- Internal: `src/runner.sh` and `src/coverage.sh` are split into `src/runner/` and `src/coverage/` modules of single-responsibility files, each behind a `source`-only `index.sh` aggregator. A pure relocation, no behavior change; see [ADR-010](adrs/adr-010-src-module-directories.md) (#924, #925)

### Fixed
- `assert_json_equals` no longer reports two invalid (unparseable) JSON strings as equal. It sorted both sides with `jq -S` but never checked jq's exit code, so two differently-invalid inputs both silently sorted to an empty string and compared equal instead of failing
- Parallel runs no longer lose results when two test files in different directories share a filename. Per-test results were bucketed by basename, so the second file overwrote the first — silently, with the run still green. A tests/ tree mirroring a src/ tree makes that layout ordinary (#959)
- Coverage no longer counts variable assignments as functions. A line like `URL="https://${host}/api"` was reported as a function, inflating `FNF`/`FNH` in the LCOV report (17 phantom entries in bashunit's own run); when the value also contained a `|`, the malformed record aborted the LCOV writer with a raw bash arithmetic error (#936)
- `build.sh` dedupes embedded files by repo-relative path. The previous basename key compared the top-level loop's relative paths against the recursion's absolute ones, so a file reached from two places could be bundled twice in the released binary; it also collided for same-named files in different directories (#923)
Expand Down
13 changes: 9 additions & 4 deletions src/assert/json.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,12 +53,17 @@ function assert_json_equals() {
local expected="$1"
local actual="$2"

local expected_sorted
expected_sorted=$(printf '%s' "$expected" | jq -S '.' 2>/dev/null)
# jq -S prints nothing (not an error message) on invalid JSON, so its exit
# code -- not just its output -- has to gate the comparison: two inputs that
# both fail to parse would otherwise both sort to "" and compare equal,
# reporting unparseable input as matching JSON instead of failing.
local expected_sorted actual_valid=true expected_valid=true
expected_sorted=$(printf '%s' "$expected" | jq -S '.' 2>/dev/null) || expected_valid=false
local actual_sorted
actual_sorted=$(printf '%s' "$actual" | jq -S '.' 2>/dev/null)
actual_sorted=$(printf '%s' "$actual" | jq -S '.' 2>/dev/null) || actual_valid=false

if [ "$expected_sorted" != "$actual_sorted" ]; then
if [ "$expected_valid" = false ] || [ "$actual_valid" = false ] ||
[ "$expected_sorted" != "$actual_sorted" ]; then
bashunit::assert::fail_with "" "${expected}" "but got " "${actual}"
return
fi
Expand Down
28 changes: 28 additions & 0 deletions tests/unit/assert/json_test.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,3 +75,31 @@ function test_unsuccessful_assert_json_equals() {
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}

# jq -S silently produces empty output (not a parse-error message) on invalid
# JSON; without checking its exit code, two differently-invalid or identically
# unparseable inputs both sort to "" and compare equal, turning "not JSON at
# all" into a false pass.
function test_unsuccessful_assert_json_equals_when_expected_is_invalid_json() {
if [ "$_JQ_AVAILABLE" = false ]; then bashunit::skip "jq required"; return; fi
local expected='not json'
local actual='{"a":1}'

assert_same \
"$(bashunit::console_results::print_failed_test \
"Unsuccessful assert json equals when expected is invalid json" \
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}

function test_unsuccessful_assert_json_equals_when_both_sides_are_invalid_json() {
if [ "$_JQ_AVAILABLE" = false ]; then bashunit::skip "jq required"; return; fi
local expected='not json'
local actual='also not json'

assert_same \
"$(bashunit::console_results::print_failed_test \
"Unsuccessful assert json equals when both sides are invalid json" \
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}
Loading
, '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(assert): assert_json_equals no longer passes on unparseable JSON by Chemaclass · Pull Request #967 · TypedDevs/bashunit · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
- Internal: `src/runner.sh` and `src/coverage.sh` are split into `src/runner/` and `src/coverage/` modules of single-responsibility files, each behind a `source`-only `index.sh` aggregator. A pure relocation, no behavior change; see [ADR-010](adrs/adr-010-src-module-directories.md) (#924, #925)

### Fixed
- `assert_json_equals` no longer reports two invalid (unparseable) JSON strings as equal. It sorted both sides with `jq -S` but never checked jq's exit code, so two differently-invalid inputs both silently sorted to an empty string and compared equal instead of failing
- Parallel runs no longer lose results when two test files in different directories share a filename. Per-test results were bucketed by basename, so the second file overwrote the first — silently, with the run still green. A tests/ tree mirroring a src/ tree makes that layout ordinary (#959)
- Coverage no longer counts variable assignments as functions. A line like `URL="https://${host}/api"` was reported as a function, inflating `FNF`/`FNH` in the LCOV report (17 phantom entries in bashunit's own run); when the value also contained a `|`, the malformed record aborted the LCOV writer with a raw bash arithmetic error (#936)
- `build.sh` dedupes embedded files by repo-relative path. The previous basename key compared the top-level loop's relative paths against the recursion's absolute ones, so a file reached from two places could be bundled twice in the released binary; it also collided for same-named files in different directories (#923)
Expand Down
13 changes: 9 additions & 4 deletions src/assert/json.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,12 +53,17 @@ function assert_json_equals() {
local expected="$1"
local actual="$2"

local expected_sorted
expected_sorted=$(printf '%s' "$expected" | jq -S '.' 2>/dev/null)
# jq -S prints nothing (not an error message) on invalid JSON, so its exit
# code -- not just its output -- has to gate the comparison: two inputs that
# both fail to parse would otherwise both sort to "" and compare equal,
# reporting unparseable input as matching JSON instead of failing.
local expected_sorted actual_valid=true expected_valid=true
expected_sorted=$(printf '%s' "$expected" | jq -S '.' 2>/dev/null) || expected_valid=false
local actual_sorted
actual_sorted=$(printf '%s' "$actual" | jq -S '.' 2>/dev/null)
actual_sorted=$(printf '%s' "$actual" | jq -S '.' 2>/dev/null) || actual_valid=false

if [ "$expected_sorted" != "$actual_sorted" ]; then
if [ "$expected_valid" = false ] || [ "$actual_valid" = false ] ||
[ "$expected_sorted" != "$actual_sorted" ]; then
bashunit::assert::fail_with "" "${expected}" "but got " "${actual}"
return
fi
Expand Down
28 changes: 28 additions & 0 deletions tests/unit/assert/json_test.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,3 +75,31 @@ function test_unsuccessful_assert_json_equals() {
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}

# jq -S silently produces empty output (not a parse-error message) on invalid
# JSON; without checking its exit code, two differently-invalid or identically
# unparseable inputs both sort to "" and compare equal, turning "not JSON at
# all" into a false pass.
function test_unsuccessful_assert_json_equals_when_expected_is_invalid_json() {
if [ "$_JQ_AVAILABLE" = false ]; then bashunit::skip "jq required"; return; fi
local expected='not json'
local actual='{"a":1}'

assert_same \
"$(bashunit::console_results::print_failed_test \
"Unsuccessful assert json equals when expected is invalid json" \
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}

function test_unsuccessful_assert_json_equals_when_both_sides_are_invalid_json() {
if [ "$_JQ_AVAILABLE" = false ]; then bashunit::skip "jq required"; return; fi
local expected='not json'
local actual='also not json'

assert_same \
"$(bashunit::console_results::print_failed_test \
"Unsuccessful assert json equals when both sides are invalid json" \
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}
Loading
, '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(assert): assert_json_equals no longer passes on unparseable JSON by Chemaclass · Pull Request #967 · TypedDevs/bashunit · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
- Internal: `src/runner.sh` and `src/coverage.sh` are split into `src/runner/` and `src/coverage/` modules of single-responsibility files, each behind a `source`-only `index.sh` aggregator. A pure relocation, no behavior change; see [ADR-010](adrs/adr-010-src-module-directories.md) (#924, #925)

### Fixed
- `assert_json_equals` no longer reports two invalid (unparseable) JSON strings as equal. It sorted both sides with `jq -S` but never checked jq's exit code, so two differently-invalid inputs both silently sorted to an empty string and compared equal instead of failing
- Parallel runs no longer lose results when two test files in different directories share a filename. Per-test results were bucketed by basename, so the second file overwrote the first — silently, with the run still green. A tests/ tree mirroring a src/ tree makes that layout ordinary (#959)
- Coverage no longer counts variable assignments as functions. A line like `URL="https://${host}/api"` was reported as a function, inflating `FNF`/`FNH` in the LCOV report (17 phantom entries in bashunit's own run); when the value also contained a `|`, the malformed record aborted the LCOV writer with a raw bash arithmetic error (#936)
- `build.sh` dedupes embedded files by repo-relative path. The previous basename key compared the top-level loop's relative paths against the recursion's absolute ones, so a file reached from two places could be bundled twice in the released binary; it also collided for same-named files in different directories (#923)
Expand Down
13 changes: 9 additions & 4 deletions src/assert/json.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,12 +53,17 @@ function assert_json_equals() {
local expected="$1"
local actual="$2"

local expected_sorted
expected_sorted=$(printf '%s' "$expected" | jq -S '.' 2>/dev/null)
# jq -S prints nothing (not an error message) on invalid JSON, so its exit
# code -- not just its output -- has to gate the comparison: two inputs that
# both fail to parse would otherwise both sort to "" and compare equal,
# reporting unparseable input as matching JSON instead of failing.
local expected_sorted actual_valid=true expected_valid=true
expected_sorted=$(printf '%s' "$expected" | jq -S '.' 2>/dev/null) || expected_valid=false
local actual_sorted
actual_sorted=$(printf '%s' "$actual" | jq -S '.' 2>/dev/null)
actual_sorted=$(printf '%s' "$actual" | jq -S '.' 2>/dev/null) || actual_valid=false

if [ "$expected_sorted" != "$actual_sorted" ]; then
if [ "$expected_valid" = false ] || [ "$actual_valid" = false ] ||
[ "$expected_sorted" != "$actual_sorted" ]; then
bashunit::assert::fail_with "" "${expected}" "but got " "${actual}"
return
fi
Expand Down
28 changes: 28 additions & 0 deletions tests/unit/assert/json_test.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,3 +75,31 @@ function test_unsuccessful_assert_json_equals() {
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}

# jq -S silently produces empty output (not a parse-error message) on invalid
# JSON; without checking its exit code, two differently-invalid or identically
# unparseable inputs both sort to "" and compare equal, turning "not JSON at
# all" into a false pass.
function test_unsuccessful_assert_json_equals_when_expected_is_invalid_json() {
if [ "$_JQ_AVAILABLE" = false ]; then bashunit::skip "jq required"; return; fi
local expected='not json'
local actual='{"a":1}'

assert_same \
"$(bashunit::console_results::print_failed_test \
"Unsuccessful assert json equals when expected is invalid json" \
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}

function test_unsuccessful_assert_json_equals_when_both_sides_are_invalid_json() {
if [ "$_JQ_AVAILABLE" = false ]; then bashunit::skip "jq required"; return; fi
local expected='not json'
local actual='also not json'

assert_same \
"$(bashunit::console_results::print_failed_test \
"Unsuccessful assert json equals when both sides are invalid json" \
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}
Loading
, '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(assert): assert_json_equals no longer passes on unparseable JSON by Chemaclass · Pull Request #967 · TypedDevs/bashunit · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
- Internal: `src/runner.sh` and `src/coverage.sh` are split into `src/runner/` and `src/coverage/` modules of single-responsibility files, each behind a `source`-only `index.sh` aggregator. A pure relocation, no behavior change; see [ADR-010](adrs/adr-010-src-module-directories.md) (#924, #925)

### Fixed
- `assert_json_equals` no longer reports two invalid (unparseable) JSON strings as equal. It sorted both sides with `jq -S` but never checked jq's exit code, so two differently-invalid inputs both silently sorted to an empty string and compared equal instead of failing
- Parallel runs no longer lose results when two test files in different directories share a filename. Per-test results were bucketed by basename, so the second file overwrote the first — silently, with the run still green. A tests/ tree mirroring a src/ tree makes that layout ordinary (#959)
- Coverage no longer counts variable assignments as functions. A line like `URL="https://${host}/api"` was reported as a function, inflating `FNF`/`FNH` in the LCOV report (17 phantom entries in bashunit's own run); when the value also contained a `|`, the malformed record aborted the LCOV writer with a raw bash arithmetic error (#936)
- `build.sh` dedupes embedded files by repo-relative path. The previous basename key compared the top-level loop's relative paths against the recursion's absolute ones, so a file reached from two places could be bundled twice in the released binary; it also collided for same-named files in different directories (#923)
Expand Down
13 changes: 9 additions & 4 deletions src/assert/json.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,12 +53,17 @@ function assert_json_equals() {
local expected="$1"
local actual="$2"

local expected_sorted
expected_sorted=$(printf '%s' "$expected" | jq -S '.' 2>/dev/null)
# jq -S prints nothing (not an error message) on invalid JSON, so its exit
# code -- not just its output -- has to gate the comparison: two inputs that
# both fail to parse would otherwise both sort to "" and compare equal,
# reporting unparseable input as matching JSON instead of failing.
local expected_sorted actual_valid=true expected_valid=true
expected_sorted=$(printf '%s' "$expected" | jq -S '.' 2>/dev/null) || expected_valid=false
local actual_sorted
actual_sorted=$(printf '%s' "$actual" | jq -S '.' 2>/dev/null)
actual_sorted=$(printf '%s' "$actual" | jq -S '.' 2>/dev/null) || actual_valid=false

if [ "$expected_sorted" != "$actual_sorted" ]; then
if [ "$expected_valid" = false ] || [ "$actual_valid" = false ] ||
[ "$expected_sorted" != "$actual_sorted" ]; then
bashunit::assert::fail_with "" "${expected}" "but got " "${actual}"
return
fi
Expand Down
28 changes: 28 additions & 0 deletions tests/unit/assert/json_test.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,3 +75,31 @@ function test_unsuccessful_assert_json_equals() {
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}

# jq -S silently produces empty output (not a parse-error message) on invalid
# JSON; without checking its exit code, two differently-invalid or identically
# unparseable inputs both sort to "" and compare equal, turning "not JSON at
# all" into a false pass.
function test_unsuccessful_assert_json_equals_when_expected_is_invalid_json() {
if [ "$_JQ_AVAILABLE" = false ]; then bashunit::skip "jq required"; return; fi
local expected='not json'
local actual='{"a":1}'

assert_same \
"$(bashunit::console_results::print_failed_test \
"Unsuccessful assert json equals when expected is invalid json" \
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}

function test_unsuccessful_assert_json_equals_when_both_sides_are_invalid_json() {
if [ "$_JQ_AVAILABLE" = false ]; then bashunit::skip "jq required"; return; fi
local expected='not json'
local actual='also not json'

assert_same \
"$(bashunit::console_results::print_failed_test \
"Unsuccessful assert json equals when both sides are invalid json" \
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}
Loading
, '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(assert): assert_json_equals no longer passes on unparseable JSON by Chemaclass · Pull Request #967 · TypedDevs/bashunit · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
- Internal: `src/runner.sh` and `src/coverage.sh` are split into `src/runner/` and `src/coverage/` modules of single-responsibility files, each behind a `source`-only `index.sh` aggregator. A pure relocation, no behavior change; see [ADR-010](adrs/adr-010-src-module-directories.md) (#924, #925)

### Fixed
- `assert_json_equals` no longer reports two invalid (unparseable) JSON strings as equal. It sorted both sides with `jq -S` but never checked jq's exit code, so two differently-invalid inputs both silently sorted to an empty string and compared equal instead of failing
- Parallel runs no longer lose results when two test files in different directories share a filename. Per-test results were bucketed by basename, so the second file overwrote the first — silently, with the run still green. A tests/ tree mirroring a src/ tree makes that layout ordinary (#959)
- Coverage no longer counts variable assignments as functions. A line like `URL="https://${host}/api"` was reported as a function, inflating `FNF`/`FNH` in the LCOV report (17 phantom entries in bashunit's own run); when the value also contained a `|`, the malformed record aborted the LCOV writer with a raw bash arithmetic error (#936)
- `build.sh` dedupes embedded files by repo-relative path. The previous basename key compared the top-level loop's relative paths against the recursion's absolute ones, so a file reached from two places could be bundled twice in the released binary; it also collided for same-named files in different directories (#923)
Expand Down
13 changes: 9 additions & 4 deletions src/assert/json.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,12 +53,17 @@ function assert_json_equals() {
local expected="$1"
local actual="$2"

local expected_sorted
expected_sorted=$(printf '%s' "$expected" | jq -S '.' 2>/dev/null)
# jq -S prints nothing (not an error message) on invalid JSON, so its exit
# code -- not just its output -- has to gate the comparison: two inputs that
# both fail to parse would otherwise both sort to "" and compare equal,
# reporting unparseable input as matching JSON instead of failing.
local expected_sorted actual_valid=true expected_valid=true
expected_sorted=$(printf '%s' "$expected" | jq -S '.' 2>/dev/null) || expected_valid=false
local actual_sorted
actual_sorted=$(printf '%s' "$actual" | jq -S '.' 2>/dev/null)
actual_sorted=$(printf '%s' "$actual" | jq -S '.' 2>/dev/null) || actual_valid=false

if [ "$expected_sorted" != "$actual_sorted" ]; then
if [ "$expected_valid" = false ] || [ "$actual_valid" = false ] ||
[ "$expected_sorted" != "$actual_sorted" ]; then
bashunit::assert::fail_with "" "${expected}" "but got " "${actual}"
return
fi
Expand Down
28 changes: 28 additions & 0 deletions tests/unit/assert/json_test.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,3 +75,31 @@ function test_unsuccessful_assert_json_equals() {
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}

# jq -S silently produces empty output (not a parse-error message) on invalid
# JSON; without checking its exit code, two differently-invalid or identically
# unparseable inputs both sort to "" and compare equal, turning "not JSON at
# all" into a false pass.
function test_unsuccessful_assert_json_equals_when_expected_is_invalid_json() {
if [ "$_JQ_AVAILABLE" = false ]; then bashunit::skip "jq required"; return; fi
local expected='not json'
local actual='{"a":1}'

assert_same \
"$(bashunit::console_results::print_failed_test \
"Unsuccessful assert json equals when expected is invalid json" \
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}

function test_unsuccessful_assert_json_equals_when_both_sides_are_invalid_json() {
if [ "$_JQ_AVAILABLE" = false ]; then bashunit::skip "jq required"; return; fi
local expected='not json'
local actual='also not json'

assert_same \
"$(bashunit::console_results::print_failed_test \
"Unsuccessful assert json equals when both sides are invalid json" \
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}
Loading
, '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(assert): assert_json_equals no longer passes on unparseable JSON by Chemaclass · Pull Request #967 · TypedDevs/bashunit · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
- Internal: `src/runner.sh` and `src/coverage.sh` are split into `src/runner/` and `src/coverage/` modules of single-responsibility files, each behind a `source`-only `index.sh` aggregator. A pure relocation, no behavior change; see [ADR-010](adrs/adr-010-src-module-directories.md) (#924, #925)

### Fixed
- `assert_json_equals` no longer reports two invalid (unparseable) JSON strings as equal. It sorted both sides with `jq -S` but never checked jq's exit code, so two differently-invalid inputs both silently sorted to an empty string and compared equal instead of failing
- Parallel runs no longer lose results when two test files in different directories share a filename. Per-test results were bucketed by basename, so the second file overwrote the first — silently, with the run still green. A tests/ tree mirroring a src/ tree makes that layout ordinary (#959)
- Coverage no longer counts variable assignments as functions. A line like `URL="https://${host}/api"` was reported as a function, inflating `FNF`/`FNH` in the LCOV report (17 phantom entries in bashunit's own run); when the value also contained a `|`, the malformed record aborted the LCOV writer with a raw bash arithmetic error (#936)
- `build.sh` dedupes embedded files by repo-relative path. The previous basename key compared the top-level loop's relative paths against the recursion's absolute ones, so a file reached from two places could be bundled twice in the released binary; it also collided for same-named files in different directories (#923)
Expand Down
13 changes: 9 additions & 4 deletions src/assert/json.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,12 +53,17 @@ function assert_json_equals() {
local expected="$1"
local actual="$2"

local expected_sorted
expected_sorted=$(printf '%s' "$expected" | jq -S '.' 2>/dev/null)
# jq -S prints nothing (not an error message) on invalid JSON, so its exit
# code -- not just its output -- has to gate the comparison: two inputs that
# both fail to parse would otherwise both sort to "" and compare equal,
# reporting unparseable input as matching JSON instead of failing.
local expected_sorted actual_valid=true expected_valid=true
expected_sorted=$(printf '%s' "$expected" | jq -S '.' 2>/dev/null) || expected_valid=false
local actual_sorted
actual_sorted=$(printf '%s' "$actual" | jq -S '.' 2>/dev/null)
actual_sorted=$(printf '%s' "$actual" | jq -S '.' 2>/dev/null) || actual_valid=false

if [ "$expected_sorted" != "$actual_sorted" ]; then
if [ "$expected_valid" = false ] || [ "$actual_valid" = false ] ||
[ "$expected_sorted" != "$actual_sorted" ]; then
bashunit::assert::fail_with "" "${expected}" "but got " "${actual}"
return
fi
Expand Down
28 changes: 28 additions & 0 deletions tests/unit/assert/json_test.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,3 +75,31 @@ function test_unsuccessful_assert_json_equals() {
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}

# jq -S silently produces empty output (not a parse-error message) on invalid
# JSON; without checking its exit code, two differently-invalid or identically
# unparseable inputs both sort to "" and compare equal, turning "not JSON at
# all" into a false pass.
function test_unsuccessful_assert_json_equals_when_expected_is_invalid_json() {
if [ "$_JQ_AVAILABLE" = false ]; then bashunit::skip "jq required"; return; fi
local expected='not json'
local actual='{"a":1}'

assert_same \
"$(bashunit::console_results::print_failed_test \
"Unsuccessful assert json equals when expected is invalid json" \
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}

function test_unsuccessful_assert_json_equals_when_both_sides_are_invalid_json() {
if [ "$_JQ_AVAILABLE" = false ]; then bashunit::skip "jq required"; return; fi
local expected='not json'
local actual='also not json'

assert_same \
"$(bashunit::console_results::print_failed_test \
"Unsuccessful assert json equals when both sides are invalid json" \
"$expected" "but got " "$actual")" \
"$(assert_json_equals "$expected" "$actual")"
}
Loading