1# $NetBSD: t_fsplit.sh,v 1.7 2017/06/24 11:06:17 kre Exp $
2#
3# Copyright (c) 2007-2016 The NetBSD Foundation, Inc.
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer.
11# 2. Redistributions in binary form must reproduce the above copyright
12#    notice, this list of conditions and the following disclaimer in the
13#    documentation and/or other materials provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
16# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
19# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25# POSSIBILITY OF SUCH DAMAGE.
26#
27
28# The standard
29# http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html
30# explains (section 2.6) that Field splitting should be performed on the
31# result of variable expansions.
32# In particular this means that in ${x-word}, 'word' must be expanded as if
33# the "${x-" and "}" were absent from the input line.
34#
35# So: sh -c 'set ${x-a b c}; echo $#' should give 3.
36# and: sh -c 'set -- ${x-}' echo $#' should give 0
37#
38
39# the implementation of "sh" to test
40: ${TEST_SH:="/bin/sh"}
41
42nl='
43'
44
45check()
46{
47	TEST=$((${TEST} + 1))
48
49	case "$#" in
50	(2)	;;
51	(*)	atf_fail "Internal test error, $# args to check test ${TEST}";;
52	esac
53
54	result=$( ${TEST_SH} -c "unset x; $1" )
55	STATUS="$?"
56
57	# Remove newlines
58	oifs="$IFS"
59	IFS="$nl"
60	result="$(echo $result)"
61	IFS="$oifs"
62
63	# trim the test text in case we use it in a message below
64	case "$1" in
65	????????????????*)
66		set -- "$(expr "$1" : '\(............\).*')..." "$2" ;;
67	esac
68
69	if [ "$2" != "$result" ]
70	then
71		if [ "${STATUS}" = "0" ]
72		then
73		  atf_fail "Test ${TEST} '$1': expected [$2], found [$result]"
74		else
75		  atf_fail \
76	  "TEST ${TEST} '$1' failed ($STATUS): expected [$2], found [$result]"
77		fi
78	elif [ "${STATUS}" != 0 ]
79	then
80		  atf_fail "TEST ${TEST} '$1' failed ($STATUS)"
81	fi
82
83	return 0
84}
85
86atf_test_case for
87for_head() {
88	atf_set "descr" "Checks field splitting in for loops"
89}
90for_body() {
91	unset x
92
93	TEST=0
94	# Since I managed to break this, leave the test in
95	check 'for f in $x; do echo x${f}y; done' ''
96}
97
98atf_test_case default_val
99default_val_head() {
100	atf_set "descr" "Checks field splitting in variable default values"
101}
102default_val_body() {
103	TEST=0
104	# Check that IFS is applied to text from ${x-...} unless it is inside
105	# any set of "..."
106	check 'set -- ${x-a b c};   echo $#'   3
107
108	check 'set -- ${x-"a b" c}; echo $#'   2
109	check 'set -- ${x-a "b c"}; echo $#'   2
110	check 'set -- ${x-"a b c"}; echo $#'   1
111
112	check "set -- \${x-'a b' c}; echo \$#" 2
113	check "set -- \${x-a 'b c'}; echo \$#" 2
114	check "set -- \${x-'a b c'}; echo \$#" 1
115
116	check 'set -- ${x-a\ b c};  echo $#'   2
117	check 'set -- ${x-a b\ c};  echo $#'   2
118	check 'set -- ${x-a\ b\ c}; echo $#'   1
119
120	check 'set -- ${x};        echo $#' 0
121	check 'set -- ${x-};       echo $#' 0
122	check 'set -- ${x-""};     echo $#' 1
123	check 'set -- ""${x};      echo $#' 1
124	check 'set -- ""${x-};     echo $#' 1
125	check 'set -- ""${x-""};   echo $#' 1
126	check 'set -- ${x}"";      echo $#' 1
127	check 'set -- ${x-}"";     echo $#' 1
128	check 'set -- ${x-""}"";   echo $#' 1
129	check 'set -- ""${x}"";    echo $#' 1
130	check 'set -- ""${x-}"";   echo $#' 1
131	check 'set -- ""${x-""}""; echo $#' 1
132
133	check 'for i in ${x-a b c};            do echo "z${i}z"; done' \
134		'zaz zbz zcz'
135	check 'for i in ${x-"a b" c};          do echo "z${i}z"; done' \
136		'za bz zcz'
137	check 'for i in ${x-"a ${x-b c}" d};   do echo "z${i}z"; done' \
138		'za b cz zdz'
139	check 'for i in ${x-a ${x-b c} d};     do echo "z${i}z"; done' \
140		'zaz zbz zcz zdz'
141
142	# I am not sure the first of these two is correct, the rules on
143	# quoting word in ${var-word} are peculiar, and hard to fathom...
144	# It is what the NetBSD shell does, and bash, not the freebsd shell
145	# and not ksh93 (as of Mar 1, 2016, and still in June 2017)
146	# The likely correct interp of the next one is 'za bz zcz zdz'
147
148	# That and the "should be" below are correct as of POSIX 7 TC2
149	# But this is going to change to "unspecified" in POSIX 8
150	# (resolution of bug 221)  so instead of being incorrect (as now)
151	# the NetBSD shell will simply be implementing is version
152	# of unspecified behaviour.  Just beware that shells differ,
153	# a shell that fails this test is not incorrect because of it.
154
155	# should be:    uuuu qqqqqq uuu q uuu   (unquoted/quoted) no nesting.
156	check 'for i in ${x-"a ${x-"b c"}" d}; do echo "z${i}z"; done' \
157		'za b cz zdz'
158	check 'for i in ${x-a ${x-"b c"} d};   do echo "z${i}z"; done' \
159		'zaz zb cz zdz'
160}
161
162atf_test_case replacement_val
163replacement_val_head() {
164	atf_set "descr" "Checks field splitting in variable replacement values"
165}
166replacement_val_body() {
167	TEST=0
168
169	# Check that IFS is applied to text from ${x+...} unless it is inside
170	# any set of "...", or whole expansion is quoted, or both...
171
172	check 'x=BOGUS; set -- ${x+a b c};   echo $#'   3
173
174	check 'x=BOGUS; set -- ${x+"a b" c}; echo $#'   2
175	check 'x=BOGUS; set -- ${x+a "b c"}; echo $#'   2
176	check 'x=BOGUS; set -- ${x+"a b c"}; echo $#'   1
177
178	check "x=BOGUS; set -- \${x+'a b' c}; echo \$#" 2
179	check "x=BOGUS; set -- \${x+a 'b c'}; echo \$#" 2
180	check "x=BOGUS; set -- \${x+'a b c'}; echo \$#" 1
181
182	check 'x=BOGUS; set -- ${x+a\ b c};  echo $#'   2
183	check 'x=BOGUS; set -- ${x+a b\ c};  echo $#'   2
184	check 'x=BOGUS; set -- ${x+a\ b\ c}; echo $#'   1
185
186	check 'x=BOGUS; set -- ${x+};       echo $#' 0
187	check 'x=BOGUS; set -- ${x+""};     echo $#' 1
188	check 'x=BOGUS; set -- ""${x+};     echo $#' 1
189	check 'x=BOGUS; set -- ""${x+""};   echo $#' 1
190	check 'x=BOGUS; set -- ${x+}"";     echo $#' 1
191	check 'x=BOGUS; set -- ${x+""}"";   echo $#' 1
192	check 'x=BOGUS; set -- ""${x+}"";   echo $#' 1
193	check 'x=BOGUS; set -- ""${x+""}""; echo $#' 1
194
195	# verify that the value of $x does not affecty the value of ${x+...}
196	check 'x=BOGUS; set -- ${x+};       echo X$1' X
197	check 'x=BOGUS; set -- ${x+""};     echo X$1' X
198	check 'x=BOGUS; set -- ""${x+};     echo X$1' X
199	check 'x=BOGUS; set -- ""${x+""};   echo X$1' X
200	check 'x=BOGUS; set -- ${x+}"";     echo X$1' X
201	check 'x=BOGUS; set -- ${x+""}"";   echo X$1' X
202	check 'x=BOGUS; set -- ""${x+}"";   echo X$1' X
203	check 'x=BOGUS; set -- ""${x+""}""; echo X$1' X
204
205	check 'x=BOGUS; set -- ${x+};       echo X${1-:}X' X:X
206	check 'x=BOGUS; set -- ${x+""};     echo X${1-:}X' XX
207	check 'x=BOGUS; set -- ""${x+};     echo X${1-:}X' XX
208	check 'x=BOGUS; set -- ""${x+""};   echo X${1-:}X' XX
209	check 'x=BOGUS; set -- ${x+}"";     echo X${1-:}X' XX
210	check 'x=BOGUS; set -- ${x+""}"";   echo X${1-:}X' XX
211	check 'x=BOGUS; set -- ""${x+}"";   echo X${1-:}X' XX
212	check 'x=BOGUS; set -- ""${x+""}""; echo X${1-:}X' XX
213
214	# and validate that the replacement can be used as expected
215	check 'x=BOGUS; for i in ${x+a b c};            do echo "z${i}z"; done'\
216		'zaz zbz zcz'
217	check 'x=BOGUS; for i in ${x+"a b" c};          do echo "z${i}z"; done'\
218		'za bz zcz'
219	check 'x=BOGUS; for i in ${x+"a ${x+b c}" d};   do echo "z${i}z"; done'\
220		'za b cz zdz'
221
222	# see the (extended) comment in the default_val test.  This will be
223	# unspecified, hence we are OK (will be) but expect differences.
224	# also incorrect:        uuuu qqqqqq uuu q uuu
225	check 'x=BOGUS; for i in ${x+"a ${x+"b c"}" d}; do echo "z${i}z"; done'\
226		'za b cz zdz'
227
228	check 'x=BOGUS; for i in ${x+a ${x+"b c"} d};   do echo "z${i}z"; done'\
229		'zaz zb cz zdz'
230	check 'x=BOGUS; for i in ${x+a ${x+b c} d};     do echo "z${i}z"; done'\
231		'zaz zbz zcz zdz'
232}
233
234atf_test_case ifs_alpha
235ifs_alpha_head() {
236	atf_set "descr" "Checks that field splitting works with alphabetic" \
237	                "characters"
238}
239ifs_alpha_body() {
240	unset x
241
242	TEST=0
243	# repeat with an alphabetic in IFS
244	check 'IFS=q; set ${x-aqbqc}; echo $#' 3
245	check 'IFS=q; for i in ${x-aqbqc};            do echo "z${i}z"; done' \
246		'zaz zbz zcz'
247	check 'IFS=q; for i in ${x-"aqb"qc};          do echo "z${i}z"; done' \
248		'zaqbz zcz'
249	check 'IFS=q; for i in ${x-"aq${x-bqc}"qd};   do echo "z${i}z"; done' \
250		'zaqbqcz zdz'
251
252	# this is another almost certainly incorrect expectation
253	# (but again, see comment in default_val test - becoming unspecified.)
254	#                        uu qqqqqq uuu q uu	(quoted/unquoted)
255	check 'IFS=q; for i in ${x-"aq${x-"bqc"}"qd}; do echo "z${i}z"; done' \
256		'zaqbqcz zdz'
257
258	check 'IFS=q; for i in ${x-aq${x-"bqc"}qd};  do echo "z${i}z"; done' \
259		'zaz zbqcz zdz'
260}
261
262atf_test_case quote
263quote_head() {
264	atf_set "descr" "Checks that field splitting works with multi-word" \
265	                "fields"
266}
267quote_body() {
268	unset x
269
270	TEST=0
271	# Some quote propagation checks
272	check 'set "${x-a b c}";   echo $#' 1
273
274	# this is another almost certainly incorrect expectation
275	# (but again, see comment in default_val test - becoming unspecified.)
276	#           qqqq uuu qqq  	(quoted/unquoted)  $1 is a $# is 2
277	check 'set "${x-"a b" c}"; echo $1' 'a b c'
278
279	check 'for i in "${x-a b c}"; do echo "z${i}z"; done' 'za b cz'
280}
281
282atf_test_case dollar_at
283dollar_at_head() {
284	atf_set "descr" "Checks that field splitting works when expanding" \
285	                "\$@"
286}
287dollar_at_body() {
288	unset x
289
290	TEST=0
291	# Check we get "$@" right
292
293	check 'set --;        for i in x"$@"x;  do echo "z${i}z"; done' 'zxxz'
294	check 'set a;         for i in x"$@"x;  do echo "z${i}z"; done' 'zxaxz'
295	check 'set a b;       for i in x"$@"x;  do echo "z${i}z"; done' 'zxaz zbxz'
296
297	check 'set --;        for i;            do echo "z${i}z"; done' ''
298	check 'set --;        for i in $@;      do echo "z${i}z"; done' ''
299	check 'set --;        for i in "$@";    do echo "z${i}z"; done' ''
300	# atf_expect_fail "PR bin/50834"
301	check 'set --;        for i in ""$@;    do echo "z${i}z"; done' 'zz'
302	# atf_expect_pass
303	check 'set --;        for i in $@"";    do echo "z${i}z"; done' 'zz'
304	check 'set --;        for i in ""$@"";  do echo "z${i}z"; done' 'zz'
305	check 'set --;        for i in """$@";  do echo "z${i}z"; done' 'zz'
306	check 'set --;        for i in "$@""";  do echo "z${i}z"; done' 'zz'
307	check 'set --;        for i in """$@""";do echo "z${i}z"; done' 'zz'
308
309	check 'set "";        for i;            do echo "z${i}z"; done' 'zz'
310	check 'set "";        for i in "$@";    do echo "z${i}z"; done' 'zz'
311	check 'set "" "";     for i;            do echo "z${i}z"; done' 'zz zz'
312	check 'set "" "";     for i in "$@";    do echo "z${i}z"; done' 'zz zz'
313	check 'set "" "";     for i in $@;      do echo "z${i}z"; done' ''
314
315	check 'set "a b" c;   for i;            do echo "z${i}z"; done' \
316		'za bz zcz'
317	check 'set "a b" c;   for i in "$@";    do echo "z${i}z"; done' \
318		'za bz zcz'
319	check 'set "a b" c;   for i in $@;      do echo "z${i}z"; done' \
320		'zaz zbz zcz'
321	check 'set " a b " c; for i in "$@";    do echo "z${i}z"; done' \
322		'z a b z zcz'
323
324	check 'set a b c;     for i in "$@$@";  do echo "z${i}z"; done' \
325		'zaz zbz zcaz zbz zcz'
326	check 'set a b c;     for i in "$@""$@";do echo "z${i}z"; done' \
327		'zaz zbz zcaz zbz zcz'
328}
329
330atf_test_case ifs
331ifs_head() {
332	atf_set "descr" "Checks that IFS correctly configures field" \
333	                "splitting behavior"
334}
335ifs_body() {
336	unset x
337
338	TEST=0
339	# Some IFS tests
340	check 't="-- ";    IFS=" ";  set $t; IFS=":"; r="$*"; IFS=; echo $# $r' '0'
341	check 't=" x";     IFS=" x"; set $t; IFS=":"; r="$*"; IFS=; echo $# $r' '1'
342	check 't=" x ";    IFS=" x"; set $t; IFS=":"; r="$*"; IFS=; echo $# $r' '1'
343	check 't=axb;      IFS="x";  set $t; IFS=":"; r="$*"; IFS=; echo $# $r' '2 a:b'
344	check 't="a x b";  IFS="x";  set $t; IFS=":"; r="$*"; IFS=; echo $# $r' '2 a : b'
345	check 't="a xx b"; IFS="x";  set $t; IFS=":"; r="$*"; IFS=; echo $# $r' '3 a :: b'
346	check 't="a xx b"; IFS="x "; set $t; IFS=":"; r="$*"; IFS=; echo $# $r' '3 a::b'
347	# A recent 'clarification' means that a single trailing IFS non-whitespace
348	# doesn't generate an empty parameter
349	check 't="xax";  IFS="x";     set $t; IFS=":"; r="$*"; IFS=; echo $# $r' '2 :a'
350	check 't="xax "; IFS="x ";   set $t; IFS=":"; r="$*"; IFS=; echo $# $r' '2 :a'
351	# Verify that IFS isn't being applied where it shouldn't be.
352	check 'IFS="x";             set axb; IFS=":"; r="$*"; IFS=; echo $# $r' '1 axb'
353}
354
355atf_test_case var_length
356var_length_head() {
357	atf_set "descr" "Checks that field splitting works when expanding" \
358	                "a variable's length"
359}
360var_length_body() {
361	TEST=0
362
363	long=12345678123456781234567812345678
364	long=$long$long$long$long
365	export long
366	unset x
367
368	# first test that the test method works...
369	check 'set -u; : ${long}; echo ${#long}' '128'
370
371	# Check that we apply IFS to ${#var}
372	check 'echo ${#long}; IFS=2; echo ${#long}; set 1 ${#long};echo $#' \
373		'128 1 8 3'
374	check 'IFS=2; set ${x-${#long}};   IFS=" "; echo $* $#'     '1 8 2'
375	check 'IFS=2; set ${x-"${#long}"}; IFS=" "; echo $* $#'     '128 1'
376	check 'IFS=2; set "${x-${#long}}"; IFS=" "; echo $* $#'     '128 1'
377	check 'IFS=2; set ${x-${#long}};   :      ; echo $* $#'     '1 8 '
378	check 'IFS=2; set ${x-${#long}};   :      ; echo $* "$#"'   '1 8 2'
379	check 'IFS=2; set ${x-${#long}};   :      ; echo "$*" "$#"' '128 2'
380	check 'IFS=2; set ${x-${#long}};   :      ; echo "$@" "$#"' '1 8 2'
381}
382
383atf_test_case split_arith
384split_arith_head() {
385	atf_set "descr" "Checks that field splitting works when expanding" \
386	                "the results from arithmetic"
387}
388split_arith_body() {
389	TEST=0
390
391	# Check that we apply IFS to $(( expr ))
392
393	# Note: we do not check the actual arithmetic operations here
394	# (there is a separate test just for that) so we just enter
395	# the "answer" inside $(( )) ... also makes it easier to visualise
396
397	check 'IFS=5; echo $(( 123456789 ))'	'1234 6789'
398	check 'IFS=5; echo "$(( 123456789 ))"'	'123456789'
399	check 'IFS=37; echo $(( 123456789 ))'	'12 456 89'
400	check 'IFS=37; echo "$(( 123456789 ))"'	'123456789'
401	check 'IFS=159; echo $(( 123456789 ))'	' 234 678'
402
403	check 'IFS=5; set -- $(( 123456789 )); echo $#: $1 $2 $3 $4' \
404		'2: 1234 6789'
405	check 'IFS=5; set -- "$(( 123456789 ))"; echo $#: $1 $2 $3 $4' \
406		'1: 1234 6789'		# go ahead: explain it!
407	check 'IFS=5; set -- "$(( 123456789 ))"; echo "$#: $1 $2 $3 $4"' \
408		'1: 123456789   '	# ah!
409
410	check 'IFS=37; set -- $(( 123456789 )); echo $#: $1 $2 $3 $4' \
411		' : 12 456 89'		# Tricky!
412	check 'IFS=5; set -- $(( 123456789 )); echo $#: $*' \
413		'2: 1234 6789'
414	check 'IFS=47; set -- $(( 123456789 )); echo $#: $*' \
415		'3: 123 56 89'
416	check 'IFS=5; set -- $(( 123456789 )); echo "$#: $*"' \
417		'2: 123456789'
418	check 'IFS=37; set -- $(( 123456789 )); echo "$#: $*"' \
419		'3: 123456389'	# [sic]
420	check 'IFS=5; set -- $(( 123456789 )); echo $#: $@' \
421		'2: 1234 6789'
422	check 'IFS=47; set -- $(( 123456789 )); echo $#: $@' \
423		'3: 123 56 89'
424	check 'IFS=5; set -- $(( 123456789 )); echo "$#: $@"' \
425		'2: 1234 6789'
426	check 'IFS=37; set -- $(( 123456789 )); echo "$#: $*"' \
427		'3: 123456389'	# [sic]
428
429	check 'IFS=1; set -- $(( 1111 )); echo "$#:" $*'	'4:   '
430	check 'IFS=" 1"; set -- $(( 1231231231 )); echo "$#: $*"' \
431		'4:  23 23 23'
432	check 'IFS="1 "; set -- $(( 1231231231 )); echo "$#: $*"' \
433		'4: 123123123'
434
435	check 'IFS=5; echo 5$(( 123456789 ))5'		'51234 67895'
436	check 'IFS=37; echo 73$(( 123456789 ))37'	'7312 456 8937'
437	check 'IFS=159; echo 11$(( 123456789 ))95'	'11 234 678 95'
438	check 'IFS="159 "; echo 11$(( 123456789 ))95'	'11 234 678 95'
439	check 'IFS="159 "; echo 11$(( 11234567899 ))95'	'11  234 678  95'
440}
441
442atf_init_test_cases() {
443	atf_add_test_case for
444	atf_add_test_case default_val
445	atf_add_test_case replacement_val
446	atf_add_test_case ifs_alpha
447	atf_add_test_case quote
448	atf_add_test_case dollar_at
449	atf_add_test_case ifs
450	atf_add_test_case var_length
451	atf_add_test_case split_arith
452}
453