tenseleyflow/bensch / ce6dda2

Browse files

extract core interactive YAML specs

posix.yaml -> suites/interactive/posix/posix_core.yaml
line_editing.yaml -> suites/interactive/editing/
history.yaml -> suites/interactive/history/

Verbatim copies. 1690 lines, ~220 test cases.

Source: fortsh/tests/interactive/test_specs/
Authored by espadonne
SHA
ce6dda24c5feda8e0592cf91d1df46fbfdc68dad
Parents
219dd82
Tree
136ff5f

3 changed files

StatusFile+-
A suites/interactive/editing/line_editing.yaml 550 0
A suites/interactive/history/history.yaml 366 0
A suites/interactive/posix/posix_core.yaml 774 0
suites/interactive/editing/line_editing.yamladded
@@ -0,0 +1,550 @@
1
+# Line Editing Tests for fortsh
2
+# Phase 2: Comprehensive cursor movement and text editing
3
+
4
+metadata:
5
+  category: "Line Editing"
6
+  description: "Comprehensive tests for cursor movement and text editing operations"
7
+  phase: 2
8
+
9
+tests:
10
+  # =============================================================
11
+  # BASIC INPUT
12
+  # =============================================================
13
+  - name: "Simple echo command"
14
+    steps:
15
+      - send_line: "echo hello"
16
+    expect_output: "hello"
17
+    match_type: "contains"
18
+
19
+  - name: "Echo with multiple arguments"
20
+    steps:
21
+      - send_line: "echo one two three"
22
+    expect_output: "one two three"
23
+    match_type: "contains"
24
+
25
+  # Removed: Empty echo test causes timeout issues
26
+
27
+  # =============================================================
28
+  # CURSOR MOVEMENT - Arrow Keys
29
+  # =============================================================
30
+  - name: "Left arrow moves cursor back"
31
+    steps:
32
+      - send: "echo test"
33
+      - send_key: "Left"
34
+      - send_key: "Left"
35
+      - send: "X"
36
+      - send_key: "Enter"
37
+    expect_output: "teXst"
38
+    match_type: "contains"
39
+
40
+  - name: "Right arrow moves cursor forward"
41
+    steps:
42
+      - send: "echo test"
43
+      - send_key: "Left"
44
+      - send_key: "Left"
45
+      - send_key: "Left"
46
+      - send_key: "Right"
47
+      - send: "X"
48
+      - send_key: "Enter"
49
+    expect_output: "teXst"
50
+    match_type: "contains"
51
+
52
+  - name: "Left arrow at beginning does nothing"
53
+    steps:
54
+      - send: "abcdef"
55
+      - send_key: "C-a"
56
+      - send_key: "Left"
57
+      - send_key: "Left"
58
+      - send: "X"
59
+      - send_key: "C-a"
60
+      - send: "echo "
61
+      - send_key: "Enter"
62
+    expect_output: "Xabcdef"
63
+    match_type: "contains"
64
+
65
+  - name: "Right arrow at end does nothing"
66
+    steps:
67
+      - send: "abcdef"
68
+      - send_key: "Right"
69
+      - send_key: "Right"
70
+      - send: "X"
71
+      - send_key: "C-a"
72
+      - send: "echo "
73
+      - send_key: "Enter"
74
+    expect_output: "abcdefX"
75
+    match_type: "contains"
76
+
77
+  # =============================================================
78
+  # CURSOR MOVEMENT - Ctrl Keys
79
+  # =============================================================
80
+  - name: "Ctrl+A moves to beginning of line"
81
+    steps:
82
+      - send: "hello world"
83
+      - send_key: "C-a"
84
+      - send: "echo "
85
+      - send_key: "Enter"
86
+    expect_output: "hello world"
87
+    match_type: "contains"
88
+
89
+  - name: "Ctrl+E moves to end of line"
90
+    steps:
91
+      - send: "echo hel"
92
+      - send_key: "C-a"
93
+      - send_key: "C-e"
94
+      - send: "lo"
95
+      - send_key: "Enter"
96
+    expect_output: "hello"
97
+    match_type: "contains"
98
+
99
+  - name: "Ctrl+B moves back one character"
100
+    steps:
101
+      - send: "echo test"
102
+      - send_key: "C-b"
103
+      - send_key: "C-b"
104
+      - send: "X"
105
+      - send_key: "Enter"
106
+    expect_output: "teXst"
107
+    match_type: "contains"
108
+
109
+  # NOTE: Ctrl+F is bound to FZF file browser in fortsh, not forward-char
110
+  # Use Right arrow for forward character movement instead
111
+  - name: "Right arrow moves forward one character"
112
+    steps:
113
+      - send: "abcdef"
114
+      - send_key: "C-a"
115
+      - send_key: "Right"
116
+      - send_key: "Right"
117
+      - send: "X"
118
+      - send_key: "C-a"
119
+      - send: "echo "
120
+      - send_key: "Enter"
121
+      - wait: 0.3
122
+    expect_output: "abXcdef"
123
+    match_type: "contains"
124
+
125
+  - name: "Multiple Ctrl+B navigation"
126
+    steps:
127
+      - send: "abcdef"
128
+      - send_key: "C-b"
129
+      - send_key: "C-b"
130
+      - send_key: "C-b"
131
+      - send: "X"
132
+      - send_key: "C-a"
133
+      - send: "echo "
134
+      - send_key: "Enter"
135
+    expect_output: "abcXdef"
136
+    match_type: "contains"
137
+
138
+  # =============================================================
139
+  # CURSOR MOVEMENT - Home/End Keys
140
+  # =============================================================
141
+  # NOTE: The Home key (ESC[H) is not explicitly handled in fortsh
142
+  # Ctrl+A already works for moving to beginning of line
143
+  - name: "Ctrl+A moves to beginning (Home alternative)"
144
+    steps:
145
+      - send: "abcdef"
146
+      - send_key: "C-a"
147
+      - send: "X"
148
+      - send_key: "C-a"
149
+      - send: "echo "
150
+      - send_key: "Enter"
151
+      - wait: 0.3
152
+    expect_output: "Xabcdef"
153
+    match_type: "contains"
154
+
155
+  - name: "End key moves to end"
156
+    steps:
157
+      - send: "abcdef"
158
+      - send_key: "Home"
159
+      - send_key: "End"
160
+      - send: "X"
161
+      - send_key: "C-a"
162
+      - send: "echo "
163
+      - send_key: "Enter"
164
+    expect_output: "abcdefX"
165
+    match_type: "contains"
166
+
167
+  # =============================================================
168
+  # CURSOR MOVEMENT - Word Movement
169
+  # =============================================================
170
+  - name: "Alt+B moves back one word"
171
+    steps:
172
+      - send: "echo one two three"
173
+      - send_key: "M-b"
174
+      - send_key: "M-b"
175
+      - send: "X"
176
+      - send_key: "Enter"
177
+    expect_output: "one Xtwo three"
178
+    match_type: "contains"
179
+
180
+  - name: "Alt+B from middle of word"
181
+    steps:
182
+      - send: "testing"
183
+      - send_key: "C-b"
184
+      - send_key: "C-b"
185
+      - send_key: "M-b"
186
+      - send: "X"
187
+      - send_key: "C-a"
188
+      - send: "echo "
189
+      - send_key: "Enter"
190
+    expect_output: "Xtesting"
191
+    match_type: "contains"
192
+
193
+  # Alt+F moves to end of current word (bash/readline behavior)
194
+  - name: "Alt+F moves forward one word"
195
+    steps:
196
+      - send: "one two"
197
+      - send_key: "C-a"
198
+      - send_key: "M-f"
199
+      - send: "X"
200
+      - send_key: "C-a"
201
+      - send: "echo "
202
+      - send_key: "Enter"
203
+      - wait: 0.3
204
+    expect_output: "oneX two"
205
+    match_type: "contains"
206
+
207
+  # Three Alt+B from end of "a b c d" moves: d->c->b->a (to beginning)
208
+  - name: "Multiple Alt+B movements"
209
+    steps:
210
+      - send: "a b c d"
211
+      - send_key: "M-b"
212
+      - send_key: "M-b"
213
+      - send_key: "M-b"
214
+      - send: "X"
215
+      - send_key: "C-a"
216
+      - send: "echo "
217
+      - send_key: "Enter"
218
+      - wait: 0.3
219
+    expect_output: "Xa b c d"
220
+    match_type: "contains"
221
+
222
+  # =============================================================
223
+  # TEXT DELETION - Backspace
224
+  # =============================================================
225
+  - name: "Backspace deletes character before cursor"
226
+    steps:
227
+      - send: "echoo"
228
+      - send_key: "Backspace"
229
+      - send: " test"
230
+      - send_key: "Enter"
231
+    expect_output: "test"
232
+    match_type: "contains"
233
+
234
+  - name: "Multiple backspaces"
235
+    steps:
236
+      - send: "echo helloXXX"
237
+      - send_key: "Backspace"
238
+      - send_key: "Backspace"
239
+      - send_key: "Backspace"
240
+      - send_key: "Enter"
241
+    expect_output: "hello"
242
+    match_type: "contains"
243
+
244
+  - name: "Backspace at beginning does nothing"
245
+    steps:
246
+      - send: "abcdef"
247
+      - send_key: "C-a"
248
+      - send_key: "Backspace"
249
+      - send_key: "Backspace"
250
+      - send_key: "C-a"
251
+      - send: "echo "
252
+      - send_key: "Enter"
253
+    expect_output: "abcdef"
254
+    match_type: "contains"
255
+
256
+  # =============================================================
257
+  # TEXT DELETION - Delete Key
258
+  # =============================================================
259
+  - name: "Delete key removes character under cursor"
260
+    steps:
261
+      - send: "Xabcdef"
262
+      - send_key: "C-a"
263
+      - send_key: "Delete"
264
+      - send_key: "C-a"
265
+      - send: "echo "
266
+      - send_key: "Enter"
267
+    expect_output: "abcdef"
268
+    match_type: "contains"
269
+
270
+  - name: "Multiple deletes"
271
+    steps:
272
+      - send: "XXXabcdef"
273
+      - send_key: "C-a"
274
+      - send_key: "Delete"
275
+      - send_key: "Delete"
276
+      - send_key: "Delete"
277
+      - send_key: "C-a"
278
+      - send: "echo "
279
+      - send_key: "Enter"
280
+    expect_output: "abcdef"
281
+    match_type: "contains"
282
+
283
+  - name: "Delete at end does nothing"
284
+    steps:
285
+      - send: "abcdef"
286
+      - send_key: "Delete"
287
+      - send_key: "Delete"
288
+      - send_key: "C-a"
289
+      - send: "echo "
290
+      - send_key: "Enter"
291
+    expect_output: "abcdef"
292
+    match_type: "contains"
293
+
294
+  # =============================================================
295
+  # TEXT DELETION - Ctrl+D
296
+  # =============================================================
297
+  - name: "Ctrl+D deletes character under cursor"
298
+    steps:
299
+      - send: "Xabcdef"
300
+      - send_key: "C-a"
301
+      - send_key: "C-d"
302
+      - send_key: "C-a"
303
+      - send: "echo "
304
+      - send_key: "Enter"
305
+    expect_output: "abcdef"
306
+    match_type: "contains"
307
+
308
+  # NOTE: Ctrl+D in fortsh only triggers EOF on empty line (doesn't forward-delete)
309
+  # Simpler test for delete in middle - use fresh_session for reliability
310
+  - name: "Delete in middle of line"
311
+    fresh_session: true
312
+    steps:
313
+      - send: "Xab"
314
+      - send_key: "C-a"
315
+      - send_key: "Delete"
316
+      - send_key: "C-a"
317
+      - send: "echo "
318
+      - send_key: "Enter"
319
+      - wait: 0.5
320
+    expect_output: "ab"
321
+    match_type: "contains"
322
+
323
+  # =============================================================
324
+  # TEXT DELETION - Kill Commands
325
+  # =============================================================
326
+  - name: "Ctrl+K kills to end of line"
327
+    steps:
328
+      - send: "echo test garbage"
329
+      - send_key: "C-a"
330
+      - send_keys: ["Right", "Right", "Right", "Right", "Right", "Right", "Right", "Right", "Right"]
331
+      - send_key: "C-k"
332
+      - send_key: "Enter"
333
+    expect_output: "test"
334
+    match_type: "contains"
335
+
336
+  - name: "Ctrl+K at end of line kills nothing"
337
+    steps:
338
+      - send: "abcdef"
339
+      - send_key: "C-k"
340
+      - send_key: "C-a"
341
+      - send: "echo "
342
+      - send_key: "Enter"
343
+    expect_output: "abcdef"
344
+    match_type: "contains"
345
+
346
+  - name: "Ctrl+U kills line before cursor"
347
+    steps:
348
+      - send: "garbage echo test"
349
+      - send_key: "C-a"
350
+      - send_keys: ["Right", "Right", "Right", "Right", "Right", "Right", "Right", "Right"]
351
+      - send_key: "C-u"
352
+      - send_key: "Enter"
353
+    expect_output: "test"
354
+    match_type: "contains"
355
+
356
+  - name: "Ctrl+U at beginning kills nothing"
357
+    steps:
358
+      - send: "abcdef"
359
+      - send_key: "C-a"
360
+      - send_key: "C-u"
361
+      - send_key: "C-a"
362
+      - send: "echo "
363
+      - send_key: "Enter"
364
+    expect_output: "abcdef"
365
+    match_type: "contains"
366
+
367
+  - name: "Ctrl+W kills word backward"
368
+    steps:
369
+      - send: "echo one two three"
370
+      - send_key: "C-w"
371
+      - send_key: "Enter"
372
+    expect_output: "one two"
373
+    match_type: "contains"
374
+
375
+  - name: "Multiple Ctrl+W kills multiple words"
376
+    steps:
377
+      - send: "echo one two three"
378
+      - send_key: "C-w"
379
+      - send_key: "C-w"
380
+      - send_key: "Enter"
381
+    expect_output: "one"
382
+    match_type: "contains"
383
+
384
+  - name: "Ctrl+W from middle of word"
385
+    steps:
386
+      - send: "testing"
387
+      - send_key: "C-b"
388
+      - send_key: "C-b"
389
+      - send_key: "C-w"
390
+      - send_key: "C-a"
391
+      - send: "echo "
392
+      - send_key: "Enter"
393
+    expect_output: "ing"
394
+    match_type: "contains"
395
+
396
+  # =============================================================
397
+  # KILL RING - Yank
398
+  # =============================================================
399
+  - name: "Ctrl+Y yanks killed text"
400
+    steps:
401
+      - send: "echo one two three"
402
+      - send_key: "C-w"
403
+      - send_key: "C-y"
404
+      - send_key: "Enter"
405
+    expect_output: "one two three"
406
+    match_type: "contains"
407
+
408
+  - name: "Ctrl+K then Ctrl+Y"
409
+    steps:
410
+      - send: "hello world"
411
+      - send_key: "C-a"
412
+      - send_key: "C-k"
413
+      - send: "echo "
414
+      - send_key: "C-y"
415
+      - send_key: "Enter"
416
+    expect_output: "hello world"
417
+    match_type: "contains"
418
+
419
+  # Simpler Ctrl+U then Ctrl+Y test
420
+  - name: "Ctrl+U then Ctrl+Y"
421
+    fresh_session: true
422
+    steps:
423
+      - send: "hello world"
424
+      - send_key: "C-u"
425
+      - send_key: "C-y"
426
+      - send_key: "C-a"
427
+      - send: "echo "
428
+      - send_key: "Enter"
429
+      - wait: 0.5
430
+    expect_output: "hello world"
431
+    match_type: "contains"
432
+
433
+  # =============================================================
434
+  # TRANSPOSE - Ctrl+T
435
+  # =============================================================
436
+  # Ctrl+T transposes char before cursor with char at cursor
437
+  # With cursor at position 1 (on 'a'), swaps 'b' and 'a' -> "abcd"
438
+  - name: "Ctrl+T transposes characters"
439
+    steps:
440
+      - send: "bacd"
441
+      - send_key: "C-a"
442
+      - send_key: "Right"
443
+      - send_key: "C-t"
444
+      - send_key: "C-a"
445
+      - send: "echo "
446
+      - send_key: "Enter"
447
+      - wait: 0.3
448
+    expect_output: "abcd"
449
+    match_type: "contains"
450
+
451
+  - name: "Ctrl+T at end transposes last two"
452
+    steps:
453
+      - send: "abdc"
454
+      - send_key: "C-t"
455
+      - send_key: "C-a"
456
+      - send: "echo "
457
+      - send_key: "Enter"
458
+    expect_output: "abcd"
459
+    match_type: "contains"
460
+
461
+  # =============================================================
462
+  # SPECIAL CHARACTERS
463
+  # =============================================================
464
+  - name: "Quotes preserve spaces"
465
+    steps:
466
+      - send_line: "echo 'hello   world'"
467
+    expect_output: "hello   world"
468
+    match_type: "contains"
469
+
470
+  - name: "Double quotes preserve spaces"
471
+    steps:
472
+      - send_line: 'echo "hello   world"'
473
+    expect_output: "hello   world"
474
+    match_type: "contains"
475
+
476
+  - name: "Escaped characters"
477
+    steps:
478
+      - send_line: "echo hello\\ world"
479
+    expect_output: "hello world"
480
+    match_type: "contains"
481
+
482
+  - name: "Tab character in quotes"
483
+    steps:
484
+      - send_line: "echo -e 'a\\tb'"
485
+    expect_output: "a\tb"
486
+    match_type: "contains"
487
+
488
+  # =============================================================
489
+  # UTF-8 MULTI-BYTE CHARACTERS
490
+  # =============================================================
491
+  - name: "UTF-8 basic output"
492
+    steps:
493
+      - send_line: "echo 'Hello 世界'"
494
+    expect_output: "Hello 世界"
495
+    match_type: "contains"
496
+
497
+  - name: "UTF-8 emoji output"
498
+    steps:
499
+      - send_line: "echo '🚀 rocket'"
500
+    expect_output: "🚀 rocket"
501
+    match_type: "contains"
502
+
503
+  - name: "UTF-8 mixed content"
504
+    steps:
505
+      - send_line: "echo 'café résumé'"
506
+    expect_output: "café résumé"
507
+    match_type: "contains"
508
+
509
+  - name: "Backspace over UTF-8 character"
510
+    steps:
511
+      - send: "echo 日本X"
512
+      - send_key: "Backspace"
513
+      - send_key: "Enter"
514
+    expect_output: "日本"
515
+    match_type: "contains"
516
+
517
+  - name: "Cursor movement with CJK characters"
518
+    steps:
519
+      - send: "echo 中文"
520
+      - send_key: "C-b"
521
+      - send: "X"
522
+      - send_key: "Enter"
523
+    expect_output: "中X文"
524
+    match_type: "contains"
525
+
526
+  - name: "Delete UTF-8 character"
527
+    steps:
528
+      - send: "日本国"
529
+      - send_key: "C-a"
530
+      - send_key: "C-d"
531
+      - send_key: "C-a"
532
+      - send: "echo "
533
+      - send_key: "Enter"
534
+    expect_output: "本国"
535
+    match_type: "contains"
536
+
537
+  # =============================================================
538
+  # EDGE CASES
539
+  # =============================================================
540
+  - name: "Very long line input"
541
+    steps:
542
+      - send_line: "echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
543
+    expect_output: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
544
+    match_type: "contains"
545
+
546
+  - name: "Line with only spaces"
547
+    steps:
548
+      - send_line: "echo '    '"
549
+    expect_output: "    "
550
+    match_type: "contains"
suites/interactive/history/history.yamladded
@@ -0,0 +1,366 @@
1
+# History Tests for fortsh
2
+# Phase 3: History navigation, search, and expansion
3
+
4
+metadata:
5
+  category: "History"
6
+  description: "Tests for command history navigation, search, and expansion"
7
+  phase: 3
8
+
9
+tests:
10
+  # =============================================================
11
+  # HISTORY NAVIGATION - Arrow Keys
12
+  # =============================================================
13
+  - name: "Up arrow recalls previous command"
14
+    steps:
15
+      - send_line: "echo first"
16
+      - send_key: "Up"
17
+      - send_key: "Enter"
18
+    expect_output: "first"
19
+    match_type: "contains"
20
+
21
+  - name: "Multiple up arrows navigate history"
22
+    steps:
23
+      - send_line: "echo one"
24
+      - send_line: "echo two"
25
+      - send_line: "echo three"
26
+      - send_key: "Up"
27
+      - send_key: "Up"
28
+      - send_key: "Enter"
29
+    expect_output: "two"
30
+    match_type: "contains"
31
+
32
+  - name: "Down arrow navigates forward in history"
33
+    steps:
34
+      - send_line: "echo first"
35
+      - send_line: "echo second"
36
+      - send_key: "Up"
37
+      - send_key: "Up"
38
+      - send_key: "Down"
39
+      - send_key: "Enter"
40
+    expect_output: "second"
41
+    match_type: "contains"
42
+
43
+  - name: "Down arrow at end returns to empty line"
44
+    steps:
45
+      - send_line: "echo test"
46
+      - send_key: "Up"
47
+      - send_key: "Down"
48
+      - send: "echo new"
49
+      - send_key: "Enter"
50
+    expect_output: "new"
51
+    match_type: "contains"
52
+
53
+  - name: "Up arrow at beginning of history stops"
54
+    steps:
55
+      - send_line: "echo only"
56
+      - send_key: "Up"
57
+      - send_key: "Up"
58
+      - send_key: "Up"
59
+      - send_key: "Enter"
60
+    expect_output: "only"
61
+    match_type: "contains"
62
+
63
+  # =============================================================
64
+  # HISTORY NAVIGATION - Ctrl Keys
65
+  # =============================================================
66
+  - name: "Ctrl+P recalls previous command"
67
+    steps:
68
+      - send_line: "echo previous"
69
+      - send_key: "C-p"
70
+      - send_key: "Enter"
71
+    expect_output: "previous"
72
+    match_type: "contains"
73
+
74
+  - name: "Ctrl+N navigates forward in history"
75
+    steps:
76
+      - send_line: "echo one"
77
+      - send_line: "echo two"
78
+      - send_key: "C-p"
79
+      - send_key: "C-p"
80
+      - send_key: "C-n"
81
+      - send_key: "Enter"
82
+    expect_output: "two"
83
+    match_type: "contains"
84
+
85
+  - name: "Mixed Ctrl+P and Up arrow"
86
+    steps:
87
+      - send_line: "echo first"
88
+      - send_line: "echo second"
89
+      - send_key: "Up"
90
+      - send_key: "C-p"
91
+      - send_key: "Enter"
92
+    expect_output: "first"
93
+    match_type: "contains"
94
+
95
+  # =============================================================
96
+  # HISTORY EDITING
97
+  # =============================================================
98
+  - name: "Edit recalled command before executing"
99
+    steps:
100
+      - send_line: "echo original"
101
+      - send_key: "Up"
102
+      - send_key: "C-a"
103
+      - send_key: "M-f"
104
+      - send_key: "C-k"
105
+      - send: " modified"
106
+      - send_key: "Enter"
107
+    expect_output: "modified"
108
+    match_type: "contains"
109
+
110
+  - name: "Cancel history navigation with Ctrl+G"
111
+    steps:
112
+      - send_line: "echo first"
113
+      - send_key: "Up"
114
+      - send_key: "C-g"
115
+      - send: "echo second"
116
+      - send_key: "Enter"
117
+    expect_output: "second"
118
+    match_type: "contains"
119
+
120
+  # =============================================================
121
+  # HISTORY SEARCH - Ctrl+R
122
+  # =============================================================
123
+  - name: "Ctrl+R starts reverse search"
124
+    steps:
125
+      - send_line: "echo findme"
126
+      - send_line: "echo other"
127
+      - send_key: "C-r"
128
+      - send: "find"
129
+      - send_key: "Enter"
130
+    expect_output: "findme"
131
+    match_type: "contains"
132
+
133
+  - name: "Ctrl+R finds partial match"
134
+    steps:
135
+      - send_line: "echo hello world"
136
+      - send_line: "echo goodbye"
137
+      - send_key: "C-r"
138
+      - send: "hello"
139
+      - send_key: "Enter"
140
+    expect_output: "hello world"
141
+    match_type: "contains"
142
+
143
+  - name: "Ctrl+R multiple matches cycles backwards"
144
+    steps:
145
+      - send_line: "echo match1"
146
+      - send_line: "echo match2"
147
+      - send_line: "echo match3"
148
+      - send_key: "C-r"
149
+      - send: "match"
150
+      - send_key: "C-r"
151
+      - send_key: "Enter"
152
+    expect_output: "match2"
153
+    match_type: "contains"
154
+
155
+  - name: "Ctrl+G cancels search"
156
+    steps:
157
+      - send_line: "echo searchme"
158
+      - send_key: "C-r"
159
+      - send: "search"
160
+      - send_key: "C-g"
161
+      - send: "echo cancelled"
162
+      - send_key: "Enter"
163
+    expect_output: "cancelled"
164
+    match_type: "contains"
165
+
166
+  - name: "Enter executes found command"
167
+    steps:
168
+      - send_line: "echo execute"
169
+      - send_key: "C-r"
170
+      - send: "exec"
171
+      - send_key: "Enter"
172
+    expect_output: "execute"
173
+    match_type: "contains"
174
+
175
+  - name: "Right arrow exits search and edits"
176
+    steps:
177
+      - send_line: "echo editme"
178
+      - send_key: "C-r"
179
+      - send: "edit"
180
+      - send_key: "Right"
181
+      - send_key: "C-e"
182
+      - send: " added"
183
+      - send_key: "Enter"
184
+    expect_output: "editme added"
185
+    match_type: "contains"
186
+
187
+  # =============================================================
188
+  # HISTORY EXPANSION - Basic
189
+  # =============================================================
190
+  - name: "!! repeats last command"
191
+    steps:
192
+      - send_line: "echo repeat"
193
+      - send_line: "!!"
194
+    expect_output: "repeat"
195
+    match_type: "contains"
196
+
197
+  - name: "!-1 repeats last command"
198
+    steps:
199
+      - send_line: "echo minusone"
200
+      - send_line: "!-1"
201
+    expect_output: "minusone"
202
+    match_type: "contains"
203
+
204
+  - name: "!-2 repeats second to last"
205
+    steps:
206
+      - send_line: "echo first"
207
+      - send_line: "echo second"
208
+      - send_line: "!-2"
209
+    expect_output: "first"
210
+    match_type: "contains"
211
+
212
+  - name: "!string finds command starting with string"
213
+    steps:
214
+      - send_line: "echo hello"
215
+      - send_line: "pwd"
216
+      - send_line: "!echo"
217
+    expect_output: "hello"
218
+    match_type: "contains"
219
+
220
+  - name: "!?string finds command containing string"
221
+    steps:
222
+      - send_line: "echo findthis"
223
+      - send_line: "pwd"
224
+      - send_line: "!?find"
225
+    expect_output: "findthis"
226
+    match_type: "contains"
227
+
228
+  # =============================================================
229
+  # HISTORY EXPANSION - Substitution
230
+  # =============================================================
231
+  - name: "^old^new substitution"
232
+    steps:
233
+      - send_line: "echo old"
234
+      - send_line: "^old^new"
235
+    expect_output: "new"
236
+    match_type: "contains"
237
+
238
+  # DISABLED: History word modifiers (:s/old/new/) not yet implemented
239
+  # - name: "!!:s/old/new/ substitution"
240
+  #   steps:
241
+  #     - send_line: "echo oldvalue"
242
+  #     - send_line: "!!:s/old/new/"
243
+  #   expect_output: "newvalue"
244
+  #   match_type: "contains"
245
+
246
+  # =============================================================
247
+  # HISTORY EXPANSION - Word Designators
248
+  # =============================================================
249
+  - name: "!!:0 gets command name"
250
+    steps:
251
+      - send_line: "echo one two three"
252
+      - send_line: "!!:0"
253
+    expect_output: "echo"
254
+    match_type: "contains"
255
+
256
+  - name: "!!:1 gets first argument"
257
+    steps:
258
+      - send_line: "echo first second third"
259
+      - send_line: "echo !!:1"
260
+    expect_output: "first"
261
+    match_type: "contains"
262
+
263
+  - name: "!!:$ gets last argument"
264
+    steps:
265
+      - send_line: "echo one two last"
266
+      - send_line: "echo !!:$"
267
+    expect_output: "last"
268
+    match_type: "contains"
269
+
270
+  - name: "!!:* gets all arguments"
271
+    steps:
272
+      - send_line: "echo a b c"
273
+      - send_line: "printf '%s\\n' !!:*"
274
+    expect_output: "a"
275
+    match_type: "contains"
276
+
277
+  - name: "!$ gets last argument of previous command"
278
+    steps:
279
+      - send_line: "echo one two three"
280
+      - send_line: "echo !$"
281
+    expect_output: "three"
282
+    match_type: "contains"
283
+
284
+  - name: "Alt+. inserts last argument"
285
+    steps:
286
+      - send_line: "echo lastarg"
287
+      - send: "echo "
288
+      - send_key: "M-."
289
+      - send_key: "Enter"
290
+    expect_output: "lastarg"
291
+    match_type: "contains"
292
+
293
+  # =============================================================
294
+  # HISTORY - Duplicates and Ignoring
295
+  # =============================================================
296
+  - name: "Duplicate commands in history"
297
+    steps:
298
+      - send_line: "echo dup"
299
+      - send_line: "echo dup"
300
+      - send_key: "Up"
301
+      - send_key: "Up"
302
+      - send_key: "Enter"
303
+    expect_output: "dup"
304
+    match_type: "contains"
305
+
306
+  - name: "Commands with leading space may be ignored"
307
+    steps:
308
+      - send_line: " echo secret"
309
+      - send_key: "Up"
310
+      - send_key: "Enter"
311
+    expect_output: "secret"
312
+    match_type: "contains"
313
+
314
+  # =============================================================
315
+  # HISTORY - Edge Cases
316
+  # =============================================================
317
+  - name: "Empty history navigation"
318
+    steps:
319
+      - send_key: "Up"
320
+      - send: "echo empty"
321
+      - send_key: "Enter"
322
+    expect_output: "empty"
323
+    match_type: "contains"
324
+
325
+  - name: "History with special characters"
326
+    steps:
327
+      - send_line: "echo 'special chars: $HOME'"
328
+      - send_key: "Up"
329
+      - send_key: "Enter"
330
+    expect_output: "special chars:"
331
+    match_type: "contains"
332
+
333
+  - name: "History with pipes"
334
+    steps:
335
+      - send_line: "echo hello | cat"
336
+      - send_key: "Up"
337
+      - send_key: "Enter"
338
+    expect_output: "hello"
339
+    match_type: "contains"
340
+
341
+  - name: "History with redirects"
342
+    steps:
343
+      - send_line: "echo test > /dev/null"
344
+      - send_key: "Up"
345
+      - send_key: "Enter"
346
+    expect_output: ""
347
+    match_type: "contains"
348
+
349
+  - name: "Long command in history"
350
+    steps:
351
+      - send_line: "echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
352
+      - send_key: "Up"
353
+      - send_key: "Enter"
354
+    expect_output: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
355
+    match_type: "contains"
356
+
357
+  # =============================================================
358
+  # HISTORY - Multiline
359
+  # =============================================================
360
+  - name: "History preserves multiline commands"
361
+    steps:
362
+      - send_line: "for i in 1 2; do echo $i; done"
363
+      - send_key: "Up"
364
+      - send_key: "Enter"
365
+    expect_output: "1"
366
+    match_type: "contains"
suites/interactive/posix/posix_core.yamladded
@@ -0,0 +1,774 @@
1
+# POSIX Shell Feature Tests for fortsh
2
+# Comprehensive tests for core POSIX shell functionality
3
+
4
+metadata:
5
+  category: "POSIX Shell Features"
6
+  description: "Tests for POSIX-compliant shell operations"
7
+  phase: 1
8
+
9
+tests:
10
+  # =============================================================
11
+  # BASIC OPERATIONS
12
+  # =============================================================
13
+  - name: "Simple echo"
14
+    steps:
15
+      - send_line: "echo hello"
16
+    expect_output: "hello"
17
+    match_type: "contains"
18
+
19
+  - name: "Exit status 0 for success"
20
+    steps:
21
+      - send_line: "true; echo $?"
22
+    expect_output: "0"
23
+    match_type: "contains"
24
+
25
+  - name: "Exit status 1 for failure"
26
+    steps:
27
+      - send_line: "false; echo $?"
28
+    expect_output: "1"
29
+    match_type: "contains"
30
+
31
+  - name: "Multiple commands with semicolon"
32
+    steps:
33
+      - send_line: "echo one; echo two; echo three"
34
+    expect_output: "three"
35
+    match_type: "contains"
36
+
37
+  - name: "Comment ignored"
38
+    steps:
39
+      - send_line: "echo visible # invisible"
40
+    expect_output: "visible"
41
+    match_type: "contains"
42
+
43
+  - name: "Command not found error"
44
+    steps:
45
+      - send_line: "nonexistentcmd123"
46
+    expect_output: "not found"
47
+    match_type: "contains"
48
+
49
+  # =============================================================
50
+  # QUOTING AND ESCAPING
51
+  # =============================================================
52
+  - name: "Single quotes preserve literal"
53
+    steps:
54
+      - send_line: "echo '$HOME'"
55
+    expect_output: "\\$HOME"
56
+    match_type: "contains"
57
+
58
+  - name: "Double quotes allow expansion"
59
+    steps:
60
+      - send_line: "echo \"$HOME\""
61
+    expect_output: "/"
62
+    match_type: "contains"
63
+
64
+  - name: "Backslash escapes special char"
65
+    steps:
66
+      - send_line: "echo \\$HOME"
67
+    expect_output: "\\$HOME"
68
+    match_type: "contains"
69
+
70
+  - name: "Backslash in double quotes"
71
+    steps:
72
+      - send_line: "echo \"\\$HOME\""
73
+    expect_output: "\\$HOME"
74
+    match_type: "contains"
75
+
76
+  - name: "Single quotes in double quotes"
77
+    steps:
78
+      - send_line: "echo \"it's\""
79
+    expect_output: "it's"
80
+    match_type: "contains"
81
+
82
+  - name: "Double quotes in single quotes"
83
+    steps:
84
+      - send_line: "echo '\"quoted\"'"
85
+    expect_output: "\"quoted\""
86
+    match_type: "contains"
87
+
88
+  - name: "Empty string"
89
+    steps:
90
+      - send_line: "echo ''"
91
+    expect_output: ""
92
+    match_type: "contains"
93
+
94
+  - name: "Concatenated quotes"
95
+    steps:
96
+      - send_line: "echo 'hello'\"world\""
97
+    expect_output: "helloworld"
98
+    match_type: "contains"
99
+
100
+  - name: "Escaped newline continues line"
101
+    steps:
102
+      - send: "echo hello \\"
103
+      - send_key: "Enter"
104
+      - send_line: "world"
105
+    expect_output: "hello world"
106
+    match_type: "contains"
107
+
108
+  - name: "Tab character with $''"
109
+    steps:
110
+      - send_line: "echo $'a\\tb'"
111
+    expect_output: "a"
112
+    match_type: "contains"
113
+
114
+  # =============================================================
115
+  # VARIABLES
116
+  # =============================================================
117
+  - name: "Variable assignment and use"
118
+    steps:
119
+      - send_line: "VAR=hello; echo $VAR"
120
+    expect_output: "hello"
121
+    match_type: "contains"
122
+
123
+  - name: "Variable with spaces in value"
124
+    steps:
125
+      - send_line: "VAR='hello world'; echo \"$VAR\""
126
+    expect_output: "hello world"
127
+    match_type: "contains"
128
+
129
+  - name: "Unset variable is empty"
130
+    steps:
131
+      - send_line: "echo \"x${UNSET_VAR}x\""
132
+    expect_output: "xx"
133
+    match_type: "contains"
134
+
135
+  - name: "Export variable"
136
+    steps:
137
+      - send_line: "export MYVAR=exported; echo $MYVAR"
138
+    expect_output: "exported"
139
+    match_type: "contains"
140
+
141
+  - name: "Variable in command"
142
+    steps:
143
+      - send_line: "CMD=echo; $CMD test"
144
+    expect_output: "test"
145
+    match_type: "contains"
146
+
147
+  - name: "Curly brace expansion"
148
+    steps:
149
+      - send_line: "VAR=test; echo ${VAR}ing"
150
+    expect_output: "testing"
151
+    match_type: "contains"
152
+
153
+  - name: "Default value ${var:-default}"
154
+    steps:
155
+      - send_line: "echo ${UNSET:-default}"
156
+    expect_output: "default"
157
+    match_type: "contains"
158
+
159
+  - name: "Assign default ${var:=default}"
160
+    steps:
161
+      - send_line: "echo ${NEWVAR:=assigned}; echo $NEWVAR"
162
+    expect_output: "assigned"
163
+    match_type: "contains"
164
+
165
+  - name: "Error if unset ${var:?msg}"
166
+    steps:
167
+      - send_line: "echo ${UNSET:?error message}"
168
+    expect_output: "error"
169
+    match_type: "contains"
170
+
171
+  - name: "Use alternate ${var:+alt}"
172
+    steps:
173
+      - send_line: "SET=yes; echo ${SET:+alternate}"
174
+    expect_output: "alternate"
175
+    match_type: "contains"
176
+
177
+  - name: "String length ${#var}"
178
+    fresh_session: true
179
+    steps:
180
+      - send_line: "VAR=hello; echo ${#VAR}"
181
+      - wait: 1.0
182
+    expect_output: "5"
183
+    match_type: "contains"
184
+
185
+  - name: "Remove prefix ${var#pattern}"
186
+    steps:
187
+      - send_line: "VAR=hello; echo ${VAR#hel}"
188
+    expect_output: "lo"
189
+    match_type: "contains"
190
+
191
+  - name: "Remove suffix ${var%pattern}"
192
+    steps:
193
+      - send_line: "VAR=hello; echo ${VAR%lo}"
194
+    expect_output: "hel"
195
+    match_type: "contains"
196
+
197
+  # =============================================================
198
+  # SPECIAL VARIABLES
199
+  # =============================================================
200
+  - name: "$? exit status"
201
+    steps:
202
+      - send_line: "true; echo $?"
203
+    expect_output: "0"
204
+    match_type: "contains"
205
+
206
+  - name: "$$ process ID"
207
+    steps:
208
+      - send_line: "echo $$"
209
+    expect_output: ""
210
+    match_type: "contains"
211
+
212
+  - name: "$0 shell name"
213
+    steps:
214
+      - send_line: "echo $0"
215
+    expect_output: "fortsh"
216
+    match_type: "contains"
217
+
218
+  - name: "$HOME environment"
219
+    steps:
220
+      - send_line: "echo $HOME"
221
+    expect_output: "/"
222
+    match_type: "contains"
223
+
224
+  - name: "$PWD current directory"
225
+    steps:
226
+      - send_line: "cd /tmp; echo $PWD"
227
+    expect_output: "/tmp"
228
+    match_type: "contains"
229
+
230
+  - name: "$OLDPWD previous directory"
231
+    steps:
232
+      - send_line: "cd /tmp; cd /; echo $OLDPWD"
233
+    expect_output: "/tmp"
234
+    match_type: "contains"
235
+
236
+  # =============================================================
237
+  # PIPELINES
238
+  # =============================================================
239
+  - name: "Simple pipe"
240
+    steps:
241
+      - send_line: "echo hello | cat"
242
+    expect_output: "hello"
243
+    match_type: "contains"
244
+
245
+  - name: "Multiple pipes"
246
+    steps:
247
+      - send_line: "echo hello | cat | cat"
248
+    expect_output: "hello"
249
+    match_type: "contains"
250
+
251
+  - name: "Pipe with grep"
252
+    steps:
253
+      - send_line: "echo -e 'one\\ntwo\\nthree' | grep two"
254
+    expect_output: "two"
255
+    match_type: "contains"
256
+
257
+  - name: "Pipe with wc"
258
+    steps:
259
+      - send_line: "echo hello | wc -c"
260
+    expect_output: "6"
261
+    match_type: "contains"
262
+
263
+  - name: "Pipe exit status is last command"
264
+    steps:
265
+      - send_line: "true | false; echo $?"
266
+    expect_output: "1"
267
+    match_type: "contains"
268
+
269
+  - name: "Negated pipeline"
270
+    steps:
271
+      - send_line: "! false; echo $?"
272
+    expect_output: "0"
273
+    match_type: "contains"
274
+
275
+  # =============================================================
276
+  # REDIRECTIONS
277
+  # =============================================================
278
+  - name: "Redirect stdout to file"
279
+    steps:
280
+      - send_line: "echo hello > /tmp/test_out.txt; cat /tmp/test_out.txt"
281
+    expect_output: "hello"
282
+    match_type: "contains"
283
+
284
+  - name: "Append to file"
285
+    steps:
286
+      - send_line: "echo one > /tmp/append.txt; echo two >> /tmp/append.txt; cat /tmp/append.txt"
287
+    expect_output: "two"
288
+    match_type: "contains"
289
+
290
+  - name: "Redirect stdin from file"
291
+    steps:
292
+      - send_line: "echo hello > /tmp/in.txt; cat < /tmp/in.txt"
293
+    expect_output: "hello"
294
+    match_type: "contains"
295
+
296
+  - name: "Redirect stderr"
297
+    steps:
298
+      - send_line: "ls /nonexistent 2> /tmp/err.txt; cat /tmp/err.txt"
299
+    expect_output: "No such file"
300
+    match_type: "contains"
301
+
302
+  - name: "Redirect stdout and stderr"
303
+    steps:
304
+      - send_line: "ls /nonexistent > /tmp/both.txt 2>&1; cat /tmp/both.txt"
305
+    expect_output: "No such file"
306
+    match_type: "contains"
307
+
308
+  - name: "Here document"
309
+    steps:
310
+      - send: "cat <<EOF"
311
+      - send_key: "Enter"
312
+      - send: "hello"
313
+      - send_key: "Enter"
314
+      - send: "EOF"
315
+      - send_key: "Enter"
316
+    expect_output: "hello"
317
+    match_type: "contains"
318
+
319
+  - name: "Here string"
320
+    steps:
321
+      - send_line: "cat <<< 'hello'"
322
+    expect_output: "hello"
323
+    match_type: "contains"
324
+
325
+  - name: "Redirect to /dev/null"
326
+    steps:
327
+      - send_line: "echo hidden > /dev/null; echo visible"
328
+    expect_output: "visible"
329
+    match_type: "contains"
330
+
331
+  # =============================================================
332
+  # COMMAND LISTS
333
+  # =============================================================
334
+  - name: "AND list - both succeed"
335
+    steps:
336
+      - send_line: "true && echo success"
337
+    expect_output: "success"
338
+    match_type: "contains"
339
+
340
+  - name: "AND list - first fails"
341
+    steps:
342
+      - send_line: "false && echo fail; echo done"
343
+    expect_output: "done"
344
+    match_type: "contains"
345
+
346
+  - name: "OR list - first succeeds"
347
+    steps:
348
+      - send_line: "true || echo fail; echo done"
349
+    expect_output: "done"
350
+    match_type: "contains"
351
+
352
+  - name: "OR list - first fails"
353
+    steps:
354
+      - send_line: "false || echo success"
355
+    expect_output: "success"
356
+    match_type: "contains"
357
+
358
+  - name: "Mixed AND/OR"
359
+    steps:
360
+      - send_line: "false || true && echo success"
361
+    expect_output: "success"
362
+    match_type: "contains"
363
+
364
+  # =============================================================
365
+  # CONTROL STRUCTURES
366
+  # =============================================================
367
+  - name: "If statement - true branch"
368
+    steps:
369
+      - send_line: "if true; then echo yes; fi"
370
+    expect_output: "yes"
371
+    match_type: "contains"
372
+
373
+  - name: "If statement - false branch"
374
+    steps:
375
+      - send_line: "if false; then echo no; else echo yes; fi"
376
+    expect_output: "yes"
377
+    match_type: "contains"
378
+
379
+  - name: "If with test command"
380
+    steps:
381
+      - send_line: "if [ 1 -eq 1 ]; then echo equal; fi"
382
+    expect_output: "equal"
383
+    match_type: "contains"
384
+
385
+  - name: "If with string test"
386
+    steps:
387
+      - send_line: "if [ 'a' = 'a' ]; then echo match; fi"
388
+    expect_output: "match"
389
+    match_type: "contains"
390
+
391
+  - name: "Elif chain"
392
+    steps:
393
+      - send_line: "if false; then echo 1; elif true; then echo 2; else echo 3; fi"
394
+    expect_output: "2"
395
+    match_type: "contains"
396
+
397
+  - name: "For loop with list"
398
+    steps:
399
+      - send_line: "for i in 1 2 3; do echo $i; done"
400
+    expect_output: "3"
401
+    match_type: "contains"
402
+
403
+  - name: "For loop with variable"
404
+    steps:
405
+      - send_line: "for i in a b c; do echo -n $i; done; echo"
406
+    expect_output: "abc"
407
+    match_type: "contains"
408
+
409
+  - name: "While loop"
410
+    steps:
411
+      - send_line: "i=0; while [ $i -lt 3 ]; do echo $i; i=$((i+1)); done"
412
+    expect_output: "2"
413
+    match_type: "contains"
414
+
415
+  - name: "Until loop"
416
+    steps:
417
+      - send_line: "i=0; until [ $i -ge 3 ]; do echo $i; i=$((i+1)); done"
418
+    expect_output: "2"
419
+    match_type: "contains"
420
+
421
+  - name: "Case statement"
422
+    steps:
423
+      - send_line: "case abc in a*) echo match;; esac"
424
+    expect_output: "match"
425
+    match_type: "contains"
426
+
427
+  - name: "Case with multiple patterns"
428
+    steps:
429
+      - send_line: "case foo in bar|foo) echo found;; esac"
430
+    expect_output: "found"
431
+    match_type: "contains"
432
+
433
+  - name: "Case default"
434
+    steps:
435
+      - send_line: "case xyz in *) echo default;; esac"
436
+    expect_output: "default"
437
+    match_type: "contains"
438
+
439
+  - name: "Break in loop"
440
+    steps:
441
+      - send_line: "for i in 1 2 3 4 5; do if [ $i -eq 3 ]; then break; fi; echo $i; done"
442
+    expect_output: "2"
443
+    match_type: "contains"
444
+
445
+  - name: "Continue in loop"
446
+    steps:
447
+      - send_line: "for i in 1 2 3; do if [ $i -eq 2 ]; then continue; fi; echo $i; done"
448
+    expect_output: "3"
449
+    match_type: "contains"
450
+
451
+  # =============================================================
452
+  # FUNCTIONS
453
+  # =============================================================
454
+  - name: "Function definition and call"
455
+    steps:
456
+      - send_line: "myfunc() { echo hello; }; myfunc"
457
+    expect_output: "hello"
458
+    match_type: "contains"
459
+
460
+  - name: "Function with arguments"
461
+    steps:
462
+      - send_line: "greet() { echo \"Hello $1\"; }; greet World"
463
+    expect_output: "Hello World"
464
+    match_type: "contains"
465
+
466
+  - name: "Function return value"
467
+    steps:
468
+      - send_line: "ret5() { return 5; }; ret5; echo $?"
469
+    expect_output: "5"
470
+    match_type: "contains"
471
+
472
+  - name: "Function local variable"
473
+    steps:
474
+      - send_line: "f() { local x=inside; echo $x; }; x=outside; f; echo $x"
475
+    expect_output: "outside"
476
+    match_type: "contains"
477
+
478
+  - name: "Function with $# argc"
479
+    steps:
480
+      - send_line: "argc() { echo $#; }; argc a b c"
481
+    expect_output: "3"
482
+    match_type: "contains"
483
+
484
+  - name: "Function with $@"
485
+    steps:
486
+      - send_line: "all() { echo \"$@\"; }; all one two three"
487
+    expect_output: "one two three"
488
+    match_type: "contains"
489
+
490
+  # =============================================================
491
+  # ARITHMETIC
492
+  # =============================================================
493
+  - name: "Arithmetic expansion"
494
+    steps:
495
+      - send_line: "echo $((2 + 3))"
496
+    expect_output: "5"
497
+    match_type: "contains"
498
+
499
+  - name: "Arithmetic subtraction"
500
+    steps:
501
+      - send_line: "echo $((10 - 4))"
502
+    expect_output: "6"
503
+    match_type: "contains"
504
+
505
+  - name: "Arithmetic multiplication"
506
+    steps:
507
+      - send_line: "echo $((3 * 4))"
508
+    expect_output: "12"
509
+    match_type: "contains"
510
+
511
+  - name: "Arithmetic division"
512
+    steps:
513
+      - send_line: "echo $((15 / 3))"
514
+    expect_output: "5"
515
+    match_type: "contains"
516
+
517
+  - name: "Arithmetic modulo"
518
+    steps:
519
+      - send_line: "echo $((17 % 5))"
520
+    expect_output: "2"
521
+    match_type: "contains"
522
+
523
+  - name: "Arithmetic with variable"
524
+    steps:
525
+      - send_line: "x=10; echo $((x * 2))"
526
+    expect_output: "20"
527
+    match_type: "contains"
528
+
529
+  - name: "Arithmetic comparison"
530
+    steps:
531
+      - send_line: "echo $((5 > 3))"
532
+    expect_output: "1"
533
+    match_type: "contains"
534
+
535
+  - name: "Nested arithmetic"
536
+    steps:
537
+      - send_line: "echo $(((2 + 3) * 4))"
538
+    expect_output: "20"
539
+    match_type: "contains"
540
+
541
+  # =============================================================
542
+  # COMMAND SUBSTITUTION
543
+  # =============================================================
544
+  - name: "Command substitution $()"
545
+    steps:
546
+      - send_line: "echo $(echo hello)"
547
+    expect_output: "hello"
548
+    match_type: "contains"
549
+
550
+  - name: "Command substitution backticks"
551
+    steps:
552
+      - send_line: "echo `echo hello`"
553
+    expect_output: "hello"
554
+    match_type: "contains"
555
+
556
+  - name: "Nested command substitution"
557
+    steps:
558
+      - send_line: "echo $(echo $(echo deep))"
559
+    expect_output: "deep"
560
+    match_type: "contains"
561
+
562
+  - name: "Command substitution in variable"
563
+    steps:
564
+      - send_line: "VAR=$(echo value); echo $VAR"
565
+    expect_output: "value"
566
+    match_type: "contains"
567
+
568
+  - name: "Command substitution in string"
569
+    steps:
570
+      - send_line: "echo \"Today is $(date +%A)\""
571
+    expect_output: "Today is"
572
+    match_type: "contains"
573
+
574
+  # =============================================================
575
+  # SUBSHELLS
576
+  # =============================================================
577
+  - name: "Subshell execution"
578
+    steps:
579
+      - send_line: "(echo subshell)"
580
+    expect_output: "subshell"
581
+    match_type: "contains"
582
+
583
+  - name: "Subshell variable isolation"
584
+    steps:
585
+      - send_line: "X=outer; (X=inner; echo $X); echo $X"
586
+    expect_output: "outer"
587
+    match_type: "contains"
588
+
589
+  - name: "Subshell cd isolation"
590
+    steps:
591
+      - send_line: "cd /tmp; (cd /); pwd"
592
+    expect_output: "/tmp"
593
+    match_type: "contains"
594
+
595
+  # =============================================================
596
+  # GLOBBING
597
+  # =============================================================
598
+  - name: "Star glob"
599
+    steps:
600
+      - send_line: "cd /tmp; touch glob1.txt glob2.txt; ls glob*.txt"
601
+    expect_output: "glob"
602
+    match_type: "contains"
603
+
604
+  - name: "Question mark glob"
605
+    steps:
606
+      - send_line: "cd /tmp; touch a1 a2 a3; ls a?"
607
+    expect_output: "a1"
608
+    match_type: "contains"
609
+
610
+  - name: "Bracket glob"
611
+    steps:
612
+      - send_line: "cd /tmp; touch b1 b2 b3; ls b[12]"
613
+    expect_output: "b1"
614
+    match_type: "contains"
615
+
616
+  - name: "No match returns literal"
617
+    steps:
618
+      - send_line: "echo /nonexistent/*.xyz"
619
+    expect_output: "/nonexistent/\\*\\.xyz"
620
+    match_type: "contains"
621
+
622
+  # =============================================================
623
+  # BUILTINS
624
+  # =============================================================
625
+  - name: "cd to directory"
626
+    steps:
627
+      - send_line: "cd /tmp; pwd"
628
+    expect_output: "/tmp"
629
+    match_type: "contains"
630
+
631
+  - name: "cd to home"
632
+    steps:
633
+      - send_line: "cd; pwd"
634
+    expect_output: "/"
635
+    match_type: "contains"
636
+
637
+  - name: "cd to previous"
638
+    steps:
639
+      - send_line: "cd /tmp; cd /; cd -; pwd"
640
+    expect_output: "/tmp"
641
+    match_type: "contains"
642
+
643
+  - name: "pwd builtin"
644
+    steps:
645
+      - send_line: "pwd"
646
+    expect_output: "/"
647
+    match_type: "contains"
648
+
649
+  - name: "echo with -n"
650
+    steps:
651
+      - send_line: "echo -n hello; echo world"
652
+    expect_output: "helloworld"
653
+    match_type: "contains"
654
+
655
+  - name: "echo with -e"
656
+    steps:
657
+      - send_line: "echo -e 'a\\tb'"
658
+    expect_output: "a"
659
+    match_type: "contains"
660
+
661
+  - name: "printf basic"
662
+    steps:
663
+      - send_line: "printf '%s\\n' hello"
664
+    expect_output: "hello"
665
+    match_type: "contains"
666
+
667
+  - name: "printf with format"
668
+    steps:
669
+      - send_line: "printf '%d + %d = %d\\n' 2 3 5"
670
+    expect_output: "2 \\+ 3 = 5"
671
+    match_type: "contains"
672
+
673
+  - name: "test numeric equality"
674
+    steps:
675
+      - send_line: "test 5 -eq 5 && echo equal"
676
+    expect_output: "equal"
677
+    match_type: "contains"
678
+
679
+  - name: "test string equality"
680
+    steps:
681
+      - send_line: "test 'a' = 'a' && echo equal"
682
+    expect_output: "equal"
683
+    match_type: "contains"
684
+
685
+  - name: "test file exists"
686
+    steps:
687
+      - send_line: "touch /tmp/exists.txt; test -e /tmp/exists.txt && echo exists"
688
+    expect_output: "exists"
689
+    match_type: "contains"
690
+
691
+  - name: "test file is regular"
692
+    steps:
693
+      - send_line: "touch /tmp/regular.txt; test -f /tmp/regular.txt && echo regular"
694
+    expect_output: "regular"
695
+    match_type: "contains"
696
+
697
+  - name: "test directory"
698
+    steps:
699
+      - send_line: "test -d /tmp && echo isdir"
700
+    expect_output: "isdir"
701
+    match_type: "contains"
702
+
703
+  - name: "read builtin"
704
+    steps:
705
+      - send_line: "echo hello | { read x; echo $x; }"
706
+    expect_output: "hello"
707
+    match_type: "contains"
708
+
709
+  - name: "type builtin"
710
+    steps:
711
+      - send_line: "type echo"
712
+    expect_output: "builtin"
713
+    match_type: "contains"
714
+
715
+  - name: "unset variable"
716
+    steps:
717
+      - send_line: "VAR=set; unset VAR; echo \"x${VAR}x\""
718
+    expect_output: "xx"
719
+    match_type: "contains"
720
+
721
+  - name: "readonly variable"
722
+    steps:
723
+      - send_line: "readonly RO=value; echo $RO"
724
+    expect_output: "value"
725
+    match_type: "contains"
726
+
727
+  - name: "eval command"
728
+    steps:
729
+      - send_line: "CMD='echo hello'; eval $CMD"
730
+    expect_output: "hello"
731
+    match_type: "contains"
732
+
733
+  - name: "exec replaces shell (test with subshell)"
734
+    steps:
735
+      - send_line: "(exec echo replaced)"
736
+    expect_output: "replaced"
737
+    match_type: "contains"
738
+
739
+  - name: "source/dot command"
740
+    steps:
741
+      - send_line: "echo 'VAR=sourced' > /tmp/src.sh; . /tmp/src.sh; echo $VAR"
742
+    expect_output: "sourced"
743
+    match_type: "contains"
744
+
745
+  - name: "shift arguments"
746
+    steps:
747
+      - send_line: "set -- a b c; shift; echo $1"
748
+    expect_output: "b"
749
+    match_type: "contains"
750
+
751
+  - name: "set positional parameters"
752
+    steps:
753
+      - send_line: "set -- x y z; echo $2"
754
+    expect_output: "y"
755
+    match_type: "contains"
756
+
757
+  - name: "trap command"
758
+    steps:
759
+      - send_line: "trap 'echo trapped' EXIT; exit"
760
+    expect_output: "trapped"
761
+    match_type: "contains"
762
+
763
+  - name: "alias creation and use"
764
+    steps:
765
+      - send_line: "alias ll='ls -l'; type ll"
766
+    expect_output: "alias"
767
+    match_type: "contains"
768
+
769
+  - name: "unalias removes alias"
770
+    steps:
771
+      - send_line: "alias foo=bar; unalias foo; type foo"
772
+    expect_output: "not found"
773
+    match_type: "contains"
774
+