fortrangoingonforty/ferp / 5b04ac6

Browse files

test fixtures, extended grep tests

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
5b04ac617113dd7e35685cb77678ae5694680981
Parents
56543cc
Tree
1e6baf8

33 changed files

StatusFile+-
A tests/extended_fixtures/backref.txt 11 0
A tests/extended_fixtures/basic.txt 7 0
A tests/extended_fixtures/binary.bin bin
A tests/extended_fixtures/context.txt 10 0
A tests/extended_fixtures/empty.txt 0 0
A tests/extended_fixtures/empty_lines.txt 5 0
A tests/extended_fixtures/empty_patterns.txt 0 0
A tests/extended_fixtures/line_endings.txt 3 0
A tests/extended_fixtures/longline.txt 1 0
A tests/extended_fixtures/many_matches.txt 1 0
A tests/extended_fixtures/multi1.txt 1 0
A tests/extended_fixtures/multi2.txt 1 0
A tests/extended_fixtures/multi3.txt 1 0
A tests/extended_fixtures/multi_pattern.txt 6 0
A tests/extended_fixtures/no_newline.txt 1 0
A tests/extended_fixtures/null_data.txt bin
A tests/extended_fixtures/only_newlines.txt 3 0
A tests/extended_fixtures/patterns.txt 3 0
A tests/extended_fixtures/patterns_upper.txt 2 0
A tests/extended_fixtures/recursive/code.c 1 0
A tests/extended_fixtures/recursive/code.h 1 0
A tests/extended_fixtures/recursive/code.o 1 0
A tests/extended_fixtures/recursive/level1/file1.txt 1 0
A tests/extended_fixtures/recursive/level1/file2.txt 1 0
A tests/extended_fixtures/recursive/level1/level2/deep.txt 1 0
A tests/extended_fixtures/recursive/link_to_basic.txt 1 0
A tests/extended_fixtures/recursive/root.txt 1 0
A tests/extended_fixtures/single_char.txt 1 0
A tests/extended_fixtures/special.txt 15 0
A tests/extended_fixtures/tabs.txt 3 0
A tests/extended_fixtures/unicode.txt 7 0
A tests/extended_fixtures/whitespace_lines.txt 4 0
A tests/extended_grep_test.sh 1381 0
tests/extended_fixtures/backref.txtadded
@@ -0,0 +1,11 @@
1
+aa
2
+bb
3
+ab
4
+aaa
5
+abba
6
+abab
7
+noon
8
+deed
9
+level
10
+hello
11
+abcabc
tests/extended_fixtures/basic.txtadded
@@ -0,0 +1,7 @@
1
+hello world
2
+Hello World
3
+HELLO WORLD
4
+goodbye world
5
+the quick brown fox
6
+testing 123 testing
7
+line with hello in middle
tests/extended_fixtures/binary.binadded
Binary file changed.
tests/extended_fixtures/context.txtadded
@@ -0,0 +1,10 @@
1
+line 1
2
+line 2
3
+MATCH A
4
+line 4
5
+MATCH B
6
+line 6
7
+line 7
8
+line 8
9
+MATCH C
10
+line 10
tests/extended_fixtures/empty.txtadded
tests/extended_fixtures/empty_lines.txtadded
@@ -0,0 +1,5 @@
1
+first
2
+
3
+second
4
+
5
+third
tests/extended_fixtures/empty_patterns.txtadded
tests/extended_fixtures/line_endings.txtadded
@@ -0,0 +1,3 @@
1
+unix line
2
+windows line
3
+old mac line
final line
tests/extended_fixtures/longline.txtadded
@@ -0,0 +1,1 @@
1
+start xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx MATCH yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy end
tests/extended_fixtures/many_matches.txtadded
@@ -0,0 +1,1 @@
1
+match match match match match
tests/extended_fixtures/multi1.txtadded
@@ -0,0 +1,1 @@
1
+has pattern here
tests/extended_fixtures/multi2.txtadded
@@ -0,0 +1,1 @@
1
+nothing special
tests/extended_fixtures/multi3.txtadded
@@ -0,0 +1,1 @@
1
+pattern again
tests/extended_fixtures/multi_pattern.txtadded
@@ -0,0 +1,6 @@
1
+apple pie
2
+banana bread
3
+cherry cake
4
+apple sauce
5
+grape juice
6
+banana split
tests/extended_fixtures/no_newline.txtadded
@@ -0,0 +1,1 @@
1
+no trailing newline
tests/extended_fixtures/null_data.txtadded
Binary file changed.
tests/extended_fixtures/only_newlines.txtadded
@@ -0,0 +1,3 @@
1
+
2
+
3
+
tests/extended_fixtures/patterns.txtadded
@@ -0,0 +1,3 @@
1
+apple
2
+cherry
3
+grape
tests/extended_fixtures/patterns_upper.txtadded
@@ -0,0 +1,2 @@
1
+APPLE
2
+CHERRY
tests/extended_fixtures/recursive/code.cadded
@@ -0,0 +1,1 @@
1
+match in c file
tests/extended_fixtures/recursive/code.hadded
@@ -0,0 +1,1 @@
1
+match in header
tests/extended_fixtures/recursive/code.oadded
@@ -0,0 +1,1 @@
1
+compiled code
tests/extended_fixtures/recursive/level1/file1.txtadded
@@ -0,0 +1,1 @@
1
+match in level1
tests/extended_fixtures/recursive/level1/file2.txtadded
@@ -0,0 +1,1 @@
1
+no match here
tests/extended_fixtures/recursive/level1/level2/deep.txtadded
@@ -0,0 +1,1 @@
1
+match in level2
tests/extended_fixtures/recursive/root.txtadded
@@ -0,0 +1,1 @@
1
+match in root
tests/extended_fixtures/single_char.txtadded
@@ -0,0 +1,1 @@
1
+x
tests/extended_fixtures/special.txtadded
@@ -0,0 +1,15 @@
1
+price is $100
2
+50% off
3
+path/to/file
4
+array[0]
5
+func()
6
+a+b=c
7
+a*b
8
+hello.world
9
+start^here
10
+end$there
11
+back\slash
12
+pipe|char
13
+question?
14
+curly{brace}
15
+(parens)
tests/extended_fixtures/tabs.txtadded
@@ -0,0 +1,3 @@
1
+short	match here
2
+verylongfilename	match here
3
+x	match here
tests/extended_fixtures/unicode.txtadded
@@ -0,0 +1,7 @@
1
+cafe
2
+café
3
+résumé
4
+naïve
5
+NAÏVE
6
+Ñoño
7
+日本語
tests/extended_fixtures/whitespace_lines.txtadded
@@ -0,0 +1,4 @@
1
+normal line
2
+
3
+
4
+normal again
tests/extended_grep_test.shadded
1381 lines changed — click to load
@@ -0,0 +1,1381 @@
1
+#!/bin/bash
2
+#
3
+# FERP Extended Grep Test Suite
4
+# Tests edge cases, corner cases, and obscure grep behaviors
5
+#
6
+# This test suite covers:
7
+# 1. Multiple pattern flags (-e, -f)
8
+# 2. Backreferences in BRE
9
+# 3. Null-data mode (-z)
10
+# 4. Binary file handling
11
+# 5. Recursive directory options
12
+# 6. Context edge cases
13
+# 7. Output format options (-T, -Z, --label)
14
+# 8. Regex edge cases (empty matches, unicode, long patterns)
15
+# 9. BRE vs ERE differences
16
+# 10. Error handling scenarios
17
+# 11. Unusual input (long lines, mixed line endings, etc.)
18
+# 12. Flag interaction edge cases
19
+# 13. PCRE-specific features (-P)
20
+#
21
+# Usage: ./extended_grep_test.sh [--verbose] [--stop-on-fail] [--filter PATTERN]
22
+#
23
+
24
+set -uo pipefail
25
+
26
+#------------------------------------------------------------------------------
27
+# Configuration
28
+#------------------------------------------------------------------------------
29
+
30
+RED='\033[0;31m'
31
+GREEN='\033[0;32m'
32
+YELLOW='\033[1;33m'
33
+BLUE='\033[0;34m'
34
+CYAN='\033[0;36m'
35
+NC='\033[0m'
36
+
37
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
38
+FERP="${SCRIPT_DIR}/../ferp"
39
+FIXTURES="${SCRIPT_DIR}/extended_fixtures"
40
+
41
+# Test counters
42
+TESTS_RUN=0
43
+TESTS_PASSED=0
44
+TESTS_FAILED=0
45
+TESTS_SKIPPED=0
46
+
47
+# Options
48
+VERBOSE=false
49
+STOP_ON_FAIL=false
50
+FILTER=""
51
+
52
+# Track failures for summary
53
+declare -a FAILED_TESTS=()
54
+
55
+#------------------------------------------------------------------------------
56
+# Argument Parsing
57
+#------------------------------------------------------------------------------
58
+
59
+while [[ $# -gt 0 ]]; do
60
+    case $1 in
61
+        --verbose|-v)
62
+            VERBOSE=true
63
+            shift
64
+            ;;
65
+        --stop-on-fail|-x)
66
+            STOP_ON_FAIL=true
67
+            shift
68
+            ;;
69
+        --filter|-f)
70
+            FILTER="$2"
71
+            shift 2
72
+            ;;
73
+        --help|-h)
74
+            echo "Usage: $0 [--verbose] [--stop-on-fail] [--filter PATTERN]"
75
+            exit 0
76
+            ;;
77
+        *)
78
+            echo "Unknown option: $1"
79
+            exit 1
80
+            ;;
81
+    esac
82
+done
83
+
84
+#------------------------------------------------------------------------------
85
+# Test Framework
86
+#------------------------------------------------------------------------------
87
+
88
+log() {
89
+    if [[ "$VERBOSE" == "true" ]]; then
90
+        echo -e "$1"
91
+    fi
92
+}
93
+
94
+section() {
95
+    echo ""
96
+    echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
97
+    echo -e "${BLUE}  $1${NC}"
98
+    echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
99
+}
100
+
101
+pass() {
102
+    ((TESTS_PASSED++))
103
+    ((TESTS_RUN++))
104
+    echo -e "${GREEN}PASS${NC}: $1"
105
+}
106
+
107
+fail() {
108
+    local name="$1"
109
+    local reason="${2:-}"
110
+    ((TESTS_FAILED++))
111
+    ((TESTS_RUN++))
112
+    echo -e "${RED}FAIL${NC}: $name"
113
+    if [[ -n "$reason" ]]; then
114
+        echo -e "      ${YELLOW}Reason:${NC} $reason"
115
+    fi
116
+    FAILED_TESTS+=("$name")
117
+    if [[ "$STOP_ON_FAIL" == "true" ]]; then
118
+        echo -e "${RED}Stopping on first failure${NC}"
119
+        print_summary
120
+        exit 1
121
+    fi
122
+}
123
+
124
+skip() {
125
+    local name="$1"
126
+    local reason="${2:-}"
127
+    ((TESTS_SKIPPED++))
128
+    echo -e "${YELLOW}SKIP${NC}: $name${reason:+ ($reason)}"
129
+}
130
+
131
+should_run() {
132
+    local name="$1"
133
+    if [[ -n "$FILTER" && ! "$name" =~ $FILTER ]]; then
134
+        return 1
135
+    fi
136
+    return 0
137
+}
138
+
139
+#------------------------------------------------------------------------------
140
+# Core Comparison Function
141
+#------------------------------------------------------------------------------
142
+
143
+compare_with_grep() {
144
+    local flags="$1"
145
+    local pattern="$2"
146
+    local file="$3"
147
+
148
+    local grep_out grep_exit ferp_out ferp_exit
149
+
150
+    grep_out=$(grep $flags -- "$pattern" "$file" 2>/dev/null) && grep_exit=0 || grep_exit=$?
151
+    ferp_out=$("$FERP" $flags -- "$pattern" "$file" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
152
+
153
+    if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
154
+        return 0
155
+    else
156
+        if [[ "$VERBOSE" == "true" ]]; then
157
+            echo "  grep output ($grep_exit): $(echo "$grep_out" | head -3 | cat -A)"
158
+            echo "  ferp output ($ferp_exit): $(echo "$ferp_out" | head -3 | cat -A)"
159
+        fi
160
+        return 1
161
+    fi
162
+}
163
+
164
+compare_with_grep_stdin() {
165
+    local flags="$1"
166
+    local pattern="$2"
167
+    local input="$3"
168
+
169
+    local grep_out grep_exit ferp_out ferp_exit
170
+
171
+    grep_out=$(printf '%s' "$input" | grep $flags -- "$pattern" 2>/dev/null) && grep_exit=0 || grep_exit=$?
172
+    ferp_out=$(printf '%s' "$input" | "$FERP" $flags -- "$pattern" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
173
+
174
+    if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
175
+        return 0
176
+    else
177
+        if [[ "$VERBOSE" == "true" ]]; then
178
+            echo "  grep output ($grep_exit): $(echo "$grep_out" | head -3 | cat -A)"
179
+            echo "  ferp output ($ferp_exit): $(echo "$ferp_out" | head -3 | cat -A)"
180
+        fi
181
+        return 1
182
+    fi
183
+}
184
+
185
+test_grep_compat() {
186
+    local name="$1"
187
+    local flags="$2"
188
+    local pattern="$3"
189
+    local file="$4"
190
+
191
+    should_run "$name" || return 0
192
+
193
+    log "${CYAN}Testing:${NC} $name"
194
+    log "  Command: grep $flags -- '$pattern' $file"
195
+
196
+    if compare_with_grep "$flags" "$pattern" "$file" "$name"; then
197
+        pass "$name"
198
+    else
199
+        fail "$name" "Output differs from grep"
200
+    fi
201
+}
202
+
203
+test_grep_compat_stdin() {
204
+    local name="$1"
205
+    local flags="$2"
206
+    local pattern="$3"
207
+    local input="$4"
208
+
209
+    should_run "$name" || return 0
210
+
211
+    log "${CYAN}Testing:${NC} $name"
212
+
213
+    if compare_with_grep_stdin "$flags" "$pattern" "$input" "$name"; then
214
+        pass "$name"
215
+    else
216
+        fail "$name" "Output differs from grep"
217
+    fi
218
+}
219
+
220
+# Compare with multiple files
221
+compare_multi_files() {
222
+    local name="$1"
223
+    local flags="$2"
224
+    local pattern="$3"
225
+    shift 3
226
+    local files=("$@")
227
+
228
+    should_run "$name" || return 0
229
+
230
+    log "${CYAN}Testing:${NC} $name"
231
+
232
+    local grep_out grep_exit ferp_out ferp_exit
233
+
234
+    grep_out=$(grep $flags -- "$pattern" "${files[@]}" 2>/dev/null) && grep_exit=0 || grep_exit=$?
235
+    ferp_out=$("$FERP" $flags -- "$pattern" "${files[@]}" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
236
+
237
+    if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
238
+        pass "$name"
239
+    else
240
+        fail "$name" "Output differs from grep"
241
+        if [[ "$VERBOSE" == "true" ]]; then
242
+            echo "  grep ($grep_exit): $(echo "$grep_out" | head -3)"
243
+            echo "  ferp ($ferp_exit): $(echo "$ferp_out" | head -3)"
244
+        fi
245
+    fi
246
+}
247
+
248
+#------------------------------------------------------------------------------
249
+# Fixture Generation
250
+#------------------------------------------------------------------------------
251
+
252
+generate_fixtures() {
253
+    echo -e "${BLUE}Generating extended test fixtures...${NC}"
254
+
255
+    rm -rf "$FIXTURES"
256
+    mkdir -p "$FIXTURES"
257
+    mkdir -p "$FIXTURES/subdir"
258
+    mkdir -p "$FIXTURES/recursive/level1/level2"
259
+
260
+    # Basic test file
261
+    cat > "$FIXTURES/basic.txt" << 'EOF'
262
+hello world
263
+Hello World
264
+HELLO WORLD
265
+goodbye world
266
+the quick brown fox
267
+testing 123 testing
268
+line with hello in middle
269
+EOF
270
+
271
+    # File for backreference tests
272
+    cat > "$FIXTURES/backref.txt" << 'EOF'
273
+aa
274
+bb
275
+ab
276
+aaa
277
+abba
278
+abab
279
+noon
280
+deed
281
+level
282
+hello
283
+abcabc
284
+EOF
285
+
286
+    # File for multiple pattern tests
287
+    cat > "$FIXTURES/multi_pattern.txt" << 'EOF'
288
+apple pie
289
+banana bread
290
+cherry cake
291
+apple sauce
292
+grape juice
293
+banana split
294
+EOF
295
+
296
+    # Pattern file for -f flag
297
+    cat > "$FIXTURES/patterns.txt" << 'EOF'
298
+apple
299
+cherry
300
+grape
301
+EOF
302
+
303
+    # File with special characters
304
+    cat > "$FIXTURES/special.txt" << 'EOF'
305
+price is $100
306
+50% off
307
+path/to/file
308
+array[0]
309
+func()
310
+a+b=c
311
+a*b
312
+hello.world
313
+start^here
314
+end$there
315
+back\slash
316
+pipe|char
317
+question?
318
+curly{brace}
319
+(parens)
320
+EOF
321
+
322
+    # Unicode test file
323
+    cat > "$FIXTURES/unicode.txt" << 'EOF'
324
+cafe
325
+café
326
+résumé
327
+naïve
328
+NAÏVE
329
+Ñoño
330
+日本語
331
+EOF
332
+
333
+    # File with various line endings
334
+    printf 'unix line\n' > "$FIXTURES/line_endings.txt"
335
+    printf 'windows line\r\n' >> "$FIXTURES/line_endings.txt"
336
+    printf 'old mac line\r' >> "$FIXTURES/line_endings.txt"
337
+    printf 'final line\n' >> "$FIXTURES/line_endings.txt"
338
+
339
+    # File for context tests (overlapping matches)
340
+    cat > "$FIXTURES/context.txt" << 'EOF'
341
+line 1
342
+line 2
343
+MATCH A
344
+line 4
345
+MATCH B
346
+line 6
347
+line 7
348
+line 8
349
+MATCH C
350
+line 10
351
+EOF
352
+
353
+    # File with empty lines
354
+    cat > "$FIXTURES/empty_lines.txt" << 'EOF'
355
+first
356
+
357
+second
358
+
359
+third
360
+EOF
361
+
362
+    # File with only whitespace lines
363
+    cat > "$FIXTURES/whitespace_lines.txt" << 'EOF'
364
+normal line
365
+
366
+
367
+normal again
368
+EOF
369
+
370
+    # Very long line
371
+    printf 'start ' > "$FIXTURES/longline.txt"
372
+    printf 'x%.0s' {1..5000} >> "$FIXTURES/longline.txt"
373
+    printf ' MATCH ' >> "$FIXTURES/longline.txt"
374
+    printf 'y%.0s' {1..5000} >> "$FIXTURES/longline.txt"
375
+    printf ' end\n' >> "$FIXTURES/longline.txt"
376
+
377
+    # Binary file with text
378
+    printf 'text before\x00binary\x00text after\nmore text\n' > "$FIXTURES/binary.bin"
379
+
380
+    # File for -T tab alignment tests
381
+    cat > "$FIXTURES/tabs.txt" << 'EOF'
382
+short	match here
383
+verylongfilename	match here
384
+x	match here
385
+EOF
386
+
387
+    # Files for recursive tests
388
+    echo "match in root" > "$FIXTURES/recursive/root.txt"
389
+    echo "match in level1" > "$FIXTURES/recursive/level1/file1.txt"
390
+    echo "no match here" > "$FIXTURES/recursive/level1/file2.txt"
391
+    echo "match in level2" > "$FIXTURES/recursive/level1/level2/deep.txt"
392
+    echo "match in c file" > "$FIXTURES/recursive/code.c"
393
+    echo "match in header" > "$FIXTURES/recursive/code.h"
394
+    echo "compiled code" > "$FIXTURES/recursive/code.o"
395
+
396
+    # Symlink for -R tests (if supported)
397
+    ln -sf "$FIXTURES/basic.txt" "$FIXTURES/recursive/link_to_basic.txt" 2>/dev/null || true
398
+
399
+    # Empty file
400
+    touch "$FIXTURES/empty.txt"
401
+
402
+    # Single line, no newline
403
+    printf 'no trailing newline' > "$FIXTURES/no_newline.txt"
404
+
405
+    # File with many matches per line
406
+    echo "match match match match match" > "$FIXTURES/many_matches.txt"
407
+
408
+    # Null-separated data
409
+    printf 'record1\0record2\0record3\0' > "$FIXTURES/null_data.txt"
410
+
411
+    # Multiple files for multi-file tests
412
+    echo "has pattern here" > "$FIXTURES/multi1.txt"
413
+    echo "nothing special" > "$FIXTURES/multi2.txt"
414
+    echo "pattern again" > "$FIXTURES/multi3.txt"
415
+
416
+    echo -e "${GREEN}Generated extended fixtures in $FIXTURES${NC}"
417
+}
418
+
419
+#------------------------------------------------------------------------------
420
+# Test: Multiple Pattern Flags (-e, -f)
421
+#------------------------------------------------------------------------------
422
+
423
+test_multiple_patterns() {
424
+    section "Multiple Pattern Flags (-e, -f)"
425
+
426
+    local name
427
+
428
+    # -e with single pattern (should work like no -e)
429
+    test_grep_compat "-e: single pattern" "-e hello" "hello" "$FIXTURES/basic.txt"
430
+
431
+    # -e with multiple patterns
432
+    name="-e: two patterns"
433
+    should_run "$name" && {
434
+        local grep_out ferp_out grep_exit ferp_exit
435
+        grep_out=$(grep -e "hello" -e "goodbye" "$FIXTURES/basic.txt" 2>/dev/null) && grep_exit=0 || grep_exit=$?
436
+        ferp_out=$("$FERP" -e "hello" -e "goodbye" "$FIXTURES/basic.txt" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
437
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
438
+            pass "$name"
439
+        else
440
+            fail "$name" "Output differs"
441
+            log "  grep: $grep_out"
442
+            log "  ferp: $ferp_out"
443
+        fi
444
+    }
445
+
446
+    # -e with three patterns
447
+    name="-e: three patterns"
448
+    should_run "$name" && {
449
+        local grep_out ferp_out grep_exit ferp_exit
450
+        grep_out=$(grep -e "apple" -e "cherry" -e "grape" "$FIXTURES/multi_pattern.txt" 2>/dev/null) && grep_exit=0 || grep_exit=$?
451
+        ferp_out=$("$FERP" -e "apple" -e "cherry" -e "grape" "$FIXTURES/multi_pattern.txt" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
452
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
453
+            pass "$name"
454
+        else
455
+            fail "$name" "Output differs"
456
+        fi
457
+    }
458
+
459
+    # -e with -i (case insensitive)
460
+    name="-e: with -i flag"
461
+    should_run "$name" && {
462
+        local grep_out ferp_out grep_exit ferp_exit
463
+        grep_out=$(grep -i -e "HELLO" -e "GOODBYE" "$FIXTURES/basic.txt" 2>/dev/null) && grep_exit=0 || grep_exit=$?
464
+        ferp_out=$("$FERP" -i -e "HELLO" -e "GOODBYE" "$FIXTURES/basic.txt" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
465
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
466
+            pass "$name"
467
+        else
468
+            fail "$name" "Output differs"
469
+        fi
470
+    }
471
+
472
+    # -f: patterns from file
473
+    name="-f: patterns from file"
474
+    should_run "$name" && {
475
+        local grep_out ferp_out grep_exit ferp_exit
476
+        grep_out=$(grep -f "$FIXTURES/patterns.txt" "$FIXTURES/multi_pattern.txt" 2>/dev/null) && grep_exit=0 || grep_exit=$?
477
+        ferp_out=$("$FERP" -f "$FIXTURES/patterns.txt" "$FIXTURES/multi_pattern.txt" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
478
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
479
+            pass "$name"
480
+        else
481
+            fail "$name" "Output differs"
482
+        fi
483
+    }
484
+
485
+    # -f with -i
486
+    name="-f: with -i flag"
487
+    should_run "$name" && {
488
+        # Create uppercase patterns file
489
+        echo -e "APPLE\nCHERRY" > "$FIXTURES/patterns_upper.txt"
490
+        local grep_out ferp_out grep_exit ferp_exit
491
+        grep_out=$(grep -if "$FIXTURES/patterns_upper.txt" "$FIXTURES/multi_pattern.txt" 2>/dev/null) && grep_exit=0 || grep_exit=$?
492
+        ferp_out=$("$FERP" -if "$FIXTURES/patterns_upper.txt" "$FIXTURES/multi_pattern.txt" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
493
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
494
+            pass "$name"
495
+        else
496
+            fail "$name" "Output differs"
497
+        fi
498
+    }
499
+
500
+    # -e and -f combined
501
+    name="-e and -f combined"
502
+    should_run "$name" && {
503
+        local grep_out ferp_out grep_exit ferp_exit
504
+        grep_out=$(grep -e "banana" -f "$FIXTURES/patterns.txt" "$FIXTURES/multi_pattern.txt" 2>/dev/null) && grep_exit=0 || grep_exit=$?
505
+        ferp_out=$("$FERP" -e "banana" -f "$FIXTURES/patterns.txt" "$FIXTURES/multi_pattern.txt" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
506
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
507
+            pass "$name"
508
+        else
509
+            fail "$name" "Output differs"
510
+        fi
511
+    }
512
+
513
+    # -f with empty pattern file
514
+    name="-f: empty pattern file"
515
+    should_run "$name" && {
516
+        touch "$FIXTURES/empty_patterns.txt"
517
+        local grep_out ferp_out grep_exit ferp_exit
518
+        grep_out=$(grep -f "$FIXTURES/empty_patterns.txt" "$FIXTURES/basic.txt" 2>/dev/null) && grep_exit=0 || grep_exit=$?
519
+        ferp_out=$("$FERP" -f "$FIXTURES/empty_patterns.txt" "$FIXTURES/basic.txt" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
520
+        if [[ "$grep_exit" == "$ferp_exit" ]]; then
521
+            pass "$name"
522
+        else
523
+            fail "$name" "Exit codes differ: grep=$grep_exit ferp=$ferp_exit"
524
+        fi
525
+    }
526
+}
527
+
528
+#------------------------------------------------------------------------------
529
+# Test: Backreferences (BRE)
530
+#------------------------------------------------------------------------------
531
+
532
+test_backreferences() {
533
+    section "Backreferences (BRE)"
534
+
535
+    # Simple backreference - doubled character
536
+    test_grep_compat "backref: doubled char" "" '\(.\)\1' "$FIXTURES/backref.txt"
537
+
538
+    # Backreference - doubled lowercase letter
539
+    test_grep_compat "backref: doubled lowercase" "" '\([a-z]\)\1' "$FIXTURES/backref.txt"
540
+
541
+    # Palindrome-like pattern
542
+    test_grep_compat "backref: abba pattern" "" '\(.\)\(.\)\2\1' "$FIXTURES/backref.txt"
543
+
544
+    # Backreference with quantifier before group
545
+    test_grep_compat "backref: group with star" "" '\(ab\)*\1' "$FIXTURES/backref.txt"
546
+
547
+    # Multiple groups
548
+    test_grep_compat "backref: two groups" "" '\(a\)\(b\)\1\2' "$FIXTURES/backref.txt"
549
+
550
+    # Backreference at word boundary
551
+    test_grep_compat "backref: with word boundary" "-w" '\([a-z]\)\1' "$FIXTURES/backref.txt"
552
+
553
+    # Backreference with -i (case insensitive)
554
+    test_grep_compat "backref: case insensitive" "-i" '\([a-z]\)\1' "$FIXTURES/backref.txt"
555
+
556
+    # Repeated group captures last match
557
+    test_grep_compat_stdin "backref: repeated group" "" '\(ab*\)*\1' $'ababbabb\nababbab\n'
558
+}
559
+
560
+#------------------------------------------------------------------------------
561
+# Test: Null-data Mode (-z)
562
+#------------------------------------------------------------------------------
563
+
564
+test_null_data() {
565
+    section "Null-data Mode (-z)"
566
+
567
+    local name
568
+
569
+    # Basic null-terminated matching
570
+    name="-z: basic null-terminated"
571
+    should_run "$name" && {
572
+        local grep_out ferp_out grep_exit ferp_exit
573
+        grep_out=$(printf 'foo\0bar\0baz\0' | grep -z 'bar' 2>/dev/null | cat -A) && grep_exit=0 || grep_exit=$?
574
+        ferp_out=$(printf 'foo\0bar\0baz\0' | "$FERP" -z 'bar' 2>/dev/null | cat -A) && ferp_exit=0 || ferp_exit=$?
575
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
576
+            pass "$name"
577
+        else
578
+            fail "$name" "Output differs"
579
+            log "  grep: $grep_out"
580
+            log "  ferp: $ferp_out"
581
+        fi
582
+    }
583
+
584
+    # -z with multiple matches
585
+    name="-z: multiple matches"
586
+    should_run "$name" && {
587
+        local grep_out ferp_out grep_exit ferp_exit
588
+        grep_out=$(printf 'match1\0nomatch\0match2\0' | grep -z 'match' 2>/dev/null | cat -A) && grep_exit=0 || grep_exit=$?
589
+        ferp_out=$(printf 'match1\0nomatch\0match2\0' | "$FERP" -z 'match' 2>/dev/null | cat -A) && ferp_exit=0 || ferp_exit=$?
590
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
591
+            pass "$name"
592
+        else
593
+            fail "$name" "Output differs"
594
+        fi
595
+    }
596
+
597
+    # -z with -c (count)
598
+    name="-z: with count"
599
+    should_run "$name" && {
600
+        local grep_out ferp_out grep_exit ferp_exit
601
+        grep_out=$(printf 'match\0nomatch\0match\0' | grep -zc 'match' 2>/dev/null) && grep_exit=0 || grep_exit=$?
602
+        ferp_out=$(printf 'match\0nomatch\0match\0' | "$FERP" -zc 'match' 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
603
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
604
+            pass "$name"
605
+        else
606
+            fail "$name" "Output differs: grep='$grep_out' ferp='$ferp_out'"
607
+        fi
608
+    }
609
+
610
+    # -z with -v (invert)
611
+    name="-z: with invert"
612
+    should_run "$name" && {
613
+        local grep_out ferp_out grep_exit ferp_exit
614
+        grep_out=$(printf 'match\0nomatch\0other\0' | grep -zv 'match' 2>/dev/null | cat -A) && grep_exit=0 || grep_exit=$?
615
+        ferp_out=$(printf 'match\0nomatch\0other\0' | "$FERP" -zv 'match' 2>/dev/null | cat -A) && ferp_exit=0 || ferp_exit=$?
616
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
617
+            pass "$name"
618
+        else
619
+            fail "$name" "Output differs"
620
+        fi
621
+    }
622
+}
623
+
624
+#------------------------------------------------------------------------------
625
+# Test: Binary File Handling
626
+#------------------------------------------------------------------------------
627
+
628
+test_binary_files() {
629
+    section "Binary File Handling"
630
+
631
+    local name
632
+
633
+    # Default behavior with binary file
634
+    name="binary: default behavior"
635
+    should_run "$name" && {
636
+        local grep_out ferp_out grep_exit ferp_exit
637
+        grep_out=$(grep 'text' "$FIXTURES/binary.bin" 2>/dev/null) && grep_exit=0 || grep_exit=$?
638
+        ferp_out=$("$FERP" 'text' "$FIXTURES/binary.bin" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
639
+        # Both should either show "Binary file matches" or similar
640
+        if [[ "$grep_exit" == "$ferp_exit" ]]; then
641
+            pass "$name"
642
+        else
643
+            fail "$name" "Exit codes differ: grep=$grep_exit ferp=$ferp_exit"
644
+        fi
645
+    }
646
+
647
+    # -a/--text: treat as text
648
+    name="binary: -a treat as text"
649
+    should_run "$name" && {
650
+        local grep_out ferp_out grep_exit ferp_exit
651
+        grep_out=$(grep -a 'text' "$FIXTURES/binary.bin" 2>/dev/null | head -1) && grep_exit=0 || grep_exit=$?
652
+        ferp_out=$("$FERP" -a 'text' "$FIXTURES/binary.bin" 2>/dev/null | head -1) && ferp_exit=0 || ferp_exit=$?
653
+        if [[ "$grep_exit" == "$ferp_exit" ]]; then
654
+            pass "$name"
655
+        else
656
+            fail "$name" "Exit codes differ: grep=$grep_exit ferp=$ferp_exit"
657
+        fi
658
+    }
659
+
660
+    # -I: ignore binary files
661
+    name="binary: -I ignore binary"
662
+    should_run "$name" && {
663
+        local grep_out ferp_out grep_exit ferp_exit
664
+        grep_out=$(grep -I 'text' "$FIXTURES/binary.bin" 2>/dev/null) && grep_exit=0 || grep_exit=$?
665
+        ferp_out=$("$FERP" -I 'text' "$FIXTURES/binary.bin" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
666
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
667
+            pass "$name"
668
+        else
669
+            fail "$name" "Output differs"
670
+        fi
671
+    }
672
+
673
+    # --binary-files=without-match
674
+    name="binary: --binary-files=without-match"
675
+    should_run "$name" && {
676
+        local grep_out ferp_out grep_exit ferp_exit
677
+        grep_out=$(grep --binary-files=without-match 'text' "$FIXTURES/binary.bin" 2>/dev/null) && grep_exit=0 || grep_exit=$?
678
+        ferp_out=$("$FERP" --binary-files=without-match 'text' "$FIXTURES/binary.bin" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
679
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
680
+            pass "$name"
681
+        else
682
+            fail "$name" "Output differs"
683
+        fi
684
+    }
685
+}
686
+
687
+#------------------------------------------------------------------------------
688
+# Test: Recursive Directory Options
689
+#------------------------------------------------------------------------------
690
+
691
+test_recursive() {
692
+    section "Recursive Directory Options"
693
+
694
+    local name
695
+
696
+    # -r: recursive search
697
+    name="-r: basic recursive"
698
+    should_run "$name" && {
699
+        local grep_out ferp_out grep_exit ferp_exit
700
+        grep_out=$(grep -r 'match' "$FIXTURES/recursive" 2>/dev/null | sort) && grep_exit=0 || grep_exit=$?
701
+        ferp_out=$("$FERP" -r 'match' "$FIXTURES/recursive" 2>/dev/null | sort) && ferp_exit=0 || ferp_exit=$?
702
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
703
+            pass "$name"
704
+        else
705
+            fail "$name" "Output differs"
706
+            log "  grep: $grep_out"
707
+            log "  ferp: $ferp_out"
708
+        fi
709
+    }
710
+
711
+    # -r with -l
712
+    name="-r: with -l list files"
713
+    should_run "$name" && {
714
+        local grep_out ferp_out grep_exit ferp_exit
715
+        grep_out=$(grep -rl 'match' "$FIXTURES/recursive" 2>/dev/null | sort) && grep_exit=0 || grep_exit=$?
716
+        ferp_out=$("$FERP" -rl 'match' "$FIXTURES/recursive" 2>/dev/null | sort) && ferp_exit=0 || ferp_exit=$?
717
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
718
+            pass "$name"
719
+        else
720
+            fail "$name" "Output differs"
721
+        fi
722
+    }
723
+
724
+    # -r with -c
725
+    name="-r: with -c count"
726
+    should_run "$name" && {
727
+        local grep_out ferp_out grep_exit ferp_exit
728
+        grep_out=$(grep -rc 'match' "$FIXTURES/recursive" 2>/dev/null | sort) && grep_exit=0 || grep_exit=$?
729
+        ferp_out=$("$FERP" -rc 'match' "$FIXTURES/recursive" 2>/dev/null | sort) && ferp_exit=0 || ferp_exit=$?
730
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
731
+            pass "$name"
732
+        else
733
+            fail "$name" "Output differs"
734
+        fi
735
+    }
736
+
737
+    # --include pattern
738
+    name="--include: only .c files"
739
+    should_run "$name" && {
740
+        local grep_out ferp_out grep_exit ferp_exit
741
+        grep_out=$(grep -r --include="*.c" 'match' "$FIXTURES/recursive" 2>/dev/null | sort) && grep_exit=0 || grep_exit=$?
742
+        ferp_out=$("$FERP" -r --include="*.c" 'match' "$FIXTURES/recursive" 2>/dev/null | sort) && ferp_exit=0 || ferp_exit=$?
743
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
744
+            pass "$name"
745
+        else
746
+            fail "$name" "Output differs"
747
+        fi
748
+    }
749
+
750
+    # --exclude pattern
751
+    name="--exclude: skip .o files"
752
+    should_run "$name" && {
753
+        local grep_out ferp_out grep_exit ferp_exit
754
+        grep_out=$(grep -r --exclude="*.o" 'match\|compiled' "$FIXTURES/recursive" 2>/dev/null | sort) && grep_exit=0 || grep_exit=$?
755
+        ferp_out=$("$FERP" -r --exclude="*.o" 'match\|compiled' "$FIXTURES/recursive" 2>/dev/null | sort) && ferp_exit=0 || ferp_exit=$?
756
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
757
+            pass "$name"
758
+        else
759
+            fail "$name" "Output differs"
760
+        fi
761
+    }
762
+
763
+    # --exclude-dir
764
+    name="--exclude-dir: skip level2"
765
+    should_run "$name" && {
766
+        local grep_out ferp_out grep_exit ferp_exit
767
+        grep_out=$(grep -r --exclude-dir="level2" 'match' "$FIXTURES/recursive" 2>/dev/null | sort) && grep_exit=0 || grep_exit=$?
768
+        ferp_out=$("$FERP" -r --exclude-dir="level2" 'match' "$FIXTURES/recursive" 2>/dev/null | sort) && ferp_exit=0 || ferp_exit=$?
769
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
770
+            pass "$name"
771
+        else
772
+            fail "$name" "Output differs"
773
+        fi
774
+    }
775
+
776
+    # Multiple --include patterns
777
+    name="--include: multiple patterns"
778
+    should_run "$name" && {
779
+        local grep_out ferp_out grep_exit ferp_exit
780
+        grep_out=$(grep -r --include="*.c" --include="*.h" 'match' "$FIXTURES/recursive" 2>/dev/null | sort) && grep_exit=0 || grep_exit=$?
781
+        ferp_out=$("$FERP" -r --include="*.c" --include="*.h" 'match' "$FIXTURES/recursive" 2>/dev/null | sort) && ferp_exit=0 || ferp_exit=$?
782
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
783
+            pass "$name"
784
+        else
785
+            fail "$name" "Output differs"
786
+        fi
787
+    }
788
+}
789
+
790
+#------------------------------------------------------------------------------
791
+# Test: Context Edge Cases
792
+#------------------------------------------------------------------------------
793
+
794
+test_context_edge_cases() {
795
+    section "Context Edge Cases"
796
+
797
+    # Overlapping context (matches close together)
798
+    test_grep_compat "context: overlapping -C1" "-C1" "MATCH" "$FIXTURES/context.txt"
799
+    test_grep_compat "context: overlapping -C2" "-C2" "MATCH" "$FIXTURES/context.txt"
800
+
801
+    # Context at file boundaries
802
+    test_grep_compat "context: at start -B3" "-B3" "line 1" "$FIXTURES/context.txt"
803
+    test_grep_compat "context: at end -A3" "-A3" "line 10" "$FIXTURES/context.txt"
804
+
805
+    # Context with -v (inverted)
806
+    test_grep_compat "context: -A2 with -v" "-A2 -v" "MATCH" "$FIXTURES/context.txt"
807
+    test_grep_compat "context: -B2 with -v" "-B2 -v" "MATCH" "$FIXTURES/context.txt"
808
+
809
+    # --group-separator
810
+    local name="context: custom group separator"
811
+    should_run "$name" && {
812
+        local grep_out ferp_out grep_exit ferp_exit
813
+        grep_out=$(grep --group-separator="===" -C1 "MATCH" "$FIXTURES/context.txt" 2>/dev/null) && grep_exit=0 || grep_exit=$?
814
+        ferp_out=$("$FERP" --group-separator="===" -C1 "MATCH" "$FIXTURES/context.txt" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
815
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
816
+            pass "$name"
817
+        else
818
+            fail "$name" "Output differs"
819
+        fi
820
+    }
821
+
822
+    # --no-group-separator
823
+    name="context: no group separator"
824
+    should_run "$name" && {
825
+        local grep_out ferp_out grep_exit ferp_exit
826
+        grep_out=$(grep --no-group-separator -C1 "MATCH" "$FIXTURES/context.txt" 2>/dev/null) && grep_exit=0 || grep_exit=$?
827
+        ferp_out=$("$FERP" --no-group-separator -C1 "MATCH" "$FIXTURES/context.txt" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
828
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
829
+            pass "$name"
830
+        else
831
+            fail "$name" "Output differs"
832
+        fi
833
+    }
834
+
835
+    # Context with -n (line numbers)
836
+    test_grep_compat "context: -C2 with -n" "-C2 -n" "MATCH" "$FIXTURES/context.txt"
837
+
838
+    # Context with -b (byte offset)
839
+    test_grep_compat "context: -C1 with -b" "-C1 -b" "MATCH" "$FIXTURES/context.txt"
840
+}
841
+
842
+#------------------------------------------------------------------------------
843
+# Test: Output Format Options
844
+#------------------------------------------------------------------------------
845
+
846
+test_output_format() {
847
+    section "Output Format Options"
848
+
849
+    # -T: initial tab for alignment
850
+    test_grep_compat "-T: initial tab" "-T" "match" "$FIXTURES/tabs.txt"
851
+    test_grep_compat "-T: with -n" "-Tn" "match" "$FIXTURES/tabs.txt"
852
+    test_grep_compat "-T: with -H" "-TH" "match" "$FIXTURES/tabs.txt"
853
+
854
+    # -Z: null after filename
855
+    local name="-Z: null after filename"
856
+    should_run "$name" && {
857
+        local grep_out ferp_out grep_exit ferp_exit
858
+        grep_out=$(grep -Z "pattern" "$FIXTURES/multi1.txt" 2>/dev/null | cat -A) && grep_exit=0 || grep_exit=$?
859
+        ferp_out=$("$FERP" -Z "pattern" "$FIXTURES/multi1.txt" 2>/dev/null | cat -A) && ferp_exit=0 || ferp_exit=$?
860
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
861
+            pass "$name"
862
+        else
863
+            fail "$name" "Output differs"
864
+        fi
865
+    }
866
+
867
+    # -Z with -l
868
+    name="-Z: with -l"
869
+    should_run "$name" && {
870
+        local grep_out ferp_out grep_exit ferp_exit
871
+        grep_out=$(grep -lZ "pattern" "$FIXTURES/multi1.txt" "$FIXTURES/multi3.txt" 2>/dev/null | cat -A) && grep_exit=0 || grep_exit=$?
872
+        ferp_out=$("$FERP" -lZ "pattern" "$FIXTURES/multi1.txt" "$FIXTURES/multi3.txt" 2>/dev/null | cat -A) && ferp_exit=0 || ferp_exit=$?
873
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
874
+            pass "$name"
875
+        else
876
+            fail "$name" "Output differs"
877
+        fi
878
+    }
879
+
880
+    # --label for stdin
881
+    name="--label: custom stdin label"
882
+    should_run "$name" && {
883
+        local grep_out ferp_out grep_exit ferp_exit
884
+        grep_out=$(echo "hello world" | grep -H --label="MYSTDIN" "hello" 2>/dev/null) && grep_exit=0 || grep_exit=$?
885
+        ferp_out=$(echo "hello world" | "$FERP" -H --label="MYSTDIN" "hello" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
886
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
887
+            pass "$name"
888
+        else
889
+            fail "$name" "Output differs: grep='$grep_out' ferp='$ferp_out'"
890
+        fi
891
+    }
892
+}
893
+
894
+#------------------------------------------------------------------------------
895
+# Test: Regex Edge Cases
896
+#------------------------------------------------------------------------------
897
+
898
+test_regex_edge_cases() {
899
+    section "Regex Edge Cases"
900
+
901
+    # Empty pattern matches everything
902
+    test_grep_compat "regex: empty pattern" "" "" "$FIXTURES/basic.txt"
903
+
904
+    # Pattern matching empty string
905
+    test_grep_compat "regex: a* matches empty" "-E" "a*" "$FIXTURES/basic.txt"
906
+
907
+    # Empty line matching
908
+    test_grep_compat "regex: match empty lines" "" "^$" "$FIXTURES/empty_lines.txt"
909
+
910
+    # Very long line
911
+    test_grep_compat "regex: very long line" "" "MATCH" "$FIXTURES/longline.txt"
912
+
913
+    # Many matches per line with -o
914
+    test_grep_compat "regex: many matches -o" "-o" "match" "$FIXTURES/many_matches.txt"
915
+
916
+    # Unicode patterns (if supported)
917
+    test_grep_compat "regex: unicode literal" "" "café" "$FIXTURES/unicode.txt"
918
+    test_grep_compat "regex: unicode case -i" "-i" "CAFÉ" "$FIXTURES/unicode.txt"
919
+
920
+    # Anchors with -o
921
+    test_grep_compat "regex: ^ anchor with -o" "-o" "^hello" "$FIXTURES/basic.txt"
922
+
923
+    # Newline in character class (should not match)
924
+    test_grep_compat_stdin "regex: dot doesn't match newline" "" "a.b" $'a\nb\nacb\n'
925
+
926
+    # Greedy vs non-greedy (ERE)
927
+    test_grep_compat_stdin "regex: greedy quantifier" "-Eo" "a.*b" "aXXbYYb"
928
+}
929
+
930
+#------------------------------------------------------------------------------
931
+# Test: BRE vs ERE Differences
932
+#------------------------------------------------------------------------------
933
+
934
+test_bre_ere_differences() {
935
+    section "BRE vs ERE Differences"
936
+
937
+    # BRE: + and ? are literal
938
+    test_grep_compat "BRE: + is literal" "" "a+b" "$FIXTURES/special.txt"
939
+    test_grep_compat "BRE: ? is literal" "" "question?" "$FIXTURES/special.txt"
940
+
941
+    # BRE: | is literal (not alternation)
942
+    test_grep_compat "BRE: | is literal" "" "pipe|char" "$FIXTURES/special.txt"
943
+
944
+    # BRE: () need escaping for grouping
945
+    test_grep_compat "BRE: escaped parens group" "" '\(hello\)' "$FIXTURES/basic.txt"
946
+
947
+    # BRE: {} need escaping for quantifiers
948
+    test_grep_compat "BRE: escaped braces quantify" "" 'l\{2\}' "$FIXTURES/basic.txt"
949
+
950
+    # ERE: + and ? are special
951
+    test_grep_compat "ERE: + is quantifier" "-E" "hel+" "$FIXTURES/basic.txt"
952
+    test_grep_compat "ERE: ? is quantifier" "-E" "hell?o" "$FIXTURES/basic.txt"
953
+
954
+    # ERE: | is alternation
955
+    test_grep_compat "ERE: | is alternation" "-E" "hello|goodbye" "$FIXTURES/basic.txt"
956
+
957
+    # ERE: () don't need escaping
958
+    test_grep_compat "ERE: unescaped parens" "-E" "(hello)" "$FIXTURES/basic.txt"
959
+
960
+    # ERE: {} don't need escaping
961
+    test_grep_compat "ERE: unescaped braces" "-E" "l{2}" "$FIXTURES/basic.txt"
962
+
963
+    # BRE with GNU extensions: \| for alternation
964
+    test_grep_compat "BRE GNU: \\| alternation" "" 'hello\|goodbye' "$FIXTURES/basic.txt"
965
+
966
+    # BRE with GNU extensions: \+ and \?
967
+    test_grep_compat "BRE GNU: \\+ quantifier" "" 'hel\+' "$FIXTURES/basic.txt"
968
+    test_grep_compat "BRE GNU: \\? quantifier" "" 'hell\?' "$FIXTURES/basic.txt"
969
+}
970
+
971
+#------------------------------------------------------------------------------
972
+# Test: Error Handling
973
+#------------------------------------------------------------------------------
974
+
975
+test_error_handling() {
976
+    section "Error Handling"
977
+
978
+    local name grep_exit ferp_exit
979
+
980
+    # Invalid regex
981
+    name="error: invalid regex ERE"
982
+    should_run "$name" && {
983
+        grep -E "[" "$FIXTURES/basic.txt" 2>/dev/null && grep_exit=0 || grep_exit=$?
984
+        "$FERP" -E "[" "$FIXTURES/basic.txt" 2>/dev/null && ferp_exit=0 || ferp_exit=$?
985
+        if [[ "$grep_exit" == "2" && "$ferp_exit" == "2" ]]; then
986
+            pass "$name"
987
+        else
988
+            fail "$name" "Exit codes differ: grep=$grep_exit ferp=$ferp_exit"
989
+        fi
990
+    }
991
+
992
+    # Invalid regex BRE
993
+    name="error: invalid regex BRE"
994
+    should_run "$name" && {
995
+        grep '\(' "$FIXTURES/basic.txt" 2>/dev/null && grep_exit=0 || grep_exit=$?
996
+        "$FERP" '\(' "$FIXTURES/basic.txt" 2>/dev/null && ferp_exit=0 || ferp_exit=$?
997
+        if [[ "$grep_exit" == "2" && "$ferp_exit" == "2" ]]; then
998
+            pass "$name"
999
+        else
1000
+            fail "$name" "Exit codes differ: grep=$grep_exit ferp=$ferp_exit"
1001
+        fi
1002
+    }
1003
+
1004
+    # Non-existent file (exit 2)
1005
+    name="error: non-existent file"
1006
+    should_run "$name" && {
1007
+        local grep_err ferp_err
1008
+        grep "pattern" "/nonexistent/file/12345" 2>/dev/null && grep_exit=0 || grep_exit=$?
1009
+        "$FERP" "pattern" "/nonexistent/file/12345" 2>/dev/null && ferp_exit=0 || ferp_exit=$?
1010
+        # grep returns 2 for errors, ferp should too
1011
+        if [[ "$grep_exit" == "$ferp_exit" ]]; then
1012
+            pass "$name"
1013
+        else
1014
+            fail "$name" "Exit codes differ: grep=$grep_exit ferp=$ferp_exit"
1015
+        fi
1016
+    }
1017
+
1018
+    # Mix of existing and non-existing files
1019
+    name="error: partial file access"
1020
+    should_run "$name" && {
1021
+        local grep_out ferp_out
1022
+        grep_out=$(grep "hello" "$FIXTURES/basic.txt" "/nonexistent" 2>/dev/null) && grep_exit=0 || grep_exit=$?
1023
+        ferp_out=$("$FERP" "hello" "$FIXTURES/basic.txt" "/nonexistent" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
1024
+        # Should still find matches in existing file
1025
+        if [[ -n "$grep_out" && -n "$ferp_out" ]]; then
1026
+            pass "$name"
1027
+        else
1028
+            fail "$name" "Should still output matches from existing file"
1029
+        fi
1030
+    }
1031
+
1032
+    # -s suppresses error messages
1033
+    name="error: -s suppresses errors"
1034
+    should_run "$name" && {
1035
+        local grep_err ferp_err
1036
+        grep_err=$(grep -s "pattern" "/nonexistent" 2>&1)
1037
+        ferp_err=$("$FERP" -s "pattern" "/nonexistent" 2>&1)
1038
+        if [[ -z "$grep_err" && -z "$ferp_err" ]]; then
1039
+            pass "$name"
1040
+        else
1041
+            fail "$name" "Error messages not suppressed"
1042
+        fi
1043
+    }
1044
+
1045
+    # Directory without -r
1046
+    name="error: directory without -r"
1047
+    should_run "$name" && {
1048
+        grep "pattern" "$FIXTURES" 2>/dev/null && grep_exit=0 || grep_exit=$?
1049
+        "$FERP" "pattern" "$FIXTURES" 2>/dev/null && ferp_exit=0 || ferp_exit=$?
1050
+        # Both should handle this gracefully
1051
+        if [[ "$grep_exit" == "$ferp_exit" ]]; then
1052
+            pass "$name"
1053
+        else
1054
+            fail "$name" "Exit codes differ: grep=$grep_exit ferp=$ferp_exit"
1055
+        fi
1056
+    }
1057
+}
1058
+
1059
+#------------------------------------------------------------------------------
1060
+# Test: Unusual Input
1061
+#------------------------------------------------------------------------------
1062
+
1063
+test_unusual_input() {
1064
+    section "Unusual Input"
1065
+
1066
+    # File with only newlines
1067
+    local name="input: only newlines"
1068
+    should_run "$name" && {
1069
+        printf '\n\n\n' > "$FIXTURES/only_newlines.txt"
1070
+        local grep_out ferp_out grep_exit ferp_exit
1071
+        grep_out=$(grep "." "$FIXTURES/only_newlines.txt" 2>/dev/null) && grep_exit=0 || grep_exit=$?
1072
+        ferp_out=$("$FERP" "." "$FIXTURES/only_newlines.txt" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
1073
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
1074
+            pass "$name"
1075
+        else
1076
+            fail "$name" "Output differs"
1077
+        fi
1078
+    }
1079
+
1080
+    # Single character file
1081
+    name="input: single character"
1082
+    should_run "$name" && {
1083
+        printf 'x' > "$FIXTURES/single_char.txt"
1084
+        local grep_out ferp_out grep_exit ferp_exit
1085
+        grep_out=$(grep "x" "$FIXTURES/single_char.txt" 2>/dev/null) && grep_exit=0 || grep_exit=$?
1086
+        ferp_out=$("$FERP" "x" "$FIXTURES/single_char.txt" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
1087
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
1088
+            pass "$name"
1089
+        else
1090
+            fail "$name" "Output differs"
1091
+        fi
1092
+    }
1093
+
1094
+    # Empty file
1095
+    test_grep_compat "input: empty file" "" "pattern" "$FIXTURES/empty.txt"
1096
+
1097
+    # No trailing newline
1098
+    test_grep_compat "input: no trailing newline" "" "newline" "$FIXTURES/no_newline.txt"
1099
+
1100
+    # Mixed line endings
1101
+    test_grep_compat "input: mixed line endings" "" "line" "$FIXTURES/line_endings.txt"
1102
+
1103
+    # Lines with only whitespace
1104
+    test_grep_compat "input: whitespace-only lines" "" "^[ \t]*$" "$FIXTURES/whitespace_lines.txt"
1105
+
1106
+    # Extremely long pattern
1107
+    name="input: long pattern"
1108
+    should_run "$name" && {
1109
+        local long_pattern
1110
+        long_pattern=$(printf 'x%.0s' {1..100})
1111
+        local grep_out ferp_out grep_exit ferp_exit
1112
+        grep_out=$(grep "$long_pattern" "$FIXTURES/longline.txt" 2>/dev/null) && grep_exit=0 || grep_exit=$?
1113
+        ferp_out=$("$FERP" "$long_pattern" "$FIXTURES/longline.txt" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
1114
+        if [[ "$grep_exit" == "$ferp_exit" ]]; then
1115
+            pass "$name"
1116
+        else
1117
+            fail "$name" "Exit codes differ"
1118
+        fi
1119
+    }
1120
+}
1121
+
1122
+#------------------------------------------------------------------------------
1123
+# Test: Flag Interaction Edge Cases
1124
+#------------------------------------------------------------------------------
1125
+
1126
+test_flag_interactions() {
1127
+    section "Flag Interaction Edge Cases"
1128
+
1129
+    # -c with -l (count vs list)
1130
+    compare_multi_files "-c with -l" "-cl" "pattern" "$FIXTURES/multi1.txt" "$FIXTURES/multi2.txt" "$FIXTURES/multi3.txt"
1131
+
1132
+    # -o with -c (count only matching parts)
1133
+    test_grep_compat "-o with -c" "-oc" "match" "$FIXTURES/many_matches.txt"
1134
+
1135
+    # -m with -c (max count affects total)
1136
+    test_grep_compat "-m with -c" "-m2 -c" "match" "$FIXTURES/many_matches.txt"
1137
+
1138
+    # -v with -o (only matching of non-matches - undefined?)
1139
+    test_grep_compat "-v with -o" "-vo" "match" "$FIXTURES/many_matches.txt"
1140
+
1141
+    # -w with -o
1142
+    test_grep_compat "-w with -o" "-wo" "match" "$FIXTURES/many_matches.txt"
1143
+
1144
+    # -x with -o
1145
+    test_grep_compat "-x with -o" "-xo" "hello world" "$FIXTURES/basic.txt"
1146
+
1147
+    # -l with -L (conflicting - last wins?)
1148
+    local name="-l with -L"
1149
+    should_run "$name" && {
1150
+        # This is undefined behavior, just check they don't crash
1151
+        "$FERP" -lL "pattern" "$FIXTURES/multi1.txt" 2>/dev/null
1152
+        if [[ $? -le 2 ]]; then
1153
+            pass "$name"
1154
+        else
1155
+            fail "$name" "Unexpected exit code"
1156
+        fi
1157
+    }
1158
+
1159
+    # -q with -c (quiet but count?)
1160
+    name="-q with -c"
1161
+    should_run "$name" && {
1162
+        local grep_out ferp_out grep_exit ferp_exit
1163
+        grep_out=$(grep -qc "hello" "$FIXTURES/basic.txt" 2>/dev/null) && grep_exit=0 || grep_exit=$?
1164
+        ferp_out=$("$FERP" -qc "hello" "$FIXTURES/basic.txt" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
1165
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
1166
+            pass "$name"
1167
+        else
1168
+            fail "$name" "Output differs"
1169
+        fi
1170
+    }
1171
+
1172
+    # -n with -b with -o (all position info)
1173
+    test_grep_compat "-n -b -o combo" "-nbo" "hello" "$FIXTURES/basic.txt"
1174
+
1175
+    # -H with -h (conflicting)
1176
+    name="-H with -h"
1177
+    should_run "$name" && {
1178
+        local grep_out ferp_out grep_exit ferp_exit
1179
+        grep_out=$(grep -Hh "hello" "$FIXTURES/basic.txt" 2>/dev/null) && grep_exit=0 || grep_exit=$?
1180
+        ferp_out=$("$FERP" -Hh "hello" "$FIXTURES/basic.txt" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
1181
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
1182
+            pass "$name"
1183
+        else
1184
+            fail "$name" "Output differs"
1185
+        fi
1186
+    }
1187
+}
1188
+
1189
+#------------------------------------------------------------------------------
1190
+# Test: PCRE Features (-P)
1191
+#------------------------------------------------------------------------------
1192
+
1193
+test_pcre_features() {
1194
+    section "PCRE Features (-P)"
1195
+
1196
+    # Check if PCRE is available
1197
+    if ! grep -P "test" "$FIXTURES/basic.txt" &>/dev/null; then
1198
+        skip "PCRE: not available in grep" "grep -P not supported"
1199
+        return
1200
+    fi
1201
+
1202
+    if ! "$FERP" -P "test" "$FIXTURES/basic.txt" &>/dev/null; then
1203
+        skip "PCRE: not available in ferp" "ferp -P not supported"
1204
+        return
1205
+    fi
1206
+
1207
+    # Basic PCRE
1208
+    test_grep_compat "PCRE: basic pattern" "-P" "hello" "$FIXTURES/basic.txt"
1209
+
1210
+    # Lookahead
1211
+    test_grep_compat "PCRE: positive lookahead" "-P" 'hello(?= world)' "$FIXTURES/basic.txt"
1212
+    test_grep_compat "PCRE: negative lookahead" "-P" 'hello(?! there)' "$FIXTURES/basic.txt"
1213
+
1214
+    # Lookbehind
1215
+    test_grep_compat "PCRE: positive lookbehind" "-P" '(?<=hello )world' "$FIXTURES/basic.txt"
1216
+    test_grep_compat "PCRE: negative lookbehind" "-P" '(?<!good)bye' "$FIXTURES/basic.txt"
1217
+
1218
+    # Non-greedy quantifiers
1219
+    test_grep_compat_stdin "PCRE: non-greedy *?" "-Po" "a.*?b" "aXXbYYb"
1220
+    test_grep_compat_stdin "PCRE: non-greedy +?" "-Po" "a.+?b" "aXXbYYb"
1221
+
1222
+    # Word boundary \b
1223
+    test_grep_compat "PCRE: word boundary \\b" "-P" '\btest\b' "$FIXTURES/basic.txt"
1224
+
1225
+    # Character classes \d, \w, \s
1226
+    test_grep_compat "PCRE: \\d digit" "-P" '\d+' "$FIXTURES/basic.txt"
1227
+    test_grep_compat "PCRE: \\w word" "-P" '\w+' "$FIXTURES/basic.txt"
1228
+    test_grep_compat "PCRE: \\s space" "-P" '\s+' "$FIXTURES/basic.txt"
1229
+
1230
+    # Negated classes \D, \W, \S
1231
+    test_grep_compat "PCRE: \\D non-digit" "-Po" '\D+' "$FIXTURES/basic.txt"
1232
+
1233
+    # PCRE with -i
1234
+    test_grep_compat "PCRE: with -i" "-Pi" "HELLO" "$FIXTURES/basic.txt"
1235
+
1236
+    # PCRE with -o
1237
+    test_grep_compat "PCRE: with -o" "-Po" '\w+' "$FIXTURES/basic.txt"
1238
+
1239
+    # PCRE with -v
1240
+    test_grep_compat "PCRE: with -v" "-Pv" "hello" "$FIXTURES/basic.txt"
1241
+
1242
+    # PCRE backreferences
1243
+    test_grep_compat "PCRE: backreference" "-P" '(\w)\1' "$FIXTURES/backref.txt"
1244
+}
1245
+
1246
+#------------------------------------------------------------------------------
1247
+# Test: Fixed String Edge Cases (-F)
1248
+#------------------------------------------------------------------------------
1249
+
1250
+test_fixed_string_edge_cases() {
1251
+    section "Fixed String Edge Cases (-F)"
1252
+
1253
+    # All regex metacharacters as literals
1254
+    test_grep_compat "-F: dot literal" "-F" "hello.world" "$FIXTURES/special.txt"
1255
+    test_grep_compat "-F: star literal" "-F" "a*b" "$FIXTURES/special.txt"
1256
+    test_grep_compat "-F: plus literal" "-F" "a+b" "$FIXTURES/special.txt"
1257
+    test_grep_compat "-F: question literal" "-F" "question?" "$FIXTURES/special.txt"
1258
+    test_grep_compat "-F: brackets literal" "-F" "array[0]" "$FIXTURES/special.txt"
1259
+    test_grep_compat "-F: parens literal" "-F" "func()" "$FIXTURES/special.txt"
1260
+    test_grep_compat "-F: braces literal" "-F" "curly{brace}" "$FIXTURES/special.txt"
1261
+    test_grep_compat "-F: caret literal" "-F" "start^here" "$FIXTURES/special.txt"
1262
+    test_grep_compat "-F: dollar literal" "-F" 'end$there' "$FIXTURES/special.txt"
1263
+    test_grep_compat "-F: pipe literal" "-F" "pipe|char" "$FIXTURES/special.txt"
1264
+    test_grep_compat "-F: backslash literal" "-F" 'back\slash' "$FIXTURES/special.txt"
1265
+
1266
+    # -F with -i
1267
+    test_grep_compat "-F with -i" "-Fi" "HELLO.WORLD" "$FIXTURES/special.txt"
1268
+
1269
+    # -F with -w
1270
+    test_grep_compat "-F with -w" "-Fw" "hello" "$FIXTURES/basic.txt"
1271
+
1272
+    # -F with -x
1273
+    test_grep_compat "-F with -x" "-Fx" "hello world" "$FIXTURES/basic.txt"
1274
+
1275
+    # -F with multiple patterns via -e
1276
+    local name="-F with -e multiple"
1277
+    should_run "$name" && {
1278
+        local grep_out ferp_out grep_exit ferp_exit
1279
+        grep_out=$(grep -F -e "a+b" -e "a*b" "$FIXTURES/special.txt" 2>/dev/null) && grep_exit=0 || grep_exit=$?
1280
+        ferp_out=$("$FERP" -F -e "a+b" -e "a*b" "$FIXTURES/special.txt" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
1281
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
1282
+            pass "$name"
1283
+        else
1284
+            fail "$name" "Output differs"
1285
+        fi
1286
+    }
1287
+
1288
+    # -F with newline-separated patterns
1289
+    name="-F with newline in pattern"
1290
+    should_run "$name" && {
1291
+        local grep_out ferp_out grep_exit ferp_exit
1292
+        grep_out=$(grep -F $'hello\ngoodbye' "$FIXTURES/basic.txt" 2>/dev/null) && grep_exit=0 || grep_exit=$?
1293
+        ferp_out=$("$FERP" -F $'hello\ngoodbye' "$FIXTURES/basic.txt" 2>/dev/null) && ferp_exit=0 || ferp_exit=$?
1294
+        if [[ "$grep_out" == "$ferp_out" && "$grep_exit" == "$ferp_exit" ]]; then
1295
+            pass "$name"
1296
+        else
1297
+            fail "$name" "Output differs"
1298
+        fi
1299
+    }
1300
+}
1301
+
1302
+#------------------------------------------------------------------------------
1303
+# Summary
1304
+#------------------------------------------------------------------------------
1305
+
1306
+print_summary() {
1307
+    echo ""
1308
+    echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
1309
+    echo -e "${BLUE}  Summary${NC}"
1310
+    echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
1311
+    echo ""
1312
+    echo "  Tests run:    $TESTS_RUN"
1313
+    echo -e "  ${GREEN}Passed:       $TESTS_PASSED${NC}"
1314
+    echo -e "  ${RED}Failed:       $TESTS_FAILED${NC}"
1315
+    echo -e "  ${YELLOW}Skipped:      $TESTS_SKIPPED${NC}"
1316
+    echo ""
1317
+
1318
+    if [[ $TESTS_FAILED -gt 0 ]]; then
1319
+        echo -e "${RED}Failed tests:${NC}"
1320
+        for test in "${FAILED_TESTS[@]}"; do
1321
+            echo "  - $test"
1322
+        done
1323
+        echo ""
1324
+        echo -e "${RED}Some tests failed!${NC}"
1325
+        return 1
1326
+    else
1327
+        echo -e "${GREEN}All tests passed!${NC}"
1328
+        return 0
1329
+    fi
1330
+}
1331
+
1332
+#------------------------------------------------------------------------------
1333
+# Main
1334
+#------------------------------------------------------------------------------
1335
+
1336
+main() {
1337
+    echo -e "${BLUE}╔════════════════════════════════════════════════════════════════════════════╗${NC}"
1338
+    echo -e "${BLUE}║              FERP Extended Grep Test Suite                                 ║${NC}"
1339
+    echo -e "${BLUE}╚════════════════════════════════════════════════════════════════════════════╝${NC}"
1340
+    echo ""
1341
+
1342
+    # Check prerequisites
1343
+    if [[ ! -x "$FERP" ]]; then
1344
+        echo -e "${RED}Error: ferp binary not found at $FERP${NC}"
1345
+        echo "Run 'make' first to build ferp"
1346
+        exit 1
1347
+    fi
1348
+
1349
+    if ! command -v grep &> /dev/null; then
1350
+        echo -e "${RED}Error: grep not found${NC}"
1351
+        exit 1
1352
+    fi
1353
+
1354
+    echo "ferp: $FERP"
1355
+    echo "grep: $(which grep) ($(grep --version | head -1))"
1356
+    echo ""
1357
+
1358
+    # Generate fixtures
1359
+    generate_fixtures
1360
+
1361
+    # Run test suites
1362
+    test_multiple_patterns
1363
+    test_backreferences
1364
+    test_null_data
1365
+    test_binary_files
1366
+    test_recursive
1367
+    test_context_edge_cases
1368
+    test_output_format
1369
+    test_regex_edge_cases
1370
+    test_bre_ere_differences
1371
+    test_error_handling
1372
+    test_unusual_input
1373
+    test_flag_interactions
1374
+    test_pcre_features
1375
+    test_fixed_string_edge_cases
1376
+
1377
+    # Print summary
1378
+    print_summary
1379
+}
1380
+
1381
+main "$@"