Repository navigation
Description
Problem
assert_matches and assert_not_matches cannot take a pattern that starts with -. The pattern reaches grep as an option, and the outcome depends on the Bash version: a vacuous pass on Bash 5, an Error on Bash 3.2.
Reproduced on main 776997e9, macOS arm64:
function test_pattern_with_leading_dash_should_pass() {
assert_matches "-[0-9]+" "-5"
}
function test_pattern_with_leading_dash_should_fail() {
assert_matches "-x" "bar"
}
function test_negated_pattern_with_leading_dash_should_fail() {
assert_not_matches "-5" "-5"
}On Bash 5.3 all three tests pass: the two that should fail pass without matching anything. On Bash 3.2 all three are reported as Error with grep's usage text and [: : integer expression expected in the output, including the one that should pass.
Where
src/assert/core.sh:564, :566, :595, :596: "$GREP" -cE "$expected" with no -- or -e. grep rejects the pattern as an option and prints nothing, so $(... || true) is empty. [ "" -eq 0 ] and [ "" -gt 0 ] exit 2, which the if reads as false: the failure branch is skipped and the assertion counts as passed. The runtime error classifier catches the shell diagnostic on Bash 3.2 only.
assert_file_contains already handles this case with -e "$string" -- (#1108); the regex assertions were not covered.
Why it matters
Negative numbers, CLI flags and diff output all start with -. A test that asserts --help output matches -v, --verbose passes without checking anything on Bash 5.
Suggested change
Pass the pattern with -e "$expected" (or after --) in all four grep calls, and compare the count only when grep exited 0 or 1, so a grep usage error becomes an assertion failure instead of a pass.
Acceptance criteria
-
assert_matches "-[0-9]+" "-5"passes on Bash 3.2 and Bash 5. -
assert_matches "-x" "bar"andassert_not_matches "-5" "-5"fail with the standard "to match" / "to not match" block, on both versions. - A test covers the three cases and fails on the current code.
Proposed fix
In assert_matches (src/assert/core.sh:550) and assert_not_matches (:579), pass the pattern as an operand with -e and fail closed when grep cannot run. Replace the four "$GREP" -cE "$expected" calls with a small helper that leaves the count in a slot and returns 1 on a grep usage error (exit code 2), so the callers never compare an empty string. Public behaviour that must not change: ERE semantics, the newline-collapsed retry, and the to match / to not match failure blocks.
_BASHUNIT_ASSERT_GREP_COUNT_OUT=0
# Count of lines of $2 matching ERE $1; returns 1 when grep refused the pattern.
function bashunit::assert::_grep_count_to_slot() {
local count rc=0
count=$(printf '%s' "$2" | "$GREP" -cE -e "$1") || rc=$?
[ "$rc" -le 1 ] || return 1
_BASHUNIT_ASSERT_GREP_COUNT_OUT=${count:-0}
}In assert_matches: if the helper returns 1 for either form, fail with fail_with "" "${actual}" "to match" "${expected}" and return; otherwise keep the current logic on the slot. In assert_not_matches: a helper failure also fails the assertion (a pattern grep cannot read is not a proven non-match). assert_string_matches_format (:1357) already prefixes ^, so it is unaffected but may take -e for consistency. Bash 3.0: plain $(), [ ], no [[ =~ ]]. Docs: one sentence under assert_matches in docs/assertions.md that a pattern may start with -. CHANGELOG: one line under Unreleased, Fixed.
Agent brief
- Branch:
fix/1398-matches-leading-dashfrom freshorigin/main. Commit:fix(assert): accept a pattern that starts with a dash in assert_matches, bodyCloses #1398. - Files:
src/assert/core.sh; tests intests/unit/assert/string_test.shnext to the existingassert_matchestests (searchfunction test_.*assert_matches), copying theirassert_same "$(bashunit::console_results::print_failed_test ...)" "$(assert_matches ...)"shape. - RED first:
function test_successful_assert_matches_with_leading_dash_pattern() {
assert_empty "$(assert_matches "-[0-9]+" "-5")"
}
function test_unsuccessful_assert_matches_with_leading_dash_pattern() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Unsuccessful assert matches with leading dash pattern" "bar" "to match" "-x")" \
"$(assert_matches "-x" "bar")"
}
function test_unsuccessful_assert_not_matches_with_leading_dash_pattern() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Unsuccessful assert not matches with leading dash pattern" "-5" "to not match" "-5")" \
"$(assert_not_matches "-5" "-5")"
}Run only that file: ./bashunit tests/unit/assert/string_test.sh. Check both shells: /bin/bash ./bashunit tests/unit/assert/string_test.sh and the Homebrew Bash 5.
- Non-goals: no change to
assert_file_contains(already fixed in Bug inassert_file_containsandassert_equals#1108), toassert_contains, or to the format assertions' regex builder. - Gate, once, when the change is complete:
./bashunit tests/,./bashunit --parallel --simple --strict tests/,make sa,make lint.make lintis the formatting authority; nevershfmt -w; 2-space indent. - Done when: every acceptance box is checked, the gate is green, CHANGELOG and docs are updated as listed above, and a PR is open with the
prskill.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
fix(assert): accept regex patterns starting with -