1#!/usr/bin/perl
2# Basic tests for "numfmt".
3
4# Copyright (C) 2012-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
21(my $program_name = $0) =~ s|.*/||;
22my $prog = 'numfmt';
23
24my $limits = getlimits ();
25
26# TODO: add localization tests with "grouping"
27# Turn off localization of executable's output.
28@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
29
30my $locale = $ENV{LOCALE_FR_UTF8};
31! defined $locale || $locale eq 'none'
32  and $locale = 'C';
33
34my $try = "Try '$prog --help' for more information.\n";
35
36my @Tests =
37    (
38     ['1', '1234',             {OUT => "1234"}],
39     ['2', '--from=si 1K',     {OUT => "1000"}],
40     ['3', '--from=iec 1K',    {OUT => "1024"}],
41     ['4', '--from=auto 1K',   {OUT => "1000"}],
42     ['5', '--from=auto 1Ki',  {OUT => "1024"}],
43     ['5.1', '--from=iec-i 1Ki',  {OUT => "1024"}],
44
45     ['6', {IN_PIPE => "1234\n"},            {OUT => "1234"}],
46     ['7', '--from=si', {IN_PIPE => "2K\n"}, {OUT => "2000"}],
47     ['7a', '--invalid=fail', {IN_PIPE => "no_NL"}, {OUT => "no_NL"},
48              {ERR => "$prog: invalid number: 'no_NL'\n"},
49              {EXIT => '2'}],
50
51     ['8',  '--to=si 2000',                   {OUT => "2.0K"}],
52     ['9',  '--to=si 2001',                   {OUT => "2.1K"}],
53     ['10', '--to=si 1999',                   {OUT => "2.0K"}],
54     ['11', '--to=si --round=down   2001',   {OUT => "2.0K"}],
55     ['12', '--to=si --round=down   1999',   {OUT => "1.9K"}],
56     ['13', '--to=si --round=up 1901',   {OUT => "2.0K"}],
57     ['14', '--to=si --round=down   1901',   {OUT => "1.9K"}],
58     ['15', '--to=si --round=nearest 1901',   {OUT => "1.9K"}],
59     ['16', '--to=si --round=nearest 1945',   {OUT => "1.9K"}],
60     ['17', '--to=si --round=nearest 1955',   {OUT => "2.0K"}],
61
62     ['18',  '--to=iec 2048',                  {OUT => "2.0K"}],
63     ['19',  '--to=iec 2049',                  {OUT => "2.1K"}],
64     ['20', '--to=iec 2047',                   {OUT => "2.0K"}],
65     ['21', '--to=iec --round=down   2049',   {OUT => "2.0K"}],
66     ['22', '--to=iec --round=down   2047',   {OUT => "1.9K"}],
67     ['23', '--to=iec --round=up 2040',   {OUT => "2.0K"}],
68     ['24', '--to=iec --round=down   2040',   {OUT => "1.9K"}],
69     ['25', '--to=iec --round=nearest 1996',   {OUT => "1.9K"}],
70     ['26', '--to=iec --round=nearest 1997',   {OUT => "2.0K"}],
71     ['27', '--to=iec-i 2048',                  {OUT => "2.0Ki"}],
72
73     ['neg-1', '-- -1234',                     {OUT => "-1234"}],
74     ['neg-2', '--padding=5 -- -1234',         {OUT => "-1234"}],
75     ['neg-3', '--padding=6 -- -1234',         {OUT => " -1234"}],
76     ['neg-4', '--to=iec -- 9100 -9100',       {OUT => "8.9K\n-8.9K"}],
77     ['neg-5', '-- -0.1',                      {OUT => "-0.1"}],
78     ['neg-6', '-- -0',                        {OUT => "0"}],
79     ['neg-7', '-- -0.-1',
80              {ERR => "$prog: invalid number: '-0.-1'\n"},
81              {EXIT => '2'}],
82
83     ['float-1', '1.1',                        {OUT => "1.1"}],
84     ['float-2', '1.22',                       {OUT => "1.22"}],
85     ['float-3', '1.22.',
86             {ERR => "$prog: invalid suffix in input: '1.22.'\n"},
87             {EXIT => '2'}],
88
89     ['unit-1', '--from-unit=512 4',   {OUT => "2048"}],
90     ['unit-2', '--to-unit=512 2048',   {OUT => "4"}],
91     ['unit-3', '--from-unit=512 --from=si 4M',   {OUT => "2048000000"}],
92     ['unit-4', '--from-unit=512 --from=iec --to=iec 4M',   {OUT => "2.0G"}],
93     ['unit-5', '--from-unit=AA --from=iec --to=iec 4M',
94             {ERR => "$prog: invalid unit size: 'AA'\n"},
95             {EXIT => '1'}],
96     ['unit-6', '--from-unit=54W --from=iec --to=iec 4M',
97             {ERR => "$prog: invalid unit size: '54W'\n"},
98             {EXIT => '1'}],
99     ['unit-7', '--from-unit=K 30', {OUT=>"30000"}],
100     ['unit-7.1', '--from-unit=Ki 30', {OUT=>"30720"}],
101     ['unit-7.2', '--from-unit=i 0',
102             {ERR => "$prog: invalid unit size: 'i'\n"},
103             {EXIT => '1'}],
104     ['unit-7.3', '--from-unit=1i 0',
105             {ERR => "$prog: invalid unit size: '1i'\n"},
106             {EXIT => '1'}],
107     ['unit-8', '--from-unit='.$limits->{UINTMAX_OFLOW}.' --to=iec 30',
108             {ERR => "$prog: invalid unit size: '$limits->{UINTMAX_OFLOW}'\n"},
109             {EXIT => '1'}],
110     ['unit-9', '--from-unit=0 1',
111             {ERR => "$prog: invalid unit size: '0'\n"},
112             {EXIT => '1'}],
113     ['unit-10', '--to-unit=0 1',
114             {ERR => "$prog: invalid unit size: '0'\n"},
115             {EXIT => '1'}],
116
117     # Test Suffix logic
118     ['suf-1', '4000',    {OUT=>'4000'}],
119     ['suf-2', '4J',
120             {ERR => "$prog: invalid suffix in input: '4J'\n"},
121             {EXIT => '2'}],
122     ['suf-2.1', '4M',
123             {ERR => "$prog: rejecting suffix " .
124             "in input: '4M' (consider using --from)\n"},
125             {EXIT => '2'}],
126     ['suf-3', '--from=si 4M',  {OUT=>'4000000'}],
127     ['suf-4', '--from=si 4J',
128             {ERR => "$prog: invalid suffix in input: '4J'\n"},
129             {EXIT => '2'}],
130     ['suf-5', '--from=si 4MJ',
131             {ERR => "$prog: invalid suffix in input '4MJ': 'J'\n"},
132             {EXIT => '2'}],
133
134     ['suf-6', '--from=iec 4M',  {OUT=>'4194304'}],
135     ['suf-7', '--from=auto 4M',  {OUT=>'4000000'}],
136     ['suf-8', '--from=auto 4Mi',  {OUT=>'4194304'}],
137     ['suf-9', '--from=auto 4MiJ',
138             {ERR => "$prog: invalid suffix in input '4MiJ': 'J'\n"},
139             {EXIT => '2'}],
140     ['suf-10', '--from=auto 4JiJ',
141             {ERR => "$prog: invalid suffix in input: '4JiJ'\n"},
142             {EXIT => '2'}],
143
144     # characters after a white space are OK - printed as-is
145     ['suf-11', '"4 M"',     {OUT=>'4 M'}],
146
147     # Custom suffix
148     ['suf-12', '--suffix=Foo 70Foo',               {OUT=>'70Foo'}],
149     ['suf-13', '--suffix=Foo 70',                  {OUT=>'70Foo'}],
150     ['suf-14', '--suffix=Foo --from=si 70K',       {OUT=>'70000Foo'}],
151     ['suf-15', '--suffix=Foo --from=si 70KFoo',    {OUT=>'70000Foo'}],
152     ['suf-16', '--suffix=Foo --to=si   7000Foo',    {OUT=>'7.0KFoo'}],
153     ['suf-17', '--suffix=Foo --to=si   7000Bar',
154              {ERR => "$prog: invalid suffix in input: '7000Bar'\n"},
155              {EXIT => '2'}],
156     ['suf-18', '--suffix=Foo --to=si   7000FooF',
157              {ERR => "$prog: invalid suffix in input: '7000FooF'\n"},
158              {EXIT => '2'}],
159     # space(s) between number and suffix.  Note only field 1 is used
160     # by default so specify the NUL delimiter to consider the whole "line".
161     ['suf-19', "-d '' --from=si '4.0 K'",         {OUT => "4000"}],
162     ['suf-20',
163      '--suffix=Foo' . 'x' x 122 . 'y 0',
164      {OUT => '0Foo' . 'x' x 122 . 'y'}],
165
166     ## GROUPING
167
168     # "C" locale - no grouping (locale-specific tests, below)
169     ['grp-1', '--from=si --grouping 7M',   {OUT=>'7000000'}],
170     ['grp-2', '--from=si --to=si --grouping 7M',
171              {ERR => "$prog: grouping cannot be combined with --to\n"},
172              {EXIT => '1'}],
173
174
175     ## Padding
176     ['pad-1', '--padding=10 5',             {OUT=>'         5'}],
177     ['pad-2', '--padding=-10 5',            {OUT=>'5         '}],
178     ['pad-3', '--padding=A 5',
179             {ERR => "$prog: invalid padding value 'A'\n"},
180             {EXIT => '1'}],
181     ['pad-3.1', '--padding=0 5',
182             {ERR => "$prog: invalid padding value '0'\n"},
183             {EXIT => '1'}],
184     ['pad-4', '--padding=10 --to=si 50000',             {OUT=>'       50K'}],
185     ['pad-5', '--padding=-10 --to=si 50000',            {OUT=>'50K       '}],
186
187     # padding too narrow
188     ['pad-6', '--padding=2 --to=si 1000', {OUT=>'1.0K'}],
189
190
191     # Padding + suffix
192     ['pad-7', '--padding=10 --suffix=foo --to=si 50000',
193             {OUT=>'    50Kfoo'}],
194     ['pad-8', '--padding=-10 --suffix=foo --to=si 50000',
195             {OUT=>'50Kfoo    '}],
196
197
198     # Delimiters
199     ['delim-1', '--delimiter=: --from=auto 40M:',   {OUT=>'40000000:'}],
200     ['delim-2', '--delimiter="" --from=auto "40 M"',{OUT=>'40000000'}],
201     ['delim-3', '--delimiter=" " --from=auto "40M Foo"',{OUT=>'40000000 Foo'}],
202     ['delim-4', '--delimiter=: --from=auto 40M:60M',  {OUT=>'40000000:60M'}],
203     ['delim-5', '-d: --field=2 --from=auto :40M:60M',  {OUT=>':40000000:60M'}],
204     ['delim-6', '-d: --field 3 --from=auto 40M:60M', {OUT=>"40M:60M"}],
205     ['delim-err-1', '-d,, --to=si 1', {EXIT=>1},
206             {ERR => "$prog: the delimiter must be a single character\n"}],
207
208     #Fields
209     ['field-1', '--field A',
210             {ERR => "$prog: invalid field value 'A'\n$try"},
211             {EXIT => '1'}],
212     ['field-2', '--field 2 --from=auto "Hello 40M World 90G"',
213             {OUT=>'Hello 40000000 World 90G'}],
214     ['field-3', '--field 3 --from=auto "Hello 40M World 90G"',
215             {OUT=>"Hello 40M "},
216             {ERR=>"$prog: invalid number: 'World'\n"},
217             {EXIT => 2},],
218     # Last field - no text after number
219     ['field-4', '--field 4 --from=auto "Hello 40M World 90G"',
220             {OUT=>"Hello 40M World 90000000000"}],
221     # Last field - a delimiter after the number
222     ['field-5', '--field 4 --from=auto "Hello 40M World 90G "',
223             {OUT=>"Hello 40M World 90000000000 "}],
224
225     # Mix Fields + Delimiters
226     ['field-6', '--delimiter=: --field 2 --from=auto "Hello:40M:World:90G"',
227             {OUT=>"Hello:40000000:World:90G"}],
228
229     # not enough fields
230     ['field-8', '--field 3 --to=si "Hello World"', {OUT=>"Hello World"}],
231
232     # Multiple fields
233     ['field-range-1', '--field 2,4 --to=si "1000 2000 3000 4000 5000"',
234             {OUT=>"1000 2.0K 3000 4.0K 5000"}],
235
236     ['field-range-2', '--field 2-4 --to=si "1000 2000 3000 4000 5000"',
237             {OUT=>"1000 2.0K 3.0K 4.0K 5000"}],
238
239     ['field-range-3', '--field 1,2,3-5 --to=si "1000 2000 3000 4000 5000"',
240             {OUT=>"1.0K 2.0K 3.0K 4.0K 5.0K"}],
241
242     ['field-range-4', '--field 1-5 --to=si "1000 2000 3000 4000 5000"',
243             {OUT=>"1.0K 2.0K 3.0K 4.0K 5.0K"}],
244
245     ['field-range-5', '--field 1-3,5 --to=si "1000 2000 3000 4000 5000"',
246             {OUT=>"1.0K 2.0K 3.0K 4000 5.0K"}],
247
248     ['field-range-6', '--field 3- --to=si "1000 2000 3000 4000 5000"',
249             {OUT=>"1000 2000 3.0K 4.0K 5.0K"}],
250
251     ['field-range-7', '--field -3 --to=si "1000 2000 3000 4000 5000"',
252             {OUT=>"1.0K 2.0K 3.0K 4000 5000"}],
253
254     ['field-range-8', '--field 1-2,4-5 --to=si "1000 2000 3000 4000 5000"',
255             {OUT=>"1.0K 2.0K 3000 4.0K 5.0K"}],
256     ['field-range-9', '--field 4-5,1-2 --to=si "1000 2000 3000 4000 5000"',
257             {OUT=>"1.0K 2.0K 3000 4.0K 5.0K"}],
258
259     ['field-range-10','--field 1-3,2-4 --to=si "1000 2000 3000 4000 5000"',
260             {OUT=>"1.0K 2.0K 3.0K 4.0K 5000"}],
261     ['field-range-11','--field 2-4,1-3 --to=si "1000 2000 3000 4000 5000"',
262             {OUT=>"1.0K 2.0K 3.0K 4.0K 5000"}],
263
264     ['field-range-12','--field 1-1,3-3 --to=si "1000 2000 3000 4000 5000"',
265             {OUT=>"1.0K 2000 3.0K 4000 5000"}],
266
267     ['field-range-13', '--field 1,-2 --to=si "1000 2000 3000"',
268             {OUT=>"1.0K 2.0K 3000"}],
269
270     ['field-range-14', '--field -2,4- --to=si "1000 2000 3000 4000 5000"',
271             {OUT=>"1.0K 2.0K 3000 4.0K 5.0K"}],
272     ['field-range-15', '--field -2,-4 --to=si "1000 2000 3000 4000 5000"',
273             {OUT=>"1.0K 2.0K 3.0K 4.0K 5000"}],
274     ['field-range-16', '--field 2-,4- --to=si "1000 2000 3000 4000 5000"',
275             {OUT=>"1000 2.0K 3.0K 4.0K 5.0K"}],
276     ['field-range-17', '--field 4-,2- --to=si "1000 2000 3000 4000 5000"',
277             {OUT=>"1000 2.0K 3.0K 4.0K 5.0K"}],
278
279     # white space are valid field separators
280     # (undocumented? but works in cut as well).
281     ['field-range-18', '--field "1,2 4" --to=si "1000 2000 3000 4000 5000"',
282             {OUT=>"1.0K 2.0K 3000 4.0K 5000"}],
283
284     # Unlike 'cut', a lone '-' means 'all fields', even as part of a list
285     # of fields.
286     ['field-range-19','--field 3,- --to=si "1000 2000 3000 4000 5000"',
287             {OUT=>"1.0K 2.0K 3.0K 4.0K 5.0K"}],
288
289     ['all-fields-1', '--field=- --to=si "1000 2000 3000 4000 5000"',
290             {OUT=>"1.0K 2.0K 3.0K 4.0K 5.0K"}],
291
292     ['field-range-err-1', '--field -foo --to=si 10',
293             {EXIT=>1}, {ERR=>"$prog: invalid field value 'foo'\n$try"}],
294     ['field-range-err-2', '--field --3 --to=si 10',
295             {EXIT=>1}, {ERR=>"$prog: invalid field range\n$try"}],
296     ['field-range-err-3', '--field 0 --to=si 10',
297             {EXIT=>1}, {ERR=>"$prog: fields are numbered from 1\n$try"}],
298     ['field-range-err-4', '--field 3-2 --to=si 10',
299             {EXIT=>1}, {ERR=>"$prog: invalid decreasing range\n$try"}],
300     ['field-range-err-6', '--field - --field 1- --to=si 10',
301             {EXIT=>1}, {ERR=>"$prog: multiple field specifications\n"}],
302     ['field-range-err-7', '--field -1 --field 1- --to=si 10',
303             {EXIT=>1}, {ERR=>"$prog: multiple field specifications\n"}],
304     ['field-range-err-8', '--field -1 --field 1,2,3 --to=si 10',
305             {EXIT=>1}, {ERR=>"$prog: multiple field specifications\n"}],
306     ['field-range-err-9', '--field 1- --field 1,2,3 --to=si 10',
307             {EXIT=>1}, {ERR=>"$prog: multiple field specifications\n"}],
308     ['field-range-err-10','--field 1,2,3 --field 1- --to=si 10',
309             {EXIT=>1}, {ERR=>"$prog: multiple field specifications\n"}],
310     ['field-range-err-11','--field 1-2-3 --to=si 10',
311             {EXIT=>1}, {ERR=>"$prog: invalid field range\n$try"}],
312     ['field-range-err-12','--field 0-1 --to=si 10',
313             {EXIT=>1}, {ERR=>"$prog: fields are numbered from 1\n$try"}],
314     ['field-range-err-13','--field '.$limits->{UINTMAX_MAX}.',22 --to=si 10',
315             {EXIT=>1}, {ERR=>"$prog: field number " .
316                            "'".$limits->{UINTMAX_MAX}."' is too large\n$try"}],
317
318     # Auto-consume white-space, setup auto-padding
319     ['whitespace-1', '--to=si --field 2 "A    500 B"', {OUT=>"A    500 B"}],
320     ['whitespace-2', '--to=si --field 2 "A   5000 B"', {OUT=>"A   5.0K B"}],
321     ['whitespace-3', '--to=si "  500"', {OUT=>"  500"}],
322     ['whitespace-4', '--to=si " 6500"', {OUT=>" 6.5K"}],
323     # NOTE: auto-padding is not enabled if the value is on the first
324     #       field and there's no white-space before it.
325     ['whitespace-5', '--to=si "6000000"', {OUT=>"6.0M"}],
326     # but if there is whitespace, assume auto-padding is desired.
327     ['whitespace-6', '--to=si " 6000000"', {OUT=>"    6.0M"}],
328
329     # auto-padding - lines have same padding-width
330     #  (padding_buffer will be alloc'd just once)
331     ['whitespace-7', '--to=si --field 2',
332             {IN_PIPE=>"rootfs    100000\n" .
333                       "udevxx   2000000\n"},
334             {OUT    =>"rootfs      100K\n" .
335                       "udevxx      2.0M"}],
336     # auto-padding - second line requires a
337     # larger padding (padding-buffer needs to be realloc'd)
338     ['whitespace-8', '--to=si --field 2',
339             {IN_PIPE=>"rootfs    100000\n" .
340                       "udev         20000000\n"},
341             {OUT    =>"rootfs      100K\n" .
342                       "udev              20M"}],
343
344
345     # Corner-cases:
346     # weird mix of identical suffix,delimiters
347     # The priority is:
348     #   1. delimiters (and fields) are parsed (in process_line()
349     #   2. optional custom suffix is removed (in process_suffixed_number())
350     #   3. Remaining suffixes must be valid SI/IEC (in human_xstrtol())
351
352     # custom suffix comes BEFORE SI/IEC suffix,
353     #   so these are 40 of "M", not 40,000,000.
354     ['mix-1', '--suffix=M --from=si 40M',     {OUT=>"40M"}],
355
356     # These are forty-million Ms .
357     ['mix-2', '--suffix=M --from=si 40MM',     {OUT=>"40000000M"}],
358
359     ['mix-3', '--suffix=M --from=auto 40MM',     {OUT=>"40000000M"}],
360     ['mix-4', '--suffix=M --from=auto 40MiM',     {OUT=>"41943040M"}],
361     ['mix-5', '--suffix=M --to=si --from=si 4MM',     {OUT=>"4.0MM"}],
362
363     # This might be confusing to the user, but it's legit:
364     # The M in the output is the custom suffix, not Mega.
365     ['mix-6', '--suffix=M 40',     {OUT=>"40M"}],
366     ['mix-7', '--suffix=M 4000000',     {OUT=>"4000000M"}],
367     ['mix-8', '--suffix=M --to=si 4000000',     {OUT=>"4.0MM"}],
368
369     # The output 'M' is the custom suffix.
370     ['mix-10', '--delimiter=M --suffix=M 40',     {OUT=>"40M"}],
371
372     # The INPUT 'M' is a delimiter (delimiters are top priority)
373     # The output contains one M for custom suffix, and one 'M' delimiter.
374     ['mix-11', '--delimiter=M --suffix=M 40M',     {OUT=>"40MM"}],
375
376     # Same as above, the "M" is NOT treated as a mega SI prefix,
377     ['mix-12', '--delimiter=M --from=si --suffix=M 40M',     {OUT=>"40MM"}],
378
379     # The 'M' is treated as a delimiter, and so the input value is '4000'
380     ['mix-13', '--delimiter=M --to=si --from=auto 4000M5000M9000',
381             {OUT=>"4.0KM5000M9000"}],
382     # 'M' is the delimiter, so the second input field is '5000'
383     ['mix-14', '--delimiter=M --field 2 --from=auto --to=si 4000M5000M9000',
384             {OUT=>"4000M5.0KM9000"}],
385
386
387
388     ## Header testing
389
390     # header - silently ignored with command line parameters
391     ['header-1', '--header --to=iec 4096', {OUT=>"4.0K"}],
392
393     # header warning with --debug
394     ['header-2', '--debug --header --to=iec 4096', {OUT=>"4.0K"},
395             {ERR=>"$prog: --header ignored with command-line input\n"}],
396
397     ['header-3', '--header=A',
398             {ERR=>"$prog: invalid header value 'A'\n"},
399             {EXIT => 1},],
400     ['header-4', '--header=0',
401             {ERR=>"$prog: invalid header value '0'\n"},
402             {EXIT => 1},],
403     ['header-5', '--header=-6',
404             {ERR=>"$prog: invalid header value '-6'\n"},
405             {EXIT => 1},],
406     ['header-6', '--debug --header --to=iec',
407             {IN_PIPE=>"size\n5000\n90000\n"},
408             {OUT=>"size\n4.9K\n88K"}],
409     ['header-7', '--debug --header=3 --to=iec',
410             {IN_PIPE=>"hello\nworld\nsize\n5000\n90000\n"},
411             {OUT=>"hello\nworld\nsize\n4.9K\n88K"}],
412     # header, but no actual content
413     ['header-8', '--header=2 --to=iec',
414             {IN_PIPE=>"hello\nworld\n"},
415             {OUT=>"hello\nworld"}],
416     # not enough header lines
417     ['header-9', '--header=3 --to=iec',
418             {IN_PIPE=>"hello\nworld\n"},
419             {OUT=>"hello\nworld"}],
420
421
422     ## human_strtod testing
423
424     # NO_DIGITS_FOUND
425     ['strtod-1', '--from=si "foo"',
426             {ERR=>"$prog: invalid number: 'foo'\n"},
427             {EXIT=> 2}],
428     ['strtod-2', '--from=si ""',
429             {ERR=>"$prog: invalid number: ''\n"},
430             {EXIT=> 2}],
431
432     # FRACTION_NO_DIGITS_FOUND
433     ['strtod-5', '--from=si 12.',
434             {ERR=>"$prog: invalid number: '12.'\n"},
435             {EXIT=>2}],
436     ['strtod-6', '--from=si 12.K',
437             {ERR=>"$prog: invalid number: '12.K'\n"},
438             {EXIT=>2}],
439
440     # whitespace is not allowed after decimal-point
441     ['strtod-6.1', '--from=si --delimiter=, "12.  2"',
442             {ERR=>"$prog: invalid number: '12.  2'\n"},
443             {EXIT=>2}],
444
445     # INVALID_SUFFIX
446     ['strtod-9', '--from=si 12.2J',
447             {ERR=>"$prog: invalid suffix in input: '12.2J'\n"},
448             {EXIT=>2}],
449
450     # VALID_BUT_FORBIDDEN_SUFFIX
451     ['strtod-10', '12M',
452             {ERR => "$prog: rejecting suffix " .
453                     "in input: '12M' (consider using --from)\n"},
454             {EXIT=>2}],
455
456     # MISSING_I_SUFFIX
457     ['strtod-11', '--from=iec-i 12M',
458             {ERR => "$prog: missing 'i' suffix in input: " .
459                     "'12M' (e.g Ki/Mi/Gi)\n"},
460             {EXIT=>2}],
461
462     #
463     # Test double_to_human()
464     #
465
466     # 1K and smaller
467     ['dbl-to-human-1','--to=si 800',  {OUT=>"800"}],
468     ['dbl-to-human-2','--to=si 0',  {OUT=>"0"}],
469     ['dbl-to-human-2.1','--to=si 999',  {OUT=>"999"}],
470     ['dbl-to-human-2.2','--to=si 1000',  {OUT=>"1.0K"}],
471     #NOTE: the following are consistent with "ls -lh" output
472     ['dbl-to-human-2.3','--to=iec 999',  {OUT=>"999"}],
473     ['dbl-to-human-2.4','--to=iec 1023',  {OUT=>"1023"}],
474     ['dbl-to-human-2.5','--to=iec 1024',  {OUT=>"1.0K"}],
475     ['dbl-to-human-2.6','--to=iec 1025',  {OUT=>"1.1K"}],
476     ['dbl-to-human-2.7','--to=iec 0',  {OUT=>"0"}],
477     # no "i" suffix if output has no suffix
478     ['dbl-to-human-2.8','--to=iec-i 0',  {OUT=>"0"}],
479
480     # values resulting in "N.Nx" output
481     ['dbl-to-human-3','--to=si 8000', {OUT=>"8.0K"}],
482     ['dbl-to-human-3.1','--to=si 8001', {OUT=>"8.1K"}],
483     ['dbl-to-human-4','--to=si --round=down 8001', {OUT=>"8.0K"}],
484
485     ['dbl-to-human-5','--to=si --round=down 3500', {OUT=>"3.5K"}],
486     ['dbl-to-human-6','--to=si --round=nearest 3500', {OUT=>"3.5K"}],
487     ['dbl-to-human-7','--to=si --round=up 3500', {OUT=>"3.5K"}],
488
489     ['dbl-to-human-8','--to=si --round=down    3501', {OUT=>"3.5K"}],
490     ['dbl-to-human-9','--to=si --round=nearest  3501', {OUT=>"3.5K"}],
491     ['dbl-to-human-10','--to=si --round=up 3501', {OUT=>"3.6K"}],
492
493     ['dbl-to-human-11','--to=si --round=nearest  3550', {OUT=>"3.6K"}],
494     ['dbl-to-human-12','--to=si --from=si 999.89K', {OUT=>"1.0M"}],
495     ['dbl-to-human-13','--to=si --from=si 9.9K', {OUT=>"9.9K"}],
496     ['dbl-to-human-14','--to=si 9900', {OUT=>"9.9K"}],
497     ['dbl-to-human-15','--to=iec --from=si 3.3K', {OUT=>"3.3K"}],
498     ['dbl-to-human-16','--to=iec --round=down --from=si 3.3K', {OUT=>"3.2K"}],
499
500     # values resulting in 'NNx' output
501     ['dbl-to-human-17','--to=si 9999', {OUT=>"10K"}],
502     ['dbl-to-human-18','--to=si --round=down 35000', {OUT=>"35K"}],
503     ['dbl-to-human-19','--to=iec 35000', {OUT=>"35K"}],
504     ['dbl-to-human-20','--to=iec --round=down 35000', {OUT=>"34K"}],
505     ['dbl-to-human-21','--to=iec 35000000', {OUT=>"34M"}],
506     ['dbl-to-human-22','--to=iec --round=down 35000000', {OUT=>"33M"}],
507     ['dbl-to-human-23','--to=si  35000001', {OUT=>"36M"}],
508     ['dbl-to-human-24','--to=si --from=si  9.99M', {OUT=>"10M"}],
509     ['dbl-to-human-25','--to=si --from=iec 9.99M', {OUT=>"11M"}],
510     ['dbl-to-human-25.1','--to=iec 99999', {OUT=>"98K"}],
511
512     # values resulting in 'NNNx' output
513     ['dbl-to-human-26','--to=si 999000000000', {OUT=>"999G"}],
514     ['dbl-to-human-27','--to=iec 999000000000', {OUT=>"931G"}],
515     ['dbl-to-human-28','--to=si 123600000000000', {OUT=>"124T"}],
516     ['dbl-to-human-29','--to=si 998123', {OUT=>"999K"}],
517     ['dbl-to-human-30','--to=si --round=nearest 998123', {OUT=>"998K"}],
518     ['dbl-to-human-31','--to=si 99999', {OUT=>"100K"}],
519     ['dbl-to-human-32','--to=iec 102399', {OUT=>"100K"}],
520     ['dbl-to-human-33','--to=iec-i 102399', {OUT=>"100Ki"}],
521
522
523     # Default --round=from-zero
524     ['round-1','--to-unit=1024 -- 6000 -6000',
525             {OUT=>"6\n-6"}],
526     ['round-2','--to-unit=1024 -- 6000.0 -6000.0',
527             {OUT=>"5.9\n-5.9"}],
528     ['round-3','--to-unit=1024 -- 6000.00 -6000.00',
529             {OUT=>"5.86\n-5.86"}],
530     ['round-4','--to-unit=1024 -- 6000.000 -6000.000',
531             {OUT=>"5.860\n-5.860"}],
532     ['round-5','--to-unit=1024 -- 6000.0000 -6000.0000',
533             {OUT=>"5.8594\n-5.8594"}],
534     # --round=up
535     ['round-1-up','--round=up --to-unit=1024 -- 6000 -6000',
536             {OUT=>"6\n-5"}],
537     ['round-2-up','--round=up --to-unit=1024 -- 6000.0 -6000.0',
538             {OUT=>"5.9\n-5.8"}],
539     ['round-3-up','--round=up --to-unit=1024 -- 6000.00 -6000.00',
540             {OUT=>"5.86\n-5.85"}],
541     ['round-4-up','--round=up --to-unit=1024 -- 6000.000 -6000.000',
542             {OUT=>"5.860\n-5.859"}],
543     ['round-5-up','--round=up --to-unit=1024 -- 6000.0000 -6000.0000',
544             {OUT=>"5.8594\n-5.8593"}],
545     # --round=down
546     ['round-1-down','--round=down --to-unit=1024 -- 6000 -6000',
547             {OUT=>"5\n-6"}],
548     ['round-2-down','--round=down --to-unit=1024 -- 6000.0 -6000.0',
549             {OUT=>"5.8\n-5.9"}],
550     ['round-3-down','--round=down --to-unit=1024 -- 6000.00 -6000.00',
551             {OUT=>"5.85\n-5.86"}],
552     ['round-4-down','--round=down --to-unit=1024 -- 6000.000 -6000.000',
553             {OUT=>"5.859\n-5.860"}],
554     ['round-5-down','--round=down --to-unit=1024 -- 6000.0000 -6000.0000',
555             {OUT=>"5.8593\n-5.8594"}],
556     # --round=towards-zero
557     ['round-1-to-zero','--ro=towards-zero --to-u=1024 -- 6000 -6000',
558             {OUT=>"5\n-5"}],
559     ['round-2-to-zero','--ro=towards-zero --to-u=1024 -- 6000.0 -6000.0',
560             {OUT=>"5.8\n-5.8"}],
561     ['round-3-to-zero','--ro=towards-zero --to-u=1024 -- 6000.00 -6000.00',
562             {OUT=>"5.85\n-5.85"}],
563     ['round-4-to-zero','--ro=towards-zero --to-u=1024 -- 6000.000 -6000.000',
564             {OUT=>"5.859\n-5.859"}],
565     ['round-5-to-zero','--ro=towards-zero --to-u=1024 -- 6000.0000 -6000.0000',
566             {OUT=>"5.8593\n-5.8593"}],
567     # --round=nearest
568     ['round-1-near','--ro=nearest --to-u=1024 -- 6000 -6000',
569             {OUT=>"6\n-6"}],
570     ['round-2-near','--ro=nearest --to-u=1024 -- 6000.0 -6000.0',
571             {OUT=>"5.9\n-5.9"}],
572     ['round-3-near','--ro=nearest --to-u=1024 -- 6000.00 -6000.00',
573             {OUT=>"5.86\n-5.86"}],
574     ['round-4-near','--ro=nearest --to-u=1024 -- 6000.000 -6000.000',
575             {OUT=>"5.859\n-5.859"}],
576     ['round-5-near','--ro=nearest --to-u=1024 -- 6000.0000 -6000.0000',
577             {OUT=>"5.8594\n-5.8594"}],
578
579
580     # Leading zeros weren't handled appropriately before 8.24
581     ['leading-1','0000000000000000000000000001', {OUT=>"1"}],
582     ['leading-2','.1', {OUT=>"0.1"}],
583     ['leading-3','bad.1',
584             {ERR => "$prog: invalid number: 'bad.1'\n"},
585             {EXIT => 2}],
586     ['leading-4','..1',
587             {ERR => "$prog: invalid suffix in input: '..1'\n"},
588             {EXIT => 2}],
589     ['leading-5','1.',
590             {ERR => "$prog: invalid number: '1.'\n"},
591             {EXIT => 2}],
592
593     # precision override
594     ['precision-1','--format=%.4f 9991239123 --to=si', {OUT=>"9.9913G"}],
595     ['precision-2','--format=%.1f 9991239123 --to=si', {OUT=>"10.0G"}],
596     ['precision-3','--format=%.1f 1', {OUT=>"1.0"}],
597     ['precision-4','--format=%.1f 1.12', {OUT=>"1.2"}],
598     ['precision-5','--format=%.1f 9991239123 --to-unit=G', {OUT=>"10.0"}],
599     ['precision-6','--format="% .1f" 9991239123 --to-unit=G', {OUT=>"10.0"}],
600     ['precision-7','--format=%.-1f 1.1',
601             {ERR => "$prog: invalid precision in format '%.-1f'\n"},
602             {EXIT => 1}],
603     ['precision-8','--format=%.+1f 1.1',
604             {ERR => "$prog: invalid precision in format '%.+1f'\n"},
605             {EXIT => 1}],
606     ['precision-9','--format="%. 1f" 1.1',
607             {ERR => "$prog: invalid precision in format '%. 1f'\n"},
608             {EXIT => 1}],
609
610     # debug warnings
611     ['debug-1', '--debug 4096', {OUT=>"4096"},
612             {ERR=>"$prog: no conversion option specified\n"}],
613     # '--padding' is a valid conversion option - no warning should be printed
614     ['debug-1.1', '--debug --padding 10 4096', {OUT=>"      4096"}],
615     ['debug-2', '--debug --grouping --from=si 4.0K', {OUT=>"4000"},
616             {ERR=>"$prog: grouping has no effect in this locale\n"}],
617
618     # dev-debug messages - the actual messages don't matter
619     # just ensure the program works, and for code coverage testing.
620     ['devdebug-1', '---debug --from=si 4.9K', {OUT=>"4900"},
621             {ERR=>""},
622             {ERR_SUBST=>"s/.*//msg"}],
623     ['devdebug-2', '---debug 4900', {OUT=>"4900"},
624             {ERR=>""},
625             {ERR_SUBST=>"s/.*//msg"}],
626     ['devdebug-3', '---debug --from=auto 4Mi', {OUT=>"4194304"},
627             {ERR=>""},
628             {ERR_SUBST=>"s/.*//msg"}],
629     ['devdebug-4', '---debug --to=si 4000000', {OUT=>"4.0M"},
630             {ERR=>""},
631             {ERR_SUBST=>"s/.*//msg"}],
632     ['devdebug-5', '---debug --to=si --padding=5 4000000', {OUT=>" 4.0M"},
633             {ERR=>""},
634             {ERR_SUBST=>"s/.*//msg"}],
635     ['devdebug-6', '---debug --suffix=Foo 1234Foo', {OUT=>"1234Foo"},
636             {ERR=>""},
637             {ERR_SUBST=>"s/.*//msg"}],
638     ['devdebug-7', '---debug --suffix=Foo 1234', {OUT=>"1234Foo"},
639             {ERR=>""},
640             {ERR_SUBST=>"s/.*//msg"}],
641     ['devdebug-9', '---debug --grouping 10000', {OUT=>"10000"},
642             {ERR=>""},
643             {ERR_SUBST=>"s/.*//msg"}],
644     ['devdebug-10', '---debug --format %f 10000', {OUT=>"10000"},
645             {ERR=>""},
646             {ERR_SUBST=>"s/.*//msg"}],
647     ['devdebug-11', '---debug --format "%\'-10f" 10000',{OUT=>"10000     "},
648             {ERR=>""},
649             {ERR_SUBST=>"s/.*//msg"}],
650
651     # Invalid parameters
652     ['help-1', '--foobar',
653             {ERR=>"$prog: unrecognized option\n$try"},
654             {ERR_SUBST=>"s/option.*/option/; s/unknown/unrecognized/"},
655             {EXIT=>1}],
656
657     ## Format string - check error detection
658     ['fmt-err-1', '--format ""',
659             {ERR=>"$prog: format '' has no % directive\n"},
660             {EXIT=>1}],
661     ['fmt-err-2', '--format "hello"',
662             {ERR=>"$prog: format 'hello' has no % directive\n"},
663             {EXIT=>1}],
664     ['fmt-err-3', '--format "hello%"',
665             {ERR=>"$prog: format 'hello%' ends in %\n"},
666             {EXIT=>1}],
667     ['fmt-err-4', '--format "%d"',
668             {ERR=>"$prog: invalid format '%d', " .
669                   "directive must be %[0]['][-][N][.][N]f\n"},
670             {EXIT=>1}],
671     ['fmt-err-5', '--format "% -43 f"',
672             {ERR=>"$prog: invalid format '% -43 f', " .
673                   "directive must be %[0]['][-][N][.][N]f\n"},
674             {EXIT=>1}],
675     ['fmt-err-6', '--format "%f %f"',
676             {ERR=>"$prog: format '%f %f' has too many % directives\n"},
677             {EXIT=>1}],
678     ['fmt-err-9', '--format "%f" --grouping',
679             {ERR=>"$prog: --grouping cannot be combined with --format\n"},
680             {EXIT=>1}],
681     ['fmt-err-10', '--format "%\'f" --to=si',
682             {ERR=>"$prog: grouping cannot be combined with --to\n"},
683             {EXIT=>1}],
684     ['fmt-err-11', '--debug --format "%\'f" 5000', {OUT=>"5000"},
685             {ERR=>"$prog: grouping has no effect in this locale\n"}],
686
687     ## Format string - check some corner cases
688     ['fmt-1', '--format "%% %f" 5000', {OUT=>"%%5000"}],
689     ['fmt-2', '--format "%f %%" 5000', {OUT=>"5000 %%"}],
690
691     ['fmt-3', '--format "--%f--" 5000000', {OUT=>"--5000000--"}],
692     ['fmt-4', '--format "--%f--" --to=si 5000000', {OUT=>"--5.0M--"}],
693
694     ['fmt-5', '--format "--%10f--" --to=si 5000000',{OUT=>"--      5.0M--"}],
695     ['fmt-6', '--format "--%-10f--" --to=si 5000000',{OUT=>"--5.0M      --"}],
696     ['fmt-7', '--format "--%10f--" 5000000',{OUT=>"--   5000000--"}],
697     ['fmt-8', '--format "--%-10f--" 5000000',{OUT=>"--5000000   --"}],
698
699     # too-short width
700     ['fmt-9', '--format "--%5f--" 5000000',{OUT=>"--5000000--"}],
701
702     # Format + Suffix
703     ['fmt-10', '--format "--%10f--" --suffix Foo 50', {OUT=>"--     50Foo--"}],
704     ['fmt-11', '--format "--%-10f--" --suffix Foo 50',{OUT=>"--50Foo     --"}],
705
706     # Grouping in C locale - no grouping effect
707     ['fmt-12', '--format "%\'f" 50000',{OUT=>"50000"}],
708     ['fmt-13', '--format "%\'10f" 50000', {OUT=>"     50000"}],
709     ['fmt-14', '--format "%\'-10f" 50000',{OUT=>"50000     "}],
710
711     # Very large format strings
712     ['fmt-15', '--format "--%100000f--" --to=si 4200',
713                  {OUT=>"--" . " " x 99996 . "4.2K--" }],
714
715     # --format padding overrides --padding
716     ['fmt-16', '--format="%6f" --padding=66 1234',{OUT=>"  1234"}],
717
718     # zero padding
719     ['fmt-17', '--format="%06f" 1234',{OUT=>"001234"}],
720     # also support spaces (which are ignored as spacing is handled separately)
721     ['fmt-18', '--format="%0 6f" 1234',{OUT=>"001234"}],
722     # handle generic padding in combination
723     ['fmt-22', '--format="%06f" --padding=7 1234',{OUT=>" 001234"}],
724     ['fmt-23', '--format="%06f" --padding=-7 1234',{OUT=>"001234 "}],
725
726
727     ## Check all errors again, this time with --invalid=fail
728     ##  Input will be printed without conversion,
729     ##  and exit code will be 2
730     ['ign-err-1', '--invalid=fail 4J',
731             {ERR => "$prog: invalid suffix in input: '4J'\n"},
732             {OUT => "4J\n"},
733             {EXIT => 2}],
734     ['ign-err-2', '--invalid=fail 4M',
735             {ERR => "$prog: rejecting suffix " .
736             "in input: '4M' (consider using --from)\n"},
737             {OUT => "4M\n"},
738             {EXIT => 2}],
739     ['ign-err-3', '--invalid=fail --from=si 4MJ',
740             {ERR => "$prog: invalid suffix in input '4MJ': 'J'\n"},
741             {OUT => "4MJ\n"},
742             {EXIT => 2}],
743     ['ign-err-4', '--invalid=fail --suffix=Foo --to=si   7000FooF',
744              {ERR => "$prog: invalid suffix in input: '7000FooF'\n"},
745              {OUT => "7000FooF\n"},
746              {EXIT => 2}],
747     ['ign-err-5','--invalid=fail --field 3 --from=auto "Hello 40M World 90G"',
748             {ERR => "$prog: invalid number: 'World'\n"},
749             {OUT => "Hello 40M World 90G\n"},
750             {EXIT => 2}],
751     ['ign-err-7', '--invalid=fail --from=si "foo"',
752             {ERR => "$prog: invalid number: 'foo'\n"},
753             {OUT => "foo\n"},
754             {EXIT=> 2}],
755     ['ign-err-8', '--invalid=fail 12M',
756             {ERR => "$prog: rejecting suffix " .
757                     "in input: '12M' (consider using --from)\n"},
758             {OUT => "12M\n"},
759             {EXIT => 2}],
760     ['ign-err-9', '--invalid=fail --from=iec-i 12M',
761             {ERR => "$prog: missing 'i' suffix in input: " .
762                     "'12M' (e.g Ki/Mi/Gi)\n"},
763             {OUT => "12M\n"},
764             {EXIT=>2}],
765
766     ## Ignore Errors with multiple conversions
767     ['ign-err-m1', '--invalid=ignore --to=si 1000 2000 bad 3000',
768             {OUT => "1.0K\n2.0K\nbad\n3.0K"},
769             {EXIT => 0}],
770     ['ign-err-m1.1', '--invalid=ignore --to=si',
771             {IN_PIPE => "1000\n2000\nbad\n3000\n"},
772             {OUT => "1.0K\n2.0K\nbad\n3.0K"},
773             {EXIT => 0}],
774     ['ign-err-m1.3', '--invalid=fail --debug --to=si 1000 2000 3000',
775             {OUT => "1.0K\n2.0K\n3.0K"},
776             {EXIT => 0}],
777     ['ign-err-m2', '--invalid=fail --to=si 1000 Foo 3000',
778             {OUT => "1.0K\nFoo\n3.0K\n"},
779             {ERR => "$prog: invalid number: 'Foo'\n"},
780             {EXIT => 2}],
781     ['ign-err-m2.1', '--invalid=warn --to=si',
782             {IN_PIPE => "1000\nFoo\n3000\n"},
783             {OUT => "1.0K\nFoo\n3.0K"},
784             {ERR => "$prog: invalid number: 'Foo'\n"},
785             {EXIT => 0}],
786
787     # --debug will trigger a final warning at EOF
788     ['ign-err-m2.2', '--invalid=fail --debug --to=si 1000 Foo 3000',
789             {OUT => "1.0K\nFoo\n3.0K\n"},
790             {ERR => "$prog: invalid number: 'Foo'\n" .
791                     "$prog: failed to convert some of the input numbers\n"},
792             {EXIT => 2}],
793
794     ['ign-err-m3', '--invalid=fail --field 2 --from=si --to=iec',
795             {IN_PIPE => "A 1K x\nB 2M y\nC 3G z\n"},
796             {OUT => "A 1000 x\nB 2.0M y\nC 2.8G z"},
797             {EXIT => 0}],
798     # invalid input on one of the fields
799     ['ign-err-m3.1', '--invalid=fail --field 2 --from=si --to=iec',
800             {IN_PIPE => "A 1K x\nB Foo y\nC 3G z\n"},
801             {OUT => "A 1000 x\nB Foo y\nC 2.8G z\n"},
802             {ERR => "$prog: invalid number: 'Foo'\n"},
803             {EXIT => 2}],
804    );
805
806# test null-terminated lines
807my @NullDelim_Tests =
808  (
809     # Input from STDIN
810     ['z1', '-z --to=iec',
811             {IN_PIPE => "1025\x002048\x00"}, {OUT=>"1.1K\x002.0K\x00"}],
812
813     # Input from the commandline - terminated by NULL vs NL
814     ['z3', '   --to=iec 1024',  {OUT=>"1.0K\n"}],
815     ['z2', '-z --to=iec 1024',  {OUT=>"1.0K\x00"}],
816
817     # Input from STDIN, with fields
818     ['z4', '-z --field=3 --to=si',
819             {IN_PIPE => "A B 1001 C\x00" .
820                         "D E 2002 F\x00"},
821             {OUT => "A B 1.1K C\x00" .
822                     "D E 2.1K F\x00"}],
823
824     # Input from STDIN, with fields and embedded NL
825     ['z5', '-z --field=3 --to=si',
826             {IN_PIPE => "A\nB 1001 C\x00" .
827                         "D E\n2002 F\x00"},
828             {OUT => "A B 1.1K C\x00" .
829                     "D E 2.1K F\x00"}],
830  );
831
832my @Limit_Tests =
833  (
834     # Large Values
835     ['large-1','1000000000000000', {OUT=>"1000000000000000"}],
836     # 18 digits is OK
837     ['large-2','1000000000000000000', {OUT=>"1000000000000000000"}],
838     # 19 digits is too much (without output scaling)
839     ['large-3','10000000000000000000',
840             {ERR => "$prog: value too large to be printed: '1e+19' " .
841                     "(consider using --to)\n"},
842             {EXIT=>2}],
843     ['large-4','1000000000000000000.0',
844             {ERR => "$prog: value/precision too large to be printed: " .
845                     "'1e+18/1' (consider using --to)\n"},
846             {EXIT=>2}],
847
848
849     # Test input:
850     # Up to 33 digits is OK.
851     ['large-3.1', '--to=si                           1', {OUT=>   "1"}],
852     ['large-3.2', '--to=si                          10', {OUT=>  "10"}],
853     ['large-3.3', '--to=si                         100', {OUT=> "100"}],
854     ['large-3.4', '--to=si                        1000', {OUT=>"1.0K"}],
855     ['large-3.5', '--to=si                       10000', {OUT=> "10K"}],
856     ['large-3.6', '--to=si                      100000', {OUT=>"100K"}],
857     ['large-3.7', '--to=si                     1000000', {OUT=>"1.0M"}],
858     ['large-3.8', '--to=si                    10000000', {OUT=> "10M"}],
859     ['large-3.9', '--to=si                   100000000', {OUT=>"100M"}],
860     ['large-3.10','--to=si                  1000000000', {OUT=>"1.0G"}],
861     ['large-3.11','--to=si                 10000000000', {OUT=> "10G"}],
862     ['large-3.12','--to=si                100000000000', {OUT=>"100G"}],
863     ['large-3.13','--to=si               1000000000000', {OUT=>"1.0T"}],
864     ['large-3.14','--to=si              10000000000000', {OUT=> "10T"}],
865     ['large-3.15','--to=si             100000000000000', {OUT=>"100T"}],
866     ['large-3.16','--to=si            1000000000000000', {OUT=>"1.0P"}],
867     ['large-3.17','--to=si           10000000000000000', {OUT=> "10P"}],
868     ['large-3.18','--to=si          100000000000000000', {OUT=>"100P"}],
869     ['large-3.19','--to=si         1000000000000000000', {OUT=>"1.0E"}],
870     ['large-3.20','--to=si        10000000000000000000', {OUT=> "10E"}],
871     ['large-3.21','--to=si       210000000000000000000', {OUT=>"210E"}],
872     ['large-3.22','--to=si      3210000000000000000000', {OUT=>"3.3Z"}],
873     ['large-3.23','--to=si     43210000000000000000000', {OUT=> "44Z"}],
874     ['large-3.24','--to=si    543210000000000000000000', {OUT=>"544Z"}],
875     ['large-3.25','--to=si   6543210000000000000000000', {OUT=>"6.6Y"}],
876     ['large-3.26','--to=si  76543210000000000000000000', {OUT=> "77Y"}],
877     ['large-3.27','--to=si 876543210000000000000000000', {OUT=>"877Y"}],
878     ['large-3.28','--to=si      9876543210000000000000000000', {OUT=>"9.9R"}],
879     ['large-3.29','--to=si     19876543210000000000000000000', {OUT=> "20R"}],
880     ['large-3.30','--to=si    219876543210000000000000000000', {OUT=>"220R"}],
881     ['large-3.31','--to=si   3219876543210000000000000000000', {OUT=>"3.3Q"}],
882     ['large-3.32','--to=si  43219876543210000000000000000000', {OUT=> "44Q"}],
883     ['large-3.33','--to=si 543219876543210000000000000000000', {OUT=>"544Q"}],
884
885     # More than 33 digits is not OK
886     ['large-3.34','--to=si 6543219876543210000000000000000000',
887             {ERR => "$prog: value too large to be converted: " .
888                     "'6543219876543210000000000000000000'\n"},
889             {EXIT => 2}],
890
891     # Test Output
892     ['large-4.1', '--from=si  9.7M',               {OUT=>"9700000"}],
893     ['large-4.2', '--from=si  10M',              {OUT =>"10000000"}],
894     ['large-4.3', '--from=si  200M',            {OUT =>"200000000"}],
895     ['large-4.4', '--from=si  3G',             {OUT =>"3000000000"}],
896     ['large-4.5', '--from=si  40G',           {OUT =>"40000000000"}],
897     ['large-4.6', '--from=si  500G',         {OUT =>"500000000000"}],
898     ['large-4.7', '--from=si  6T',          {OUT =>"6000000000000"}],
899     ['large-4.8', '--from=si  70T',        {OUT =>"70000000000000"}],
900     ['large-4.9', '--from=si  800T',      {OUT =>"800000000000000"}],
901     ['large-4.10','--from=si  9P',       {OUT =>"9000000000000000"}],
902     ['large-4.11','--from=si  10P',     {OUT =>"10000000000000000"}],
903     ['large-4.12','--from=si  200P',   {OUT =>"200000000000000000"}],
904     ['large-4.13','--from=si  3E',    {OUT =>"3000000000000000000"}],
905
906     # More than 18 digits of output without scaling - no good.
907     ['large-4.14','--from=si  40E',
908             {ERR => "$prog: value too large to be printed: '4e+19' " .
909                     "(consider using --to)\n"},
910             {EXIT => 2}],
911     ['large-4.15','--from=si  500E',
912             {ERR => "$prog: value too large to be printed: '5e+20' " .
913                     "(consider using --to)\n"},
914             {EXIT => 2}],
915     ['large-4.16','--from=si  6Z',
916             {ERR => "$prog: value too large to be printed: '6e+21' " .
917                     "(consider using --to)\n"},
918             {EXIT => 2}],
919     ['large-4.17','--from=si  70Z',
920             {ERR => "$prog: value too large to be printed: '7e+22' " .
921                     "(consider using --to)\n"},
922             {EXIT => 2}],
923     ['large-4.18','--from=si  800Z',
924             {ERR => "$prog: value too large to be printed: '8e+23' " .
925                     "(consider using --to)\n"},
926             {EXIT => 2}],
927     ['large-4.19','--from=si  9Y',
928             {ERR => "$prog: value too large to be printed: '9e+24' " .
929                     "(consider using --to)\n"},
930             {EXIT => 2}],
931     ['large-4.20','--from=si  10Y',
932             {ERR => "$prog: value too large to be printed: '1e+25' " .
933                     "(consider using --to)\n"},
934             {EXIT => 2}],
935     ['large-4.21','--from=si  200Y',
936             {ERR => "$prog: value too large to be printed: '2e+26' " .
937                     "(consider using --to)\n"},
938             {EXIT => 2}],
939
940     ['large-5.1','--to=si 1000000000000000000', {OUT=>"1.0E"}],
941     ['large-5','--from=si --to=si 2E', {OUT=>"2.0E"}],
942     ['large-6','--from=si --to=si 3.4Z', {OUT=>"3.4Z"}],
943     ['large-7','--from=si --to=si 80Y', {OUT=>"80Y"}],
944     ['large-8','--from=si --to=si 9000Z', {OUT=>"9.0Y"}],
945
946     ['large-10','--from=si --to=si 999Q', {OUT=>"999Q"}],
947     ['large-11','--from=si --to=iec 999Q', {OUT=>"789Q"}],
948     ['large-12','--from=si --round=down --to=iec 999Q', {OUT=>"788Q"}],
949
950     # units can also affect the output
951     ['large-13','--from=si --from-unit=1000000 9P',
952             {ERR => "$prog: value too large to be printed: '9e+21' " .
953                     "(consider using --to)\n"},
954             {EXIT => 2}],
955     ['large-13.1','--from=si --from-unit=1000000 --to=si 9P', {OUT=>"9.0Z"}],
956
957     # Numbers>999Q are never acceptable, regardless of scaling
958     ['large-14','--from=si --to=si 999Q', {OUT=>"999Q"}],
959     ['large-14.1','--from=si --to=si 1000Q',
960             {ERR => "$prog: value too large to be printed: '1e+33' " .
961                     "(cannot handle values > 999Q)\n"},
962             {EXIT => 2}],
963     ['large-14.2','--from=si --to=si --from-unit=10000 1Q',
964             {ERR => "$prog: value too large to be printed: '1e+34' " .
965                     "(cannot handle values > 999Q)\n"},
966             {EXIT => 2}],
967
968     # intmax_t overflow when rounding caused this to fail before 8.24
969     ['large-15',$limits->{INTMAX_OFLOW}, {OUT=>$limits->{INTMAX_OFLOW}}],
970     ['large-16','9.300000000000000000', {OUT=>'9.300000000000000000'}],
971
972     # INTEGRAL_OVERFLOW
973     ['strtod-3', '--from=si "1234567890123456789012345678901234567890'.
974                  '1234567890123456789012345678901234567890"',
975             {ERR=>"$prog: value too large to be converted: '" .
976                     "1234567890123456789012345678901234567890" .
977                     "1234567890123456789012345678901234567890'\n",
978                     },
979             {EXIT=> 2}],
980
981     # FRACTION_OVERFLOW
982     ['strtod-7', '--from=si "12.1234567890123456789012345678901234567890'.
983                  '1234567890123456789012345678901234567890"',
984             {ERR=>"$prog: value too large to be converted: '" .
985                     "12.1234567890123456789012345678901234567890" .
986                     "1234567890123456789012345678901234567890'\n",
987                     },
988             {EXIT=> 2}],
989
990     ['debug-4', '--to=si --debug 12345678901234567890',
991             {OUT=>"13E"},
992             {ERR=>"$prog: large input value '12345678901234567890':" .
993                   " possible precision loss\n"}],
994     ['debug-5', '--to=si --from=si --debug 1.12345678901234567890Y',
995             {OUT=>"1.2Y"},
996             {ERR=>"$prog: large input value '1.12345678901234567890Y':" .
997                   " possible precision loss\n"}],
998
999     ['ign-err-10','--invalid=fail 10000000000000000000',
1000             {ERR => "$prog: value too large to be printed: '1e+19' " .
1001                     "(consider using --to)\n"},
1002             {OUT => "10000000000000000000\n"},
1003             {EXIT=>2}],
1004     ['ign-err-11','--invalid=fail --to=si 6543219876543210000000000000000000',
1005             {ERR => "$prog: value too large to be converted: " .
1006                     "'6543219876543210000000000000000000'\n"},
1007             {OUT => "6543219876543210000000000000000000\n"},
1008             {EXIT => 2}],
1009  );
1010# Restrict these tests to systems with LDBL_DIG == 18
1011(system "$prog ---debug 1 2>&1|grep 'MAX_UNSCALED_DIGITS: 18' > /dev/null") == 0
1012  and push @Tests, @Limit_Tests;
1013
1014my @Locale_Tests =
1015  (
1016     # Locale that supports grouping, but without '--grouping' parameter
1017     ['lcl-grp-1', '--from=si 7M',   {OUT=>"7000000"},
1018             {ENV=>"LC_ALL=$locale"}],
1019
1020     # Locale with grouping
1021     ['lcl-grp-2', '--from=si --grouping 7M',   {OUT=>"7 000 000"},
1022             {ENV=>"LC_ALL=$locale"}],
1023
1024     # Locale with grouping and debug - no debug warning message
1025     ['lcl-grp-3', '--from=si --debug --grouping 7M',   {OUT=>"7 000 000"},
1026             {ENV=>"LC_ALL=$locale"}],
1027
1028     # Input with locale'd decimal-point
1029     ['lcl-stdtod-1', '--from=si 12,2K', {OUT=>"12200"},
1030             {ENV=>"LC_ALL=$locale"}],
1031
1032     ['lcl-dbl-to-human-1', '--to=si 1100', {OUT=>"1,1K"},
1033             {ENV=>"LC_ALL=$locale"}],
1034
1035     # Format + Grouping
1036     ['lcl-fmt-1', '--format "%\'f" 50000',{OUT=>"50 000"},
1037             {ENV=>"LC_ALL=$locale"}],
1038     ['lcl-fmt-2', '--format "--%\'10f--" 50000', {OUT=>"--    50 000--"},
1039             {ENV=>"LC_ALL=$locale"}],
1040     ['lcl-fmt-3', '--format "--%\'-10f--" 50000',{OUT=>"--50 000    --"},
1041             {ENV=>"LC_ALL=$locale"}],
1042     ['lcl-fmt-4', '--format "--%-10f--" --to=si 5000000',
1043             {OUT=>"--5,0M      --"},
1044             {ENV=>"LC_ALL=$locale"}],
1045     # handle zero/grouping in combination
1046     ['lcl-fmt-5', '--format="%\'06f" 1234',{OUT=>"01 234"},
1047             {ENV=>"LC_ALL=$locale"}],
1048     ['lcl-fmt-6', '--format="%0\'6f" 1234',{OUT=>"01 234"},
1049             {ENV=>"LC_ALL=$locale"}],
1050     ['lcl-fmt-7', '--format="%0\'\'6f" 1234',{OUT=>"01 234"},
1051             {ENV=>"LC_ALL=$locale"}],
1052
1053  );
1054if ($locale ne 'C')
1055  {
1056    # Reset locale to 'C' if LOCALE_FR_UTF8 doesn't output as expected
1057    # as determined by the separate printf program.
1058    open(LOC_NUM, "env LC_ALL=$locale printf \"%'d\" 1234|")
1059      or die "Can't fork command: $!";
1060    my $loc_num = <LOC_NUM>;
1061    close(LOC_NUM) || die "Failed to read grouped number from printf";
1062    if ($loc_num ne '1 234')
1063      {
1064        warn "skipping locale grouping tests as 1234 groups like $loc_num\n";
1065        $locale = 'C';
1066      }
1067  }
1068push @Tests, @Locale_Tests if $locale ne 'C';
1069
1070## Check all valid/invalid suffixes
1071foreach my $suf ( 'A' .. 'Z', 'a' .. 'z' ) {
1072  if ( $suf =~ /^[KMGTPEZYRQ]$/ )
1073    {
1074      push @Tests, ["auto-suf-si-$suf","--from=si --to=si 1$suf",
1075              {OUT=>"1.0$suf"}];
1076      push @Tests, ["auto-suf-iec-$suf","--from=iec --to=iec 1$suf",
1077              {OUT=>"1.0$suf"}];
1078      push @Tests, ["auto-suf-auto-$suf","--from=auto --to=iec 1${suf}i",
1079              {OUT=>"1.0$suf"}];
1080      push @Tests, ["auto-suf-iec-to-ieci-$suf","--from=iec --to=iec-i 1${suf}",
1081              {OUT=>"1.0${suf}i"}];
1082      push @Tests, ["auto-suf-ieci-to-iec-$suf",
1083              "--from=iec-i --to=iec 1${suf}i",{OUT=>"1.0${suf}"}];
1084    }
1085  else
1086    {
1087      push @Tests, ["auto-suf-si-$suf","--from=si --to=si 1$suf",
1088              {ERR=>"$prog: invalid suffix in input: '1${suf}'\n"},
1089              {EXIT=>2}];
1090    }
1091}
1092
1093# Prepend the command line argument and append a newline to end
1094# of each expected 'OUT' string.
1095my $t;
1096
1097Test:
1098foreach $t (@Tests)
1099  {
1100    # Don't fiddle with expected OUT string if there's a nonzero exit status.
1101    foreach my $e (@$t)
1102      {
1103        ref $e eq 'HASH' && exists $e->{EXIT} && $e->{EXIT}
1104          and next Test;
1105      }
1106
1107    foreach my $e (@$t)
1108      {
1109        ref $e eq 'HASH' && exists $e->{OUT}
1110          and $e->{OUT} .= "\n"
1111      }
1112  }
1113
1114# Add test for null-terminated lines (after adjusting the OUT string, above).
1115push @Tests, @NullDelim_Tests;
1116
1117my $save_temps = $ENV{SAVE_TEMPS};
1118my $verbose = $ENV{VERBOSE};
1119
1120my $fail = run_tests ($program_name, $prog, \@Tests, $save_temps, $verbose);
1121exit $fail;
1122