1#!/usr/bin/perl
2# Exercise expand.
3
4# Copyright (C) 2004-2023 Free Software Foundation, Inc.
5
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15
16# You should have received a copy of the GNU General Public License
17# along with this program.  If not, see <https://www.gnu.org/licenses/>.
18
19use strict;
20
21my $limits = getlimits ();
22my $UINTMAX_OFLOW = $limits->{UINTMAX_OFLOW};
23
24(my $program_name = $0) =~ s|.*/||;
25my $prog = 'expand';
26
27# Turn off localization of executable's output.
28@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
29
30my @Tests =
31  (
32   ['t1', '--tabs=3',     {IN=>"a\tb"}, {OUT=>"a  b"}],
33   ['t2', '--tabs=3,6,9', {IN=>"a\tb\tc\td\te"}, {OUT=>"a  b  c  d e"}],
34   ['t3', '--tabs="3 6 9"',   {IN=>"a\tb\tc\td\te"}, {OUT=>"a  b  c  d e"}],
35   # Leading space/commas are silently ignored; Mixing space/commas is allowed.
36   # (a side-effect of allowing direct "-3,9" parameter).
37   ['t4', '--tabs=", 3,6 9"', {IN=>"a\tb\tc\td\te"}, {OUT=>"a  b  c  d e"}],
38   # tab stops parameter without values
39   ['t5', '--tabs=""',        {IN=>"a\tb\tc"}, {OUT=>"a       b       c"}],
40   ['t6', '--tabs=","',       {IN=>"a\tb\tc"}, {OUT=>"a       b       c"}],
41   ['t7', '--tabs=" "',       {IN=>"a\tb\tc"}, {OUT=>"a       b       c"}],
42   ['t8', '--tabs="/"',       {IN=>"a\tb\tc"}, {OUT=>"a       b       c"}],
43
44   # Input field wider than the specified tab list
45   ['if', '--tabs=6,9', {IN=>"a\tbbbbbbbbbbbbb\tc"},
46    {OUT=>"a     bbbbbbbbbbbbb c"}],
47
48   ['i1', '--tabs=3 -i', {IN=>"\ta\tb"}, {OUT=>"   a\tb"}],
49   ['i2', '--tabs=3 -i', {IN=>" \ta\tb"}, {OUT=>"   a\tb"}],
50
51   # Undocumented feature:
52   #   treat "expand -7"  as "expand --tabs 7" ,
53   #   and   "expand -90" as "expand --tabs 90",
54   ['u1', '-3',    {IN=>"a\tb\tc"}, {OUT=>"a  b  c"}],
55   ['u2', '-4 -9', {IN=>"a\tb\tc"}, {OUT=>"a   b    c"}],
56   ['u3', '-11',   {IN=>"a\tb\tc"}, {OUT=>"a          b          c"}],
57   # Test all digits (for full code coverage)
58   ['u4', '-2 -6', {IN=>"a\tb\tc"}, {OUT=>"a b   c"}],
59   ['u5', '-7',    {IN=>"a\tb"},    {OUT=>"a      b"}],
60   ['u6', '-8',    {IN=>"a\tb"},    {OUT=>"a       b"}],
61   # This syntax is handled internally as "-3, -9"
62   ['u7', '-3,9',  {IN=>"a\tb\tc"}, {OUT=>"a  b     c"}],
63
64   # Multiple non-empty files
65   ['f1', '--tabs=4',
66    {IN=>{"in1" => "a\tb\n"}}, {IN=>{"in2" => "c\td\n"}},
67    {OUT=>"a   b\nc   d\n"}],
68   # Multiple files, first file is empty
69   ['f2', '--tabs=4',
70    {IN=>{"in1" => ""}}, {IN=>{"in2" => "c\td\n"}},
71    {OUT=>"c   d\n"}],
72   # Multiple files, second file is empty
73   ['f3', '--tabs=4',
74    {IN=>{"in1" => "a\tb\n"}}, {IN=>{"in2" => ""}},
75    {OUT=>"a   b\n"}],
76
77
78   # Test '\b' (back-space) - subtract one column.
79   #
80   # Note:
81   # In a terminal window, 'expand' will appear to erase the 'a' characters
82   # due to overwriting them with spaces:
83   #
84   #    $ printf 'aaa\b\b\bc\td\n'
85   #    caa     d
86   #    $ printf 'aaa\b\b\bc\td\n' | expand
87   #    c       d
88   #
89   # However the characters are all printed:
90   #
91   #    $ printf 'aaa\b\b\bc\td\n' | expand | od -An -ta
92   #      a   a   a  bs  bs  bs   c  sp  sp  sp  sp  sp  sp  sp   d  nl
93   #
94   # If users ever report a problem with these tests and just
95   # copy&paste from the terminal, their report will be confusing
96   # (the 'a' will not appear).
97   #
98   # To see an example, enable the 'b-confusing' test, and examine the
99   # reported log:
100   #
101   #     expand.pl: test b-confusing: stdout mismatch
102   #     *** b-confusing.2       Fri Jun 24 15:43:21 2016
103   #     --- b-confusing.O       Fri Jun 24 15:43:21 2016
104   #     ***************
105   #     *** 1 ****
106   #     ! c       d
107   #     --- 1 ----
108   #     ! c       d
109   #
110   # ['b-confusing','', {IN=>"aaa\b\b\bc\td\n"}, {OUT=>"c       d\n"}],
111
112   ['b1','', {IN=>"aaa\b\b\bc\td\n"}, {OUT=>"aaa\b\b\bc       d\n"}],
113
114   # \b as first character, when column is zero
115   ['b2','', {IN=>"\bc\td"}, {OUT=>"\bc       d"}],
116
117   # Testing tab list adjusted due to backspaces
118   # ('b3' is the baseline without backspaces).
119   ['b3','--tabs 2,4,6,10',
120    {IN=>"1\t2\t3\t4\t5\n" .
121         "a\tb\tc\td\te\n"},
122    {OUT=>"1 2 3 4   5\n" .
123          "a b c d   e\n"}],
124
125   # On screen this will appear the same as 'b3'
126   ['b4','--tabs 2,4,6,10',
127    {IN=>"1\t2\t3\t4\t5\n" .
128         "a\tbHELLO\b\b\b\b\b\tc\td\te\n"},
129    {OUT=>"1 2 3 4   5\n" .
130          "a bHELLO\b\b\b\b\b c d   e\n"}],
131
132   # On screen on 'bHE' will appear (LLO overwritten by spaces),
133   # 'c' should align with 4, 'd' with 5:
134   #   1 2 3 4   5
135   #   a bHE c   d e
136   ['b5','--tabs 2,4,6,10',
137    {IN=>"1\t2\t3\t4\t5\n" .
138         "a\tbHELLO\b\b\b\tc\td\te\n"},
139    {OUT=>"1 2 3 4   5\n" .
140          "a bHELLO\b\b\b c   d e\n"}],
141
142   # Test the trailing '/' feature which specifies the
143   # tab size to use after the last specified stop
144   ['trail1', '--tabs=1,/5',   {IN=>"\ta\tb\tc"}, {OUT=>" a   b    c"}],
145   ['trail2', '--tabs=2,/5',   {IN=>"\ta\tb\tc"}, {OUT=>"  a  b    c"}],
146   ['trail3', '--tabs=1,2,/5', {IN=>"\ta\tb\tc"}, {OUT=>" a   b    c"}],
147   ['trail4', '--tabs=/5',     {IN=>"\ta\tb"},    {OUT=>"     a    b"}],
148   ['trail5', '--tabs=//5',    {IN=>"\ta\tb"},    {OUT=>"     a    b"}],
149   ['trail5a','--tabs=+/5',    {IN=>"\ta\tb"},    {OUT=>"     a    b"}],
150   ['trail6', '--tabs=/,/5',   {IN=>"\ta\tb"},    {OUT=>"     a    b"}],
151   ['trail7', '--tabs=,/5',    {IN=>"\ta\tb"},    {OUT=>"     a    b"}],
152   ['trail8', '--tabs=1 -t/5', {IN=>"\ta\tb\tc"}, {OUT=>" a   b    c"}],
153   ['trail9', '--tab=1,2 -t/5',{IN=>"\ta\tb\tc"}, {OUT=>" a   b    c"}],
154
155   # Test incremental trailing '+' feature which specifies that
156   # tab stops should continue every increment
157   ['incre0', '--tab=1,+5',    {IN=>"+\t\ta\tb"}, {OUT=>"+          a    b"}],
158   ['incre1', '--tabs=1,+5',   {IN=>"\ta\tb\tc"}, {OUT=>" a    b    c"}],
159   ['incre2', '--tabs=2,+5',   {IN=>"\ta\tb\tc"}, {OUT=>"  a    b    c"}],
160   ['incre3', '--tabs=1,2,+5', {IN=>"\ta\tb\tc"}, {OUT=>" a     b    c"}],
161   ['incre4', '--tabs=+5',     {IN=>"\ta\tb"},    {OUT=>"     a    b"}],
162   ['incre5', '--tabs=++5',    {IN=>"\ta\tb"},    {OUT=>"     a    b"}],
163   ['incre5a','--tabs=/+5',    {IN=>"\ta\tb"},    {OUT=>"     a    b"}],
164   ['incre6', '--tabs=+,+5',   {IN=>"\ta\tb"},    {OUT=>"     a    b"}],
165   ['incre7', '--tabs=,+5',    {IN=>"\ta\tb"},    {OUT=>"     a    b"}],
166   ['incre8', '--tabs=1 -t+5', {IN=>"\ta\tb\tc"}, {OUT=>" a    b    c"}],
167   ['incre9', '--tab=1,2 -t+5',{IN=>"\ta\tb\tc"}, {OUT=>" a     b    c"}],
168
169
170   # Test errors
171   ['e1', '--tabs="a"', {IN=>''}, {OUT=>''}, {EXIT=>1},
172    {ERR => "$prog: tab size contains invalid character(s): 'a'\n"}],
173   ['e2', "-t $UINTMAX_OFLOW", {IN=>''}, {OUT=>''}, {EXIT=>1},
174    {ERR => "$prog: tab stop is too large '$UINTMAX_OFLOW'\n"}],
175   ['e3', '--tabs=0',   {IN=>''}, {OUT=>''}, {EXIT=>1},
176    {ERR => "$prog: tab size cannot be 0\n"}],
177   ['e4', '--tabs=3,3', {IN=>''}, {OUT=>''}, {EXIT=>1},
178    {ERR => "$prog: tab sizes must be ascending\n"}],
179   ['e5', '--tabs=/3,6,8', {IN=>''}, {OUT=>''}, {EXIT=>1},
180    {ERR => "$prog: '/' specifier only allowed with the last value\n"}],
181   ['e6', '-t/3 -t/6', {IN=>''}, {OUT=>''}, {EXIT=>1},
182    {ERR => "$prog: '/' specifier only allowed with the last value\n"}],
183   ['e7', '--tabs=3/', {IN=>''}, {OUT=>''}, {EXIT=>1},
184    {ERR => "$prog: '/' specifier not at start of number: '/'\n"}],
185  );
186
187my $save_temps = $ENV{DEBUG};
188my $verbose = $ENV{VERBOSE};
189
190my $fail = run_tests ($program_name, $prog, \@Tests, $save_temps, $verbose);
191exit $fail;
192