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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
- Named snapshot assertions support multiple snapshots per test; mismatches show the resolved path and `--snapshot-update` hint (#986)

### Changed
- `assert_true` / `assert_false` name the problem instead of printing a bare number: exit code 127 now reports `unknown command: <arg>` and 126 `not executable: <arg>`. The most common cause is passing a test expression like `[ -d /tmp ]`, which is run as a command word
- Core comparison assertions report missing required arguments as usage errors instead of comparing against empty values (#983)
- Performance: Literal snapshots bypass placeholder regex processing unless they contain a placeholder (about 13x faster) (#985)
- Performance: `assert_within_delta` uses fixed-point arithmetic for common values, with a `bc`/`awk` fallback for unsupported inputs (about 6.6x faster) (#979)
Expand All@@ -14,6 +15,7 @@
- Internal: Split `src/runner.sh` and `src/coverage.sh` into focused modules with no behavior change; see [ADR-010](adrs/adr-010-src-module-directories.md) (#924, #925)

### Fixed
- `assert_false` no longer passes when the command does not exist. Exit code 127 is non-zero, so a typo in the command name satisfied the assertion while testing nothing; 127 and 126 now fail both `assert_true` and `assert_false`, because they mean the command never ran
- `assert_within_delta` accepts a leading `+` on any operand (#979)
- Invalid `BASHUNIT_SHARD_INDEX` / `BASHUNIT_SHARD_TOTAL` values now fail with a clear error instead of reaching raw arithmetic or reporting no tests
- Date assertions reject unparseable values instead of crashing or treating them as epoch 0
Expand Down
6 changes: 6 additions & 0 deletions docs/assertions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,6 +36,12 @@ to narrow it (`bashunit doc json`).
## assert_true
> `assert_true bool|function|command`

Takes a **command**, not a test expression. A bracketed condition of the form
used inside `if` is run as a command word, so the shell reports exit code 127 and
the assertion fails with `unknown command`. Use the `test` builtin instead —
`assert_true "test -d /tmp"` — or a purpose-built assertion such as
`assert_directory_exists`.

Reports an error if the argument result in a truthy value: `true` or `0`.

- [assert_false](#assert-false) is similar but different.
Expand Down
44 changes: 38 additions & 6 deletions src/assert/core.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -114,6 +114,30 @@ function bashunit::fail() {
bashunit::console_results::print_failure_message "${label}" "$message"
}

_BASHUNIT_ASSERT_EXIT_DESC_OUT=""

##
# Describes a failing exit code for assert_true / assert_false. 127 and 126 are
# the two codes the shell reserves for "I could not run this at all", and a bare
# number tells the reader nothing: the most natural shell idiom,
# `assert_true "[ -d /tmp ]"`, hits 127 because the argument is run as a command
# word rather than evaluated, and the failure used to point nowhere near the
# cause.
#
# The wording deliberately avoids the literal phrase "command not found".
# runner/diagnostics.sh classifies a test as a runtime error by scanning its
# output for that exact string, so using it here made every such failure report
# as both Failed and Error for the one cause.
# Arguments: $1 - exit code, $2 - the command as written
##
function bashunit::assert::_describe_exit_code() {
case "$1" in
127) _BASHUNIT_ASSERT_EXIT_DESC_OUT="unknown command: $2" ;;
126) _BASHUNIT_ASSERT_EXIT_DESC_OUT="not executable: $2" ;;
*) _BASHUNIT_ASSERT_EXIT_DESC_OUT="exit code: $1" ;;
esac
}

function assert_true() {
bashunit::assert::should_skip && return 0

Expand All@@ -140,7 +164,9 @@ function assert_true() {
local exit_code=$?

if [ "$exit_code" -ne 0 ]; then
bashunit::handle_bool_assertion_failure "command or function with zero exit code" "exit code: $exit_code"
bashunit::assert::_describe_exit_code "$exit_code" "$actual"
bashunit::handle_bool_assertion_failure \
"command or function with zero exit code" "$_BASHUNIT_ASSERT_EXIT_DESC_OUT"
else
bashunit::state::add_assertions_passed
fi
Expand DownExpand Up@@ -171,11 +197,17 @@ function assert_false() {
bashunit::run_command_or_eval "$actual"
local exit_code=$?

if [ "$exit_code" -eq 0 ]; then
bashunit::handle_bool_assertion_failure "command or function with non-zero exit code" "exit code: $exit_code"
else
bashunit::state::add_assertions_passed
fi
# 127/126 mean the command never ran. Treating "did not run" as "returned
# false" let a typo in the command name satisfy this assertion while testing
# nothing, so those are failures here as well as in assert_true.
case "$exit_code" in
0 | 126 | 127)
bashunit::assert::_describe_exit_code "$exit_code" "$actual"
bashunit::handle_bool_assertion_failure \
"command or function with non-zero exit code" "$_BASHUNIT_ASSERT_EXIT_DESC_OUT"
;;
*) bashunit::state::add_assertions_passed ;;
esac
}

function bashunit::run_command_or_eval() {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,12 @@
--------------
> `assert_true bool|function|command`

Takes a **command**, not a test expression. A bracketed condition of the form
used inside `if` is run as a command word, so the shell reports exit code 127 and
the assertion fails with `unknown command`. Use the `test` builtin instead —
`assert_true "test -d /tmp"` — or a purpose-built assertion such as
`assert_directory_exists`.

Reports an error if the argument result in a truthy value: `true` or `0`.

- assert_false is similar but different.
Expand Down
34 changes: 34 additions & 0 deletions tests/unit/assert/basic_test.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -183,3 +183,37 @@ function test_assert_not_same_with_custom_label() {
"$(bashunit::console_results::print_failed_test "my custom label" "foo" "to not be" "foo")" \
"$(assert_not_same "foo" "foo" "my custom label")"
}

# Exit code 127 is the shell's not-found code. Reporting the bare number
# gives the reader nothing to act on -- and the most natural shell idiom,
# a `[ ... ]` test expression, produces exactly this because assert_true runs
# its argument as a command word rather than evaluating it.
function test_assert_true_reports_a_missing_command_by_name() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert true reports a missing command by name" \
"command or function with zero exit code" "but got " \
"unknown command: definitely_not_a_command")" \
"$(assert_true "definitely_not_a_command")"
}

function test_assert_true_reports_a_bracket_expression_as_not_found() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert true reports a bracket expression as not found" \
"command or function with zero exit code" "but got " \
"unknown command: [ -d /tmp ]")" \
"$(assert_true "[ -d /tmp ]")"
}

# assert_false only failed when the exit code was 0, so a command that does not
# exist satisfied it: 127 is non-zero, therefore "false". A typo in the command
# name made the assertion pass while testing nothing.
function test_assert_false_fails_when_the_command_does_not_exist() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert false fails when the command does not exist" \
"command or function with non-zero exit code" "but got " \
"unknown command: definitely_not_a_command")" \
"$(assert_false "definitely_not_a_command")"
}
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" + '
feat(assert): name the problem instead of printing exit code 127 by Chemaclass · Pull Request #991 · 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
- Named snapshot assertions support multiple snapshots per test; mismatches show the resolved path and `--snapshot-update` hint (#986)

### Changed
- `assert_true` / `assert_false` name the problem instead of printing a bare number: exit code 127 now reports `unknown command: <arg>` and 126 `not executable: <arg>`. The most common cause is passing a test expression like `[ -d /tmp ]`, which is run as a command word
- Core comparison assertions report missing required arguments as usage errors instead of comparing against empty values (#983)
- Performance: Literal snapshots bypass placeholder regex processing unless they contain a placeholder (about 13x faster) (#985)
- Performance: `assert_within_delta` uses fixed-point arithmetic for common values, with a `bc`/`awk` fallback for unsupported inputs (about 6.6x faster) (#979)
Expand All@@ -14,6 +15,7 @@
- Internal: Split `src/runner.sh` and `src/coverage.sh` into focused modules with no behavior change; see [ADR-010](adrs/adr-010-src-module-directories.md) (#924, #925)

### Fixed
- `assert_false` no longer passes when the command does not exist. Exit code 127 is non-zero, so a typo in the command name satisfied the assertion while testing nothing; 127 and 126 now fail both `assert_true` and `assert_false`, because they mean the command never ran
- `assert_within_delta` accepts a leading `+` on any operand (#979)
- Invalid `BASHUNIT_SHARD_INDEX` / `BASHUNIT_SHARD_TOTAL` values now fail with a clear error instead of reaching raw arithmetic or reporting no tests
- Date assertions reject unparseable values instead of crashing or treating them as epoch 0
Expand Down
6 changes: 6 additions & 0 deletions docs/assertions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,6 +36,12 @@ to narrow it (`bashunit doc json`).
## assert_true
> `assert_true bool|function|command`

Takes a **command**, not a test expression. A bracketed condition of the form
used inside `if` is run as a command word, so the shell reports exit code 127 and
the assertion fails with `unknown command`. Use the `test` builtin instead —
`assert_true "test -d /tmp"` — or a purpose-built assertion such as
`assert_directory_exists`.

Reports an error if the argument result in a truthy value: `true` or `0`.

- [assert_false](#assert-false) is similar but different.
Expand Down
44 changes: 38 additions & 6 deletions src/assert/core.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -114,6 +114,30 @@ function bashunit::fail() {
bashunit::console_results::print_failure_message "${label}" "$message"
}

_BASHUNIT_ASSERT_EXIT_DESC_OUT=""

##
# Describes a failing exit code for assert_true / assert_false. 127 and 126 are
# the two codes the shell reserves for "I could not run this at all", and a bare
# number tells the reader nothing: the most natural shell idiom,
# `assert_true "[ -d /tmp ]"`, hits 127 because the argument is run as a command
# word rather than evaluated, and the failure used to point nowhere near the
# cause.
#
# The wording deliberately avoids the literal phrase "command not found".
# runner/diagnostics.sh classifies a test as a runtime error by scanning its
# output for that exact string, so using it here made every such failure report
# as both Failed and Error for the one cause.
# Arguments: $1 - exit code, $2 - the command as written
##
function bashunit::assert::_describe_exit_code() {
case "$1" in
127) _BASHUNIT_ASSERT_EXIT_DESC_OUT="unknown command: $2" ;;
126) _BASHUNIT_ASSERT_EXIT_DESC_OUT="not executable: $2" ;;
*) _BASHUNIT_ASSERT_EXIT_DESC_OUT="exit code: $1" ;;
esac
}

function assert_true() {
bashunit::assert::should_skip && return 0

Expand All@@ -140,7 +164,9 @@ function assert_true() {
local exit_code=$?

if [ "$exit_code" -ne 0 ]; then
bashunit::handle_bool_assertion_failure "command or function with zero exit code" "exit code: $exit_code"
bashunit::assert::_describe_exit_code "$exit_code" "$actual"
bashunit::handle_bool_assertion_failure \
"command or function with zero exit code" "$_BASHUNIT_ASSERT_EXIT_DESC_OUT"
else
bashunit::state::add_assertions_passed
fi
Expand DownExpand Up@@ -171,11 +197,17 @@ function assert_false() {
bashunit::run_command_or_eval "$actual"
local exit_code=$?

if [ "$exit_code" -eq 0 ]; then
bashunit::handle_bool_assertion_failure "command or function with non-zero exit code" "exit code: $exit_code"
else
bashunit::state::add_assertions_passed
fi
# 127/126 mean the command never ran. Treating "did not run" as "returned
# false" let a typo in the command name satisfy this assertion while testing
# nothing, so those are failures here as well as in assert_true.
case "$exit_code" in
0 | 126 | 127)
bashunit::assert::_describe_exit_code "$exit_code" "$actual"
bashunit::handle_bool_assertion_failure \
"command or function with non-zero exit code" "$_BASHUNIT_ASSERT_EXIT_DESC_OUT"
;;
*) bashunit::state::add_assertions_passed ;;
esac
}

function bashunit::run_command_or_eval() {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,12 @@
--------------
> `assert_true bool|function|command`

Takes a **command**, not a test expression. A bracketed condition of the form
used inside `if` is run as a command word, so the shell reports exit code 127 and
the assertion fails with `unknown command`. Use the `test` builtin instead —
`assert_true "test -d /tmp"` — or a purpose-built assertion such as
`assert_directory_exists`.

Reports an error if the argument result in a truthy value: `true` or `0`.

- assert_false is similar but different.
Expand Down
34 changes: 34 additions & 0 deletions tests/unit/assert/basic_test.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -183,3 +183,37 @@ function test_assert_not_same_with_custom_label() {
"$(bashunit::console_results::print_failed_test "my custom label" "foo" "to not be" "foo")" \
"$(assert_not_same "foo" "foo" "my custom label")"
}

# Exit code 127 is the shell's not-found code. Reporting the bare number
# gives the reader nothing to act on -- and the most natural shell idiom,
# a `[ ... ]` test expression, produces exactly this because assert_true runs
# its argument as a command word rather than evaluating it.
function test_assert_true_reports_a_missing_command_by_name() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert true reports a missing command by name" \
"command or function with zero exit code" "but got " \
"unknown command: definitely_not_a_command")" \
"$(assert_true "definitely_not_a_command")"
}

function test_assert_true_reports_a_bracket_expression_as_not_found() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert true reports a bracket expression as not found" \
"command or function with zero exit code" "but got " \
"unknown command: [ -d /tmp ]")" \
"$(assert_true "[ -d /tmp ]")"
}

# assert_false only failed when the exit code was 0, so a command that does not
# exist satisfied it: 127 is non-zero, therefore "false". A typo in the command
# name made the assertion pass while testing nothing.
function test_assert_false_fails_when_the_command_does_not_exist() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert false fails when the command does not exist" \
"command or function with non-zero exit code" "but got " \
"unknown command: definitely_not_a_command")" \
"$(assert_false "definitely_not_a_command")"
}
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('^' + ".*" + ' feat(assert): name the problem instead of printing exit code 127 by Chemaclass · Pull Request #991 · 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
- Named snapshot assertions support multiple snapshots per test; mismatches show the resolved path and `--snapshot-update` hint (#986)

### Changed
- `assert_true` / `assert_false` name the problem instead of printing a bare number: exit code 127 now reports `unknown command: <arg>` and 126 `not executable: <arg>`. The most common cause is passing a test expression like `[ -d /tmp ]`, which is run as a command word
- Core comparison assertions report missing required arguments as usage errors instead of comparing against empty values (#983)
- Performance: Literal snapshots bypass placeholder regex processing unless they contain a placeholder (about 13x faster) (#985)
- Performance: `assert_within_delta` uses fixed-point arithmetic for common values, with a `bc`/`awk` fallback for unsupported inputs (about 6.6x faster) (#979)
Expand All@@ -14,6 +15,7 @@
- Internal: Split `src/runner.sh` and `src/coverage.sh` into focused modules with no behavior change; see [ADR-010](adrs/adr-010-src-module-directories.md) (#924, #925)

### Fixed
- `assert_false` no longer passes when the command does not exist. Exit code 127 is non-zero, so a typo in the command name satisfied the assertion while testing nothing; 127 and 126 now fail both `assert_true` and `assert_false`, because they mean the command never ran
- `assert_within_delta` accepts a leading `+` on any operand (#979)
- Invalid `BASHUNIT_SHARD_INDEX` / `BASHUNIT_SHARD_TOTAL` values now fail with a clear error instead of reaching raw arithmetic or reporting no tests
- Date assertions reject unparseable values instead of crashing or treating them as epoch 0
Expand Down
6 changes: 6 additions & 0 deletions docs/assertions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,6 +36,12 @@ to narrow it (`bashunit doc json`).
## assert_true
> `assert_true bool|function|command`

Takes a **command**, not a test expression. A bracketed condition of the form
used inside `if` is run as a command word, so the shell reports exit code 127 and
the assertion fails with `unknown command`. Use the `test` builtin instead —
`assert_true "test -d /tmp"` — or a purpose-built assertion such as
`assert_directory_exists`.

Reports an error if the argument result in a truthy value: `true` or `0`.

- [assert_false](#assert-false) is similar but different.
Expand Down
44 changes: 38 additions & 6 deletions src/assert/core.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -114,6 +114,30 @@ function bashunit::fail() {
bashunit::console_results::print_failure_message "${label}" "$message"
}

_BASHUNIT_ASSERT_EXIT_DESC_OUT=""

##
# Describes a failing exit code for assert_true / assert_false. 127 and 126 are
# the two codes the shell reserves for "I could not run this at all", and a bare
# number tells the reader nothing: the most natural shell idiom,
# `assert_true "[ -d /tmp ]"`, hits 127 because the argument is run as a command
# word rather than evaluated, and the failure used to point nowhere near the
# cause.
#
# The wording deliberately avoids the literal phrase "command not found".
# runner/diagnostics.sh classifies a test as a runtime error by scanning its
# output for that exact string, so using it here made every such failure report
# as both Failed and Error for the one cause.
# Arguments: $1 - exit code, $2 - the command as written
##
function bashunit::assert::_describe_exit_code() {
case "$1" in
127) _BASHUNIT_ASSERT_EXIT_DESC_OUT="unknown command: $2" ;;
126) _BASHUNIT_ASSERT_EXIT_DESC_OUT="not executable: $2" ;;
*) _BASHUNIT_ASSERT_EXIT_DESC_OUT="exit code: $1" ;;
esac
}

function assert_true() {
bashunit::assert::should_skip && return 0

Expand All@@ -140,7 +164,9 @@ function assert_true() {
local exit_code=$?

if [ "$exit_code" -ne 0 ]; then
bashunit::handle_bool_assertion_failure "command or function with zero exit code" "exit code: $exit_code"
bashunit::assert::_describe_exit_code "$exit_code" "$actual"
bashunit::handle_bool_assertion_failure \
"command or function with zero exit code" "$_BASHUNIT_ASSERT_EXIT_DESC_OUT"
else
bashunit::state::add_assertions_passed
fi
Expand DownExpand Up@@ -171,11 +197,17 @@ function assert_false() {
bashunit::run_command_or_eval "$actual"
local exit_code=$?

if [ "$exit_code" -eq 0 ]; then
bashunit::handle_bool_assertion_failure "command or function with non-zero exit code" "exit code: $exit_code"
else
bashunit::state::add_assertions_passed
fi
# 127/126 mean the command never ran. Treating "did not run" as "returned
# false" let a typo in the command name satisfy this assertion while testing
# nothing, so those are failures here as well as in assert_true.
case "$exit_code" in
0 | 126 | 127)
bashunit::assert::_describe_exit_code "$exit_code" "$actual"
bashunit::handle_bool_assertion_failure \
"command or function with non-zero exit code" "$_BASHUNIT_ASSERT_EXIT_DESC_OUT"
;;
*) bashunit::state::add_assertions_passed ;;
esac
}

function bashunit::run_command_or_eval() {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,12 @@
--------------
> `assert_true bool|function|command`

Takes a **command**, not a test expression. A bracketed condition of the form
used inside `if` is run as a command word, so the shell reports exit code 127 and
the assertion fails with `unknown command`. Use the `test` builtin instead —
`assert_true "test -d /tmp"` — or a purpose-built assertion such as
`assert_directory_exists`.

Reports an error if the argument result in a truthy value: `true` or `0`.

- assert_false is similar but different.
Expand Down
34 changes: 34 additions & 0 deletions tests/unit/assert/basic_test.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -183,3 +183,37 @@ function test_assert_not_same_with_custom_label() {
"$(bashunit::console_results::print_failed_test "my custom label" "foo" "to not be" "foo")" \
"$(assert_not_same "foo" "foo" "my custom label")"
}

# Exit code 127 is the shell's not-found code. Reporting the bare number
# gives the reader nothing to act on -- and the most natural shell idiom,
# a `[ ... ]` test expression, produces exactly this because assert_true runs
# its argument as a command word rather than evaluating it.
function test_assert_true_reports_a_missing_command_by_name() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert true reports a missing command by name" \
"command or function with zero exit code" "but got " \
"unknown command: definitely_not_a_command")" \
"$(assert_true "definitely_not_a_command")"
}

function test_assert_true_reports_a_bracket_expression_as_not_found() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert true reports a bracket expression as not found" \
"command or function with zero exit code" "but got " \
"unknown command: [ -d /tmp ]")" \
"$(assert_true "[ -d /tmp ]")"
}

# assert_false only failed when the exit code was 0, so a command that does not
# exist satisfied it: 127 is non-zero, therefore "false". A typo in the command
# name made the assertion pass while testing nothing.
function test_assert_false_fails_when_the_command_does_not_exist() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert false fails when the command does not exist" \
"command or function with non-zero exit code" "but got " \
"unknown command: definitely_not_a_command")" \
"$(assert_false "definitely_not_a_command")"
}
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('^' + ".*" + ' feat(assert): name the problem instead of printing exit code 127 by Chemaclass · Pull Request #991 · 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
- Named snapshot assertions support multiple snapshots per test; mismatches show the resolved path and `--snapshot-update` hint (#986)

### Changed
- `assert_true` / `assert_false` name the problem instead of printing a bare number: exit code 127 now reports `unknown command: <arg>` and 126 `not executable: <arg>`. The most common cause is passing a test expression like `[ -d /tmp ]`, which is run as a command word
- Core comparison assertions report missing required arguments as usage errors instead of comparing against empty values (#983)
- Performance: Literal snapshots bypass placeholder regex processing unless they contain a placeholder (about 13x faster) (#985)
- Performance: `assert_within_delta` uses fixed-point arithmetic for common values, with a `bc`/`awk` fallback for unsupported inputs (about 6.6x faster) (#979)
Expand All@@ -14,6 +15,7 @@
- Internal: Split `src/runner.sh` and `src/coverage.sh` into focused modules with no behavior change; see [ADR-010](adrs/adr-010-src-module-directories.md) (#924, #925)

### Fixed
- `assert_false` no longer passes when the command does not exist. Exit code 127 is non-zero, so a typo in the command name satisfied the assertion while testing nothing; 127 and 126 now fail both `assert_true` and `assert_false`, because they mean the command never ran
- `assert_within_delta` accepts a leading `+` on any operand (#979)
- Invalid `BASHUNIT_SHARD_INDEX` / `BASHUNIT_SHARD_TOTAL` values now fail with a clear error instead of reaching raw arithmetic or reporting no tests
- Date assertions reject unparseable values instead of crashing or treating them as epoch 0
Expand Down
6 changes: 6 additions & 0 deletions docs/assertions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,6 +36,12 @@ to narrow it (`bashunit doc json`).
## assert_true
> `assert_true bool|function|command`

Takes a **command**, not a test expression. A bracketed condition of the form
used inside `if` is run as a command word, so the shell reports exit code 127 and
the assertion fails with `unknown command`. Use the `test` builtin instead —
`assert_true "test -d /tmp"` — or a purpose-built assertion such as
`assert_directory_exists`.

Reports an error if the argument result in a truthy value: `true` or `0`.

- [assert_false](#assert-false) is similar but different.
Expand Down
44 changes: 38 additions & 6 deletions src/assert/core.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -114,6 +114,30 @@ function bashunit::fail() {
bashunit::console_results::print_failure_message "${label}" "$message"
}

_BASHUNIT_ASSERT_EXIT_DESC_OUT=""

##
# Describes a failing exit code for assert_true / assert_false. 127 and 126 are
# the two codes the shell reserves for "I could not run this at all", and a bare
# number tells the reader nothing: the most natural shell idiom,
# `assert_true "[ -d /tmp ]"`, hits 127 because the argument is run as a command
# word rather than evaluated, and the failure used to point nowhere near the
# cause.
#
# The wording deliberately avoids the literal phrase "command not found".
# runner/diagnostics.sh classifies a test as a runtime error by scanning its
# output for that exact string, so using it here made every such failure report
# as both Failed and Error for the one cause.
# Arguments: $1 - exit code, $2 - the command as written
##
function bashunit::assert::_describe_exit_code() {
case "$1" in
127) _BASHUNIT_ASSERT_EXIT_DESC_OUT="unknown command: $2" ;;
126) _BASHUNIT_ASSERT_EXIT_DESC_OUT="not executable: $2" ;;
*) _BASHUNIT_ASSERT_EXIT_DESC_OUT="exit code: $1" ;;
esac
}

function assert_true() {
bashunit::assert::should_skip && return 0

Expand All@@ -140,7 +164,9 @@ function assert_true() {
local exit_code=$?

if [ "$exit_code" -ne 0 ]; then
bashunit::handle_bool_assertion_failure "command or function with zero exit code" "exit code: $exit_code"
bashunit::assert::_describe_exit_code "$exit_code" "$actual"
bashunit::handle_bool_assertion_failure \
"command or function with zero exit code" "$_BASHUNIT_ASSERT_EXIT_DESC_OUT"
else
bashunit::state::add_assertions_passed
fi
Expand DownExpand Up@@ -171,11 +197,17 @@ function assert_false() {
bashunit::run_command_or_eval "$actual"
local exit_code=$?

if [ "$exit_code" -eq 0 ]; then
bashunit::handle_bool_assertion_failure "command or function with non-zero exit code" "exit code: $exit_code"
else
bashunit::state::add_assertions_passed
fi
# 127/126 mean the command never ran. Treating "did not run" as "returned
# false" let a typo in the command name satisfy this assertion while testing
# nothing, so those are failures here as well as in assert_true.
case "$exit_code" in
0 | 126 | 127)
bashunit::assert::_describe_exit_code "$exit_code" "$actual"
bashunit::handle_bool_assertion_failure \
"command or function with non-zero exit code" "$_BASHUNIT_ASSERT_EXIT_DESC_OUT"
;;
*) bashunit::state::add_assertions_passed ;;
esac
}

function bashunit::run_command_or_eval() {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,12 @@
--------------
> `assert_true bool|function|command`

Takes a **command**, not a test expression. A bracketed condition of the form
used inside `if` is run as a command word, so the shell reports exit code 127 and
the assertion fails with `unknown command`. Use the `test` builtin instead —
`assert_true "test -d /tmp"` — or a purpose-built assertion such as
`assert_directory_exists`.

Reports an error if the argument result in a truthy value: `true` or `0`.

- assert_false is similar but different.
Expand Down
34 changes: 34 additions & 0 deletions tests/unit/assert/basic_test.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -183,3 +183,37 @@ function test_assert_not_same_with_custom_label() {
"$(bashunit::console_results::print_failed_test "my custom label" "foo" "to not be" "foo")" \
"$(assert_not_same "foo" "foo" "my custom label")"
}

# Exit code 127 is the shell's not-found code. Reporting the bare number
# gives the reader nothing to act on -- and the most natural shell idiom,
# a `[ ... ]` test expression, produces exactly this because assert_true runs
# its argument as a command word rather than evaluating it.
function test_assert_true_reports_a_missing_command_by_name() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert true reports a missing command by name" \
"command or function with zero exit code" "but got " \
"unknown command: definitely_not_a_command")" \
"$(assert_true "definitely_not_a_command")"
}

function test_assert_true_reports_a_bracket_expression_as_not_found() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert true reports a bracket expression as not found" \
"command or function with zero exit code" "but got " \
"unknown command: [ -d /tmp ]")" \
"$(assert_true "[ -d /tmp ]")"
}

# assert_false only failed when the exit code was 0, so a command that does not
# exist satisfied it: 127 is non-zero, therefore "false". A typo in the command
# name made the assertion pass while testing nothing.
function test_assert_false_fails_when_the_command_does_not_exist() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert false fails when the command does not exist" \
"command or function with non-zero exit code" "but got " \
"unknown command: definitely_not_a_command")" \
"$(assert_false "definitely_not_a_command")"
}
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" + ' feat(assert): name the problem instead of printing exit code 127 by Chemaclass · Pull Request #991 · 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
- Named snapshot assertions support multiple snapshots per test; mismatches show the resolved path and `--snapshot-update` hint (#986)

### Changed
- `assert_true` / `assert_false` name the problem instead of printing a bare number: exit code 127 now reports `unknown command: <arg>` and 126 `not executable: <arg>`. The most common cause is passing a test expression like `[ -d /tmp ]`, which is run as a command word
- Core comparison assertions report missing required arguments as usage errors instead of comparing against empty values (#983)
- Performance: Literal snapshots bypass placeholder regex processing unless they contain a placeholder (about 13x faster) (#985)
- Performance: `assert_within_delta` uses fixed-point arithmetic for common values, with a `bc`/`awk` fallback for unsupported inputs (about 6.6x faster) (#979)
Expand All@@ -14,6 +15,7 @@
- Internal: Split `src/runner.sh` and `src/coverage.sh` into focused modules with no behavior change; see [ADR-010](adrs/adr-010-src-module-directories.md) (#924, #925)

### Fixed
- `assert_false` no longer passes when the command does not exist. Exit code 127 is non-zero, so a typo in the command name satisfied the assertion while testing nothing; 127 and 126 now fail both `assert_true` and `assert_false`, because they mean the command never ran
- `assert_within_delta` accepts a leading `+` on any operand (#979)
- Invalid `BASHUNIT_SHARD_INDEX` / `BASHUNIT_SHARD_TOTAL` values now fail with a clear error instead of reaching raw arithmetic or reporting no tests
- Date assertions reject unparseable values instead of crashing or treating them as epoch 0
Expand Down
6 changes: 6 additions & 0 deletions docs/assertions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,6 +36,12 @@ to narrow it (`bashunit doc json`).
## assert_true
> `assert_true bool|function|command`

Takes a **command**, not a test expression. A bracketed condition of the form
used inside `if` is run as a command word, so the shell reports exit code 127 and
the assertion fails with `unknown command`. Use the `test` builtin instead —
`assert_true "test -d /tmp"` — or a purpose-built assertion such as
`assert_directory_exists`.

Reports an error if the argument result in a truthy value: `true` or `0`.

- [assert_false](#assert-false) is similar but different.
Expand Down
44 changes: 38 additions & 6 deletions src/assert/core.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -114,6 +114,30 @@ function bashunit::fail() {
bashunit::console_results::print_failure_message "${label}" "$message"
}

_BASHUNIT_ASSERT_EXIT_DESC_OUT=""

##
# Describes a failing exit code for assert_true / assert_false. 127 and 126 are
# the two codes the shell reserves for "I could not run this at all", and a bare
# number tells the reader nothing: the most natural shell idiom,
# `assert_true "[ -d /tmp ]"`, hits 127 because the argument is run as a command
# word rather than evaluated, and the failure used to point nowhere near the
# cause.
#
# The wording deliberately avoids the literal phrase "command not found".
# runner/diagnostics.sh classifies a test as a runtime error by scanning its
# output for that exact string, so using it here made every such failure report
# as both Failed and Error for the one cause.
# Arguments: $1 - exit code, $2 - the command as written
##
function bashunit::assert::_describe_exit_code() {
case "$1" in
127) _BASHUNIT_ASSERT_EXIT_DESC_OUT="unknown command: $2" ;;
126) _BASHUNIT_ASSERT_EXIT_DESC_OUT="not executable: $2" ;;
*) _BASHUNIT_ASSERT_EXIT_DESC_OUT="exit code: $1" ;;
esac
}

function assert_true() {
bashunit::assert::should_skip && return 0

Expand All@@ -140,7 +164,9 @@ function assert_true() {
local exit_code=$?

if [ "$exit_code" -ne 0 ]; then
bashunit::handle_bool_assertion_failure "command or function with zero exit code" "exit code: $exit_code"
bashunit::assert::_describe_exit_code "$exit_code" "$actual"
bashunit::handle_bool_assertion_failure \
"command or function with zero exit code" "$_BASHUNIT_ASSERT_EXIT_DESC_OUT"
else
bashunit::state::add_assertions_passed
fi
Expand DownExpand Up@@ -171,11 +197,17 @@ function assert_false() {
bashunit::run_command_or_eval "$actual"
local exit_code=$?

if [ "$exit_code" -eq 0 ]; then
bashunit::handle_bool_assertion_failure "command or function with non-zero exit code" "exit code: $exit_code"
else
bashunit::state::add_assertions_passed
fi
# 127/126 mean the command never ran. Treating "did not run" as "returned
# false" let a typo in the command name satisfy this assertion while testing
# nothing, so those are failures here as well as in assert_true.
case "$exit_code" in
0 | 126 | 127)
bashunit::assert::_describe_exit_code "$exit_code" "$actual"
bashunit::handle_bool_assertion_failure \
"command or function with non-zero exit code" "$_BASHUNIT_ASSERT_EXIT_DESC_OUT"
;;
*) bashunit::state::add_assertions_passed ;;
esac
}

function bashunit::run_command_or_eval() {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,12 @@
--------------
> `assert_true bool|function|command`

Takes a **command**, not a test expression. A bracketed condition of the form
used inside `if` is run as a command word, so the shell reports exit code 127 and
the assertion fails with `unknown command`. Use the `test` builtin instead —
`assert_true "test -d /tmp"` — or a purpose-built assertion such as
`assert_directory_exists`.

Reports an error if the argument result in a truthy value: `true` or `0`.

- assert_false is similar but different.
Expand Down
34 changes: 34 additions & 0 deletions tests/unit/assert/basic_test.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -183,3 +183,37 @@ function test_assert_not_same_with_custom_label() {
"$(bashunit::console_results::print_failed_test "my custom label" "foo" "to not be" "foo")" \
"$(assert_not_same "foo" "foo" "my custom label")"
}

# Exit code 127 is the shell's not-found code. Reporting the bare number
# gives the reader nothing to act on -- and the most natural shell idiom,
# a `[ ... ]` test expression, produces exactly this because assert_true runs
# its argument as a command word rather than evaluating it.
function test_assert_true_reports_a_missing_command_by_name() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert true reports a missing command by name" \
"command or function with zero exit code" "but got " \
"unknown command: definitely_not_a_command")" \
"$(assert_true "definitely_not_a_command")"
}

function test_assert_true_reports_a_bracket_expression_as_not_found() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert true reports a bracket expression as not found" \
"command or function with zero exit code" "but got " \
"unknown command: [ -d /tmp ]")" \
"$(assert_true "[ -d /tmp ]")"
}

# assert_false only failed when the exit code was 0, so a command that does not
# exist satisfied it: 127 is non-zero, therefore "false". A typo in the command
# name made the assertion pass while testing nothing.
function test_assert_false_fails_when_the_command_does_not_exist() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert false fails when the command does not exist" \
"command or function with non-zero exit code" "but got " \
"unknown command: definitely_not_a_command")" \
"$(assert_false "definitely_not_a_command")"
}
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('^' + ".*" + ' feat(assert): name the problem instead of printing exit code 127 by Chemaclass · Pull Request #991 · 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
- Named snapshot assertions support multiple snapshots per test; mismatches show the resolved path and `--snapshot-update` hint (#986)

### Changed
- `assert_true` / `assert_false` name the problem instead of printing a bare number: exit code 127 now reports `unknown command: <arg>` and 126 `not executable: <arg>`. The most common cause is passing a test expression like `[ -d /tmp ]`, which is run as a command word
- Core comparison assertions report missing required arguments as usage errors instead of comparing against empty values (#983)
- Performance: Literal snapshots bypass placeholder regex processing unless they contain a placeholder (about 13x faster) (#985)
- Performance: `assert_within_delta` uses fixed-point arithmetic for common values, with a `bc`/`awk` fallback for unsupported inputs (about 6.6x faster) (#979)
Expand All@@ -14,6 +15,7 @@
- Internal: Split `src/runner.sh` and `src/coverage.sh` into focused modules with no behavior change; see [ADR-010](adrs/adr-010-src-module-directories.md) (#924, #925)

### Fixed
- `assert_false` no longer passes when the command does not exist. Exit code 127 is non-zero, so a typo in the command name satisfied the assertion while testing nothing; 127 and 126 now fail both `assert_true` and `assert_false`, because they mean the command never ran
- `assert_within_delta` accepts a leading `+` on any operand (#979)
- Invalid `BASHUNIT_SHARD_INDEX` / `BASHUNIT_SHARD_TOTAL` values now fail with a clear error instead of reaching raw arithmetic or reporting no tests
- Date assertions reject unparseable values instead of crashing or treating them as epoch 0
Expand Down
6 changes: 6 additions & 0 deletions docs/assertions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,6 +36,12 @@ to narrow it (`bashunit doc json`).
## assert_true
> `assert_true bool|function|command`

Takes a **command**, not a test expression. A bracketed condition of the form
used inside `if` is run as a command word, so the shell reports exit code 127 and
the assertion fails with `unknown command`. Use the `test` builtin instead —
`assert_true "test -d /tmp"` — or a purpose-built assertion such as
`assert_directory_exists`.

Reports an error if the argument result in a truthy value: `true` or `0`.

- [assert_false](#assert-false) is similar but different.
Expand Down
44 changes: 38 additions & 6 deletions src/assert/core.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -114,6 +114,30 @@ function bashunit::fail() {
bashunit::console_results::print_failure_message "${label}" "$message"
}

_BASHUNIT_ASSERT_EXIT_DESC_OUT=""

##
# Describes a failing exit code for assert_true / assert_false. 127 and 126 are
# the two codes the shell reserves for "I could not run this at all", and a bare
# number tells the reader nothing: the most natural shell idiom,
# `assert_true "[ -d /tmp ]"`, hits 127 because the argument is run as a command
# word rather than evaluated, and the failure used to point nowhere near the
# cause.
#
# The wording deliberately avoids the literal phrase "command not found".
# runner/diagnostics.sh classifies a test as a runtime error by scanning its
# output for that exact string, so using it here made every such failure report
# as both Failed and Error for the one cause.
# Arguments: $1 - exit code, $2 - the command as written
##
function bashunit::assert::_describe_exit_code() {
case "$1" in
127) _BASHUNIT_ASSERT_EXIT_DESC_OUT="unknown command: $2" ;;
126) _BASHUNIT_ASSERT_EXIT_DESC_OUT="not executable: $2" ;;
*) _BASHUNIT_ASSERT_EXIT_DESC_OUT="exit code: $1" ;;
esac
}

function assert_true() {
bashunit::assert::should_skip && return 0

Expand All@@ -140,7 +164,9 @@ function assert_true() {
local exit_code=$?

if [ "$exit_code" -ne 0 ]; then
bashunit::handle_bool_assertion_failure "command or function with zero exit code" "exit code: $exit_code"
bashunit::assert::_describe_exit_code "$exit_code" "$actual"
bashunit::handle_bool_assertion_failure \
"command or function with zero exit code" "$_BASHUNIT_ASSERT_EXIT_DESC_OUT"
else
bashunit::state::add_assertions_passed
fi
Expand DownExpand Up@@ -171,11 +197,17 @@ function assert_false() {
bashunit::run_command_or_eval "$actual"
local exit_code=$?

if [ "$exit_code" -eq 0 ]; then
bashunit::handle_bool_assertion_failure "command or function with non-zero exit code" "exit code: $exit_code"
else
bashunit::state::add_assertions_passed
fi
# 127/126 mean the command never ran. Treating "did not run" as "returned
# false" let a typo in the command name satisfy this assertion while testing
# nothing, so those are failures here as well as in assert_true.
case "$exit_code" in
0 | 126 | 127)
bashunit::assert::_describe_exit_code "$exit_code" "$actual"
bashunit::handle_bool_assertion_failure \
"command or function with non-zero exit code" "$_BASHUNIT_ASSERT_EXIT_DESC_OUT"
;;
*) bashunit::state::add_assertions_passed ;;
esac
}

function bashunit::run_command_or_eval() {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,12 @@
--------------
> `assert_true bool|function|command`

Takes a **command**, not a test expression. A bracketed condition of the form
used inside `if` is run as a command word, so the shell reports exit code 127 and
the assertion fails with `unknown command`. Use the `test` builtin instead —
`assert_true "test -d /tmp"` — or a purpose-built assertion such as
`assert_directory_exists`.

Reports an error if the argument result in a truthy value: `true` or `0`.

- assert_false is similar but different.
Expand Down
34 changes: 34 additions & 0 deletions tests/unit/assert/basic_test.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -183,3 +183,37 @@ function test_assert_not_same_with_custom_label() {
"$(bashunit::console_results::print_failed_test "my custom label" "foo" "to not be" "foo")" \
"$(assert_not_same "foo" "foo" "my custom label")"
}

# Exit code 127 is the shell's not-found code. Reporting the bare number
# gives the reader nothing to act on -- and the most natural shell idiom,
# a `[ ... ]` test expression, produces exactly this because assert_true runs
# its argument as a command word rather than evaluating it.
function test_assert_true_reports_a_missing_command_by_name() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert true reports a missing command by name" \
"command or function with zero exit code" "but got " \
"unknown command: definitely_not_a_command")" \
"$(assert_true "definitely_not_a_command")"
}

function test_assert_true_reports_a_bracket_expression_as_not_found() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert true reports a bracket expression as not found" \
"command or function with zero exit code" "but got " \
"unknown command: [ -d /tmp ]")" \
"$(assert_true "[ -d /tmp ]")"
}

# assert_false only failed when the exit code was 0, so a command that does not
# exist satisfied it: 127 is non-zero, therefore "false". A typo in the command
# name made the assertion pass while testing nothing.
function test_assert_false_fails_when_the_command_does_not_exist() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert false fails when the command does not exist" \
"command or function with non-zero exit code" "but got " \
"unknown command: definitely_not_a_command")" \
"$(assert_false "definitely_not_a_command")"
}
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('^' + ".*" + ' feat(assert): name the problem instead of printing exit code 127 by Chemaclass · Pull Request #991 · 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
- Named snapshot assertions support multiple snapshots per test; mismatches show the resolved path and `--snapshot-update` hint (#986)

### Changed
- `assert_true` / `assert_false` name the problem instead of printing a bare number: exit code 127 now reports `unknown command: <arg>` and 126 `not executable: <arg>`. The most common cause is passing a test expression like `[ -d /tmp ]`, which is run as a command word
- Core comparison assertions report missing required arguments as usage errors instead of comparing against empty values (#983)
- Performance: Literal snapshots bypass placeholder regex processing unless they contain a placeholder (about 13x faster) (#985)
- Performance: `assert_within_delta` uses fixed-point arithmetic for common values, with a `bc`/`awk` fallback for unsupported inputs (about 6.6x faster) (#979)
Expand All@@ -14,6 +15,7 @@
- Internal: Split `src/runner.sh` and `src/coverage.sh` into focused modules with no behavior change; see [ADR-010](adrs/adr-010-src-module-directories.md) (#924, #925)

### Fixed
- `assert_false` no longer passes when the command does not exist. Exit code 127 is non-zero, so a typo in the command name satisfied the assertion while testing nothing; 127 and 126 now fail both `assert_true` and `assert_false`, because they mean the command never ran
- `assert_within_delta` accepts a leading `+` on any operand (#979)
- Invalid `BASHUNIT_SHARD_INDEX` / `BASHUNIT_SHARD_TOTAL` values now fail with a clear error instead of reaching raw arithmetic or reporting no tests
- Date assertions reject unparseable values instead of crashing or treating them as epoch 0
Expand Down
6 changes: 6 additions & 0 deletions docs/assertions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,6 +36,12 @@ to narrow it (`bashunit doc json`).
## assert_true
> `assert_true bool|function|command`

Takes a **command**, not a test expression. A bracketed condition of the form
used inside `if` is run as a command word, so the shell reports exit code 127 and
the assertion fails with `unknown command`. Use the `test` builtin instead —
`assert_true "test -d /tmp"` — or a purpose-built assertion such as
`assert_directory_exists`.

Reports an error if the argument result in a truthy value: `true` or `0`.

- [assert_false](#assert-false) is similar but different.
Expand Down
44 changes: 38 additions & 6 deletions src/assert/core.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -114,6 +114,30 @@ function bashunit::fail() {
bashunit::console_results::print_failure_message "${label}" "$message"
}

_BASHUNIT_ASSERT_EXIT_DESC_OUT=""

##
# Describes a failing exit code for assert_true / assert_false. 127 and 126 are
# the two codes the shell reserves for "I could not run this at all", and a bare
# number tells the reader nothing: the most natural shell idiom,
# `assert_true "[ -d /tmp ]"`, hits 127 because the argument is run as a command
# word rather than evaluated, and the failure used to point nowhere near the
# cause.
#
# The wording deliberately avoids the literal phrase "command not found".
# runner/diagnostics.sh classifies a test as a runtime error by scanning its
# output for that exact string, so using it here made every such failure report
# as both Failed and Error for the one cause.
# Arguments: $1 - exit code, $2 - the command as written
##
function bashunit::assert::_describe_exit_code() {
case "$1" in
127) _BASHUNIT_ASSERT_EXIT_DESC_OUT="unknown command: $2" ;;
126) _BASHUNIT_ASSERT_EXIT_DESC_OUT="not executable: $2" ;;
*) _BASHUNIT_ASSERT_EXIT_DESC_OUT="exit code: $1" ;;
esac
}

function assert_true() {
bashunit::assert::should_skip && return 0

Expand All@@ -140,7 +164,9 @@ function assert_true() {
local exit_code=$?

if [ "$exit_code" -ne 0 ]; then
bashunit::handle_bool_assertion_failure "command or function with zero exit code" "exit code: $exit_code"
bashunit::assert::_describe_exit_code "$exit_code" "$actual"
bashunit::handle_bool_assertion_failure \
"command or function with zero exit code" "$_BASHUNIT_ASSERT_EXIT_DESC_OUT"
else
bashunit::state::add_assertions_passed
fi
Expand DownExpand Up@@ -171,11 +197,17 @@ function assert_false() {
bashunit::run_command_or_eval "$actual"
local exit_code=$?

if [ "$exit_code" -eq 0 ]; then
bashunit::handle_bool_assertion_failure "command or function with non-zero exit code" "exit code: $exit_code"
else
bashunit::state::add_assertions_passed
fi
# 127/126 mean the command never ran. Treating "did not run" as "returned
# false" let a typo in the command name satisfy this assertion while testing
# nothing, so those are failures here as well as in assert_true.
case "$exit_code" in
0 | 126 | 127)
bashunit::assert::_describe_exit_code "$exit_code" "$actual"
bashunit::handle_bool_assertion_failure \
"command or function with non-zero exit code" "$_BASHUNIT_ASSERT_EXIT_DESC_OUT"
;;
*) bashunit::state::add_assertions_passed ;;
esac
}

function bashunit::run_command_or_eval() {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,12 @@
--------------
> `assert_true bool|function|command`

Takes a **command**, not a test expression. A bracketed condition of the form
used inside `if` is run as a command word, so the shell reports exit code 127 and
the assertion fails with `unknown command`. Use the `test` builtin instead —
`assert_true "test -d /tmp"` — or a purpose-built assertion such as
`assert_directory_exists`.

Reports an error if the argument result in a truthy value: `true` or `0`.

- assert_false is similar but different.
Expand Down
34 changes: 34 additions & 0 deletions tests/unit/assert/basic_test.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -183,3 +183,37 @@ function test_assert_not_same_with_custom_label() {
"$(bashunit::console_results::print_failed_test "my custom label" "foo" "to not be" "foo")" \
"$(assert_not_same "foo" "foo" "my custom label")"
}

# Exit code 127 is the shell's not-found code. Reporting the bare number
# gives the reader nothing to act on -- and the most natural shell idiom,
# a `[ ... ]` test expression, produces exactly this because assert_true runs
# its argument as a command word rather than evaluating it.
function test_assert_true_reports_a_missing_command_by_name() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert true reports a missing command by name" \
"command or function with zero exit code" "but got " \
"unknown command: definitely_not_a_command")" \
"$(assert_true "definitely_not_a_command")"
}

function test_assert_true_reports_a_bracket_expression_as_not_found() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert true reports a bracket expression as not found" \
"command or function with zero exit code" "but got " \
"unknown command: [ -d /tmp ]")" \
"$(assert_true "[ -d /tmp ]")"
}

# assert_false only failed when the exit code was 0, so a command that does not
# exist satisfied it: 127 is non-zero, therefore "false". A typo in the command
# name made the assertion pass while testing nothing.
function test_assert_false_fails_when_the_command_does_not_exist() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert false fails when the command does not exist" \
"command or function with non-zero exit code" "but got " \
"unknown command: definitely_not_a_command")" \
"$(assert_false "definitely_not_a_command")"
}
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); } })(); })(); feat(assert): name the problem instead of printing exit code 127 by Chemaclass · Pull Request #991 · 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
- Named snapshot assertions support multiple snapshots per test; mismatches show the resolved path and `--snapshot-update` hint (#986)

### Changed
- `assert_true` / `assert_false` name the problem instead of printing a bare number: exit code 127 now reports `unknown command: <arg>` and 126 `not executable: <arg>`. The most common cause is passing a test expression like `[ -d /tmp ]`, which is run as a command word
- Core comparison assertions report missing required arguments as usage errors instead of comparing against empty values (#983)
- Performance: Literal snapshots bypass placeholder regex processing unless they contain a placeholder (about 13x faster) (#985)
- Performance: `assert_within_delta` uses fixed-point arithmetic for common values, with a `bc`/`awk` fallback for unsupported inputs (about 6.6x faster) (#979)
Expand All@@ -14,6 +15,7 @@
- Internal: Split `src/runner.sh` and `src/coverage.sh` into focused modules with no behavior change; see [ADR-010](adrs/adr-010-src-module-directories.md) (#924, #925)

### Fixed
- `assert_false` no longer passes when the command does not exist. Exit code 127 is non-zero, so a typo in the command name satisfied the assertion while testing nothing; 127 and 126 now fail both `assert_true` and `assert_false`, because they mean the command never ran
- `assert_within_delta` accepts a leading `+` on any operand (#979)
- Invalid `BASHUNIT_SHARD_INDEX` / `BASHUNIT_SHARD_TOTAL` values now fail with a clear error instead of reaching raw arithmetic or reporting no tests
- Date assertions reject unparseable values instead of crashing or treating them as epoch 0
Expand Down
6 changes: 6 additions & 0 deletions docs/assertions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,6 +36,12 @@ to narrow it (`bashunit doc json`).
## assert_true
> `assert_true bool|function|command`

Takes a **command**, not a test expression. A bracketed condition of the form
used inside `if` is run as a command word, so the shell reports exit code 127 and
the assertion fails with `unknown command`. Use the `test` builtin instead —
`assert_true "test -d /tmp"` — or a purpose-built assertion such as
`assert_directory_exists`.

Reports an error if the argument result in a truthy value: `true` or `0`.

- [assert_false](#assert-false) is similar but different.
Expand Down
44 changes: 38 additions & 6 deletions src/assert/core.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -114,6 +114,30 @@ function bashunit::fail() {
bashunit::console_results::print_failure_message "${label}" "$message"
}

_BASHUNIT_ASSERT_EXIT_DESC_OUT=""

##
# Describes a failing exit code for assert_true / assert_false. 127 and 126 are
# the two codes the shell reserves for "I could not run this at all", and a bare
# number tells the reader nothing: the most natural shell idiom,
# `assert_true "[ -d /tmp ]"`, hits 127 because the argument is run as a command
# word rather than evaluated, and the failure used to point nowhere near the
# cause.
#
# The wording deliberately avoids the literal phrase "command not found".
# runner/diagnostics.sh classifies a test as a runtime error by scanning its
# output for that exact string, so using it here made every such failure report
# as both Failed and Error for the one cause.
# Arguments: $1 - exit code, $2 - the command as written
##
function bashunit::assert::_describe_exit_code() {
case "$1" in
127) _BASHUNIT_ASSERT_EXIT_DESC_OUT="unknown command: $2" ;;
126) _BASHUNIT_ASSERT_EXIT_DESC_OUT="not executable: $2" ;;
*) _BASHUNIT_ASSERT_EXIT_DESC_OUT="exit code: $1" ;;
esac
}

function assert_true() {
bashunit::assert::should_skip && return 0

Expand All@@ -140,7 +164,9 @@ function assert_true() {
local exit_code=$?

if [ "$exit_code" -ne 0 ]; then
bashunit::handle_bool_assertion_failure "command or function with zero exit code" "exit code: $exit_code"
bashunit::assert::_describe_exit_code "$exit_code" "$actual"
bashunit::handle_bool_assertion_failure \
"command or function with zero exit code" "$_BASHUNIT_ASSERT_EXIT_DESC_OUT"
else
bashunit::state::add_assertions_passed
fi
Expand DownExpand Up@@ -171,11 +197,17 @@ function assert_false() {
bashunit::run_command_or_eval "$actual"
local exit_code=$?

if [ "$exit_code" -eq 0 ]; then
bashunit::handle_bool_assertion_failure "command or function with non-zero exit code" "exit code: $exit_code"
else
bashunit::state::add_assertions_passed
fi
# 127/126 mean the command never ran. Treating "did not run" as "returned
# false" let a typo in the command name satisfy this assertion while testing
# nothing, so those are failures here as well as in assert_true.
case "$exit_code" in
0 | 126 | 127)
bashunit::assert::_describe_exit_code "$exit_code" "$actual"
bashunit::handle_bool_assertion_failure \
"command or function with non-zero exit code" "$_BASHUNIT_ASSERT_EXIT_DESC_OUT"
;;
*) bashunit::state::add_assertions_passed ;;
esac
}

function bashunit::run_command_or_eval() {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,12 @@
--------------
> `assert_true bool|function|command`

Takes a **command**, not a test expression. A bracketed condition of the form
used inside `if` is run as a command word, so the shell reports exit code 127 and
the assertion fails with `unknown command`. Use the `test` builtin instead —
`assert_true "test -d /tmp"` — or a purpose-built assertion such as
`assert_directory_exists`.

Reports an error if the argument result in a truthy value: `true` or `0`.

- assert_false is similar but different.
Expand Down
34 changes: 34 additions & 0 deletions tests/unit/assert/basic_test.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -183,3 +183,37 @@ function test_assert_not_same_with_custom_label() {
"$(bashunit::console_results::print_failed_test "my custom label" "foo" "to not be" "foo")" \
"$(assert_not_same "foo" "foo" "my custom label")"
}

# Exit code 127 is the shell's not-found code. Reporting the bare number
# gives the reader nothing to act on -- and the most natural shell idiom,
# a `[ ... ]` test expression, produces exactly this because assert_true runs
# its argument as a command word rather than evaluating it.
function test_assert_true_reports_a_missing_command_by_name() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert true reports a missing command by name" \
"command or function with zero exit code" "but got " \
"unknown command: definitely_not_a_command")" \
"$(assert_true "definitely_not_a_command")"
}

function test_assert_true_reports_a_bracket_expression_as_not_found() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert true reports a bracket expression as not found" \
"command or function with zero exit code" "but got " \
"unknown command: [ -d /tmp ]")" \
"$(assert_true "[ -d /tmp ]")"
}

# assert_false only failed when the exit code was 0, so a command that does not
# exist satisfied it: 127 is non-zero, therefore "false". A typo in the command
# name made the assertion pass while testing nothing.
function test_assert_false_fails_when_the_command_does_not_exist() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Assert false fails when the command does not exist" \
"command or function with non-zero exit code" "but got " \
"unknown command: definitely_not_a_command")" \
"$(assert_false "definitely_not_a_command")"
}
Loading