Module Name:    src
Committed By:   kre
Date:           Tue Jul 10 06:49:30 UTC 2018

Modified Files:
        src/distrib/sets/lists/tests: mi
        src/tests/bin/sh: Makefile
Added Files:
        src/tests/bin/sh: t_patterns.sh

Log Message:
Add tests for pattern matching (filename expansion (glob), case statement
patterns, and variable expansion substring matching)

Currently (2018-07-10) all 3 sub-tests fail (sh bugs...)
Expect to see 14 (of 261) case matching sub-tests fail, 11 (of 167) filename
expansion (glob) sub-tests fail, and 6 (of 87) var substring sub-tests fail.

Also expect those numbers to reduce as sh bugs are fixed.


To generate a diff of this commit:
cvs rdiff -u -r1.788 -r1.789 src/distrib/sets/lists/tests/mi
cvs rdiff -u -r1.12 -r1.13 src/tests/bin/sh/Makefile
cvs rdiff -u -r0 -r1.1 src/tests/bin/sh/t_patterns.sh

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/distrib/sets/lists/tests/mi
diff -u src/distrib/sets/lists/tests/mi:1.788 src/distrib/sets/lists/tests/mi:1.789
--- src/distrib/sets/lists/tests/mi:1.788	Wed Jun 20 03:51:27 2018
+++ src/distrib/sets/lists/tests/mi	Tue Jul 10 06:49:29 2018
@@ -1,4 +1,4 @@
-# $NetBSD: mi,v 1.788 2018/06/20 03:51:27 maya Exp $
+# $NetBSD: mi,v 1.789 2018/07/10 06:49:29 kre Exp $
 #
 # Note: don't delete entries from here - mark them as "obsolete" instead.
 #
@@ -1277,6 +1277,7 @@
 ./usr/tests/bin/sh/t_fsplit			tests-bin-tests		compattestfile,atf
 ./usr/tests/bin/sh/t_here			tests-bin-tests		compattestfile,atf
 ./usr/tests/bin/sh/t_option			tests-bin-tests		compattestfile,atf
+./usr/tests/bin/sh/t_patterns			tests-bin-tests		compattestfile,atf
 ./usr/tests/bin/sh/t_redir			tests-bin-tests		compattestfile,atf
 ./usr/tests/bin/sh/t_redircloexec		tests-bin-tests		compattestfile,atf
 ./usr/tests/bin/sh/t_set_e			tests-bin-tests		compattestfile,atf

Index: src/tests/bin/sh/Makefile
diff -u src/tests/bin/sh/Makefile:1.12 src/tests/bin/sh/Makefile:1.13
--- src/tests/bin/sh/Makefile:1.12	Sat May 20 16:35:55 2017
+++ src/tests/bin/sh/Makefile	Tue Jul 10 06:49:29 2018
@@ -1,4 +1,4 @@
-# $NetBSD: Makefile,v 1.12 2017/05/20 16:35:55 kre Exp $
+# $NetBSD: Makefile,v 1.13 2018/07/10 06:49:29 kre Exp $
 #
 
 .include <bsd.own.mk>
@@ -15,6 +15,7 @@ TESTS_SH+=	t_expand
 TESTS_SH+=	t_fsplit
 TESTS_SH+=	t_here
 TESTS_SH+=	t_option
+TESTS_SH+=	t_patterns
 TESTS_SH+=	t_redir
 TESTS_SH+=	t_redircloexec
 TESTS_SH+=	t_set_e

Added files:

