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