xref: /freebsd/bin/ed/tests/ed_test.sh (revision 7c2c2c2a2253370c88fe428cf1c0ecebd68fe864)
1# SPDX-License-Identifier: BSD-2-Clause
2# Copyright (c) 2025 Baptiste Daroussin <bapt@FreeBSD.org>
3
4# Helper: create standard 5-line data file
5create_std_data()
6{
7	cat > "$1" <<'EOF'
8line 1
9line 2
10line 3
11line 4
12line5
13EOF
14}
15
16# ---------------------------------------------------------------------------
17# Append (a)
18# ---------------------------------------------------------------------------
19atf_test_case append
20append_head()
21{
22	atf_set "descr" "Test append command (a)"
23}
24
25append_body()
26{
27	create_std_data input.txt
28	ed -s - <<'CMDS'
29H
30r input.txt
310a
32hello world
33.
342a
35hello world!
36.
37$a
38hello world!!
39.
40w output.txt
41CMDS
42	cat > expected.txt <<'EOF'
43hello world
44line 1
45hello world!
46line 2
47line 3
48line 4
49line5
50hello world!!
51EOF
52	atf_check cmp output.txt expected.txt
53}
54
55# ---------------------------------------------------------------------------
56# Address parsing (addr)
57# ---------------------------------------------------------------------------
58atf_test_case address
59address_head()
60{
61	atf_set "descr" "Test complex address parsing"
62}
63address_body()
64{
65	cat > input.txt <<'EOF'
66line 1
67line 2
68line 3
69line 4
70line5
711ine6
72line7
73line8
74line9
75EOF
76	ed -s - <<'CMDS'
77H
78r input.txt
791 d
801 1 d
811,2,d
821;+ + ,d
831,2;., + 2d
84w output.txt
85CMDS
86	cat > expected.txt <<'EOF'
87line 2
88line9
89EOF
90	atf_check cmp output.txt expected.txt
91}
92
93# ---------------------------------------------------------------------------
94# Change (c)
95# ---------------------------------------------------------------------------
96atf_test_case change
97change_head()
98{
99	atf_set "descr" "Test change command (c)"
100}
101change_body()
102{
103	create_std_data input.txt
104	ed -s - <<'CMDS'
105H
106r input.txt
1071c
108at the top
109.
1104c
111in the middle
112.
113$c
114at the bottom
115.
1162,3c
117between top/middle
118.
119w output.txt
120CMDS
121	cat > expected.txt <<'EOF'
122at the top
123between top/middle
124in the middle
125at the bottom
126EOF
127	atf_check cmp output.txt expected.txt
128}
129
130# ---------------------------------------------------------------------------
131# Delete (d)
132# ---------------------------------------------------------------------------
133atf_test_case delete
134delete_head()
135{
136	atf_set "descr" "Test delete command (d)"
137}
138delete_body()
139{
140	create_std_data input.txt
141	ed -s - <<'CMDS'
142H
143r input.txt
1441d
1452;+1d
146$d
147w output.txt
148CMDS
149	printf 'line 2\n' > expected.txt
150	atf_check cmp output.txt expected.txt
151}
152
153# ---------------------------------------------------------------------------
154# Insert (i)
155# ---------------------------------------------------------------------------
156atf_test_case insert
157insert_head()
158{
159	atf_set "descr" "Test insert command (i)"
160}
161insert_body()
162{
163	create_std_data input.txt
164	ed -s - <<'CMDS'
165H
166r input.txt
1671i
168hello world
169.
1702i
171hello world!
172.
173$i
174hello world!!
175.
176w output.txt
177CMDS
178	cat > expected.txt <<'EOF'
179hello world
180hello world!
181line 1
182line 2
183line 3
184line 4
185hello world!!
186line5
187EOF
188	atf_check cmp output.txt expected.txt
189}
190
191# ---------------------------------------------------------------------------
192# Join (j)
193# ---------------------------------------------------------------------------
194atf_test_case join
195join_head()
196{
197	atf_set "descr" "Test join command (j)"
198}
199join_body()
200{
201	create_std_data input.txt
202	ed -s - <<'CMDS'
203H
204r input.txt
2051,1j
2062,3j
207w output.txt
208CMDS
209	cat > expected.txt <<'EOF'
210line 1
211line 2line 3
212line 4
213line5
214EOF
215	atf_check cmp output.txt expected.txt
216}
217
218# ---------------------------------------------------------------------------
219# Mark (k)
220# ---------------------------------------------------------------------------
221atf_test_case mark
222mark_head()
223{
224	atf_set "descr" "Test mark and reference commands (k, ')"
225}
226mark_body()
227{
228	create_std_data input.txt
229	ed -s - <<'CMDS'
230H
231r input.txt
2322ka
2331d
234'am$
2351ka
2360a
237hello world
238.
239'ad
240u
241'am0
242w output.txt
243CMDS
244	cat > expected.txt <<'EOF'
245line 3
246hello world
247line 4
248line5
249line 2
250EOF
251	atf_check cmp output.txt expected.txt
252}
253
254# ---------------------------------------------------------------------------
255# Move (m)
256# ---------------------------------------------------------------------------
257atf_test_case move
258move_head()
259{
260	atf_set "descr" "Test move command (m)";
261}
262move_body()
263{
264
265	create_std_data input.txt
266	ed -s - <<'CMDS'
267H
268r input.txt
2691,2m$
2701,2m$
2711,2m$
272$m0
273$m0
2742,3m1
2752,3m3
276w output.txt
277CMDS
278	cat > expected.txt <<'EOF'
279line5
280line 1
281line 2
282line 3
283line 4
284EOF
285	atf_check cmp output.txt expected.txt
286}
287
288# ---------------------------------------------------------------------------
289# Transfer / Copy (t)
290# ---------------------------------------------------------------------------
291atf_test_case transfer
292transfer_head()
293{
294	atf_set "descr" "Test transfer/copy command (t)";
295}
296transfer_body()
297{
298
299	create_std_data input.txt
300	ed -s - <<'CMDS'
301H
302r input.txt
3031t0
3042,3t2
305,t$
306w output.txt
307CMDS
308	cat > expected.txt <<'EOF'
309line 1
310line 1
311line 1
312line 2
313line 2
314line 3
315line 4
316line5
317line 1
318line 1
319line 1
320line 2
321line 2
322line 3
323line 4
324line5
325EOF
326	atf_check cmp output.txt expected.txt
327}
328
329atf_test_case transfer_search
330transfer_search_head()
331{
332	atf_set "descr" "Test transfer with address search (t)";
333}
334transfer_search_body()
335{
336
337	create_std_data input.txt
338	ed -s - <<'CMDS'
339H
340r input.txt
341t0;/./
342w output.txt
343CMDS
344	cat > expected.txt <<'EOF'
345line 1
346line5
347line 2
348line 3
349line 4
350line5
351EOF
352	atf_check cmp output.txt expected.txt
353}
354
355# ---------------------------------------------------------------------------
356# Undo (u)
357# ---------------------------------------------------------------------------
358atf_test_case undo
359undo_head()
360{
361	atf_set "descr" "Test undo command (u)";
362}
363undo_body()
364{
365
366	create_std_data input.txt
367	printf 'dummy\n' > readfile.txt
368	ed -s - <<'CMDS'
369H
370r input.txt
3711;r readfile.txt
372u
373a
374hello
375world
376.
377g/./s//x/\
378a\
379hello\
380world
381u
382u
383u
384a
385hello world!
386.
387u
3881,$d
389u
3902,3d
391u
392c
393hello world!!
394.
395u
396u
397-1;.,+1j
398u
399u
400u
401.,+1t$
402w output.txt
403CMDS
404	cat > expected.txt <<'EOF'
405line 1
406hello
407hello world!!
408line 2
409line 3
410line 4
411line5
412hello
413hello world!!
414EOF
415	atf_check cmp output.txt expected.txt
416}
417
418# ---------------------------------------------------------------------------
419# Global (g)
420# ---------------------------------------------------------------------------
421atf_test_case global_move
422global_move_head()
423{
424	atf_set "descr" "Test global command with move (g)";
425}
426global_move_body()
427{
428
429	create_std_data input.txt
430	ed -s - <<'CMDS'
431H
432r input.txt
433g/./m0
434g/./s/$/\
435hello world
436g/hello /s/lo/p!/\
437a\
438order
439w output.txt
440CMDS
441	cat > expected.txt <<'EOF'
442line5
443help! world
444order
445line 4
446help! world
447order
448line 3
449help! world
450order
451line 2
452help! world
453order
454line 1
455help! world
456order
457EOF
458	atf_check cmp output.txt expected.txt
459}
460
461atf_test_case global_change
462global_change_head()
463{
464	atf_set "descr" "Test global command with change (g)";
465}
466global_change_body()
467{
468
469	create_std_data input.txt
470	ed -s - <<'CMDS'
471H
472r input.txt
473g/[2-4]/-1,+1c\
474hello world
475w output.txt
476CMDS
477	printf 'hello world\n' > expected.txt
478	atf_check cmp output.txt expected.txt
479}
480
481atf_test_case global_substitute
482global_substitute_head()
483{
484	atf_set "descr" "Test global with substitute and move (g)";
485}
486global_substitute_body()
487{
488
489	create_std_data input.txt
490	ed -s - <<'CMDS'
491H
492r input.txt
493g/./s//x/\
4943m0
495g/./s/e/c/\
4962,3m1
497w output.txt
498CMDS
499	cat > expected.txt <<'EOF'
500linc 3
501xine 1
502xine 2
503xinc 4
504xinc5
505EOF
506	atf_check cmp output.txt expected.txt
507}
508
509atf_test_case global_undo
510global_undo_head()
511{
512	atf_set "descr" "Test global with undo (g)";
513}
514global_undo_body()
515{
516
517	create_std_data input.txt
518	ed -s - <<'CMDS'
519H
520r input.txt
521g/./s/./x/\
522u\
523s/./y/\
524u\
525s/./z/\
526u
527u
5280a
529hello
530.
531$a
532world
533.
534w output.txt
535CMDS
536	cat > expected.txt <<'EOF'
537hello
538zine 1
539line 2
540line 3
541line 4
542line5
543world
544EOF
545	atf_check cmp output.txt expected.txt
546}
547
548atf_test_case global_copy
549global_copy_head()
550{
551	atf_set "descr" "Test global with copy (g)";
552}
553global_copy_body()
554{
555
556	cat > input.txt <<'EOF'
557line 1
558line 2
559line 3
560EOF
561	ed -s - <<'CMDS'
562H
563r input.txt
564g/./1,3t$\
5651d
566w output.txt
567CMDS
568	cat > expected.txt <<'EOF'
569line 1
570line 2
571line 3
572line 2
573line 3
574line 1
575line 3
576line 1
577line 2
578EOF
579	atf_check cmp output.txt expected.txt
580}
581
582# ---------------------------------------------------------------------------
583# Inverse global (v)
584# ---------------------------------------------------------------------------
585atf_test_case inverse_global
586inverse_global_head()
587{
588	atf_set "descr" "Test inverse global command (v)";
589}
590inverse_global_body()
591{
592
593	create_std_data input.txt
594	ed -s - <<'CMDS'
595H
596r input.txt
597v/[ ]/m0
598v/[ ]/s/$/\
599hello world
600v/hello /s/lo/p!/\
601a\
602order
603w output.txt
604CMDS
605	cat > expected.txt <<'EOF'
606line5
607order
608hello world
609line 1
610order
611line 2
612order
613line 3
614order
615line 4
616order
617EOF
618	atf_check cmp output.txt expected.txt
619}
620
621# ---------------------------------------------------------------------------
622# Substitution (s)
623# ---------------------------------------------------------------------------
624atf_test_case subst_backreference
625subst_backreference_head()
626{
627	atf_set "descr" "Test substitute with backreferences (s)";
628}
629subst_backreference_body()
630{
631
632	create_std_data input.txt
633	ed -s - <<'CMDS'
634H
635r input.txt
636s/\([^ ][^ ]*\)/(\1)/g
6372s
638/3/s
639/\(4\)/sr
640/\(.\)/srg
641%s/i/&e/
642w output.txt
643CMDS
644	cat > expected.txt <<'EOF'
645liene 1
646(liene) (2)
647(liene) (3)
648liene (4)
649(()liene5)
650EOF
651	atf_check cmp output.txt expected.txt
652}
653
654atf_test_case subst_range
655subst_range_head()
656{
657	atf_set "descr" "Test substitute on range with count and repeat (s)";
658}
659subst_range_body()
660{
661
662	create_std_data input.txt
663	ed -s - <<'CMDS'
664H
665r input.txt
666,s/./(&)/3
667s/$/00
6682s//%/g
669s/^l
670w output.txt
671CMDS
672	cat > expected.txt <<'EOF'
673li(n)e 1
674i(n)e 200
675li(n)e 3
676li(n)e 4
677li(n)e500
678EOF
679	atf_check cmp output.txt expected.txt
680}
681
682atf_test_case subst_charclass
683subst_charclass_head()
684{
685	atf_set "descr" "Test substitute with character classes (s)";
686}
687subst_charclass_body()
688{
689
690	ed -s - <<'CMDS'
691H
692a
693hello/[]world
694.
695s/[/]/ /
696s/[[:digit:][]/ /
697s/[]]/ /
698w output.txt
699CMDS
700	printf 'hello   world\n' > expected.txt
701	atf_check cmp output.txt expected.txt
702}
703
704# ---------------------------------------------------------------------------
705# Edit (e/E)
706# ---------------------------------------------------------------------------
707atf_test_case edit_file
708edit_file_head()
709{
710	atf_set "descr" "Test edit file command (E)";
711}
712edit_file_body()
713{
714
715	printf 'hello world\n' > input.txt
716	printf 'E e1_data.txt\n' > e1_data.txt
717	ed -s - <<'CMDS'
718H
719r input.txt
720E e1_data.txt
721w output.txt
722CMDS
723	printf 'E e1_data.txt\n' > expected.txt
724	atf_check cmp output.txt expected.txt
725}
726
727atf_test_case edit_command
728edit_command_head()
729{
730	atf_set "descr" "Test edit with shell command (E !)";
731}
732edit_command_body()
733{
734
735	printf 'E !echo hello world-\n' > input.txt
736	ed -s - <<'CMDS'
737H
738r input.txt
739E !echo hello world-
740w output.txt
741CMDS
742	printf 'hello world-\n' > expected.txt
743	atf_check cmp output.txt expected.txt
744}
745
746atf_test_case edit_reread
747edit_reread_head()
748{
749	atf_set "descr" "Test edit re-read default file (E)";
750}
751edit_reread_body()
752{
753
754	printf 'E !echo hello world-\n' > input.txt
755	ed -s - <<'CMDS'
756H
757r input.txt
758E
759w output.txt
760CMDS
761	printf 'E !echo hello world-\n' > expected.txt
762	atf_check cmp output.txt expected.txt
763}
764
765atf_test_case edit_lowercase
766edit_lowercase_head()
767{
768	atf_set "descr" "Test lowercase edit re-read (e)";
769}
770edit_lowercase_body()
771{
772
773	printf 'E !echo hello world-\n' > input.txt
774	ed -s - <<'CMDS'
775H
776r input.txt
777e
778w output.txt
779CMDS
780	printf 'E !echo hello world-\n' > expected.txt
781	atf_check cmp output.txt expected.txt
782}
783
784# ---------------------------------------------------------------------------
785# Read (r)
786# ---------------------------------------------------------------------------
787atf_test_case read_command
788read_command_head()
789{
790	atf_set "descr" "Test read with shell command (r !)";
791}
792read_command_body()
793{
794
795	create_std_data input.txt
796	ed -s - <<'CMDS'
797H
798r input.txt
7991;r !echo hello world
8001
801r !echo hello world
802w output.txt
803CMDS
804	cat > expected.txt <<'EOF'
805line 1
806hello world
807line 2
808line 3
809line 4
810line5
811hello world
812EOF
813	atf_check cmp output.txt expected.txt
814}
815
816atf_test_case read_default
817read_default_head()
818{
819	atf_set "descr" "Test read with default filename (r)";
820}
821read_default_body()
822{
823
824	create_std_data input.txt
825	ed -s - <<'CMDS'
826H
827r input.txt
828r
829w output.txt
830CMDS
831	cat > expected.txt <<'EOF'
832line 1
833line 2
834line 3
835line 4
836line5
837line 1
838line 2
839line 3
840line 4
841line5
842EOF
843	atf_check cmp output.txt expected.txt
844}
845
846atf_test_case read_file
847read_file_head()
848{
849	atf_set "descr" "Test read from file (r)";
850}
851read_file_body()
852{
853
854	printf 'r r3_data.txt\n' > r3_data.txt
855	ed -s - <<'CMDS'
856H
857r r3_data.txt
858r r3_data.txt
859w output.txt
860CMDS
861	cat > expected.txt <<'EOF'
862r r3_data.txt
863r r3_data.txt
864EOF
865	atf_check cmp output.txt expected.txt
866}
867
868# ---------------------------------------------------------------------------
869# Write (w)
870# ---------------------------------------------------------------------------
871atf_test_case write_pipe
872write_pipe_head()
873{
874	atf_set "descr" "Test write to shell command (w !)";
875}
876write_pipe_body()
877{
878
879	create_std_data input.txt
880	ed -s - <<'CMDS'
881H
882r input.txt
883w !cat >\!.z
884r \!.z
885w output.txt
886CMDS
887	cat > expected.txt <<'EOF'
888line 1
889line 2
890line 3
891line 4
892line5
893line 1
894line 2
895line 3
896line 4
897line5
898EOF
899	atf_check cmp output.txt expected.txt
900}
901
902# ---------------------------------------------------------------------------
903# Quit (q)
904# ---------------------------------------------------------------------------
905atf_test_case quit
906quit_head()
907{
908	atf_set "descr" "Test quit command (q)";
909}
910quit_body()
911{
912
913	ed -s - <<'CMDS'
914H
915w output.txt
916a
917hello
918.
919q
920CMDS
921	atf_check -s exit:0 test ! -s output.txt
922}
923
924# ---------------------------------------------------------------------------
925# Shell command (!)
926# ---------------------------------------------------------------------------
927atf_test_case shell_command
928shell_command_head()
929{
930	atf_set "descr" "Test shell command execution (!)";
931}
932shell_command_body()
933{
934
935	ed -s - <<'CMDS'
936H
937!read one
938hello, world
939a
940okay
941.
942w output.txt
943CMDS
944	printf 'okay\n' > expected.txt
945	atf_check cmp output.txt expected.txt
946}
947
948# ---------------------------------------------------------------------------
949# Newline handling (nl)
950# ---------------------------------------------------------------------------
951atf_test_case newline_insert
952newline_insert_head()
953{
954	atf_set "descr" "Test inserting blank lines";
955}
956newline_insert_body()
957{
958
959	create_std_data input.txt
960	ed -s - <<'CMDS'
961H
962r input.txt
9631
964
965
9660a
967
968
969hello world
970.
971w output.txt
972CMDS
973	cat > expected.txt <<'EOF'
974
975
976hello world
977line 1
978line 2
979line 3
980line 4
981line5
982EOF
983	atf_check cmp output.txt expected.txt
984}
985
986atf_test_case newline_search
987newline_search_head()
988{
989	atf_set "descr" "Test address search with semicolon";
990}
991newline_search_body()
992{
993
994	create_std_data input.txt
995	ed -s - <<'CMDS'
996H
997r input.txt
998a
999hello world
1000.
10010;/./
1002w output.txt
1003CMDS
1004	cat > expected.txt <<'EOF'
1005line 1
1006line 2
1007line 3
1008line 4
1009line5
1010hello world
1011EOF
1012	atf_check cmp output.txt expected.txt
1013}
1014
1015# ---------------------------------------------------------------------------
1016# Error tests
1017# ---------------------------------------------------------------------------
1018atf_test_case err_append_suffix
1019err_append_suffix_head()
1020{
1021	atf_set "descr" "Error: invalid append suffix (aa)";
1022}
1023err_append_suffix_body()
1024{
1025
1026	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1027H
1028aa
1029hello world
1030.
1031CMDS
1032}
1033
1034atf_test_case err_addr_out_of_range
1035err_addr_out_of_range_head()
1036{
1037	atf_set "descr" "Error: address out of range";
1038}
1039err_addr_out_of_range_body()
1040{
1041
1042	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1043H
1044100
1045CMDS
1046}
1047
1048atf_test_case err_addr_negative
1049err_addr_negative_head()
1050{
1051	atf_set "descr" "Error: negative address";
1052}
1053err_addr_negative_body()
1054{
1055
1056	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1057H
1058-100
1059CMDS
1060}
1061
1062atf_test_case err_bang_addr
1063err_bang_addr_head()
1064{
1065	atf_set "descr" "Error: shell command with address";
1066}
1067err_bang_addr_body()
1068{
1069
1070	printf 'test\n' > input.txt
1071	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1072H
1073r input.txt
1074.!date
1075CMDS
1076}
1077
1078atf_test_case err_bang_double
1079err_bang_double_head()
1080{
1081	atf_set "descr" "Error: double bang without previous command";
1082}
1083err_bang_double_body()
1084{
1085
1086	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1087H
1088!!
1089CMDS
1090}
1091
1092atf_test_case err_change_suffix
1093err_change_suffix_head()
1094{
1095	atf_set "descr" "Error: invalid change suffix (cc)";
1096}
1097err_change_suffix_body()
1098{
1099
1100	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1101H
1102cc
1103hello world
1104.
1105CMDS
1106}
1107
1108atf_test_case err_change_zero
1109err_change_zero_head()
1110{
1111	atf_set "descr" "Error: change at line 0";
1112}
1113err_change_zero_body()
1114{
1115
1116	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1117H
11180c
1119hello world
1120.
1121CMDS
1122}
1123
1124atf_test_case err_delete_suffix
1125err_delete_suffix_head()
1126{
1127	atf_set "descr" "Error: invalid delete suffix (dd)";
1128}
1129err_delete_suffix_body()
1130{
1131
1132	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1133H
1134dd
1135CMDS
1136}
1137
1138atf_test_case err_edit_suffix
1139err_edit_suffix_head()
1140{
1141	atf_set "descr" "Error: invalid edit suffix (ee)";
1142}
1143err_edit_suffix_body()
1144{
1145
1146	printf 'test\n' > e1.err
1147	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1148H
1149ee e1.err
1150CMDS
1151}
1152
1153atf_test_case err_edit_addr
1154err_edit_addr_head()
1155{
1156	atf_set "descr" "Error: edit with address";
1157}
1158err_edit_addr_body()
1159{
1160
1161	printf 'test\n' > e2.err
1162	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1163H
1164r e2.err
1165.e e2.err
1166CMDS
1167}
1168
1169atf_test_case err_edit_nosuffix
1170err_edit_nosuffix_head()
1171{
1172	atf_set "descr" "Error: edit without space before filename";
1173}
1174err_edit_nosuffix_body()
1175{
1176
1177	printf 'test\n' > ee.err
1178	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1179H
1180ee.err
1181CMDS
1182}
1183
1184atf_test_case err_file_addr
1185err_file_addr_head()
1186{
1187	atf_set "descr" "Error: file command with address";
1188}
1189err_file_addr_body()
1190{
1191
1192	printf 'test\n' > input.txt
1193	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1194H
1195r input.txt
1196.f f1.err
1197CMDS
1198}
1199
1200atf_test_case err_file_suffix
1201err_file_suffix_head()
1202{
1203	atf_set "descr" "Error: invalid file suffix";
1204}
1205err_file_suffix_body()
1206{
1207
1208	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1209H
1210ff1.err
1211CMDS
1212}
1213
1214atf_test_case err_global_delim
1215err_global_delim_head()
1216{
1217	atf_set "descr" "Error: invalid pattern delimiter in global";
1218}
1219err_global_delim_body()
1220{
1221
1222	printf 'test\n' > input.txt
1223	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1224H
1225r input.txt
1226g/./s //x/
1227CMDS
1228}
1229
1230atf_test_case err_global_empty
1231err_global_empty_head()
1232{
1233	atf_set "descr" "Error: empty pattern in global";
1234}
1235err_global_empty_body()
1236{
1237
1238	printf 'test\n' > input.txt
1239	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1240H
1241r input.txt
1242g//s/./x/
1243CMDS
1244}
1245
1246atf_test_case err_global_incomplete
1247err_global_incomplete_head()
1248{
1249	atf_set "descr" "Error: incomplete global command";
1250}
1251err_global_incomplete_body()
1252{
1253
1254	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1255H
1256g
1257CMDS
1258}
1259
1260atf_test_case err_help_addr
1261err_help_addr_head()
1262{
1263	atf_set "descr" "Error: help with address";
1264}
1265err_help_addr_body()
1266{
1267
1268	printf 'test\n' > input.txt
1269	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1270H
1271r input.txt
1272.h
1273CMDS
1274}
1275
1276atf_test_case err_insert_suffix
1277err_insert_suffix_head()
1278{
1279	atf_set "descr" "Error: invalid insert suffix (ii)";
1280}
1281err_insert_suffix_body()
1282{
1283
1284	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1285H
1286ii
1287hello world
1288.
1289CMDS
1290}
1291
1292atf_test_case err_insert_zero
1293err_insert_zero_head()
1294{
1295	atf_set "descr" "Error: insert at line 0";
1296}
1297err_insert_zero_body()
1298{
1299
1300	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1301H
13020i
1303hello world
1304.
1305CMDS
1306}
1307
1308atf_test_case err_mark_upper
1309err_mark_upper_head()
1310{
1311	atf_set "descr" "Error: mark with uppercase letter";
1312}
1313err_mark_upper_body()
1314{
1315
1316	printf 'test\n' > input.txt
1317	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1318H
1319r input.txt
1320kA
1321CMDS
1322}
1323
1324atf_test_case err_mark_zero
1325err_mark_zero_head()
1326{
1327	atf_set "descr" "Error: mark at line 0";
1328}
1329err_mark_zero_body()
1330{
1331
1332	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1333H
13340ka
1335CMDS
1336}
1337
1338atf_test_case err_mark_ref
1339err_mark_ref_head()
1340{
1341	atf_set "descr" "Error: reference to deleted mark";
1342}
1343err_mark_ref_body()
1344{
1345
1346	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1347H
1348a
1349hello
1350.
1351.ka
1352'ad
1353'ap
1354CMDS
1355}
1356
1357atf_test_case err_move_dest
1358err_move_dest_head()
1359{
1360	atf_set "descr" "Error: move to own range";
1361}
1362err_move_dest_body()
1363{
1364
1365	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1366H
1367a
1368hello
1369world
1370.
13711,$m1
1372CMDS
1373}
1374
1375atf_test_case err_quit_addr
1376err_quit_addr_head()
1377{
1378	atf_set "descr" "Error: quit with address";
1379}
1380err_quit_addr_body()
1381{
1382
1383	printf 'test\n' > input.txt
1384	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1385H
1386r input.txt
1387.q
1388CMDS
1389}
1390
1391atf_test_case err_read_nofile
1392err_read_nofile_head()
1393{
1394	atf_set "descr" "Error: read nonexistent file";
1395}
1396err_read_nofile_body()
1397{
1398
1399	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1400H
1401r a-good-book
1402CMDS
1403}
1404
1405atf_test_case err_subst_delim
1406err_subst_delim_head()
1407{
1408	atf_set "descr" "Error: invalid substitute delimiter";
1409}
1410err_subst_delim_body()
1411{
1412
1413	printf 'test\n' > input.txt
1414	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1415H
1416r input.txt
1417s . x
1418CMDS
1419}
1420
1421atf_test_case err_subst_infinite
1422err_subst_infinite_head()
1423{
1424	atf_set "descr" "Error: infinite substitution loop";
1425}
1426err_subst_infinite_body()
1427{
1428
1429	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1430H
1431a
1432a
1433.
1434s/x*/a/g
1435CMDS
1436}
1437
1438atf_test_case err_subst_bracket
1439err_subst_bracket_head()
1440{
1441	atf_set "descr" "Error: unbalanced brackets in substitute";
1442}
1443err_subst_bracket_body()
1444{
1445
1446	printf 'test\n' > input.txt
1447	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1448H
1449r input.txt
1450s/[xyx/a/
1451CMDS
1452}
1453
1454atf_test_case err_subst_escape
1455err_subst_escape_head()
1456{
1457	atf_set "descr" "Error: invalid escape in substitute pattern";
1458}
1459err_subst_escape_body()
1460{
1461
1462	printf 'test\n' > input.txt
1463	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1464H
1465r input.txt
1466s/\a\b\c/xyz/
1467CMDS
1468}
1469
1470atf_test_case err_subst_empty
1471err_subst_empty_head()
1472{
1473	atf_set "descr" "Error: empty substitute pattern";
1474}
1475err_subst_empty_body()
1476{
1477
1478	printf 'test\n' > input.txt
1479	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1480H
1481r input.txt
1482s//xyz/
1483CMDS
1484}
1485
1486atf_test_case err_subst_bare
1487err_subst_bare_head()
1488{
1489	atf_set "descr" "Error: bare substitute without previous";
1490}
1491err_subst_bare_body()
1492{
1493
1494	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1495H
1496s
1497CMDS
1498}
1499
1500atf_test_case err_subst_sr
1501err_subst_sr_head()
1502{
1503	atf_set "descr" "Error: invalid sr suffix";
1504}
1505err_subst_sr_body()
1506{
1507
1508	atf_check -s exit:2 -o ignore -e not-empty ed -s - <<'CMDS'
1509H
1510a
1511hello world
1512.
1513/./
1514sr
1515CMDS
1516}
1517
1518atf_test_case err_subst_equiv
1519err_subst_equiv_head()
1520{
1521	atf_set "descr" "Error: invalid equivalence class in substitute";
1522}
1523err_subst_equiv_body()
1524{
1525
1526	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1527H
1528a
1529hello
1530.
1531s/[h[=]/x/
1532CMDS
1533}
1534
1535atf_test_case err_subst_class
1536err_subst_class_head()
1537{
1538	atf_set "descr" "Error: unterminated character class";
1539}
1540err_subst_class_body()
1541{
1542
1543	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1544H
1545a
1546hello
1547.
1548s/[h[:]/x/
1549CMDS
1550}
1551
1552atf_test_case err_subst_collate
1553err_subst_collate_head()
1554{
1555	atf_set "descr" "Error: invalid collation class";
1556}
1557err_subst_collate_body()
1558{
1559
1560	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1561H
1562a
1563hello
1564.
1565s/[h[.]/x/
1566CMDS
1567}
1568
1569atf_test_case err_transfer_suffix
1570err_transfer_suffix_head()
1571{
1572	atf_set "descr" "Error: invalid transfer suffix (tt)";
1573}
1574err_transfer_suffix_body()
1575{
1576
1577	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1578H
1579tt
1580CMDS
1581}
1582
1583atf_test_case err_transfer_addr
1584err_transfer_addr_head()
1585{
1586	atf_set "descr" "Error: invalid transfer address";
1587}
1588err_transfer_addr_body()
1589{
1590
1591	printf 'test\n' > input.txt
1592	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1593H
1594r input.txt
1595t0;-1
1596CMDS
1597}
1598
1599atf_test_case err_undo_addr
1600err_undo_addr_head()
1601{
1602	atf_set "descr" "Error: undo with address";
1603}
1604err_undo_addr_body()
1605{
1606
1607	printf 'test\n' > input.txt
1608	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1609H
1610r input.txt
1611.u
1612CMDS
1613}
1614
1615atf_test_case err_write_nopath
1616err_write_nopath_head()
1617{
1618	atf_set "descr" "Error: write to invalid path";
1619}
1620err_write_nopath_body()
1621{
1622
1623	printf 'test\n' > input.txt
1624	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1625H
1626r input.txt
1627w /to/some/far-away/place
1628CMDS
1629}
1630
1631atf_test_case err_write_suffix
1632err_write_suffix_head()
1633{
1634	atf_set "descr" "Error: invalid write suffix (ww)";
1635}
1636err_write_suffix_body()
1637{
1638
1639	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1640H
1641ww.o
1642CMDS
1643}
1644
1645atf_test_case err_write_flags
1646err_write_flags_head()
1647{
1648	atf_set "descr" "Error: invalid write flags (wqp)";
1649}
1650err_write_flags_body()
1651{
1652
1653	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1654H
1655wqp w.o
1656CMDS
1657}
1658
1659atf_test_case err_crypt_addr
1660err_crypt_addr_head()
1661{
1662	atf_set "descr" "Error: crypt with address";
1663}
1664err_crypt_addr_body()
1665{
1666
1667	printf 'test\n' > input.txt
1668	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1669H
1670r input.txt
1671.x
1672CMDS
1673}
1674
1675atf_test_case err_scroll
1676err_scroll_head()
1677{
1678	atf_set "descr" "Error: invalid scroll command";
1679}
1680err_scroll_body()
1681{
1682
1683	atf_check -s exit:2 -e not-empty ed -s - <<'CMDS'
1684H
1685z
1686z
1687CMDS
1688}
1689
1690# ---------------------------------------------------------------------------
1691# Unicode support
1692# ---------------------------------------------------------------------------
1693atf_test_case unicode_list_multibyte
1694unicode_list_multibyte_head()
1695{
1696	atf_set "descr" "l command displays multibyte UTF-8 as-is";
1697}
1698unicode_list_multibyte_body()
1699{
1700
1701	export LC_CTYPE=C.UTF-8
1702	printf 'café\n' > input.txt
1703	atf_check -o inline:'café$\n' ed -s - <<'CMDS'
1704H
1705r input.txt
1706l
1707Q
1708CMDS
1709}
1710
1711atf_test_case unicode_list_cjk
1712unicode_list_cjk_head()
1713{
1714	atf_set "descr" "l command displays CJK characters as-is";
1715}
1716unicode_list_cjk_body()
1717{
1718
1719	export LC_CTYPE=C.UTF-8
1720	printf '日本語テスト\n' > input.txt
1721	atf_check -o inline:'日本語テスト$\n' ed -s - <<'CMDS'
1722H
1723r input.txt
1724l
1725Q
1726CMDS
1727}
1728
1729atf_test_case unicode_list_mixed
1730unicode_list_mixed_head()
1731{
1732	atf_set "descr" "l command displays mixed ASCII/UTF-8 correctly";
1733}
1734unicode_list_mixed_body()
1735{
1736
1737	export LC_CTYPE=C.UTF-8
1738	printf 'hello café 世界\n' > input.txt
1739	atf_check -o inline:'hello café 世界$\n' ed -s - <<'CMDS'
1740H
1741r input.txt
1742l
1743Q
1744CMDS
1745}
1746
1747atf_test_case unicode_list_invalid
1748unicode_list_invalid_head()
1749{
1750	atf_set "descr" "l command escapes invalid UTF-8 as octal";
1751}
1752unicode_list_invalid_body()
1753{
1754
1755	export LC_CTYPE=C.UTF-8
1756	printf '\200\201\376\377\n' > input.txt
1757	atf_check -o inline:'\\200\\201\\376\\377$\n' ed -s - <<'CMDS'
1758H
1759r input.txt
1760l
1761Q
1762CMDS
1763}
1764
1765atf_test_case unicode_list_wrap_cjk
1766unicode_list_wrap_cjk_head()
1767{
1768	atf_set "descr" "l command wraps correctly around double-width CJK";
1769}
1770unicode_list_wrap_cjk_body()
1771{
1772
1773	export LC_CTYPE=C.UTF-8
1774	# 69 A's + 日本 (2 CJK chars): 69 + 2 = 71 cols for 日 (fits),
1775	# 71 + 2 = 73 for 本 (exceeds 72), so 本 wraps to next line.
1776	printf 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA日本\n' > input.txt
1777	ed -s - <<'CMDS' > output.txt
1778H
1779r input.txt
1780l
1781Q
1782CMDS
1783	printf 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA日\\\n本$\n' > expected.txt
1784	atf_check cmp output.txt expected.txt
1785}
1786
1787atf_test_case unicode_print
1788unicode_print_head()
1789{
1790	atf_set "descr" "p command passes through UTF-8 correctly";
1791}
1792unicode_print_body()
1793{
1794
1795	export LC_CTYPE=C.UTF-8
1796	printf 'café 日本語\n' > input.txt
1797	atf_check -o inline:'café 日本語\n' ed -s - <<'CMDS'
1798H
1799r input.txt
1800p
1801Q
1802CMDS
1803}
1804
1805atf_test_case unicode_number
1806unicode_number_head()
1807{
1808	atf_set "descr" "n command displays line number with UTF-8";
1809}
1810unicode_number_body()
1811{
1812
1813	export LC_CTYPE=C.UTF-8
1814	printf 'café 日本語\n' > input.txt
1815	atf_check -o inline:'1\tcafé 日本語\n' ed -s - <<'CMDS'
1816H
1817r input.txt
1818n
1819Q
1820CMDS
1821}
1822
1823atf_test_case unicode_regex
1824unicode_regex_head()
1825{
1826	atf_set "descr" "Regex search matches UTF-8 characters";
1827}
1828unicode_regex_body()
1829{
1830
1831	export LC_CTYPE=C.UTF-8
1832	printf 'café\ntest\nüber\n' > input.txt
1833	atf_check -o inline:'café\n' ed -s - <<'CMDS'
1834H
1835r input.txt
1836g/é/p
1837Q
1838CMDS
1839}
1840
1841atf_test_case unicode_regex_charclass
1842unicode_regex_charclass_head()
1843{
1844	atf_set "descr" "Regex character classes work with UTF-8";
1845}
1846unicode_regex_charclass_body()
1847{
1848
1849	export LC_CTYPE=C.UTF-8
1850	printf 'café123\ntest456\n' > input.txt
1851	atf_check -o inline:'café123\n' ed -s - <<'CMDS'
1852H
1853r input.txt
1854g/[[:alpha:]]*é/p
1855Q
1856CMDS
1857}
1858
1859atf_test_case unicode_substitute
1860unicode_substitute_head()
1861{
1862	atf_set "descr" "Substitute replaces UTF-8 characters";
1863}
1864unicode_substitute_body()
1865{
1866
1867	export LC_CTYPE=C.UTF-8
1868	printf 'café\n' > input.txt
1869	ed -s - <<'CMDS'
1870H
1871r input.txt
1872s/é/e/
1873w output.txt
1874Q
1875CMDS
1876	printf 'cafe\n' > expected.txt
1877	atf_check cmp output.txt expected.txt
1878}
1879
1880atf_test_case unicode_substitute_cjk
1881unicode_substitute_cjk_head()
1882{
1883	atf_set "descr" "Substitute replaces CJK characters";
1884}
1885unicode_substitute_cjk_body()
1886{
1887
1888	export LC_CTYPE=C.UTF-8
1889	printf 'hello 世界\n' > input.txt
1890	ed -s - <<'CMDS'
1891H
1892r input.txt
1893s/世界/world/
1894w output.txt
1895Q
1896CMDS
1897	printf 'hello world\n' > expected.txt
1898	atf_check cmp output.txt expected.txt
1899}
1900
1901atf_test_case unicode_global_substitute
1902unicode_global_substitute_head()
1903{
1904	atf_set "descr" "Global substitute works with UTF-8";
1905}
1906unicode_global_substitute_body()
1907{
1908
1909	export LC_CTYPE=C.UTF-8
1910	printf 'à la carte\nà bientôt\nhello\n' > input.txt
1911	ed -s - <<'CMDS'
1912H
1913r input.txt
1914g/à/s/à/a/
1915w output.txt
1916Q
1917CMDS
1918	cat > expected.txt <<'EOF'
1919a la carte
1920a bientôt
1921hello
1922EOF
1923	atf_check cmp output.txt expected.txt
1924}
1925
1926atf_test_case unicode_join
1927unicode_join_head()
1928{
1929	atf_set "descr" "Join preserves UTF-8 content";
1930}
1931unicode_join_body()
1932{
1933
1934	export LC_CTYPE=C.UTF-8
1935	printf 'café\n世界\n' > input.txt
1936	ed -s - <<'CMDS'
1937H
1938r input.txt
19391,2j
1940w output.txt
1941Q
1942CMDS
1943	printf 'café世界\n' > expected.txt
1944	atf_check cmp output.txt expected.txt
1945}
1946
1947atf_test_case unicode_append
1948unicode_append_head()
1949{
1950	atf_set "descr" "Append preserves UTF-8 text";
1951}
1952unicode_append_body()
1953{
1954
1955	export LC_CTYPE=C.UTF-8
1956	ed -s - <<'CMDS'
1957H
1958a
1959première
1960deuxième
1961.
1962w output.txt
1963Q
1964CMDS
1965	cat > expected.txt <<'EOF'
1966première
1967deuxième
1968EOF
1969	atf_check cmp output.txt expected.txt
1970}
1971
1972atf_test_case unicode_cyrillic
1973unicode_cyrillic_head()
1974{
1975	atf_set "descr" "Cyrillic: append, substitute, print, regex search";
1976}
1977unicode_cyrillic_body()
1978{
1979
1980	export LC_CTYPE=C.UTF-8
1981	ed -s - <<'CMDS' > output.txt
1982H
1983a
1984Привет
1985.
1986s/ривет/ока/
19871p
1988a
1989Строка
1990.
19911
1992/а/p
19931,$p
1994Q
1995CMDS
1996	cat > expected.txt <<'EOF'
1997Пока
1998Пока
1999Строка
2000Пока
2001Строка
2002EOF
2003	atf_check cmp output.txt expected.txt
2004}
2005
2006# ---------------------------------------------------------------------------
2007# Registration
2008# ---------------------------------------------------------------------------
2009atf_init_test_cases()
2010{
2011
2012	# Basic commands
2013	atf_add_test_case append
2014	atf_add_test_case address
2015	atf_add_test_case change
2016	atf_add_test_case delete
2017	atf_add_test_case insert
2018	atf_add_test_case join
2019	atf_add_test_case mark
2020	atf_add_test_case move
2021	atf_add_test_case transfer
2022	atf_add_test_case transfer_search
2023	atf_add_test_case undo
2024
2025	# Global commands
2026	atf_add_test_case global_move
2027	atf_add_test_case global_change
2028	atf_add_test_case global_substitute
2029	atf_add_test_case global_undo
2030	atf_add_test_case global_copy
2031	atf_add_test_case inverse_global
2032
2033	# Substitution
2034	atf_add_test_case subst_backreference
2035	atf_add_test_case subst_range
2036	atf_add_test_case subst_charclass
2037
2038	# File operations
2039	atf_add_test_case edit_file
2040	atf_add_test_case edit_command
2041	atf_add_test_case edit_reread
2042	atf_add_test_case edit_lowercase
2043	atf_add_test_case read_command
2044	atf_add_test_case read_default
2045	atf_add_test_case read_file
2046	atf_add_test_case write_pipe
2047	atf_add_test_case quit
2048	atf_add_test_case shell_command
2049
2050	# Newline handling
2051	atf_add_test_case newline_insert
2052	atf_add_test_case newline_search
2053
2054	# Unicode support
2055	atf_add_test_case unicode_list_multibyte
2056	atf_add_test_case unicode_list_cjk
2057	atf_add_test_case unicode_list_mixed
2058	atf_add_test_case unicode_list_invalid
2059	atf_add_test_case unicode_list_wrap_cjk
2060	atf_add_test_case unicode_print
2061	atf_add_test_case unicode_number
2062	atf_add_test_case unicode_regex
2063	atf_add_test_case unicode_regex_charclass
2064	atf_add_test_case unicode_substitute
2065	atf_add_test_case unicode_substitute_cjk
2066	atf_add_test_case unicode_global_substitute
2067	atf_add_test_case unicode_join
2068	atf_add_test_case unicode_append
2069	atf_add_test_case unicode_cyrillic
2070
2071	# Error tests
2072	atf_add_test_case err_append_suffix
2073	atf_add_test_case err_addr_out_of_range
2074	atf_add_test_case err_addr_negative
2075	atf_add_test_case err_bang_addr
2076	atf_add_test_case err_bang_double
2077	atf_add_test_case err_change_suffix
2078	atf_add_test_case err_change_zero
2079	atf_add_test_case err_delete_suffix
2080	atf_add_test_case err_edit_suffix
2081	atf_add_test_case err_edit_addr
2082	atf_add_test_case err_edit_nosuffix
2083	atf_add_test_case err_file_addr
2084	atf_add_test_case err_file_suffix
2085	atf_add_test_case err_global_delim
2086	atf_add_test_case err_global_empty
2087	atf_add_test_case err_global_incomplete
2088	atf_add_test_case err_help_addr
2089	atf_add_test_case err_insert_suffix
2090	atf_add_test_case err_insert_zero
2091	atf_add_test_case err_mark_upper
2092	atf_add_test_case err_mark_zero
2093	atf_add_test_case err_mark_ref
2094	atf_add_test_case err_move_dest
2095	atf_add_test_case err_quit_addr
2096	atf_add_test_case err_read_nofile
2097	atf_add_test_case err_subst_delim
2098	atf_add_test_case err_subst_infinite
2099	atf_add_test_case err_subst_bracket
2100	atf_add_test_case err_subst_escape
2101	atf_add_test_case err_subst_empty
2102	atf_add_test_case err_subst_bare
2103	atf_add_test_case err_subst_sr
2104	atf_add_test_case err_subst_equiv
2105	atf_add_test_case err_subst_class
2106	atf_add_test_case err_subst_collate
2107	atf_add_test_case err_transfer_suffix
2108	atf_add_test_case err_transfer_addr
2109	atf_add_test_case err_undo_addr
2110	atf_add_test_case err_write_nopath
2111	atf_add_test_case err_write_suffix
2112	atf_add_test_case err_write_flags
2113	atf_add_test_case err_crypt_addr
2114	atf_add_test_case err_scroll
2115}
2116