Index: src/tests/bin/sh/t_patterns.sh
diff -u /dev/null src/tests/bin/sh/t_patterns.sh:1.1
--- /dev/null	Tue Jul 10 06:49:30 2018
+++ src/tests/bin/sh/t_patterns.sh	Tue Jul 10 06:49:29 2018
@@ -0,0 +1,784 @@
+# $NetBSD: t_patterns.sh,v 1.1 2018/07/10 06:49:29 kre Exp $
+#
+# Copyright (c) 2018 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# the implementation of "sh" to test
+: ${TEST_SH:=/bin/sh}
+
+#
+# This file tests pattern matching (glob)
+#
+# Three forms:
+#	standard filename expansion (echo *.c)
+#	case statements (case word in (*.c) ...;;)
+#	var expansions with substring matching ${var%*.c}
+#
+# Note: the emphasis here is on testing the various possible patterns,
+# not that case statements, or var expansions (etc) work in general.
+
+### Helper functions
+
+nl='
+'
+reset()
+{
+	TEST_NUM=0
+	TEST_FAILURES=''
+	TEST_FAIL_COUNT=0
+	TEST_ID="$1"
+}
+
+# Test run & validate.
+#
+#	$1 is the command to run (via sh -c)
+#	$2 is the expected output (with any \n's in output replaced by spaces)
+#	$3 is the expected exit status from sh
+#
+# Stderr is exxpected to be empty, unless the expected exit code ($3) is != 0
+# in which case some message there is expected (and nothing is a failure).
+# When non-zero exit is expected, we note a different (non-zero) value
+# observed, but do not fail the test because of that.
+
+check()
+{
+	fail=false
+	# Note TEMP_FILE must not be in the current directory (or nearby).
+	TEMP_FILE=$( mktemp /tmp/OUT.XXXXXX )
+	TEST_NUM=$(( $TEST_NUM + 1 ))
+	MSG=
+
+	# our local shell (ATF_SHELL) better do quoting correctly...
+	# some of the tests expect us to expand $nl internally...
+	CMD="$1"
+
+	result="$( ${TEST_SH} -c "${CMD}" 2>"${TEMP_FILE}" )"
+	STATUS=$?
+
+	if [ "${STATUS}" -ne "$3" ]; then
+		MSG="${MSG}${MSG:+${nl}}[$TEST_NUM]"
+		MSG="${MSG} expected exit code $3, got ${STATUS}"
+
+		# don't actually fail just because of wrong exit code
+		# unless we either expected, or received "good"
+		# or something else is detected as incorrect as well.
+		case "$3/${STATUS}" in
+		(*/0|0/*) fail=true;;
+		esac
+	fi
+
+	if [ "$3" -eq 0 ]; then
+		if [ -s "${TEMP_FILE}" ]; then
+			MSG="${MSG}${MSG:+${nl}}[$TEST_NUM]"
+			MSG="${MSG} Messages produced on stderr unexpected..."
+			MSG="${MSG}${nl}$( cat "${TEMP_FILE}" )"
+			fail=true
+		fi
+	else
+		if ! [ -s "${TEMP_FILE}" ]; then
+			MSG="${MSG}${MSG:+${nl}}[$TEST_NUM]"
+			MSG="${MSG} Expected messages on stderr,"
+			MSG="${MSG} nothing produced"
+			fail=true
+		fi
+	fi
+	rm -f "${TEMP_FILE}"
+
+	case "${result}" in
+	(*[!0-9" 	$nl"]*)
+		# A word of some kind: at least 1 char that is not digit or wsp
+		# Remove newlines (use local shell for this)
+		result="$(
+			set -f
+			IFS="$nl"
+			set -- $result
+			IFS=' '
+			printf '%s' "$*"
+		)"
+		;;
+	(*[0-9]*)
+		# a numeric result, return just the number, trim whitespace
+		result=$(( ${result} ))
+		;;
+	(*)
+		# whitespace only, or empty string: just leave it as is
+		;;
+	esac
+
+	if [ "$2" != "${result}" ]
+	then
+		MSG="${MSG}${MSG:+${nl}}[$TEST_NUM]"
+		MSG="${MSG} Expected output '$2', received '$result'"
+		fail=true
+	fi
+
+	if $fail
+	then
+		MSG="${MSG}${MSG:+${nl}}[$TEST_NUM]"
+		MSG="${MSG} Full command: <<${CMD}>>"
+	fi
+
+	$fail && test -n "$TEST_ID" && {
+		TEST_FAILURES="${TEST_FAILURES}${TEST_FAILURES:+${nl}}"
+		TEST_FAILURES="${TEST_FAILURES}${TEST_ID}[$TEST_NUM]:"
+		TEST_FAILURES="${TEST_FAILURES} Test of '$1' failed.";
+		TEST_FAILURES="${TEST_FAILURES}${nl}${MSG}"
+		TEST_FAIL_COUNT=$(( $TEST_FAIL_COUNT + 1 ))
+		return 0
+	}
+	$fail && atf_fail "Test[$TEST_NUM] failed: $(
+	    # ATF does not like newlines in messages, so change them...
+		    printf '%s' "${MSG}" | tr '\n' ';'
+	    )"
+	return 0
+}
+
+results()
+{
+	test -n "$1" && atf_expect_fail "$1"
+
+	test -z "${TEST_ID}" && return 0
+	test -z "${TEST_FAILURES}" && return 0
+
+	echo >&2 "=========================================="
+	echo >&2 "While testing '${TEST_ID}'"
+	echo >&2 " - - - - - - - - - - - - - - - - -"
+	echo >&2 "${TEST_FAILURES}"
+
+	atf_fail \
+ "Test ${TEST_ID}: $TEST_FAIL_COUNT (of $TEST_NUM) subtests failed - see stderr"
+}
+
+####### End helpers
+
+atf_test_case filename_expansion
+filename_expansion() {
+	atf_set descr "Test correct operation of filename expansion"
+}
+filename_expansion_body() {
+	atf_require_prog mktemp
+	atf_require_prog wc
+	atf_require_prog mv
+	atf_require_prog rm
+	atf_require_prog mkdir
+
+	reset filename_expansion
+
+	# First create a known set of filenames to match against
+
+	# Note: This creates almost 17000 files/directories, so
+	# needs at least that many free inodes (only space consumed
+	# is for the directory contents, with a 1K frag size, it
+	# should be about 1.2MiB).  Switching to making links would
+	# save inodes, but would require running "ln" many times, so
+	# would be a lot slower.
+
+	# This should work on a case insensitive, but preserving,
+	# filesystem - but case sensitive filesystems are preferred.
+
+	D=$(mktemp -d "DIR.$$.XXXXXX") || atf_fail "mktemp -d failed"
+	cd "${D}" || atf_fail "cd to temp dir '$D' failed"
+
+	# we need another level of directory, so we know what
+	# files to expect in ".." (ie: in $D) - only ".D".
+	mkdir .D && cd .D || atf_fail "failed to make or enter .D in $D"
+
+	> Xx || atf_fail "Unable to make files in temporary directory"
+	case "$( printf '%s\n' *)" in
+		(Xx) rm Xx || atf_fail "Unable to delete file";;
+		(\*) atf_fail "Created file vanished";;
+		(xx|XX|xX) atf_skip "Case preserving filesystem required";;
+		(*) atf_fail "Unexpected file expansion for '*'";;
+	esac
+
+	# from here on we make files/directories that we will be
+	# using in the tests.
+
+	# CAUTION: Change *any* of this and the expected results from the
+	# tests will all need verifying and updating as well.
+
+	mkdir D || atf_fail "mkdir D failed"
+
+	for F in a b c e V W X Y 1 2 3 4 5 \\ \] \[ \* \? \- \! \^ \| \# \' \"
+	do
+		> "${F}"
+		> ".${F}"
+		> "${F}.${F}"
+		> "${F}-${F}"
+		> "${F}${F}${F}${F}"
+		> "x${F}z"
+		> ab"${F}"yz
+		> "m${F}n${F}p${F}q"
+
+		> "D/${F}"
+		> "D/.${F}"
+		> "D/${F}${F}${F}${F}"
+
+		mkdir "D${F}" || atf_fail "mkdir 'D${F}' failed"
+		mkdir ".D${F}" || atf_fail "mkdir '.D${F}' failed"
+
+		for G in a b c e W X Y 0 2 4 6 \\ \] \[ \* \? \- \! \^ \| \#
+		do
+			> "${F}${G}"
+			> "${F}${G}${G}"
+			> "${F}${G}${F}"
+			> "${G}${F}${G}"
+			> "${F}${G}${G}${F}"
+			> "${G}${F}${F}${G}"
+			> "${F}.${G}"
+			> "${F}${G}.${G}${G}"
+			> "${F}${G}${F}${G}.${G}"
+			> "x${F}${G}y"
+			> "${F}z${G}"
+			> "${G}zz${F}"
+			> "${G}+${G}"
+
+			> "D${F}/${G}"
+			> "D${F}/.${G}"
+			> "D${F}/${G}${F}${G}"
+			> "D${F}/.${G}${F}${G}"
+
+			> ".D${F}/${G}"
+			> ".D${F}/.${G}"
+			> ".D${F}/${G}${F}${G}"
+			> ".D${F}/.${G}${F}${G}"
+
+			mkdir "D${F}/D${G}" "D${F}/D${F}${G}" ||
+				atf_fail \
+			    "subdir mkdirs failed D${F}/D${G} D${F}/D${F}${G}"
+
+			> "D${F}/D${G}/${G}"
+			> "D${F}/D${G}.${G}"
+			> "D${F}/D${G}/${F}${G}"
+			> "D${F}/D${G}/${G}${F}${G}"
+			> "D${F}/D${G}/.${G}${F}${G}"
+			> "D${F}/D${G}/.${G}${F}${G}"
+
+			> "D${F}/D${F}${G}/${G}"
+			> "D${F}/D${F}${G}.${G}"
+			> "D${F}/D${F}${G}/${G}${F}"
+			> "D${F}/D${F}${G}/${G}${G}${F}"
+			> "D${F}/D${F}${G}/.${F}${F}${G}"
+			> "D${F}/D${F}${G}/.${G}${F}${F}"
+
+		done
+	done
+
+	# Debug hooks ... run with environment var set to filename
+
+	case "${ATF_TEST_SAVE_FILENAMES}" in
+	'')	;;
+	/*)	ls -R >"${ATF_TEST_SAVE_FILENAMES}" ;;
+	*)	ls -R >"${TMPDIR:-/tmp}/${ATF_TEST_SAVE_FILENAMES}" ;;
+	esac
+	case "${ATF_TEST_SAVE_FILES}" in
+	'')	;;
+	/*)	(cd ../..; tar cf "${ATF_TEST_SAVE_FILES}" D) ;;
+	*)	(cd ../..; tar cf "${TMPDIR:-/tmp}/${ATF_TEST_SAVE_FILES}" D) ;;
+	esac
+
+	# Now we have lots of files, try some matching
+
+	# First just check that "printf | wc -l" works properly...
+	check 'printf "%s\n" 1 2 3 | wc -l'		'3'	0	#1
+
+	# Next a whole bunch of elementary patterns
+	check 'printf "%s\n" ab* | wc -l'		'31'	0
+	check 'printf "%s\n" x*y | wc -l'		'525'	0
+	check 'printf "%s\n" * | wc -l'			'5718'	0
+	check 'printf "%s\n" ? | wc -l'			'26'	0	#5
+	check 'printf "%s\n" ?? | wc -l'		'550'	0
+	check 'printf "%s\n" ??? | wc -l'		'2297'	0
+	check 'printf "%s\n" ???? | wc -l'		'1745'	0
+	check 'printf "%s\n" ????? | wc -l'		'550'	0
+
+	check 'printf "%s\n" ?????? | wc -l'		'525'	0	#10
+	check 'printf "%s\n" ??????? | wc -l'		'25'	0
+	check 'printf "%s\n" ???????? | wc -l'		'1'	0
+	check 'printf "%s\n" ????????'			'????????'	0
+	check 'printf "%s\n" m* | wc -l'		'25'	0
+	check 'printf "%s\n" -* | wc -l'		'206'	0	#15
+	check 'printf "%s\n" *- | wc -l'		'227'	0
+	check 'printf "%s\n" -? | wc -l'		'21'	0
+	check 'printf "%s\n" ?- | wc -l'		'26'	0
+	check 'printf "%s\n" [ab] | wc -l'		'2'	0
+
+	check 'printf "%s\n" [ab]* | wc -l'		'437'	0	#20
+	check 'printf "%s\n" [A-Z]* | wc -l'		'815'	0
+	check 'printf "%s\n" [0-4]* | wc -l'		'830'	0
+	check 'printf "%s\n" [-04]* | wc -l'		'488'	0
+	check 'printf "%s\n" [40-]* | wc -l'		'488'	0
+	check 'printf "%s\n" *[0-9] | wc -l'		'1057'	0	#25
+	check 'printf "%s\n" *[0-9]* | wc -l'		'2109'	0
+	check 'printf "%s\n" ?[0-9]* | wc -l'		'855'	0
+	check 'printf "%s\n" ?[0-9]? | wc -l'		'270'	0
+	check 'printf "%s\n" *[0-9]? | wc -l'		'750'	0
+
+	check 'printf "%s\n" [a-c][0-9]? | wc -l'	'33'	0	#30
+	check 'printf "%s\n" [[:alpha:]] | wc -l'	'9'	0
+	check 'printf "%s\n" [[:alpha:][:digit:]] | wc -l' '14'	0
+	check 'printf "%s\n" [[:alpha:]][[:digit:]] | wc -l' '37' 0
+	check								    \
+	   'printf "%s\n" [[:alpha:][:digit:]][[:alpha:][:digit:]] | wc -l' \
+							'156'	0
+	check 'printf "%s\n" D*/*a | wc -l'		'152'	0	#35
+	check 'printf "%s\n" D?/*a | wc -l'		'150'	0
+	check 'printf "%s\n" D*/?a | wc -l'		'25'	0
+	check 'printf "%s\n" D?/?a | wc -l'		'25'	0
+	check 'printf "%s\n" */*a | wc -l'		'152'	0
+
+	check 'printf "%s\n" [A-Z]*/*a | wc -l'		'152'	0	#40
+	check 'printf "%s\n" ??/*a | wc -l'		'150'	0
+	check 'printf "%s\n" .*/*a | wc -l'		'277'	0
+	check 'printf "%s\n" .?*/*a | wc -l'		'50'	0
+	check 'printf "%s\n" *-/-* | wc -l'		'2'	0
+	check 'printf "%s\n" *-/-*'		'D-/- D-/---'	0	#45
+
+	# now some literal magic chars
+	check 'printf "%s\n" \?* | wc -l'		'206'	0
+	check 'printf "%s\n" *\?* | wc -l'		'471'	0
+	check 'printf "%s\n" \*? | wc -l'		'21'	0
+	check 'printf "%s\n" \** | wc -l'		'206'	0
+
+	check 'printf "%s\n" *\?* | wc -l'		'471'	0	#50
+	check 'printf "%s\n" \[?] | wc -l'		'3'	0
+	check 'printf "%s\n" \[?]'		'[.] []] [z]'	0
+	check 'printf "%s\n" *\[* | wc -l'		'471'	0
+	check 'printf "%s\n" \?\?* | wc -l'		'5'	0
+	check 'printf "%s\n" \?\?*'	'?? ??.?? ??? ???? ????.?' 0	#55
+	check 'printf "%s\n" [A\-C]* | wc -l'		'206'	0
+	check 'printf "%s\n" [-AC]* | wc -l'		'206'	0
+	check 'printf "%s\n" [CA-]* | wc -l'		'206'	0
+	check 'printf "%s\n" [A\]-]? | wc -l'		'42'	0
+
+	check 'printf "%s\n" []A\-]? | wc -l'		'42'	0	#60
+	check 'printf "%s\n" []A-]? | wc -l'		'42'	0
+	check 'printf "%s\n" \\* | wc -l'		'206'	0
+	check 'printf "%s\n" [[-\]]?\?* | wc -l'	'12'	0
+	check 'printf "%s\n" []\\[]?\? | wc -l'		'9'	0
+	check 'printf "%s\n" *\\\\ | wc -l'		'52'	0	#65
+	check 'printf "%s\n" [*][?]* | wc -l'		'6'	0
+	check 'printf "%s\n" "*?"* | wc -l'		'6'	0
+	check "printf '%s\\n' '\\'*\\\\ | wc -l"	'61'	0
+	check 'printf "%s\n" ["a-b"]* | wc -l'		'643'	0
+
+	check 'printf "%s\n" ["A-C"]z[[] | wc -l'	'1'	0	#70
+	check 'printf "%s\n" ["A-C"]z[[]'		'-z['	0
+	check 'printf "%s\n" ?"??"* | wc -l'		'54'	0
+	check 'printf "%s\n" \??\?* | wc -l'		'52'	0
+	check 'printf "%s\n" [?][\?]* | wc -l'		'5'	0
+	check 'printf "%s\n" [?][\?]*'	'?? ??.?? ??? ???? ????.?' 0	#75
+	check 'printf "%s\n" [!ab] | wc -l'		'24'	0
+	check 'printf "%s\n" [!ab]* | wc -l'		'5281'	0
+	check 'printf "%s\n" [!A-D]* | wc -l'		'5692'	0
+	check 'printf "%s\n" [!0-3]* | wc -l'		'5094'	0
+
+	check 'printf "%s\n" [!-03]* | wc -l'		'5265'	0	#80
+	check 'printf "%s\n" [!30-]* | wc -l'		'5265'	0
+	check 'printf "%s\n" [!0\-3]* | wc -l'		'5265'	0
+	check 'printf "%s\n" [\!0-3]* | wc -l'		'830'	0
+	check 'printf "%s\n" [0-3!]* | wc -l'		'830'	0
+	check 'printf "%s\n" [0!-3]* | wc -l'		'1790'	0	#85
+	check 'printf "%s\n" *[!0-3] | wc -l'		'5156'	0
+	check 'printf "%s\n" *[!0-3]* | wc -l'		'5680'	0
+	check 'printf "%s\n" ?[!0-3]* | wc -l'		'5231'	0
+	check 'printf "%s\n" ?[!0-3]? | wc -l'		'2151'	0
+
+	check 'printf "%s\n" *[!0-3]? | wc -l'		'5284'	0	#90
+	check 'printf "%s\n" [!a-c][!0-3]? | wc -l'	'1899'	0
+	check 'printf "%s\n" [![:alpha:]] | wc -l'	'17'	0
+	check 'printf "%s\n" [![:alpha:][:digit:]] | wc -l' '12' 0
+	check 'printf "%s\n" [![:alpha:]][[:digit:]] | wc -l'	'68' 0
+	check 'printf "%s\n" [[:alpha:]][![:digit:]] | wc -l'	'156' 0	#95
+	check 'printf "%s\n" [![:alpha:]][![:digit:]] | wc -l'	'289' 0
+	check 'printf "%s\n" [!A-Z]*/*a | wc -l'	'1'	0
+	check 'printf "%s\n" [!A-Z]*/*a'	'[!A-Z]*/*a'	0
+	check 'printf "%s\n" [!A\-D]* | wc -l'		'5486'	0
+
+	check 'printf "%s\n" [!-AD]* | wc -l'		'5486'	0	#100
+	check 'printf "%s\n" [!DA-]* | wc -l'		'5486'	0
+	check 'printf "%s\n" [!A\]-]? | wc -l'		'508'	0
+	check 'printf "%s\n" [!]A\-]? | wc -l'		'508'	0
+	check 'printf "%s\n" [!]A-]? | wc -l'		'508'	0
+	check 'printf "%s\n" [![-\]]?\?* | wc -l'	'164'	0	#105
+	check 'printf "%s\n" [!]\\[]?\? | wc -l'	'93'	0
+	check 'printf "%s\n" [!*][?]* | wc -l'		'171'	0
+	check 'printf "%s\n" [*][!?]* | wc -l'		'199'	0
+	check 'printf "%s\n" [!*][!?]* | wc -l'		'5316'	0
+
+	check 'printf "%s\n" [!"a-b"]* | wc -l'		'5075'	0	#110
+	check 'printf "%s\n" ["!a-b"]* | wc -l'		'849'	0
+	check 'printf "%s\n" [!"A-D"]z[[] | wc -l'	'24'	0
+	check 'printf "%s\n" ["!A-D"]z[[] | wc -l'	'2'	0
+	check 'printf "%s\n" ["!A-D"]z[[]'	'!z[ -z['	0
+	check 'printf "%s\n" ["A-D"]z[![] | wc -l'	'20'	0	#115
+	check 'printf "%s\n" [!"A-D"]z[![] | wc -l'	'480'	0
+	check 'printf "%s\n" ["!A-D"]z[![] | wc -l'	'40'	0
+	check 'printf "%s\n" [!?][\?]* | wc -l'		'172'	0
+	check 'printf "%s\n" [?][!\?]* | wc -l'		'200'	0
+
+	check 'printf "%s\n" [!?][!\?]* | wc -l'	'5315'	0	#120
+	check 'printf "%s\n" [!?][?!]* | wc -l'		'343'	0
+	check 'printf "%s\n" [?][\?!]* | wc -l'		'11'	0
+	check "printf '%s\\n' [\']*[!#] | wc -l"	'164'	0
+	check 'printf "%s\n" [\"]*[\|] | wc -l'		'6'	0
+	check 'printf "%s\n" [\"]*[\|]' '".| "z| "| "|"|.| "|.|| "||' 0	#125
+	check "printf '%s\\n' '\"['* | wc -l"		'6'	0
+	check "printf '%s\\n' '\"['*" '"[ "[" "["[.[ "[.[[ "[[ "[["' 0
+
+	# Now test cases where the pattern is the result of a
+	# variable expansion (will assume, for now, that cmdsub & arith
+	# work the same way, so omit tests using those)
+	# we need to check both unquoted & quoted var expansions,
+	# expansions that result from ${xxx-xxx} and ${xxx%yyy}
+	# and expansions that form just part of the eventual pattern.
+
+	check 'var="x*y";printf "%s\n" ${var} | wc -l'	'525'	0
+	check 'var="[a-e]?[0-9]";printf "%s\n" ${var} | wc -l' '48' 0
+
+	check 'var="[a-e]?.*";printf "%s\n" ${var} | wc -l' '84' 0	#130
+	check 'var="[a-e]\?.*";printf "%s\n" ${var} | wc -l' '4' 0
+	check 'var="[a-e]\?.*";printf "%s\n" ${var}' 'a?.?? b?.?? c?.?? e?.??' 0
+
+	# and if you're looking for truly weird...
+
+	check 'set -- a b; IFS=\?; printf "%s\n" "$*" | wc -l' '1' 0
+	check 'set -- a b; IFS=\?; printf "%s\n" "$*"'	'a?b'	0
+	check 'set -- a b; IFS=\?; printf "%s\n" $* | wc -l' '2' 0 #boring #135
+	check 'set -- a b; IFS=\?; var=$*; unset IFS; printf "%s\n" ${var}' \
+						'a.b abb azb'	0
+	check 'set -- a b; IFS=\?; var=$*; unset IFS; printf "%s\n" "${var}"' \
+						'a?b'	0
+	check 'set -- a \?; IFS=\\; printf "%s\n" "$*"'	'a\?'	0
+	check 'set -- a \?; IFS=\\; var=$*; unset IFS; printf "%s\n" "${var}"' \
+						'a\?'	0
+
+	check 'set -- a \?; IFS=\\; var=$*; unset IFS; printf "%s\n" ${var}' \
+						'a?'	0		#140
+	mv 'a?' 'a@'
+	check 'set -- a \?; IFS=\\; var=$*; unset IFS; printf "%s\n" ${var}' \
+						'a\?'	0
+	mv 'a@' 'a?'
+
+	# This is unspecified by POSIX, but everyone (sane) does it this way
+	check 'printf "%s\n"  D*[/*] | wc -l'		'6'	0
+	check 'printf "%s\n"  D*[\/*] | wc -l'		'6'	0
+	check 'printf "%s\n"  D*\[/*] | wc -l'		'6'	0
+	check 'printf "%s\n"  D*\[\/*] | wc -l'		'6'	0	#145
+	check 'printf "%s\n"  D*[/*]' \
+		'D[/D[] D[/D[].] D[/D] D[/D].] D[/] D[/][]'	0
+
+	# '^' as the first char in a bracket expr is unspecified by POSIX,
+	# but for compat with REs everyone (sane) makes it the same as !
+	check 'printf "%s\n" [^ab] | wc -l'		'24'	0
+	check 'printf "%s\n" [^ab]* | wc -l'		'5281'	0
+	check 'printf "%s\n" [^A-D]* | wc -l'		'5692'	0
+
+	check 'printf "%s\n" [^0-3]* | wc -l'		'5094'	0	#150
+	check 'printf "%s\n" [^-03]* | wc -l'		'5265'	0
+	check 'printf "%s\n" [^0\-3]* | wc -l'		'5265'	0
+	check 'printf "%s\n" [^-a3]* | wc -l'		'5110'	0
+	check 'printf "%s\n" [\^-a3]* | wc -l'		'608'	0
+	check 'printf "%s\n" [\^0-3]* | wc -l'		'830'	0	#155
+	check 'printf "%s\n" [0-3^]* | wc -l'		'830'	0
+	check 'printf "%s\n" [0^-a]* | wc -l'		'513'	0
+	check 'printf "%s\n" *[^0-3] | wc -l'		'5156'	0
+	check 'printf "%s\n" [!^]? | wc -l'		'529'	0
+
+	check 'printf "%s\n" [^!]? | wc -l'		'529'	0	#160
+	check 'printf "%s\n" [!!^]? | wc -l'		'508'	0
+	check 'printf "%s\n" [!^!]? | wc -l'		'508'	0
+	check 'printf "%s\n" [^!]? | wc -l'		'529'	0
+	check 'printf "%s\n" [^!^]? | wc -l'		'508'	0
+	check 'printf "%s\n" [^^!]? | wc -l'		'508'	0	#165
+	check 'printf "%s\n" [!^-b]? | wc -l'		'487'	0
+	check 'printf "%s\n" [^!-b]? | wc -l'		'63'	0
+
+	# No need to clean up the directory, we're in the ATF working
+	# directory, and ATF cleans up for us.
+
+	results
+}
+
+atf_test_case case_matching
+case_matching_head() {
+	atf_set descr "Test expansion of vars with embedded cmdsub"
+}
+
+# helper functions for case matching
+#
+# usage: cm word [ pattern ] [ preamble ]	(expect word to match pattern)
+#        cf word [ pattern ] [ preamble ]	(expect word to fail to match)
+#
+# The last used (non-null) pattern, and the last used preamble, are
+# remembered and used again if only the word is given.  To give a
+# new preamble while using the last pattern, give '' as the pattern.
+#
+# nb: a null (empty) pattern is a syntax error, to get '' use "''"
+#
+cm() {
+	case "$2" in
+	'')	set -- "$1" "${LAST_PATTERN}" "${3:-${LAST_PFX}}";;
+	*)	LAST_PATTERN="$2";;
+	esac
+	LAST_PFX="$3"
+
+	check \
+	    "${3:+${3}; }case $1 in ($2) printf M;; (*) printf X;; esac" M 0
+}
+cf() {
+	case "$2" in
+	'')	set -- "$1" "${LAST_PATTERN}" "${3:-${LAST_PFX}}";;
+	*)	LAST_PATTERN="$2";;
+	esac
+	LAST_PFX="$3"
+
+	check \
+	    "${3:+${3}; }case $1 in ($2) printf M;; (*) printf X;; esac" X 0
+}
+
+case_matching_body() {
+
+	# nb: we are not testing execution of case, so no ;& or alternate
+	# patterns (etc) are needed here, we just want to validate the
+	# case variant of pattern matching, so simple one word, one pattern
+	# match or not match.
+
+	reset case_matching
+
+	cm abcd 'ab*'; cf bcda; cf aabce; cm ab				#  4
+	cm abcd '$var' 'var="ab*"'; cf abcd '"$var"' 'var="ab*"'	#  6
+
+	cm xy 'x*y'; cm xyxy; cm '"x*y"'; cf xxyz 			# 10
+
+	cm '""' '*'; cm '\*'; cm '\?'; cm -; cm 12345			# 15
+	cm abcd '$var' 'var="*"'; cf abcd '"$var"' 'var="*"'		# 17
+	cm '"*"' '\*'; cm '"*"' '"*"'; cm '"*"' '"$var"' 'var="*"'	# 20
+
+	cm X '?'; cf XX '?'; cf X '"?"'; cm Y '$var' 'var="?"'		# 24
+	cf Z '"$var"' 'var="?"'; cm '"?"' '"$var"' 'var="?"'		# 26
+
+	cm XA '??'; cf X '??'; cf XX '"??"'; cm YZ '$var' 'var="??"'	# 30
+	cf ZZ '"$var"' 'var="??"'; cm '"??"' '"$var"' 'var="??"'	# 32
+
+	cm a '[ab]'; cm b; cf c; cf aa; cf '"[ab]"'			# 37
+	cm '"[ab]"' '"[ab]"'; cm '"[ab]"' '\[ab]'			# 39
+	cm a '$var' 'var="[ab]"'; cf a '"$var"' 'var="[ab]"'		# 41
+	cm '"[ab]"' '"$var"' 'var="[ab]"'; cm a '["$var"]' 'var=ab'	# 43
+
+	cm b '[a-c]'; cm a '[a-c]'; cm c '[a-c]'; cf d '[a-c]'		# 47
+	cf '"[a-c]"' '[a-c]'; cm '"[a-c]"' '"[a-c]"'			# 49
+	cm '"[a-c]"' '\[a-c]'; cm '"[a-c]"' '[a-c\]'			# 51
+	cm a '$var' 'var="[a-c]"'; cf a '"$var"' 'var="[a-c]"'		# 53
+	cm '"[a-c]"' '"$var"' 'var="[a-c]"'; cf b '["$var"]' 'var=a-c'	# 55
+
+	cm 2 '[0-4]'; cm 0 '[0-4]'; cf - '[0-4]'; cm 0 '[-04]'		# 59
+	cf 2 '[-04]'; cf 2 '[40-]'; cm 0 '[40-]'; cm - '[-04]'		# 63
+	cf 2 '[0\-4]'; cm - '[0\-4]'; cf 2 '["0-4"]'; cm - '["0-4"]'	# 67
+	cf 2 "[0'-'4]"; cm - "[0'-'4]"; cm 4 "[0'-'4]"			# 70
+	cm 0 "['0'-'4']"; cf '"\\"' '[0\-4]'; cm '"\\"' '[\\0-\\4]'	# 73
+
+	cm a '[[:alpha:]]'; cf 0; cf '"["'; cm Z; cf aa; cf .; cf '""'	# 80
+	cf a '[[:digit:]]'; cm 0; cf '"["'; cm 9; cf 10; cf .; cf '""'	# 87
+	cm '"["' '[][:alpha:][]'; cf a '[\[:alpha:]]'; cf a '[[\:alpha:]]' #90
+	cm a '[$var]' 'var="[:alpha:]"'; cm a '[[$var]]' 'var=":alpha:"' # 92
+	cm a '[[:$var:]]' 'var=alpha'; cm B '[[:"$var":]]' 'var=alpha'	# 94
+	cf B '["$var"]' 'var="[:alpha:]"'; cf B '[["$var"]]' 'var=":alpha:"' #96
+	cm '"["' '["$var"]' 'var="[:alpha:]"'				# 97
+	cm '"[]"' '[["$var"]]' 'var=":alpha:"'; 			# 98
+	cm A3 '[[:alpha:]][[:digit:]]'; cf '"[["'			#100
+	cm 3 '[[:alpha:][:digit:]]'; cf '"["'; cm A; cf '":"'		#104
+	for W in AA A7 8x 77; do
+		cm "$W" '[[:alpha:][:digit:]][[:alpha:][:digit:]]'	#108
+	done
+
+	cm dir/file '*/*'; cm /dir/file; cm /dir/file '*/file'		#111
+	for W in aa/bcd /x/y/z .x/.abc --/--- '\\//\\//' '[]/[][]'
+	do
+		cm "'$W'" '??/*'; cm "'$W'" '[-a/.\\[]?/??*[]dzc/-]'
+	done								#123
+
+	cm '"?abc"' '\?*'; cf '"\\abc"'; cm '"?"'			#126
+
+	cm '\\z' '"\\z"'; cf '\z'; cf z; cf '"\\"'			#130
+
+	cm '"[x?abc"' '[[-\]]?\?*'; cm '"]x?abc"'; cm '"\\x?abc"'	#133
+		cf '"-x?abc"'; cf '"[xyzabc"'; cm '"[]?"'		#136
+
+	cm '"[x?"' '[]\\[]?\?'; cm '"]x?"'; cm '"\\y?"'; cm '"[]?"'	#140
+
+	cm "'\z'" '"\z"'; cf z; cm '\\z'; cm '$var' '' 'var="\z"'	#144
+	cm '${var}' '' "var='\z'"; cm '"${var}"'			#146
+	cf '${var}' '${var}' "var='\z'"; cf '${var}' '"${var}"' "var='\z'" #148
+	cf "'${var}'"; cm "'${var}'" "'${var}'" "var='\z'"		#150
+
+	cf abc '"$*"' 'IFS=?; set -- a c';cf '"a c"';cm "'a?c'";cm '"$*"' #154
+	cf abc '"$*"' 'IFS=*; set -- a c';cf '"a c"';cm "'a*c'";cm '"$*"' #158
+	cf abc '"$*"' 'IFS=\\;set -- a c';cf '"a c"';cm "'a\c'";cm '"$*"' #162
+	cf abc '"$*"' 'IFS="";set -- a c';cf '"a c"';cm "'ac'"; cm '"$*"' #166
+
+	cm a '["$*"]' 'IFS=-; set -- a c';cf b;cm c;cm '-';   cf "']'"	#171
+	cm a '["$*"]' 'IFS=?; set -- a c';cf b;cm c;cm '"?"'; cf "'['"	#176
+	cm a '["$*"]' 'IFS=*; set -- a c';cf b;cm c;cm '"*"'; cf -	#181
+	cm a '["$*"]' 'IFS=\\;set -- a c';cf b;cm c;cm "'\\'";cf "'$'"	#186
+	cm a '["$*"]' 'IFS="";set -- a c';cf b;cm c			#189
+
+
+	# Now repeat the ones using bracket expressions, adding !
+
+	cf a '[!ab]'; cf b; cm c; cf aa; cf '"[!ab]"'; cm a '[ab!]'; cm ! #196
+	cf a '$var' 'var="[!ab]"';cm x;cf a '"$var"' 'var="[!ab]"'; cf x  #200
+	cm '"[!ab]"' '"$var"' 'var="[!ab]"'; cf a; cf b; cf !; cf "'['"	  #205
+	cf a '[!"$var"]' 'var=ab'; cm x; cm a '["!$var"]' 'var=ab'	  #208
+	cf x; cm !; cm a '["$var"]' 'var=!ab'; cf x			  #212
+	cf a '[$var]' 'var=!ab'; cm !					  #214
+
+	cf b '[!a-c]'; cf a; cf c; cm d; cm !; cm -; cm _; cm '\\'	#222
+	cf a '$var' 'var="[!a-c]"'; cf b; cf c; cm d; cm !; cm -	#228
+
+	cf 2 '[!0-4]'; cf 0; cm -; cf 4; cm !; cm "'['"; cm "']'"	#235
+	cm 2 '[!-04]'; cm 2 '[!40-]'; cf 0; cf -; cm !;			#240
+	cm 2 '[!0\-4]'; cf -; cm 2 '[!"0-4"]'; cf -			#244
+
+	cf a '[![:alpha:]]'; cm 0; cm '"["'; cf aa; cm .; cf '""'	#250
+	cf '"["' '[!][:alpha:][!]'; cf a; cm 0; cf !; cf "']'"; cm %	#256
+	cf a '[$var]' 'var="![:alpha:]"'; cm 0; cm !; cm "']'"; cm @	#261
+
+	results
+}
+
+atf_test_case var_substring_matching
+var_substring_matching_head() {
+	atf_set descr 'Test pattern matching in var expansions'
+}
+
+# Helper function for var substring matching
+#	$1 is the input string
+#	$2 the substring matching operator (# % ## or %%)
+#	$3 is the pattern to match (or reference to one)
+#	$4 is the expected output (result of applying op($2) with pat($3) to $1
+#	$5 (if given, and not null) is a command (or commands) to run first
+#	$6 (if given, and not null) cause the var expansion to be quoted
+#		(ie "${var%pattern}" instead of just ${var%pattern})
+#		any quotes needed in "pattern" should be in $3
+# Note: a variable called "var" is used (set to $1, then expanded).
+vm()
+{
+	check "${5:+${5}; }var='$1';printf '%s\n' ${6:+\"}\${var$2$3}${6:+\"}" \
+		"$4" 0
+}
+
+var_substring_matching_body() {
+
+	reset var_substring_matching
+
+	vm abc \# a bc; vm aaab \# a aab; vm aaab \## 'a*a' b		#  3
+	vm aaab % ab aa; vm xawab %% 'a*ab' x; vm abcd \# xyz abcd	#  6
+	vm file.c % .c 'f le' IFS=i ; vm file.c % .c file IFS=i Q	#  8
+	vm file.c % ?c file ; vm file.c % '"?c"' file.c			# 10
+
+	vm abcabcabcded \# 'a*b' cabcabcded; vm abcabcabcded \## 'a*b' cded # 12
+	vm abcabcabcded % 'c*d' abcabcab; vm abcabcabcded %% 'c*d' ab	# 14
+
+	vm abc.jpg % '.[a-z][!0-9]?' abc				# 15
+
+	vm xxxyyy \# '${P}' yyy P=xxx; vm xxxyyy \# '${P}' yyy 'P=x?x'	# 17
+	vm xxxyyy \# '${P}' yyy 'P=x?x' Q				# 18
+	vm 'x?xyyy' \# '${P}' yyy 'P=x[?]x';vm xxxyyy \# '${P}' xxxyyy 'P=x[?]x'
+	vm 'x?xyyy' \# '${P}' yyy 'P=x?x' Q;vm xxxyyy \# '${P}' yyy 'P=x?x' Q
+	vm 'x?xyyy' \# '${P}' 'x?xyyy' 'P="x\?x"'			# 23
+	vm 'x?xyyy' \# '${P}' 'x?xyyy' 'P="x\?x"' Q			# 24
+	vm 'x?xyyy' \# '${P}' yyy 'P="x?x"' 				# 25
+	vm 'x?xyyy' \# '${P}' yyy 'P="x?x"' Q				# 26
+
+	vm abc \# '*' abc; vm abc \# '*' abc '' Q			# 28
+	vm abc \# '"*"' abc; vm abc \# '"*"' abc '' Q			# 30
+	vm abc \# '"a"' bc; vm abc \# '"a"' bc '' Q			# 32
+	vm abc \## '*' ''; vm abc \## '*' '' '' Q			# 34
+	vm abc % '*' abc; vm abc % '*' abc '' Q				# 36
+	vm abc %% '*' ''; vm abc %% '*' '' '' Q				# 38
+	vm abc \# '$P' abc 'P="*"'; vm abc \# '$P' abc 'P="*"' Q	# 40
+	vm abc \# '"$P"' abc 'P="*"'; vm abc \# '"$P"' abc 'P="*"' Q	# 42
+	vm abc \# '$P' bc 'P="[a]"'; vm abc \# '$P' bc 'P="[a]"' Q	# 44
+	vm abc \# '"$P"' abc 'P="[a]"'; vm abc \# '"$P"' abc 'P="[a]"' Q # 46
+	vm '[a]bc' \# '$P' '[a]bc' 'P="[a]"'				# 47
+	vm '[a]bc' \# '"$P"' bc 'P="[a]"'				# 48
+	vm '[a]bc' \# '"$P"' bc 'P="[a]"' Q				# 49
+
+	# The following two (50 & 51) are actually the same test.
+	# Technically #50 is unspecified, not in the matching, but
+	# in the assignment to P, in a "" string a \ not followed
+	# by one of a specific set of chars has unspecified results.
+	# However, sane shells simply leave the pair of chars as is.
+	# #51 is just the way that #50 should be written (the \\ in
+	# the string is assigned as a single \ which is what is wanted)
+	# Tests 52 & 53 have the same issue as #50, but unless 50
+	# fails and 51 succeeds (which is unlikely) we won't worry.
+
+	vm '[a]bc' \# '$P' bc 'P="\[a]"'				# 50
+	vm '[a]bc' \# '$P' bc 'P="\\[a]"'				# 51
+	vm '[a]bc' \# '"$P"' '[a]bc' 'P="\[a]"'				# 52
+	vm '\[a]bc' \# '"$P"' bc 'P="\[a]"'				# 53
+
+	vm ababcdabcd \# '[ab]*[ab]' abcdabcd				# 54
+	vm ababcdabcd \## '[ab]*[ab]' cd				# 55
+	vm ababcdabcd \# '$P' abcdabcd 'P="[ab]*[ab]"'			# 56
+	vm ababcdabcd \## '$P' cd "P='[ab]*[ab]'"			# 57
+	vm ababcdabcd \# '$P' 'ab dab d' 'P="[ab]*[ab]";IFS=c'		# 58
+	vm ababcdabcd \# '$P' abcdabcd 'P="[ab]*[ab]";IFS=c' Q		# 59
+
+	vm ababcdabcd \# '[ab]*[ba]' abcdabcd				# 60
+	vm ababcdabcd \# '[ab]*[a-b]' abcdabcd				# 61
+	vm ababcdabcd \## '[ba]*[ba]' cd				# 62
+	vm ababcdabcd \## '[a-b]*[ab]' cd				# 63
+
+	vm abcde \# '?[b-d]?' de; vm abcde \## '?[b-d]?' de		# 65
+	vm abcde % '?[b-d]?' ab; vm abcde %% '?[b-d]?' ab		# 67
+
+	vm .123. \# '.[0-9][1-8]' 3.; vm .123. % '[0-9][1-8].' .1	# 69
+	vm .123. \# '?[0-9][1-8]' 3.; vm .123. % '[0-9][1-8]?' .1	# 71
+	vm .123. \# '*[0-9][1-8]' 3.; vm .123. % '[0-9][1-8]*' .1	# 73
+	vm .123. \## '*[0-9][1-8]' .; vm .123. %% '[0-9][1-8]*' .	# 75
+	vm .123. \# '[.][1][2]' 3.  ; vm .123. % '[2][3][.]' .1		# 77
+	vm .123. \# '[?]1[2]' .123. ; vm .123. % '2[3][?]' .123.	# 79
+	vm .123. \# '\.[0-9][1-8]' 3.;vm .123. % '[0-9][1-8]\.' .1	# 81
+
+	vm '[a-c]d-f' \# '[a-c\]' d-f					# 82
+	vm '[abcd]' \# '[[:alpha:]]' '[abcd]'				# 83
+	vm '[1?234' \# '[[-\]]?\?' 234					# 84
+	vm '1-2-3-\?' % '-${P}' '1-2-3-\?' 'P="\\?"'			# 85
+	vm '1-2-3-\?' % '${P}' '1-2-3-\' 'P="\\?"'			# 86
+	vm '1-2-3-\?' % '-"${P}"' 1-2-3 'P="\\?"'			# 87
+
+	results
+}
+
+
+atf_init_test_cases() {
+	# Listed here in the order ATF runs them, not the order from above
+
+	atf_add_test_case filename_expansion
+	atf_add_test_case case_matching
+	atf_add_test_case var_substring_matching
+}

Reply via email to