| 1 | #!/bin/bash |
| 2 | # |
| 3 | # FERP Integration Test Suite |
| 4 | # Comprehensive tests for all command-line flags |
| 5 | # |
| 6 | # Usage: ./integration_test.sh [--compare-grep] [--verbose] [--filter PATTERN] |
| 7 | # |
| 8 | # Options: |
| 9 | # --compare-grep Compare ferp output against GNU grep |
| 10 | # --verbose Show detailed output for each test |
| 11 | # --filter PATTERN Only run tests matching PATTERN |
| 12 | # |
| 13 | |
| 14 | set -uo pipefail |
| 15 | # Note: not using -e because we want tests to continue on failure |
| 16 | |
| 17 | # Colors for output |
| 18 | RED='\033[0;31m' |
| 19 | GREEN='\033[0;32m' |
| 20 | YELLOW='\033[1;33m' |
| 21 | BLUE='\033[0;34m' |
| 22 | NC='\033[0m' # No Color |
| 23 | |
| 24 | # Test counters |
| 25 | TESTS_RUN=0 |
| 26 | TESTS_PASSED=0 |
| 27 | TESTS_FAILED=0 |
| 28 | TESTS_SKIPPED=0 |
| 29 | |
| 30 | # Options |
| 31 | COMPARE_GREP=false |
| 32 | VERBOSE=false |
| 33 | FILTER="" |
| 34 | |
| 35 | # Paths |
| 36 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 37 | FERP="${SCRIPT_DIR}/../ferp" |
| 38 | FIXTURES="${SCRIPT_DIR}/fixtures" |
| 39 | |
| 40 | # Parse arguments |
| 41 | while [[ $# -gt 0 ]]; do |
| 42 | case $1 in |
| 43 | --compare-grep) |
| 44 | COMPARE_GREP=true |
| 45 | shift |
| 46 | ;; |
| 47 | --verbose) |
| 48 | VERBOSE=true |
| 49 | shift |
| 50 | ;; |
| 51 | --filter) |
| 52 | FILTER="$2" |
| 53 | shift 2 |
| 54 | ;; |
| 55 | *) |
| 56 | echo "Unknown option: $1" |
| 57 | exit 1 |
| 58 | ;; |
| 59 | esac |
| 60 | done |
| 61 | |
| 62 | # Check ferp exists |
| 63 | if [[ ! -x "$FERP" ]]; then |
| 64 | echo -e "${RED}Error: ferp binary not found at $FERP${NC}" |
| 65 | echo "Run 'make' first to build ferp" |
| 66 | exit 1 |
| 67 | fi |
| 68 | |
| 69 | #------------------------------------------------------------------------------ |
| 70 | # Test Framework Functions |
| 71 | #------------------------------------------------------------------------------ |
| 72 | |
| 73 | log_test() { |
| 74 | local name="$1" |
| 75 | if [[ "$VERBOSE" == "true" ]]; then |
| 76 | echo -e "${BLUE}Running:${NC} $name" |
| 77 | fi |
| 78 | } |
| 79 | |
| 80 | pass() { |
| 81 | local name="$1" |
| 82 | ((TESTS_PASSED++)) |
| 83 | ((TESTS_RUN++)) |
| 84 | echo -e "${GREEN}PASS${NC}: $name" |
| 85 | } |
| 86 | |
| 87 | fail() { |
| 88 | local name="$1" |
| 89 | local reason="${2:-}" |
| 90 | ((TESTS_FAILED++)) |
| 91 | ((TESTS_RUN++)) |
| 92 | echo -e "${RED}FAIL${NC}: $name" |
| 93 | if [[ -n "$reason" ]]; then |
| 94 | echo -e " ${YELLOW}Reason:${NC} $reason" |
| 95 | fi |
| 96 | } |
| 97 | |
| 98 | skip() { |
| 99 | local name="$1" |
| 100 | local reason="${2:-}" |
| 101 | ((TESTS_SKIPPED++)) |
| 102 | echo -e "${YELLOW}SKIP${NC}: $name${reason:+ ($reason)}" |
| 103 | } |
| 104 | |
| 105 | should_run() { |
| 106 | local name="$1" |
| 107 | if [[ -n "$FILTER" && ! "$name" =~ $FILTER ]]; then |
| 108 | return 1 |
| 109 | fi |
| 110 | return 0 |
| 111 | } |
| 112 | |
| 113 | # Run ferp and check exit code |
| 114 | # Usage: run_ferp_expect EXIT_CODE ARGS... |
| 115 | run_ferp_expect() { |
| 116 | local expected_exit="$1" |
| 117 | shift |
| 118 | local actual_exit=0 |
| 119 | "$FERP" "$@" >/dev/null 2>&1 || actual_exit=$? |
| 120 | [[ "$actual_exit" -eq "$expected_exit" ]] |
| 121 | } |
| 122 | |
| 123 | # Run ferp and capture output |
| 124 | # Usage: run_ferp ARGS... |
| 125 | run_ferp() { |
| 126 | "$FERP" "$@" 2>/dev/null || true |
| 127 | } |
| 128 | |
| 129 | # Compare ferp output to expected string |
| 130 | # Usage: assert_output "expected" ferp_args... |
| 131 | assert_output() { |
| 132 | local expected="$1" |
| 133 | shift |
| 134 | local actual |
| 135 | actual=$("$FERP" "$@" 2>/dev/null) || true |
| 136 | [[ "$actual" == "$expected" ]] |
| 137 | } |
| 138 | |
| 139 | # Compare ferp output to grep output |
| 140 | # Usage: assert_matches_grep ARGS... |
| 141 | assert_matches_grep() { |
| 142 | if [[ "$COMPARE_GREP" != "true" ]]; then |
| 143 | return 0 |
| 144 | fi |
| 145 | local ferp_out grep_out |
| 146 | ferp_out=$("$FERP" "$@" 2>/dev/null) || true |
| 147 | grep_out=$(grep "$@" 2>/dev/null) || true |
| 148 | [[ "$ferp_out" == "$grep_out" ]] |
| 149 | } |
| 150 | |
| 151 | # Count output lines |
| 152 | # Usage: assert_line_count EXPECTED ferp_args... |
| 153 | assert_line_count() { |
| 154 | local expected="$1" |
| 155 | shift |
| 156 | local actual |
| 157 | actual=$("$FERP" "$@" 2>/dev/null | wc -l) || true |
| 158 | [[ "$actual" -eq "$expected" ]] |
| 159 | } |
| 160 | |
| 161 | # Check that output contains a string |
| 162 | # Usage: assert_contains "substring" ferp_args... |
| 163 | assert_contains() { |
| 164 | local substring="$1" |
| 165 | shift |
| 166 | local output |
| 167 | output=$("$FERP" "$@" 2>/dev/null) || true |
| 168 | [[ "$output" == *"$substring"* ]] |
| 169 | } |
| 170 | |
| 171 | # Check that output does NOT contain a string |
| 172 | # Usage: assert_not_contains "substring" ferp_args... |
| 173 | assert_not_contains() { |
| 174 | local substring="$1" |
| 175 | shift |
| 176 | local output |
| 177 | output=$("$FERP" "$@" 2>/dev/null) || true |
| 178 | [[ "$output" != *"$substring"* ]] |
| 179 | } |
| 180 | |
| 181 | #------------------------------------------------------------------------------ |
| 182 | # Test Categories |
| 183 | #------------------------------------------------------------------------------ |
| 184 | |
| 185 | section() { |
| 186 | echo "" |
| 187 | echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" |
| 188 | echo -e "${BLUE} $1${NC}" |
| 189 | echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" |
| 190 | } |
| 191 | |
| 192 | #------------------------------------------------------------------------------ |
| 193 | # PATTERN TYPE TESTS (-E, -F, -G, -P) |
| 194 | #------------------------------------------------------------------------------ |
| 195 | |
| 196 | test_pattern_types() { |
| 197 | section "Pattern Type Tests (-E, -F, -G, -P)" |
| 198 | local name |
| 199 | |
| 200 | # -G / --basic-regexp (BRE - default) |
| 201 | name="-G: BRE is default mode" |
| 202 | should_run "$name" && { |
| 203 | log_test "$name" |
| 204 | # "hello" should match lines containing "hello" |
| 205 | if assert_contains "hello world" "hello" "$FIXTURES/simple.txt"; then |
| 206 | pass "$name" |
| 207 | else |
| 208 | fail "$name" |
| 209 | fi |
| 210 | } |
| 211 | |
| 212 | name="-G: BRE treats + as literal" |
| 213 | should_run "$name" && { |
| 214 | log_test "$name" |
| 215 | if assert_contains "a+b equals c" "a+b" "$FIXTURES/special_chars.txt"; then |
| 216 | pass "$name" |
| 217 | else |
| 218 | fail "$name" |
| 219 | fi |
| 220 | } |
| 221 | |
| 222 | name="-G: BRE treats | as literal" |
| 223 | should_run "$name" && { |
| 224 | log_test "$name" |
| 225 | if assert_contains "pipe|character" "pipe|character" "$FIXTURES/special_chars.txt"; then |
| 226 | pass "$name" |
| 227 | else |
| 228 | fail "$name" |
| 229 | fi |
| 230 | } |
| 231 | |
| 232 | name="-G: BRE escaped grouping \\(\\)" |
| 233 | should_run "$name" && { |
| 234 | log_test "$name" |
| 235 | if echo "abab" | "$FERP" '\(ab\)\{2\}' | grep -q "abab"; then |
| 236 | pass "$name" |
| 237 | else |
| 238 | fail "$name" |
| 239 | fi |
| 240 | } |
| 241 | |
| 242 | # -E / --extended-regexp (ERE) |
| 243 | name="-E: ERE + quantifier" |
| 244 | should_run "$name" && { |
| 245 | log_test "$name" |
| 246 | if echo "helllo" | "$FERP" -E "hel+o" | grep -q "helllo"; then |
| 247 | pass "$name" |
| 248 | else |
| 249 | fail "$name" |
| 250 | fi |
| 251 | } |
| 252 | |
| 253 | name="-E: ERE ? quantifier (match)" |
| 254 | should_run "$name" && { |
| 255 | log_test "$name" |
| 256 | if echo "helo" | "$FERP" -E "hel?o" | grep -q "helo"; then |
| 257 | pass "$name" |
| 258 | else |
| 259 | fail "$name" |
| 260 | fi |
| 261 | } |
| 262 | |
| 263 | name="-E: ERE ? quantifier (zero match)" |
| 264 | should_run "$name" && { |
| 265 | log_test "$name" |
| 266 | if echo "heo" | "$FERP" -E "hel?o" | grep -q "heo"; then |
| 267 | pass "$name" |
| 268 | else |
| 269 | fail "$name" |
| 270 | fi |
| 271 | } |
| 272 | |
| 273 | name="-E: ERE alternation |" |
| 274 | should_run "$name" && { |
| 275 | log_test "$name" |
| 276 | local out |
| 277 | out=$(printf "cat\ndog\nbird\n" | "$FERP" -E "cat|dog") |
| 278 | if [[ "$out" == $'cat\ndog' ]]; then |
| 279 | pass "$name" |
| 280 | else |
| 281 | fail "$name" "got: $out" |
| 282 | fi |
| 283 | } |
| 284 | |
| 285 | name="-E: ERE unescaped grouping ()" |
| 286 | should_run "$name" && { |
| 287 | log_test "$name" |
| 288 | if echo "abab" | "$FERP" -E "(ab){2}" | grep -q "abab"; then |
| 289 | pass "$name" |
| 290 | else |
| 291 | fail "$name" |
| 292 | fi |
| 293 | } |
| 294 | |
| 295 | name="--extended-regexp: long form works" |
| 296 | should_run "$name" && { |
| 297 | log_test "$name" |
| 298 | if echo "hello" | "$FERP" --extended-regexp "hel+" | grep -q "hello"; then |
| 299 | pass "$name" |
| 300 | else |
| 301 | fail "$name" |
| 302 | fi |
| 303 | } |
| 304 | |
| 305 | # -F / --fixed-strings |
| 306 | name="-F: treats regex chars as literal" |
| 307 | should_run "$name" && { |
| 308 | log_test "$name" |
| 309 | if assert_contains "regex: foo.*bar" -F "foo.*bar" "$FIXTURES/special_chars.txt"; then |
| 310 | pass "$name" |
| 311 | else |
| 312 | fail "$name" |
| 313 | fi |
| 314 | } |
| 315 | |
| 316 | name="-F: literal $ match" |
| 317 | should_run "$name" && { |
| 318 | log_test "$name" |
| 319 | if assert_contains 'price is $100' -F '$100' "$FIXTURES/special_chars.txt"; then |
| 320 | pass "$name" |
| 321 | else |
| 322 | fail "$name" |
| 323 | fi |
| 324 | } |
| 325 | |
| 326 | name="-F: literal brackets match" |
| 327 | should_run "$name" && { |
| 328 | log_test "$name" |
| 329 | if assert_contains "brackets [test] here" -F "[test]" "$FIXTURES/special_chars.txt"; then |
| 330 | pass "$name" |
| 331 | else |
| 332 | fail "$name" |
| 333 | fi |
| 334 | } |
| 335 | |
| 336 | name="--fixed-strings: long form works" |
| 337 | should_run "$name" && { |
| 338 | log_test "$name" |
| 339 | if assert_contains "a+b equals c" --fixed-strings "a+b" "$FIXTURES/special_chars.txt"; then |
| 340 | pass "$name" |
| 341 | else |
| 342 | fail "$name" |
| 343 | fi |
| 344 | } |
| 345 | |
| 346 | # -P / --perl-regexp (PCRE) |
| 347 | name="-P: basic PCRE match" |
| 348 | should_run "$name" && { |
| 349 | log_test "$name" |
| 350 | if echo "hello world" | "$FERP" -P "hello" | grep -q "hello"; then |
| 351 | pass "$name" |
| 352 | else |
| 353 | fail "$name" |
| 354 | fi |
| 355 | } |
| 356 | |
| 357 | name="-P: PCRE \\d digit class" |
| 358 | should_run "$name" && { |
| 359 | log_test "$name" |
| 360 | if echo "test123" | "$FERP" -P '\d+' | grep -q "test123"; then |
| 361 | pass "$name" |
| 362 | else |
| 363 | fail "$name" |
| 364 | fi |
| 365 | } |
| 366 | |
| 367 | name="-P: PCRE \\w word class" |
| 368 | should_run "$name" && { |
| 369 | log_test "$name" |
| 370 | if echo "hello_world" | "$FERP" -P '\w+' | grep -q "hello_world"; then |
| 371 | pass "$name" |
| 372 | else |
| 373 | fail "$name" |
| 374 | fi |
| 375 | } |
| 376 | |
| 377 | name="-P: PCRE positive lookahead (?=)" |
| 378 | should_run "$name" && { |
| 379 | log_test "$name" |
| 380 | if echo "foobar" | "$FERP" -P 'foo(?=bar)' | grep -q "foobar"; then |
| 381 | pass "$name" |
| 382 | else |
| 383 | fail "$name" |
| 384 | fi |
| 385 | } |
| 386 | |
| 387 | name="-P: PCRE negative lookahead (?!)" |
| 388 | should_run "$name" && { |
| 389 | log_test "$name" |
| 390 | if echo "foobaz" | "$FERP" -P 'foo(?!bar)' | grep -q "foobaz"; then |
| 391 | pass "$name" |
| 392 | else |
| 393 | fail "$name" |
| 394 | fi |
| 395 | } |
| 396 | |
| 397 | name="-P: PCRE positive lookbehind (?<=)" |
| 398 | should_run "$name" && { |
| 399 | log_test "$name" |
| 400 | if echo "foobar" | "$FERP" -P '(?<=foo)bar' | grep -q "foobar"; then |
| 401 | pass "$name" |
| 402 | else |
| 403 | fail "$name" |
| 404 | fi |
| 405 | } |
| 406 | |
| 407 | name="-P: PCRE non-capturing group (?:)" |
| 408 | should_run "$name" && { |
| 409 | log_test "$name" |
| 410 | if echo "abcabc" | "$FERP" -P '(?:abc)+' | grep -q "abcabc"; then |
| 411 | pass "$name" |
| 412 | else |
| 413 | fail "$name" |
| 414 | fi |
| 415 | } |
| 416 | } |
| 417 | |
| 418 | #------------------------------------------------------------------------------ |
| 419 | # MATCHING CONTROL TESTS (-i, -v, -w, -x) |
| 420 | #------------------------------------------------------------------------------ |
| 421 | |
| 422 | test_matching_control() { |
| 423 | section "Matching Control Tests (-i, -v, -w, -x)" |
| 424 | local name |
| 425 | |
| 426 | # -i / --ignore-case |
| 427 | name="-i: case insensitive match (lowercase pattern)" |
| 428 | should_run "$name" && { |
| 429 | log_test "$name" |
| 430 | local out |
| 431 | out=$("$FERP" -i "hello" "$FIXTURES/simple.txt" | wc -l) |
| 432 | if [[ "$out" -eq 4 ]]; then # hello, Hello, HELLO, line with hello |
| 433 | pass "$name" |
| 434 | else |
| 435 | fail "$name" "expected 4 lines, got $out" |
| 436 | fi |
| 437 | } |
| 438 | |
| 439 | name="-i: case insensitive match (uppercase pattern)" |
| 440 | should_run "$name" && { |
| 441 | log_test "$name" |
| 442 | local out |
| 443 | out=$("$FERP" -i "HELLO" "$FIXTURES/simple.txt" | wc -l) |
| 444 | if [[ "$out" -eq 4 ]]; then |
| 445 | pass "$name" |
| 446 | else |
| 447 | fail "$name" "expected 4 lines, got $out" |
| 448 | fi |
| 449 | } |
| 450 | |
| 451 | name="-i: case insensitive with -F" |
| 452 | should_run "$name" && { |
| 453 | log_test "$name" |
| 454 | local out |
| 455 | out=$("$FERP" -iF "HELLO" "$FIXTURES/simple.txt" | wc -l) |
| 456 | if [[ "$out" -eq 4 ]]; then |
| 457 | pass "$name" |
| 458 | else |
| 459 | fail "$name" "expected 4 lines, got $out" |
| 460 | fi |
| 461 | } |
| 462 | |
| 463 | name="-i: case insensitive with -E" |
| 464 | should_run "$name" && { |
| 465 | log_test "$name" |
| 466 | local out |
| 467 | out=$("$FERP" -iE "hel+" "$FIXTURES/simple.txt" | wc -l) |
| 468 | if [[ "$out" -eq 4 ]]; then |
| 469 | pass "$name" |
| 470 | else |
| 471 | fail "$name" "expected 4 lines, got $out" |
| 472 | fi |
| 473 | } |
| 474 | |
| 475 | name="--ignore-case: long form works" |
| 476 | should_run "$name" && { |
| 477 | log_test "$name" |
| 478 | local out |
| 479 | out=$("$FERP" --ignore-case "HELLO" "$FIXTURES/simple.txt" | wc -l) |
| 480 | if [[ "$out" -eq 4 ]]; then |
| 481 | pass "$name" |
| 482 | else |
| 483 | fail "$name" |
| 484 | fi |
| 485 | } |
| 486 | |
| 487 | name="--no-ignore-case: disables case insensitivity" |
| 488 | should_run "$name" && { |
| 489 | log_test "$name" |
| 490 | local out |
| 491 | out=$("$FERP" -i --no-ignore-case "hello" "$FIXTURES/simple.txt" | wc -l) |
| 492 | if [[ "$out" -eq 2 ]]; then # only lowercase hello |
| 493 | pass "$name" |
| 494 | else |
| 495 | fail "$name" "expected 2 lines, got $out" |
| 496 | fi |
| 497 | } |
| 498 | |
| 499 | # -v / --invert-match |
| 500 | name="-v: invert match" |
| 501 | should_run "$name" && { |
| 502 | log_test "$name" |
| 503 | if assert_not_contains "hello" -v "hello" "$FIXTURES/simple.txt"; then |
| 504 | pass "$name" |
| 505 | else |
| 506 | fail "$name" |
| 507 | fi |
| 508 | } |
| 509 | |
| 510 | name="-v: inverted results count" |
| 511 | should_run "$name" && { |
| 512 | log_test "$name" |
| 513 | local total matching inverted |
| 514 | total=$("$FERP" "" "$FIXTURES/simple.txt" | wc -l) |
| 515 | matching=$("$FERP" "hello" "$FIXTURES/simple.txt" | wc -l) |
| 516 | inverted=$("$FERP" -v "hello" "$FIXTURES/simple.txt" | wc -l) |
| 517 | if [[ $((matching + inverted)) -eq "$total" ]]; then |
| 518 | pass "$name" |
| 519 | else |
| 520 | fail "$name" "matching($matching) + inverted($inverted) != total($total)" |
| 521 | fi |
| 522 | } |
| 523 | |
| 524 | name="--invert-match: long form works" |
| 525 | should_run "$name" && { |
| 526 | log_test "$name" |
| 527 | if assert_not_contains "hello" --invert-match "hello" "$FIXTURES/simple.txt"; then |
| 528 | pass "$name" |
| 529 | else |
| 530 | fail "$name" |
| 531 | fi |
| 532 | } |
| 533 | |
| 534 | # -w / --word-regexp |
| 535 | name="-w: matches whole word only" |
| 536 | should_run "$name" && { |
| 537 | log_test "$name" |
| 538 | local out |
| 539 | out=$("$FERP" -w "test" "$FIXTURES/words.txt") |
| 540 | # Should match "test" but not "testing", "tested", "tester" |
| 541 | if [[ "$out" == "test testing tested tester" ]]; then |
| 542 | pass "$name" |
| 543 | else |
| 544 | fail "$name" "got: $out" |
| 545 | fi |
| 546 | } |
| 547 | |
| 548 | name="-w: does not match partial word" |
| 549 | should_run "$name" && { |
| 550 | log_test "$name" |
| 551 | local out |
| 552 | out=$("$FERP" -w "foo" "$FIXTURES/words.txt" | wc -l) |
| 553 | # "foobar foo bar foo-bar" - should match "foo" twice (standalone and in foo-bar) |
| 554 | if [[ "$out" -eq 1 ]]; then |
| 555 | pass "$name" |
| 556 | else |
| 557 | fail "$name" "expected 1 line, got $out" |
| 558 | fi |
| 559 | } |
| 560 | |
| 561 | name="-w: word with -F" |
| 562 | should_run "$name" && { |
| 563 | log_test "$name" |
| 564 | local out |
| 565 | out=$("$FERP" -wF "hello" "$FIXTURES/words.txt") |
| 566 | if [[ "$out" == "hello helloworld worldhello" ]]; then |
| 567 | pass "$name" |
| 568 | else |
| 569 | fail "$name" "got: $out" |
| 570 | fi |
| 571 | } |
| 572 | |
| 573 | name="--word-regexp: long form works" |
| 574 | should_run "$name" && { |
| 575 | log_test "$name" |
| 576 | local out |
| 577 | out=$("$FERP" --word-regexp "test" "$FIXTURES/words.txt") |
| 578 | if [[ "$out" == "test testing tested tester" ]]; then |
| 579 | pass "$name" |
| 580 | else |
| 581 | fail "$name" |
| 582 | fi |
| 583 | } |
| 584 | |
| 585 | # -x / --line-regexp |
| 586 | name="-x: matches whole line only" |
| 587 | should_run "$name" && { |
| 588 | log_test "$name" |
| 589 | local out |
| 590 | out=$("$FERP" -x "hello world" "$FIXTURES/simple.txt") |
| 591 | if [[ "$out" == "hello world" ]]; then |
| 592 | pass "$name" |
| 593 | else |
| 594 | fail "$name" "got: $out" |
| 595 | fi |
| 596 | } |
| 597 | |
| 598 | name="-x: does not match partial line" |
| 599 | should_run "$name" && { |
| 600 | log_test "$name" |
| 601 | if run_ferp_expect 1 -x "hello" "$FIXTURES/simple.txt"; then |
| 602 | pass "$name" |
| 603 | else |
| 604 | fail "$name" |
| 605 | fi |
| 606 | } |
| 607 | |
| 608 | name="-x: with regex" |
| 609 | should_run "$name" && { |
| 610 | log_test "$name" |
| 611 | local out |
| 612 | out=$("$FERP" -xE "hello.*" "$FIXTURES/simple.txt") |
| 613 | # Should match lines starting with hello |
| 614 | if echo "$out" | grep -q "hello world"; then |
| 615 | pass "$name" |
| 616 | else |
| 617 | fail "$name" |
| 618 | fi |
| 619 | } |
| 620 | |
| 621 | name="--line-regexp: long form works" |
| 622 | should_run "$name" && { |
| 623 | log_test "$name" |
| 624 | local out |
| 625 | out=$("$FERP" --line-regexp "line 5" "$FIXTURES/numbers.txt") |
| 626 | if [[ "$out" == "line 5" ]]; then |
| 627 | pass "$name" |
| 628 | else |
| 629 | fail "$name" |
| 630 | fi |
| 631 | } |
| 632 | |
| 633 | # Combined flags |
| 634 | name="-iv: case insensitive inverted" |
| 635 | should_run "$name" && { |
| 636 | log_test "$name" |
| 637 | local out |
| 638 | out=$("$FERP" -iv "HELLO" "$FIXTURES/simple.txt" | wc -l) |
| 639 | # Should exclude all hello variants |
| 640 | if [[ "$out" -eq 4 ]]; then # 8 lines - 4 with hello |
| 641 | pass "$name" |
| 642 | else |
| 643 | fail "$name" "expected 4 lines, got $out" |
| 644 | fi |
| 645 | } |
| 646 | |
| 647 | name="-iw: case insensitive word match" |
| 648 | should_run "$name" && { |
| 649 | log_test "$name" |
| 650 | local out |
| 651 | out=$(echo -e "TEST\nTesting\ntest" | "$FERP" -iw "test" | wc -l) |
| 652 | if [[ "$out" -eq 2 ]]; then # TEST and test, not Testing |
| 653 | pass "$name" |
| 654 | else |
| 655 | fail "$name" "expected 2, got $out" |
| 656 | fi |
| 657 | } |
| 658 | } |
| 659 | |
| 660 | #------------------------------------------------------------------------------ |
| 661 | # OUTPUT CONTROL TESTS (-c, -l, -L, -o, -q, -s) |
| 662 | #------------------------------------------------------------------------------ |
| 663 | |
| 664 | test_output_control() { |
| 665 | section "Output Control Tests (-c, -l, -L, -o, -q, -s)" |
| 666 | local name |
| 667 | |
| 668 | # -c / --count |
| 669 | name="-c: count matching lines" |
| 670 | should_run "$name" && { |
| 671 | log_test "$name" |
| 672 | local out |
| 673 | out=$("$FERP" -c "hello" "$FIXTURES/simple.txt") |
| 674 | if [[ "$out" == "2" ]]; then |
| 675 | pass "$name" |
| 676 | else |
| 677 | fail "$name" "expected 2, got $out" |
| 678 | fi |
| 679 | } |
| 680 | |
| 681 | name="-c: count with multiple files shows filename" |
| 682 | should_run "$name" && { |
| 683 | log_test "$name" |
| 684 | local out |
| 685 | out=$("$FERP" -c "match" "$FIXTURES/multifile1.txt" "$FIXTURES/multifile2.txt") |
| 686 | if echo "$out" | grep -q "multifile1.txt:2" && echo "$out" | grep -q "multifile2.txt:1"; then |
| 687 | pass "$name" |
| 688 | else |
| 689 | fail "$name" "got: $out" |
| 690 | fi |
| 691 | } |
| 692 | |
| 693 | name="-c: zero count when no matches" |
| 694 | should_run "$name" && { |
| 695 | log_test "$name" |
| 696 | local out |
| 697 | out=$("$FERP" -c "nonexistent" "$FIXTURES/simple.txt") |
| 698 | if [[ "$out" == "0" ]]; then |
| 699 | pass "$name" |
| 700 | else |
| 701 | fail "$name" "expected 0, got $out" |
| 702 | fi |
| 703 | } |
| 704 | |
| 705 | name="--count: long form works" |
| 706 | should_run "$name" && { |
| 707 | log_test "$name" |
| 708 | local out |
| 709 | out=$("$FERP" --count "line" "$FIXTURES/numbers.txt") |
| 710 | if [[ "$out" == "10" ]]; then |
| 711 | pass "$name" |
| 712 | else |
| 713 | fail "$name" |
| 714 | fi |
| 715 | } |
| 716 | |
| 717 | # -l / --files-with-matches |
| 718 | name="-l: list files with matches" |
| 719 | should_run "$name" && { |
| 720 | log_test "$name" |
| 721 | local out |
| 722 | out=$("$FERP" -l "match" "$FIXTURES/multifile1.txt" "$FIXTURES/multifile2.txt" "$FIXTURES/multifile3.txt") |
| 723 | if echo "$out" | grep -q "multifile1.txt" && echo "$out" | grep -q "multifile2.txt" && ! echo "$out" | grep -q "multifile3.txt"; then |
| 724 | pass "$name" |
| 725 | else |
| 726 | fail "$name" "got: $out" |
| 727 | fi |
| 728 | } |
| 729 | |
| 730 | name="-l: only prints filename once per file" |
| 731 | should_run "$name" && { |
| 732 | log_test "$name" |
| 733 | local out |
| 734 | out=$("$FERP" -l "match" "$FIXTURES/multifile1.txt" | wc -l) |
| 735 | if [[ "$out" -eq 1 ]]; then |
| 736 | pass "$name" |
| 737 | else |
| 738 | fail "$name" "expected 1 line, got $out" |
| 739 | fi |
| 740 | } |
| 741 | |
| 742 | name="--files-with-matches: long form works" |
| 743 | should_run "$name" && { |
| 744 | log_test "$name" |
| 745 | local out |
| 746 | out=$("$FERP" --files-with-matches "match" "$FIXTURES/multifile1.txt") |
| 747 | if echo "$out" | grep -q "multifile1.txt"; then |
| 748 | pass "$name" |
| 749 | else |
| 750 | fail "$name" |
| 751 | fi |
| 752 | } |
| 753 | |
| 754 | # -L / --files-without-match |
| 755 | name="-L: list files without matches" |
| 756 | should_run "$name" && { |
| 757 | log_test "$name" |
| 758 | local out |
| 759 | out=$("$FERP" -L "match" "$FIXTURES/multifile1.txt" "$FIXTURES/multifile2.txt" "$FIXTURES/multifile3.txt") |
| 760 | if echo "$out" | grep -q "multifile3.txt" && ! echo "$out" | grep -q "multifile1.txt"; then |
| 761 | pass "$name" |
| 762 | else |
| 763 | fail "$name" "got: $out" |
| 764 | fi |
| 765 | } |
| 766 | |
| 767 | name="--files-without-match: long form works" |
| 768 | should_run "$name" && { |
| 769 | log_test "$name" |
| 770 | local out |
| 771 | out=$("$FERP" --files-without-match "match" "$FIXTURES/multifile3.txt") |
| 772 | if echo "$out" | grep -q "multifile3.txt"; then |
| 773 | pass "$name" |
| 774 | else |
| 775 | fail "$name" |
| 776 | fi |
| 777 | } |
| 778 | |
| 779 | # -o / --only-matching |
| 780 | name="-o: show only matching part" |
| 781 | should_run "$name" && { |
| 782 | log_test "$name" |
| 783 | local out |
| 784 | out=$("$FERP" -o "hello" "$FIXTURES/simple.txt") |
| 785 | # Should output "hello" for each match, not the whole line |
| 786 | if [[ "$out" == $'hello\nhello' ]]; then |
| 787 | pass "$name" |
| 788 | else |
| 789 | fail "$name" "got: $out" |
| 790 | fi |
| 791 | } |
| 792 | |
| 793 | name="-o: multiple matches per line" |
| 794 | should_run "$name" && { |
| 795 | log_test "$name" |
| 796 | local out |
| 797 | out=$(echo "hello world hello" | "$FERP" -o "hello" | wc -l) |
| 798 | if [[ "$out" -eq 2 ]]; then |
| 799 | pass "$name" |
| 800 | else |
| 801 | fail "$name" "expected 2 lines, got $out" |
| 802 | fi |
| 803 | } |
| 804 | |
| 805 | name="-o: with regex captures match" |
| 806 | should_run "$name" && { |
| 807 | log_test "$name" |
| 808 | local out |
| 809 | out=$(echo "test123test456" | "$FERP" -oE "[0-9]+") |
| 810 | if [[ "$out" == $'123\n456' ]]; then |
| 811 | pass "$name" |
| 812 | else |
| 813 | fail "$name" "got: $out" |
| 814 | fi |
| 815 | } |
| 816 | |
| 817 | name="--only-matching: long form works" |
| 818 | should_run "$name" && { |
| 819 | log_test "$name" |
| 820 | local out |
| 821 | out=$(echo "abc123def" | "$FERP" --only-matching -E "[0-9]+") |
| 822 | if [[ "$out" == "123" ]]; then |
| 823 | pass "$name" |
| 824 | else |
| 825 | fail "$name" |
| 826 | fi |
| 827 | } |
| 828 | |
| 829 | # -q / --quiet / --silent |
| 830 | name="-q: no output on match" |
| 831 | should_run "$name" && { |
| 832 | log_test "$name" |
| 833 | local out |
| 834 | out=$("$FERP" -q "hello" "$FIXTURES/simple.txt") |
| 835 | if [[ -z "$out" ]]; then |
| 836 | pass "$name" |
| 837 | else |
| 838 | fail "$name" "expected empty output, got: $out" |
| 839 | fi |
| 840 | } |
| 841 | |
| 842 | name="-q: exit 0 on match" |
| 843 | should_run "$name" && { |
| 844 | log_test "$name" |
| 845 | if run_ferp_expect 0 -q "hello" "$FIXTURES/simple.txt"; then |
| 846 | pass "$name" |
| 847 | else |
| 848 | fail "$name" |
| 849 | fi |
| 850 | } |
| 851 | |
| 852 | name="-q: exit 1 on no match" |
| 853 | should_run "$name" && { |
| 854 | log_test "$name" |
| 855 | if run_ferp_expect 1 -q "nonexistent" "$FIXTURES/simple.txt"; then |
| 856 | pass "$name" |
| 857 | else |
| 858 | fail "$name" |
| 859 | fi |
| 860 | } |
| 861 | |
| 862 | name="--quiet: long form works" |
| 863 | should_run "$name" && { |
| 864 | log_test "$name" |
| 865 | if run_ferp_expect 0 --quiet "hello" "$FIXTURES/simple.txt"; then |
| 866 | pass "$name" |
| 867 | else |
| 868 | fail "$name" |
| 869 | fi |
| 870 | } |
| 871 | |
| 872 | name="--silent: alias works" |
| 873 | should_run "$name" && { |
| 874 | log_test "$name" |
| 875 | if run_ferp_expect 0 --silent "hello" "$FIXTURES/simple.txt"; then |
| 876 | pass "$name" |
| 877 | else |
| 878 | fail "$name" |
| 879 | fi |
| 880 | } |
| 881 | |
| 882 | # -s / --no-messages |
| 883 | name="-s: suppress error messages" |
| 884 | should_run "$name" && { |
| 885 | log_test "$name" |
| 886 | local err |
| 887 | err=$("$FERP" -s "test" "/nonexistent/file" 2>&1) || true |
| 888 | if [[ -z "$err" ]]; then |
| 889 | pass "$name" |
| 890 | else |
| 891 | fail "$name" "expected no output, got: $err" |
| 892 | fi |
| 893 | } |
| 894 | |
| 895 | name="--no-messages: long form works" |
| 896 | should_run "$name" && { |
| 897 | log_test "$name" |
| 898 | local err |
| 899 | err=$("$FERP" --no-messages "test" "/nonexistent/file" 2>&1) || true |
| 900 | if [[ -z "$err" ]]; then |
| 901 | pass "$name" |
| 902 | else |
| 903 | fail "$name" |
| 904 | fi |
| 905 | } |
| 906 | } |
| 907 | |
| 908 | #------------------------------------------------------------------------------ |
| 909 | # LINE PREFIX TESTS (-n, -b, -H, -h, -Z, -T) |
| 910 | #------------------------------------------------------------------------------ |
| 911 | |
| 912 | test_line_prefix() { |
| 913 | section "Line Prefix Tests (-n, -b, -H, -h, -Z, -T)" |
| 914 | local name |
| 915 | |
| 916 | # -n / --line-number |
| 917 | name="-n: show line numbers" |
| 918 | should_run "$name" && { |
| 919 | log_test "$name" |
| 920 | local out |
| 921 | out=$("$FERP" -n "line 5" "$FIXTURES/numbers.txt") |
| 922 | if [[ "$out" == "5:line 5" ]]; then |
| 923 | pass "$name" |
| 924 | else |
| 925 | fail "$name" "got: $out" |
| 926 | fi |
| 927 | } |
| 928 | |
| 929 | name="-n: line numbers start at 1" |
| 930 | should_run "$name" && { |
| 931 | log_test "$name" |
| 932 | local out |
| 933 | # Use -x for exact line match to avoid "line 10" also matching |
| 934 | out=$("$FERP" -nx "line 1" "$FIXTURES/numbers.txt") |
| 935 | if [[ "$out" == "1:line 1" ]]; then |
| 936 | pass "$name" |
| 937 | else |
| 938 | fail "$name" "got: $out" |
| 939 | fi |
| 940 | } |
| 941 | |
| 942 | name="--line-number: long form works" |
| 943 | should_run "$name" && { |
| 944 | log_test "$name" |
| 945 | local out |
| 946 | out=$("$FERP" --line-number "line 3" "$FIXTURES/numbers.txt") |
| 947 | if [[ "$out" == "3:line 3" ]]; then |
| 948 | pass "$name" |
| 949 | else |
| 950 | fail "$name" |
| 951 | fi |
| 952 | } |
| 953 | |
| 954 | # -b / --byte-offset |
| 955 | name="-b: show byte offset" |
| 956 | should_run "$name" && { |
| 957 | log_test "$name" |
| 958 | local out |
| 959 | # Use -x for exact line match to avoid "line 10" also matching |
| 960 | out=$("$FERP" -bx "line 1" "$FIXTURES/numbers.txt") |
| 961 | if [[ "$out" == "0:line 1" ]]; then |
| 962 | pass "$name" |
| 963 | else |
| 964 | fail "$name" "got: $out" |
| 965 | fi |
| 966 | } |
| 967 | |
| 968 | name="-b: byte offset increases with lines" |
| 969 | should_run "$name" && { |
| 970 | log_test "$name" |
| 971 | local out |
| 972 | out=$("$FERP" -b "line 2" "$FIXTURES/numbers.txt") |
| 973 | # "line 1\n" is 7 bytes, so line 2 starts at 7 |
| 974 | if [[ "$out" == "7:line 2" ]]; then |
| 975 | pass "$name" |
| 976 | else |
| 977 | fail "$name" "expected 7:line 2, got: $out" |
| 978 | fi |
| 979 | } |
| 980 | |
| 981 | name="-nb: line number and byte offset together" |
| 982 | should_run "$name" && { |
| 983 | log_test "$name" |
| 984 | local out |
| 985 | out=$("$FERP" -nb "line 2" "$FIXTURES/numbers.txt") |
| 986 | if [[ "$out" == "2:7:line 2" ]]; then |
| 987 | pass "$name" |
| 988 | else |
| 989 | fail "$name" "got: $out" |
| 990 | fi |
| 991 | } |
| 992 | |
| 993 | name="--byte-offset: long form works" |
| 994 | should_run "$name" && { |
| 995 | log_test "$name" |
| 996 | local out |
| 997 | # Use -x for exact line match |
| 998 | out=$("$FERP" --byte-offset -x "line 1" "$FIXTURES/numbers.txt") |
| 999 | if [[ "$out" == "0:line 1" ]]; then |
| 1000 | pass "$name" |
| 1001 | else |
| 1002 | fail "$name" |
| 1003 | fi |
| 1004 | } |
| 1005 | |
| 1006 | # -H / --with-filename |
| 1007 | name="-H: show filename on single file" |
| 1008 | should_run "$name" && { |
| 1009 | log_test "$name" |
| 1010 | local out |
| 1011 | out=$("$FERP" -H "hello" "$FIXTURES/simple.txt" | head -1) |
| 1012 | if echo "$out" | grep -q "simple.txt:"; then |
| 1013 | pass "$name" |
| 1014 | else |
| 1015 | fail "$name" "got: $out" |
| 1016 | fi |
| 1017 | } |
| 1018 | |
| 1019 | name="--with-filename: long form works" |
| 1020 | should_run "$name" && { |
| 1021 | log_test "$name" |
| 1022 | local out |
| 1023 | out=$("$FERP" --with-filename "hello" "$FIXTURES/simple.txt" | head -1) |
| 1024 | if echo "$out" | grep -q "simple.txt:"; then |
| 1025 | pass "$name" |
| 1026 | else |
| 1027 | fail "$name" |
| 1028 | fi |
| 1029 | } |
| 1030 | |
| 1031 | # -h / --no-filename |
| 1032 | name="-h: hide filename on multiple files" |
| 1033 | should_run "$name" && { |
| 1034 | log_test "$name" |
| 1035 | local out |
| 1036 | out=$("$FERP" -h "match" "$FIXTURES/multifile1.txt" "$FIXTURES/multifile2.txt" | head -1) |
| 1037 | if ! echo "$out" | grep -q ":"; then |
| 1038 | pass "$name" |
| 1039 | else |
| 1040 | fail "$name" "got: $out" |
| 1041 | fi |
| 1042 | } |
| 1043 | |
| 1044 | name="--no-filename: long form works" |
| 1045 | should_run "$name" && { |
| 1046 | log_test "$name" |
| 1047 | local out |
| 1048 | out=$("$FERP" --no-filename "match" "$FIXTURES/multifile1.txt" "$FIXTURES/multifile2.txt" | head -1) |
| 1049 | if ! echo "$out" | grep -q ":"; then |
| 1050 | pass "$name" |
| 1051 | else |
| 1052 | fail "$name" |
| 1053 | fi |
| 1054 | } |
| 1055 | |
| 1056 | # -Z / --null (null byte after filename) |
| 1057 | name="-Z: null byte after filename" |
| 1058 | should_run "$name" && { |
| 1059 | log_test "$name" |
| 1060 | local out |
| 1061 | # Check that null byte exists between filename and content |
| 1062 | out=$("$FERP" -HZ "hello" "$FIXTURES/simple.txt" | head -1 | od -c) |
| 1063 | if echo "$out" | grep -q '\\0'; then |
| 1064 | pass "$name" |
| 1065 | else |
| 1066 | fail "$name" "expected null byte, got: $out" |
| 1067 | fi |
| 1068 | } |
| 1069 | |
| 1070 | name="--null: long form works" |
| 1071 | should_run "$name" && { |
| 1072 | log_test "$name" |
| 1073 | local out |
| 1074 | # Check that null byte exists between filename and content |
| 1075 | out=$("$FERP" -H --null "hello" "$FIXTURES/simple.txt" | head -1 | od -c) |
| 1076 | if echo "$out" | grep -q '\\0'; then |
| 1077 | pass "$name" |
| 1078 | else |
| 1079 | fail "$name" |
| 1080 | fi |
| 1081 | } |
| 1082 | |
| 1083 | # -T / --initial-tab |
| 1084 | name="-T: initial tab for alignment" |
| 1085 | should_run "$name" && { |
| 1086 | log_test "$name" |
| 1087 | local out |
| 1088 | out=$("$FERP" -nT "hello" "$FIXTURES/simple.txt" | head -1) |
| 1089 | # Should have tab between prefix and content |
| 1090 | if echo "$out" | grep -qP '^\d+:\t'; then |
| 1091 | pass "$name" |
| 1092 | else |
| 1093 | fail "$name" "got: $out" |
| 1094 | fi |
| 1095 | } |
| 1096 | |
| 1097 | name="--initial-tab: long form works" |
| 1098 | should_run "$name" && { |
| 1099 | log_test "$name" |
| 1100 | local out |
| 1101 | out=$("$FERP" -n --initial-tab "hello" "$FIXTURES/simple.txt" | head -1) |
| 1102 | if echo "$out" | grep -qP '^\d+:\t'; then |
| 1103 | pass "$name" |
| 1104 | else |
| 1105 | fail "$name" |
| 1106 | fi |
| 1107 | } |
| 1108 | } |
| 1109 | |
| 1110 | #------------------------------------------------------------------------------ |
| 1111 | # CONTEXT CONTROL TESTS (-A, -B, -C, -NUM) |
| 1112 | #------------------------------------------------------------------------------ |
| 1113 | |
| 1114 | test_context_control() { |
| 1115 | section "Context Control Tests (-A, -B, -C, -NUM)" |
| 1116 | local name |
| 1117 | |
| 1118 | # -A / --after-context |
| 1119 | name="-A: after context lines" |
| 1120 | should_run "$name" && { |
| 1121 | log_test "$name" |
| 1122 | local out |
| 1123 | out=$("$FERP" -A2 "MATCH LINE" "$FIXTURES/context.txt" | wc -l) |
| 1124 | if [[ "$out" -eq 3 ]]; then # match + 2 after |
| 1125 | pass "$name" |
| 1126 | else |
| 1127 | fail "$name" "expected 3 lines, got $out" |
| 1128 | fi |
| 1129 | } |
| 1130 | |
| 1131 | name="-A: after context content" |
| 1132 | should_run "$name" && { |
| 1133 | log_test "$name" |
| 1134 | local out |
| 1135 | out=$("$FERP" -A2 "MATCH LINE" "$FIXTURES/context.txt") |
| 1136 | if echo "$out" | grep -q "after 1" && echo "$out" | grep -q "after 2"; then |
| 1137 | pass "$name" |
| 1138 | else |
| 1139 | fail "$name" "got: $out" |
| 1140 | fi |
| 1141 | } |
| 1142 | |
| 1143 | name="--after-context: long form with =" |
| 1144 | should_run "$name" && { |
| 1145 | log_test "$name" |
| 1146 | local out |
| 1147 | out=$("$FERP" --after-context=1 "MATCH LINE" "$FIXTURES/context.txt" | wc -l) |
| 1148 | if [[ "$out" -eq 2 ]]; then |
| 1149 | pass "$name" |
| 1150 | else |
| 1151 | fail "$name" |
| 1152 | fi |
| 1153 | } |
| 1154 | |
| 1155 | # -B / --before-context |
| 1156 | name="-B: before context lines" |
| 1157 | should_run "$name" && { |
| 1158 | log_test "$name" |
| 1159 | local out |
| 1160 | out=$("$FERP" -B2 "MATCH LINE" "$FIXTURES/context.txt" | wc -l) |
| 1161 | if [[ "$out" -eq 3 ]]; then # 2 before + match |
| 1162 | pass "$name" |
| 1163 | else |
| 1164 | fail "$name" "expected 3 lines, got $out" |
| 1165 | fi |
| 1166 | } |
| 1167 | |
| 1168 | name="-B: before context content" |
| 1169 | should_run "$name" && { |
| 1170 | log_test "$name" |
| 1171 | local out |
| 1172 | out=$("$FERP" -B2 "MATCH LINE" "$FIXTURES/context.txt") |
| 1173 | if echo "$out" | grep -q "before 2" && echo "$out" | grep -q "before 3"; then |
| 1174 | pass "$name" |
| 1175 | else |
| 1176 | fail "$name" "got: $out" |
| 1177 | fi |
| 1178 | } |
| 1179 | |
| 1180 | name="--before-context: long form with =" |
| 1181 | should_run "$name" && { |
| 1182 | log_test "$name" |
| 1183 | local out |
| 1184 | out=$("$FERP" --before-context=1 "MATCH LINE" "$FIXTURES/context.txt" | wc -l) |
| 1185 | if [[ "$out" -eq 2 ]]; then |
| 1186 | pass "$name" |
| 1187 | else |
| 1188 | fail "$name" |
| 1189 | fi |
| 1190 | } |
| 1191 | |
| 1192 | # -C / --context |
| 1193 | name="-C: both context lines" |
| 1194 | should_run "$name" && { |
| 1195 | log_test "$name" |
| 1196 | local out |
| 1197 | out=$("$FERP" -C2 "MATCH LINE" "$FIXTURES/context.txt" | wc -l) |
| 1198 | if [[ "$out" -eq 5 ]]; then # 2 before + match + 2 after |
| 1199 | pass "$name" |
| 1200 | else |
| 1201 | fail "$name" "expected 5 lines, got $out" |
| 1202 | fi |
| 1203 | } |
| 1204 | |
| 1205 | name="--context: long form with =" |
| 1206 | should_run "$name" && { |
| 1207 | log_test "$name" |
| 1208 | local out |
| 1209 | out=$("$FERP" --context=1 "MATCH LINE" "$FIXTURES/context.txt" | wc -l) |
| 1210 | if [[ "$out" -eq 3 ]]; then |
| 1211 | pass "$name" |
| 1212 | else |
| 1213 | fail "$name" |
| 1214 | fi |
| 1215 | } |
| 1216 | |
| 1217 | # -NUM shorthand |
| 1218 | name="-NUM: numeric context shorthand" |
| 1219 | should_run "$name" && { |
| 1220 | log_test "$name" |
| 1221 | local out |
| 1222 | out=$("$FERP" -2 "MATCH LINE" "$FIXTURES/context.txt" | wc -l) |
| 1223 | if [[ "$out" -eq 5 ]]; then # Same as -C2 |
| 1224 | pass "$name" |
| 1225 | else |
| 1226 | fail "$name" "expected 5 lines, got $out" |
| 1227 | fi |
| 1228 | } |
| 1229 | |
| 1230 | name="-3: larger context" |
| 1231 | should_run "$name" && { |
| 1232 | log_test "$name" |
| 1233 | local out |
| 1234 | out=$("$FERP" -3 "MATCH LINE" "$FIXTURES/context.txt" | wc -l) |
| 1235 | if [[ "$out" -eq 7 ]]; then # 3 before + match + 3 after |
| 1236 | pass "$name" |
| 1237 | else |
| 1238 | fail "$name" "expected 7 lines, got $out" |
| 1239 | fi |
| 1240 | } |
| 1241 | |
| 1242 | # Context with multiple matches |
| 1243 | name="context: overlapping context groups" |
| 1244 | should_run "$name" && { |
| 1245 | log_test "$name" |
| 1246 | local out |
| 1247 | # Both MATCH and ANOTHER MATCH with context should not duplicate lines |
| 1248 | out=$("$FERP" -C2 "MATCH" "$FIXTURES/context.txt" | wc -l) |
| 1249 | # Should merge overlapping contexts appropriately |
| 1250 | if [[ "$out" -ge 8 ]]; then |
| 1251 | pass "$name" |
| 1252 | else |
| 1253 | fail "$name" "expected at least 8 lines, got $out" |
| 1254 | fi |
| 1255 | } |
| 1256 | |
| 1257 | # --group-separator |
| 1258 | name="--group-separator: custom separator" |
| 1259 | should_run "$name" && { |
| 1260 | log_test "$name" |
| 1261 | local out |
| 1262 | out=$("$FERP" -C1 --group-separator="===" "MATCH" "$FIXTURES/context.txt") |
| 1263 | if echo "$out" | grep -q "==="; then |
| 1264 | pass "$name" |
| 1265 | else |
| 1266 | fail "$name" "got: $out" |
| 1267 | fi |
| 1268 | } |
| 1269 | |
| 1270 | # --no-group-separator |
| 1271 | name="--no-group-separator: suppress separator" |
| 1272 | should_run "$name" && { |
| 1273 | log_test "$name" |
| 1274 | local out |
| 1275 | out=$("$FERP" -C1 --no-group-separator "MATCH" "$FIXTURES/context.txt") |
| 1276 | if ! echo "$out" | grep -q "^--$"; then |
| 1277 | pass "$name" |
| 1278 | else |
| 1279 | fail "$name" "separator should be suppressed, got: $out" |
| 1280 | fi |
| 1281 | } |
| 1282 | |
| 1283 | # Context with line numbers |
| 1284 | name="-n with context: context lines marked differently" |
| 1285 | should_run "$name" && { |
| 1286 | log_test "$name" |
| 1287 | local out |
| 1288 | out=$("$FERP" -nB1 "MATCH LINE" "$FIXTURES/context.txt") |
| 1289 | # Context lines should use - separator, match lines use : |
| 1290 | if echo "$out" | grep -qE "^[0-9]+-" && echo "$out" | grep -qE "^[0-9]+:"; then |
| 1291 | pass "$name" |
| 1292 | else |
| 1293 | fail "$name" "got: $out" |
| 1294 | fi |
| 1295 | } |
| 1296 | } |
| 1297 | |
| 1298 | #------------------------------------------------------------------------------ |
| 1299 | # FILE SELECTION TESTS (-r, -R, --include, --exclude) |
| 1300 | #------------------------------------------------------------------------------ |
| 1301 | |
| 1302 | test_file_selection() { |
| 1303 | section "File Selection Tests (-r, -R, --include, --exclude, -d, -D)" |
| 1304 | local name |
| 1305 | |
| 1306 | # -r / --recursive |
| 1307 | name="-r: recursive search" |
| 1308 | should_run "$name" && { |
| 1309 | log_test "$name" |
| 1310 | local out |
| 1311 | out=$("$FERP" -r "match" "$FIXTURES" | wc -l) |
| 1312 | # Should find matches in nested files |
| 1313 | if [[ "$out" -ge 4 ]]; then |
| 1314 | pass "$name" |
| 1315 | else |
| 1316 | fail "$name" "expected at least 4 matches, got $out" |
| 1317 | fi |
| 1318 | } |
| 1319 | |
| 1320 | name="-r: searches subdirectories" |
| 1321 | should_run "$name" && { |
| 1322 | log_test "$name" |
| 1323 | local out |
| 1324 | out=$("$FERP" -r "nested" "$FIXTURES") |
| 1325 | if echo "$out" | grep -q "subdir1" && echo "$out" | grep -q "subdir2"; then |
| 1326 | pass "$name" |
| 1327 | else |
| 1328 | fail "$name" "got: $out" |
| 1329 | fi |
| 1330 | } |
| 1331 | |
| 1332 | name="--recursive: long form works" |
| 1333 | should_run "$name" && { |
| 1334 | log_test "$name" |
| 1335 | local out |
| 1336 | out=$("$FERP" --recursive "match" "$FIXTURES" | wc -l) |
| 1337 | if [[ "$out" -ge 4 ]]; then |
| 1338 | pass "$name" |
| 1339 | else |
| 1340 | fail "$name" |
| 1341 | fi |
| 1342 | } |
| 1343 | |
| 1344 | # --include |
| 1345 | name="--include: filter by glob" |
| 1346 | should_run "$name" && { |
| 1347 | log_test "$name" |
| 1348 | local out |
| 1349 | out=$("$FERP" -r --include="*.txt" "match" "$FIXTURES") |
| 1350 | # Should only show .txt files |
| 1351 | if echo "$out" | grep -q ".txt:" && ! echo "$out" | grep -q ".f90:"; then |
| 1352 | pass "$name" |
| 1353 | else |
| 1354 | fail "$name" "got: $out" |
| 1355 | fi |
| 1356 | } |
| 1357 | |
| 1358 | name="--include: *.f90 filter" |
| 1359 | should_run "$name" && { |
| 1360 | log_test "$name" |
| 1361 | local out |
| 1362 | out=$("$FERP" -r --include="*.f90" "match" "$FIXTURES") |
| 1363 | if echo "$out" | grep -q ".f90:"; then |
| 1364 | pass "$name" |
| 1365 | else |
| 1366 | fail "$name" "got: $out" |
| 1367 | fi |
| 1368 | } |
| 1369 | |
| 1370 | # --exclude |
| 1371 | name="--exclude: skip matching files" |
| 1372 | should_run "$name" && { |
| 1373 | log_test "$name" |
| 1374 | local out |
| 1375 | out=$("$FERP" -r --exclude="*multifile*" "match" "$FIXTURES") |
| 1376 | if ! echo "$out" | grep -q "multifile"; then |
| 1377 | pass "$name" |
| 1378 | else |
| 1379 | fail "$name" "got: $out" |
| 1380 | fi |
| 1381 | } |
| 1382 | |
| 1383 | # --exclude-dir |
| 1384 | name="--exclude-dir: skip directories" |
| 1385 | should_run "$name" && { |
| 1386 | log_test "$name" |
| 1387 | local out |
| 1388 | out=$("$FERP" -r --exclude-dir="skipme" "match" "$FIXTURES") |
| 1389 | if ! echo "$out" | grep -q "skipme"; then |
| 1390 | pass "$name" |
| 1391 | else |
| 1392 | fail "$name" "got: $out" |
| 1393 | fi |
| 1394 | } |
| 1395 | |
| 1396 | name="--exclude-dir: multiple exclusions" |
| 1397 | should_run "$name" && { |
| 1398 | log_test "$name" |
| 1399 | local out |
| 1400 | out=$("$FERP" -r --exclude-dir="skipme" --exclude-dir="subdir1" "match" "$FIXTURES") |
| 1401 | if ! echo "$out" | grep -q "skipme" && ! echo "$out" | grep -q "subdir1"; then |
| 1402 | pass "$name" |
| 1403 | else |
| 1404 | fail "$name" "got: $out" |
| 1405 | fi |
| 1406 | } |
| 1407 | |
| 1408 | # -d / --directories |
| 1409 | name="-d skip: skip directories" |
| 1410 | should_run "$name" && { |
| 1411 | log_test "$name" |
| 1412 | # When given a directory without -r, -d skip should skip it silently |
| 1413 | local exit_code=0 |
| 1414 | "$FERP" -d skip "test" "$FIXTURES" >/dev/null 2>&1 || exit_code=$? |
| 1415 | if [[ "$exit_code" -eq 1 ]]; then # No matches since dir was skipped |
| 1416 | pass "$name" |
| 1417 | else |
| 1418 | fail "$name" "expected exit 1, got $exit_code" |
| 1419 | fi |
| 1420 | } |
| 1421 | |
| 1422 | name="--directories=recurse: same as -r" |
| 1423 | should_run "$name" && { |
| 1424 | log_test "$name" |
| 1425 | local out |
| 1426 | out=$("$FERP" --directories=recurse "nested" "$FIXTURES" | wc -l) |
| 1427 | if [[ "$out" -ge 2 ]]; then |
| 1428 | pass "$name" |
| 1429 | else |
| 1430 | fail "$name" |
| 1431 | fi |
| 1432 | } |
| 1433 | } |
| 1434 | |
| 1435 | #------------------------------------------------------------------------------ |
| 1436 | # MULTI-PATTERN TESTS (-e, -f) |
| 1437 | #------------------------------------------------------------------------------ |
| 1438 | |
| 1439 | test_multi_pattern() { |
| 1440 | section "Multi-Pattern Tests (-e, -f)" |
| 1441 | local name |
| 1442 | |
| 1443 | # -e / --regexp |
| 1444 | name="-e: single explicit pattern" |
| 1445 | should_run "$name" && { |
| 1446 | log_test "$name" |
| 1447 | local out |
| 1448 | out=$("$FERP" -e "hello" "$FIXTURES/simple.txt" | wc -l) |
| 1449 | if [[ "$out" -eq 2 ]]; then |
| 1450 | pass "$name" |
| 1451 | else |
| 1452 | fail "$name" "expected 2 lines, got $out" |
| 1453 | fi |
| 1454 | } |
| 1455 | |
| 1456 | name="-e: multiple patterns (OR)" |
| 1457 | should_run "$name" && { |
| 1458 | log_test "$name" |
| 1459 | local out |
| 1460 | out=$("$FERP" -e "hello" -e "goodbye" "$FIXTURES/simple.txt") |
| 1461 | if echo "$out" | grep -q "hello" && echo "$out" | grep -q "goodbye"; then |
| 1462 | pass "$name" |
| 1463 | else |
| 1464 | fail "$name" "got: $out" |
| 1465 | fi |
| 1466 | } |
| 1467 | |
| 1468 | name="-e: three patterns" |
| 1469 | should_run "$name" && { |
| 1470 | log_test "$name" |
| 1471 | local out |
| 1472 | out=$("$FERP" -e "hello" -e "goodbye" -e "foo" "$FIXTURES/simple.txt" | wc -l) |
| 1473 | if [[ "$out" -eq 4 ]]; then # 2 hello + 1 goodbye + 1 foo |
| 1474 | pass "$name" |
| 1475 | else |
| 1476 | fail "$name" "expected 4 lines, got $out" |
| 1477 | fi |
| 1478 | } |
| 1479 | |
| 1480 | name="--regexp=PATTERN: long form with =" |
| 1481 | should_run "$name" && { |
| 1482 | log_test "$name" |
| 1483 | local out |
| 1484 | out=$("$FERP" --regexp="hello" "$FIXTURES/simple.txt" | wc -l) |
| 1485 | if [[ "$out" -eq 2 ]]; then |
| 1486 | pass "$name" |
| 1487 | else |
| 1488 | fail "$name" |
| 1489 | fi |
| 1490 | } |
| 1491 | |
| 1492 | # -f / --file |
| 1493 | name="-f: patterns from file" |
| 1494 | should_run "$name" && { |
| 1495 | log_test "$name" |
| 1496 | local out |
| 1497 | out=$("$FERP" -f "$FIXTURES/patterns.txt" "$FIXTURES/simple.txt") |
| 1498 | # patterns.txt contains: hello, world, test |
| 1499 | if echo "$out" | grep -q "hello" && echo "$out" | grep -q "world"; then |
| 1500 | pass "$name" |
| 1501 | else |
| 1502 | fail "$name" "got: $out" |
| 1503 | fi |
| 1504 | } |
| 1505 | |
| 1506 | name="-f: combined with -e" |
| 1507 | should_run "$name" && { |
| 1508 | log_test "$name" |
| 1509 | local out |
| 1510 | out=$("$FERP" -f "$FIXTURES/patterns.txt" -e "foo" "$FIXTURES/simple.txt") |
| 1511 | if echo "$out" | grep -q "hello" && echo "$out" | grep -q "foo"; then |
| 1512 | pass "$name" |
| 1513 | else |
| 1514 | fail "$name" "got: $out" |
| 1515 | fi |
| 1516 | } |
| 1517 | |
| 1518 | name="-f: nonexistent file error" |
| 1519 | should_run "$name" && { |
| 1520 | log_test "$name" |
| 1521 | if run_ferp_expect 2 -f "/nonexistent/patterns.txt" "test" "$FIXTURES/simple.txt"; then |
| 1522 | pass "$name" |
| 1523 | else |
| 1524 | fail "$name" |
| 1525 | fi |
| 1526 | } |
| 1527 | } |
| 1528 | |
| 1529 | #------------------------------------------------------------------------------ |
| 1530 | # BINARY FILE HANDLING TESTS (-a, -I, --binary-files) |
| 1531 | #------------------------------------------------------------------------------ |
| 1532 | |
| 1533 | test_binary_handling() { |
| 1534 | section "Binary File Handling Tests (-a, -I, --binary-files)" |
| 1535 | local name |
| 1536 | |
| 1537 | # Default binary detection |
| 1538 | name="binary: detected and shows message" |
| 1539 | should_run "$name" && { |
| 1540 | log_test "$name" |
| 1541 | local out |
| 1542 | out=$("$FERP" "text" "$FIXTURES/binary.bin" 2>/dev/null) || true |
| 1543 | if echo "$out" | grep -q "Binary file"; then |
| 1544 | pass "$name" |
| 1545 | else |
| 1546 | fail "$name" "got: $out" |
| 1547 | fi |
| 1548 | } |
| 1549 | |
| 1550 | # -a / --text |
| 1551 | name="-a: treat binary as text" |
| 1552 | should_run "$name" && { |
| 1553 | log_test "$name" |
| 1554 | local out |
| 1555 | out=$("$FERP" -a "match" "$FIXTURES/binary.bin" 2>/dev/null) || true |
| 1556 | if echo "$out" | grep -q "match"; then |
| 1557 | pass "$name" |
| 1558 | else |
| 1559 | fail "$name" "got: $out" |
| 1560 | fi |
| 1561 | } |
| 1562 | |
| 1563 | name="--text: long form works" |
| 1564 | should_run "$name" && { |
| 1565 | log_test "$name" |
| 1566 | local out |
| 1567 | out=$("$FERP" --text "match" "$FIXTURES/binary.bin" 2>/dev/null) || true |
| 1568 | if echo "$out" | grep -q "match"; then |
| 1569 | pass "$name" |
| 1570 | else |
| 1571 | fail "$name" |
| 1572 | fi |
| 1573 | } |
| 1574 | |
| 1575 | # -I (ignore binary / without-match) |
| 1576 | name="-I: skip binary files" |
| 1577 | should_run "$name" && { |
| 1578 | log_test "$name" |
| 1579 | if run_ferp_expect 1 -I "text" "$FIXTURES/binary.bin"; then |
| 1580 | pass "$name" |
| 1581 | else |
| 1582 | fail "$name" |
| 1583 | fi |
| 1584 | } |
| 1585 | |
| 1586 | # --binary-files |
| 1587 | name="--binary-files=text: same as -a" |
| 1588 | should_run "$name" && { |
| 1589 | log_test "$name" |
| 1590 | local out |
| 1591 | out=$("$FERP" --binary-files=text "match" "$FIXTURES/binary.bin" 2>/dev/null) || true |
| 1592 | if echo "$out" | grep -q "match"; then |
| 1593 | pass "$name" |
| 1594 | else |
| 1595 | fail "$name" |
| 1596 | fi |
| 1597 | } |
| 1598 | |
| 1599 | name="--binary-files=without-match: same as -I" |
| 1600 | should_run "$name" && { |
| 1601 | log_test "$name" |
| 1602 | if run_ferp_expect 1 --binary-files=without-match "text" "$FIXTURES/binary.bin"; then |
| 1603 | pass "$name" |
| 1604 | else |
| 1605 | fail "$name" |
| 1606 | fi |
| 1607 | } |
| 1608 | |
| 1609 | name="--binary-files=binary: show message (default)" |
| 1610 | should_run "$name" && { |
| 1611 | log_test "$name" |
| 1612 | local out |
| 1613 | out=$("$FERP" --binary-files=binary "text" "$FIXTURES/binary.bin" 2>/dev/null) || true |
| 1614 | if echo "$out" | grep -q "Binary file"; then |
| 1615 | pass "$name" |
| 1616 | else |
| 1617 | fail "$name" |
| 1618 | fi |
| 1619 | } |
| 1620 | } |
| 1621 | |
| 1622 | #------------------------------------------------------------------------------ |
| 1623 | # MISCELLANEOUS TESTS (--max-count, --label, -z, --color) |
| 1624 | #------------------------------------------------------------------------------ |
| 1625 | |
| 1626 | test_misc_flags() { |
| 1627 | section "Miscellaneous Flag Tests (-m, --label, -z, --color)" |
| 1628 | local name |
| 1629 | |
| 1630 | # -m / --max-count |
| 1631 | name="-m: limit match count" |
| 1632 | should_run "$name" && { |
| 1633 | log_test "$name" |
| 1634 | local out |
| 1635 | out=$("$FERP" -m2 "line" "$FIXTURES/numbers.txt" | wc -l) |
| 1636 | if [[ "$out" -eq 2 ]]; then |
| 1637 | pass "$name" |
| 1638 | else |
| 1639 | fail "$name" "expected 2 lines, got $out" |
| 1640 | fi |
| 1641 | } |
| 1642 | |
| 1643 | name="-m1: stop after first match" |
| 1644 | should_run "$name" && { |
| 1645 | log_test "$name" |
| 1646 | local out |
| 1647 | out=$("$FERP" -m1 "line" "$FIXTURES/numbers.txt") |
| 1648 | if [[ "$out" == "line 1" ]]; then |
| 1649 | pass "$name" |
| 1650 | else |
| 1651 | fail "$name" "got: $out" |
| 1652 | fi |
| 1653 | } |
| 1654 | |
| 1655 | name="--max-count=N: long form" |
| 1656 | should_run "$name" && { |
| 1657 | log_test "$name" |
| 1658 | local out |
| 1659 | out=$("$FERP" --max-count=3 "line" "$FIXTURES/numbers.txt" | wc -l) |
| 1660 | if [[ "$out" -eq 3 ]]; then |
| 1661 | pass "$name" |
| 1662 | else |
| 1663 | fail "$name" |
| 1664 | fi |
| 1665 | } |
| 1666 | |
| 1667 | name="-m with -c: count respects limit" |
| 1668 | should_run "$name" && { |
| 1669 | log_test "$name" |
| 1670 | local out |
| 1671 | out=$("$FERP" -m5 -c "line" "$FIXTURES/numbers.txt") |
| 1672 | if [[ "$out" == "5" ]]; then |
| 1673 | pass "$name" |
| 1674 | else |
| 1675 | fail "$name" "got: $out" |
| 1676 | fi |
| 1677 | } |
| 1678 | |
| 1679 | # --label |
| 1680 | name="--label: custom stdin label" |
| 1681 | should_run "$name" && { |
| 1682 | log_test "$name" |
| 1683 | local out |
| 1684 | out=$(echo "hello world" | "$FERP" -H --label="custom_input" "hello") |
| 1685 | if echo "$out" | grep -q "custom_input:"; then |
| 1686 | pass "$name" |
| 1687 | else |
| 1688 | fail "$name" "got: $out" |
| 1689 | fi |
| 1690 | } |
| 1691 | |
| 1692 | # -z / --null-data |
| 1693 | name="-z: null-terminated input" |
| 1694 | should_run "$name" && { |
| 1695 | log_test "$name" |
| 1696 | local out |
| 1697 | out=$(printf 'hello\0world\0' | "$FERP" -z "hello") |
| 1698 | if [[ -n "$out" ]]; then |
| 1699 | pass "$name" |
| 1700 | else |
| 1701 | fail "$name" |
| 1702 | fi |
| 1703 | } |
| 1704 | |
| 1705 | name="--null-data: long form works" |
| 1706 | should_run "$name" && { |
| 1707 | log_test "$name" |
| 1708 | local out |
| 1709 | out=$(printf 'test\0line\0' | "$FERP" --null-data "test") |
| 1710 | if [[ -n "$out" ]]; then |
| 1711 | pass "$name" |
| 1712 | else |
| 1713 | fail "$name" |
| 1714 | fi |
| 1715 | } |
| 1716 | |
| 1717 | # --color |
| 1718 | name="--color=always: ANSI codes in output" |
| 1719 | should_run "$name" && { |
| 1720 | log_test "$name" |
| 1721 | local out |
| 1722 | out=$("$FERP" --color=always "hello" "$FIXTURES/simple.txt") |
| 1723 | if echo "$out" | grep -q $'\033\['; then |
| 1724 | pass "$name" |
| 1725 | else |
| 1726 | fail "$name" "expected ANSI escape codes" |
| 1727 | fi |
| 1728 | } |
| 1729 | |
| 1730 | name="--color=never: no ANSI codes" |
| 1731 | should_run "$name" && { |
| 1732 | log_test "$name" |
| 1733 | local out |
| 1734 | out=$("$FERP" --color=never "hello" "$FIXTURES/simple.txt") |
| 1735 | if ! echo "$out" | grep -q $'\033\['; then |
| 1736 | pass "$name" |
| 1737 | else |
| 1738 | fail "$name" "expected no ANSI codes" |
| 1739 | fi |
| 1740 | } |
| 1741 | |
| 1742 | name="--color=auto: no ANSI when piped" |
| 1743 | should_run "$name" && { |
| 1744 | log_test "$name" |
| 1745 | local out |
| 1746 | out=$("$FERP" --color=auto "hello" "$FIXTURES/simple.txt" | cat) |
| 1747 | if ! echo "$out" | grep -q $'\033\['; then |
| 1748 | pass "$name" |
| 1749 | else |
| 1750 | fail "$name" "expected no ANSI codes when piped" |
| 1751 | fi |
| 1752 | } |
| 1753 | |
| 1754 | # --line-buffered |
| 1755 | name="--line-buffered: accepted" |
| 1756 | should_run "$name" && { |
| 1757 | log_test "$name" |
| 1758 | if "$FERP" --line-buffered "hello" "$FIXTURES/simple.txt" >/dev/null 2>&1; then |
| 1759 | pass "$name" |
| 1760 | else |
| 1761 | fail "$name" |
| 1762 | fi |
| 1763 | } |
| 1764 | } |
| 1765 | |
| 1766 | #------------------------------------------------------------------------------ |
| 1767 | # EXIT CODE TESTS |
| 1768 | #------------------------------------------------------------------------------ |
| 1769 | |
| 1770 | test_exit_codes() { |
| 1771 | section "Exit Code Tests" |
| 1772 | local name |
| 1773 | |
| 1774 | name="exit 0: match found" |
| 1775 | should_run "$name" && { |
| 1776 | log_test "$name" |
| 1777 | if run_ferp_expect 0 "hello" "$FIXTURES/simple.txt"; then |
| 1778 | pass "$name" |
| 1779 | else |
| 1780 | fail "$name" |
| 1781 | fi |
| 1782 | } |
| 1783 | |
| 1784 | name="exit 1: no match found" |
| 1785 | should_run "$name" && { |
| 1786 | log_test "$name" |
| 1787 | if run_ferp_expect 1 "nonexistent_pattern_xyz" "$FIXTURES/simple.txt"; then |
| 1788 | pass "$name" |
| 1789 | else |
| 1790 | fail "$name" |
| 1791 | fi |
| 1792 | } |
| 1793 | |
| 1794 | name="exit 2: invalid regex" |
| 1795 | should_run "$name" && { |
| 1796 | log_test "$name" |
| 1797 | if run_ferp_expect 2 "[invalid" "$FIXTURES/simple.txt"; then |
| 1798 | pass "$name" |
| 1799 | else |
| 1800 | fail "$name" |
| 1801 | fi |
| 1802 | } |
| 1803 | |
| 1804 | name="exit 2: missing pattern" |
| 1805 | should_run "$name" && { |
| 1806 | log_test "$name" |
| 1807 | if run_ferp_expect 2; then |
| 1808 | pass "$name" |
| 1809 | else |
| 1810 | fail "$name" |
| 1811 | fi |
| 1812 | } |
| 1813 | } |
| 1814 | |
| 1815 | #------------------------------------------------------------------------------ |
| 1816 | # EDGE CASES AND REGRESSIONS |
| 1817 | #------------------------------------------------------------------------------ |
| 1818 | |
| 1819 | test_edge_cases() { |
| 1820 | section "Edge Cases and Regressions" |
| 1821 | local name |
| 1822 | |
| 1823 | name="empty pattern matches all lines" |
| 1824 | should_run "$name" && { |
| 1825 | log_test "$name" |
| 1826 | local out |
| 1827 | out=$("$FERP" "" "$FIXTURES/numbers.txt" | wc -l) |
| 1828 | if [[ "$out" -eq 10 ]]; then |
| 1829 | pass "$name" |
| 1830 | else |
| 1831 | fail "$name" "expected 10 lines, got $out" |
| 1832 | fi |
| 1833 | } |
| 1834 | |
| 1835 | name="empty file no crash" |
| 1836 | should_run "$name" && { |
| 1837 | log_test "$name" |
| 1838 | local tmpfile |
| 1839 | tmpfile=$(mktemp) |
| 1840 | if run_ferp_expect 1 "test" "$tmpfile"; then |
| 1841 | pass "$name" |
| 1842 | else |
| 1843 | fail "$name" |
| 1844 | fi |
| 1845 | rm -f "$tmpfile" |
| 1846 | } |
| 1847 | |
| 1848 | name="very long line handling" |
| 1849 | should_run "$name" && { |
| 1850 | log_test "$name" |
| 1851 | local tmpfile long_line |
| 1852 | tmpfile=$(mktemp) |
| 1853 | long_line=$(printf 'a%.0s' {1..10000}) |
| 1854 | echo "${long_line}MATCH${long_line}" > "$tmpfile" |
| 1855 | if "$FERP" "MATCH" "$tmpfile" | grep -q "MATCH"; then |
| 1856 | pass "$name" |
| 1857 | else |
| 1858 | fail "$name" |
| 1859 | fi |
| 1860 | rm -f "$tmpfile" |
| 1861 | } |
| 1862 | |
| 1863 | name="special regex chars in pattern" |
| 1864 | should_run "$name" && { |
| 1865 | log_test "$name" |
| 1866 | if "$FERP" '\$' "$FIXTURES/special_chars.txt" | grep -q "dollar"; then |
| 1867 | pass "$name" |
| 1868 | else |
| 1869 | fail "$name" |
| 1870 | fi |
| 1871 | } |
| 1872 | |
| 1873 | name="multiple files with mixed results" |
| 1874 | should_run "$name" && { |
| 1875 | log_test "$name" |
| 1876 | local out |
| 1877 | out=$("$FERP" "match" "$FIXTURES/multifile1.txt" "$FIXTURES/multifile3.txt" "$FIXTURES/multifile2.txt") |
| 1878 | # Should show results from files that have matches |
| 1879 | if echo "$out" | grep -q "multifile1" && echo "$out" | grep -q "multifile2"; then |
| 1880 | pass "$name" |
| 1881 | else |
| 1882 | fail "$name" "got: $out" |
| 1883 | fi |
| 1884 | } |
| 1885 | |
| 1886 | name="stdin input" |
| 1887 | should_run "$name" && { |
| 1888 | log_test "$name" |
| 1889 | local out |
| 1890 | out=$(echo "test line" | "$FERP" "test") |
| 1891 | if [[ "$out" == "test line" ]]; then |
| 1892 | pass "$name" |
| 1893 | else |
| 1894 | fail "$name" "got: $out" |
| 1895 | fi |
| 1896 | } |
| 1897 | |
| 1898 | name="-- ends option parsing" |
| 1899 | should_run "$name" && { |
| 1900 | log_test "$name" |
| 1901 | # Pattern starting with - should work after -- |
| 1902 | local out |
| 1903 | out=$(echo "-test-" | "$FERP" -- "-test-") |
| 1904 | if [[ "$out" == "-test-" ]]; then |
| 1905 | pass "$name" |
| 1906 | else |
| 1907 | fail "$name" "got: $out" |
| 1908 | fi |
| 1909 | } |
| 1910 | |
| 1911 | name="combined short options -inv" |
| 1912 | should_run "$name" && { |
| 1913 | log_test "$name" |
| 1914 | local out |
| 1915 | out=$("$FERP" -inv "HELLO" "$FIXTURES/simple.txt" | wc -l) |
| 1916 | # Case insensitive, inverted, with line numbers |
| 1917 | if [[ "$out" -eq 4 ]]; then |
| 1918 | pass "$name" |
| 1919 | else |
| 1920 | fail "$name" "expected 4 lines, got $out" |
| 1921 | fi |
| 1922 | } |
| 1923 | } |
| 1924 | |
| 1925 | #------------------------------------------------------------------------------ |
| 1926 | # Main |
| 1927 | #------------------------------------------------------------------------------ |
| 1928 | |
| 1929 | main() { |
| 1930 | echo "" |
| 1931 | echo -e "${BLUE}╔════════════════════════════════════════════════════════════════════════════╗${NC}" |
| 1932 | echo -e "${BLUE}║ FERP Integration Test Suite ║${NC}" |
| 1933 | echo -e "${BLUE}╚════════════════════════════════════════════════════════════════════════════╝${NC}" |
| 1934 | echo "" |
| 1935 | echo "ferp binary: $FERP" |
| 1936 | echo "Fixtures: $FIXTURES" |
| 1937 | if [[ "$COMPARE_GREP" == "true" ]]; then |
| 1938 | echo "Mode: Comparing against GNU grep" |
| 1939 | fi |
| 1940 | if [[ -n "$FILTER" ]]; then |
| 1941 | echo "Filter: $FILTER" |
| 1942 | fi |
| 1943 | |
| 1944 | # Run all test categories |
| 1945 | test_pattern_types |
| 1946 | test_matching_control |
| 1947 | test_output_control |
| 1948 | test_line_prefix |
| 1949 | test_context_control |
| 1950 | test_file_selection |
| 1951 | test_multi_pattern |
| 1952 | test_binary_handling |
| 1953 | test_misc_flags |
| 1954 | test_exit_codes |
| 1955 | test_edge_cases |
| 1956 | |
| 1957 | # Summary |
| 1958 | echo "" |
| 1959 | echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" |
| 1960 | echo -e "${BLUE} Summary${NC}" |
| 1961 | echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" |
| 1962 | echo "" |
| 1963 | echo -e " Tests run: ${TESTS_RUN}" |
| 1964 | echo -e " ${GREEN}Passed: ${TESTS_PASSED}${NC}" |
| 1965 | echo -e " ${RED}Failed: ${TESTS_FAILED}${NC}" |
| 1966 | echo -e " ${YELLOW}Skipped: ${TESTS_SKIPPED}${NC}" |
| 1967 | echo "" |
| 1968 | |
| 1969 | if [[ "$TESTS_FAILED" -gt 0 ]]; then |
| 1970 | echo -e "${RED}Some tests failed!${NC}" |
| 1971 | exit 1 |
| 1972 | else |
| 1973 | echo -e "${GREEN}All tests passed!${NC}" |
| 1974 | exit 0 |
| 1975 | fi |
| 1976 | } |
| 1977 | |
| 1978 | main "$@" |