1########################################################################
2#                                                                      #
3#               This software is part of the ast package               #
4#          Copyright (c) 1994-2011 AT&T Intellectual Property          #
5#                      and is licensed under the                       #
6#                 Eclipse Public License, Version 1.0                  #
7#                    by AT&T Intellectual Property                     #
8#                                                                      #
9#                A copy of the License is available at                 #
10#          http://www.eclipse.org/org/documents/epl-v10.html           #
11#         (with md5 checksum b35adb5213ca9657e911e9befb180842)         #
12#                                                                      #
13#              Information and Software Systems Research               #
14#                            AT&T Research                             #
15#                           Florham Park NJ                            #
16#                                                                      #
17#                 Glenn Fowler <gsf@research.att.com>                  #
18#                                                                      #
19########################################################################
20: mktest - generate regress or shell regression test scripts
21
22command=mktest
23stdin=8
24stdout=9
25PREFIX=test
26STYLE=regress
27WIDTH=80
28
29eval "exec $stdout>&1"
30
31case $(getopts '[-][123:xyz]' opt --xyz 2>/dev/null; echo 0$opt) in
320123)	ARGV0="-a $command"
33	USAGE=$'
34[-?
35@(#)$Id: mktest (AT&T Labs Research) 2010-08-11 $
36]
37'$USAGE_LICENSE$'
38[+NAME?mktest - generate a regression test scripts]
39[+DESCRIPTION?\bmktest\b generates regression test scripts from test
40    template commands in the \aunit\a.\brt\b file. The generated test
41    script writes temporary output to '$PREFIX$'\aunit\a.tmp and compares
42    it to the expected output in '$PREFIX$'\aunit\a.out. Run the test
43    script with the \b--accept\b option to (re)generate the
44    '$PREFIX$'\aunit\a.out.]
45[s:style?The script style:]:[style:='$STYLE$']
46    {
47        [+regress?\bregress\b(1) command input.]
48        [+shell?Standalone test shell script.]
49    }
50[w:width?Set the output format width to approximately
51    \awidth\a.]:[width:='$WIDTH$']
52
53unit.rt [ unit [ arg ... ] ]
54
55[+INPUT FILES?The regression test command file \aunit\a\b.rt\b is a
56    \bksh\b(1) script that makes calls to the following functions:]
57    {
58        [+DATA \afile\a [ - | [ options ]] data]]?Create input data
59            \afile\a that is empty (-) or contains \adata\a subject to
60            \bprint\b(1) \aoptions\a or that is a copy of the DATA command
61            standard input. Set \afile\a to \b-\b to name the standard
62            input.]
63        [+DIAGNOSTICS?Diagnostic messages of unspecified format are
64	    expected.]
65        [+DO \acommand\a [ \aarg\a ... ]]?Execute \acommand\a if the
66	    current test is active.]
67        [+EXEC [ \aarg\a ... ]]?Run the command under test with
68            optional arguments. If the standard input is not specified then
69            the standard input of the previous EXEC is used. The standard
70            input of the first EXEC in a TEST group is an empty regular
71            file.]
72        [+EXPORT \aname\a=\avalue\a ...?Export list for subsequent
73            commands in the TEST group or for all TEST groups if before
74	    the first TEST group.]
75        [+IGNORESPACE [ 0 | 1 ]
76            ?Ignore space differences when comparing expected output.]
77        [+KEEP \apattern\a ...?File match patterns of files to retain
78            between TEST groups.]
79        [+NOTE \acomment\a?\acomment\a is added to the current test
80            script.]
81        [+PROG \acommand\a [ \aarg\a ... ]]?Run \acommand\a with
82            optional arguments.]
83        [+TEST [ \anumber\a ]] [ \adescription\a ... ]]?Define a new
84            test group with optional \anumber\a and \adescripion\a.]
85        [+TWD [ \adir\a ... ]]?Set the temporary test dir to \adir\a.
86            The default is \aunit\a\b.tmp\b, where \aunit\a is the test
87            input file sans directory and suffix. If \adir\a matches \b/*\b
88            then it is the directory name; if \adir\a is non-null then the
89            prefix \b${TMPDIR:-/tmp}\b is added; otherwise if \adir\a is
90            omitted then
91            \b${TMPDIR:-/tmp}/tst-\b\aunit\a-$$-$RANDOM.\b\aunit\a is
92            used.]
93        [+UMASK [ \amask\a ]]?Run subsequent tests with \bumask\b(1)
94            \amask\a. If \amask\a is omitted then the original \bumask\b is
95            used.]
96        [+UNIT \acommand\a [ \aarg\a ... ]]?Define the command and
97            optional default arguments to be tested. \bUNIT\b explicitly
98            overrides the default command name derived from the test script
99            file name.]
100        [+WIDTH \awidth\a?Set the output format width to approximately
101            \awidth\a.]
102    }
103[+SEE ALSO?\bregress\b(1), \bksh\b(1)]
104'
105	;;
106*)	ARGV0=""
107	USAGE='s: unit.rt [ arg ... ]'
108	;;
109esac
110
111typeset ARG SCRIPT UNIT TEMP=${TMPDIR:-/tmp}/$command.$$.tmp WORK
112typeset IO INPUT INPUT_N OUTPUT OUTPUT_N ERROR ERROR_N KEEP
113typeset -C STATE
114typeset -A DATA STATE.RESET REMOVE FORMAT
115integer KEEP_UNIT=0 SCRIPT_UNIT=0 TEST=0 CODE=0 EXIT=0 ACCEPT=0 DIAGNOSTICS=0 code
116
117while	getopts $ARGV0 "$USAGE" OPT
118do	case $OPT in
119	s)	case $OPTARG in
120		regress|shell)
121			STYLE=$OPTARG
122			;;
123		*)	print -u2 -r -- $command: --style=$OPTARG: regress or shell expected
124			exit 1
125			;;
126		esac
127		;;
128	w)	WIDTH=$OPTARG
129		;;
130	*)	OPTIND=0
131		getopts $ARGV0 "$USAGE" OPT '-?'
132		exit 2
133		;;
134	esac
135done
136shift $OPTIND-1
137
138typeset SINGLE= quote='%${SINGLE}..${WIDTH}q'
139
140if	[[ $1 == - ]]
141then	shift
142fi
143if	(( ! $# ))
144then
145	print -u2 -r -- $command: test command script path expected
146	exit 1
147fi
148SCRIPT=$1
149shift
150if	[[ ! -r $SCRIPT ]]
151then	print -u2 -r -- $command: $SCRIPT: cannot read
152	exit 1
153fi
154(ulimit -c 0) >/dev/null 2>&1 && ulimit -c 0
155if	(( $# ))
156then	set -A UNIT -- "$@"
157	KEEP_UNIT=1
158else	ARG=${SCRIPT##*/}
159	set -A UNIT -- "${ARG%.*}"
160fi
161WORK=${UNIT[0]}.tmp
162rm -rf $WORK
163mkdir $WORK || exit
164export PATH=$PWD:$PATH
165
166function LINE
167{
168	if	[[ $STYLE == regress ]]
169	then	print -u$stdout
170	fi
171}
172
173function NOTE
174{
175	case $STYLE in
176	regress)LINE
177		print -u$stdout -r -- '#' "$@"
178		;;
179	shell)	print -u$stdout -r -f ": $QUOTE"$'\n' -- "$*"
180		;;
181	esac
182}
183
184function UNIT
185{
186	(( KEEP_UNIT )) || set -A UNIT -- "$@"
187	case $STYLE in
188	regress)LINE
189		print -u$stdout -r -f $'UNIT'
190		for ARG in "$@"
191		do	print -u$stdout -r -f " $QUOTE" -- "$ARG"
192		done
193		print -u$stdout
194		;;
195	shell)	print -u$stdout -r -f $'set x'
196		for ARG in "$@"
197		do	print -u$stdout -r -f " $QUOTE" -- "$ARG"
198		done
199		print -u$stdout
200		print -u$stdout shift
201		;;
202	esac
203}
204
205function TEST
206{
207	typeset i
208	typeset -A REM
209	if	(( ${#STATE.RESET[@]} ))
210	then	unset ${!STATE.RESET[@]}
211		case $STYLE in
212		shell)	print -u$stdout -r -- unset ${!STATE.RESET[@]} ;;
213		esac
214		unset STATE.RESET
215		typeset -A STATE.RESET
216	fi
217	if	(( ${#REMOVE[@]} ))
218	then	rm -f -- "${!REMOVE[@]}"
219		case $STYLE in
220		shell)	print -u$stdout -r -f $'rm -f'
221			for i in ${!REMOVE[@]}
222			do	print -u$stdout -r -f " $QUOTE" "$i"
223			done
224			print -u$stdout
225			;;
226		esac
227		for i in ${!REMOVE[@]}
228		do	unset REMOVE[$i]
229		done
230	fi
231	rm -rf $WORK/*
232	if	[[ $1 == +([0-9]) ]]
233	then	TEST=${1##0}
234		shift
235	else	((TEST++))
236	fi
237	LINE
238	case $STYLE in
239	regress)print -u$stdout -r -f "TEST %02d $QUOTE"$'\n' -- $TEST "$*"
240		;;
241	shell)	print -u$stdout -r -f ": TEST %02d $QUOTE"$'\n' -- $TEST "$*"
242		;;
243	esac
244	: > $TEMP.INPUT > $TEMP.in
245	INPUT=
246	INPUT_N=
247	OUTPUT=
248	OUTPUT_N=
249	ERROR=
250	ERROR_N=
251	UMASK=$UMASK_ORIG
252	UMASK_DONE=$UMASK
253	CODE=0
254}
255
256function TWD
257{
258	case $STYLE in
259	regress)LINE
260		print -u$stdout -r -f $'TWD'
261		for ARG in "$@"
262		do	print -u$stdout -r -f " $QUOTE" -- "$ARG"
263		done
264		print -u$stdout
265		;;
266	esac
267}
268
269function RUN
270{
271	typeset i n p op unit sep output=1 error=1 exitcode=1
272	op=$1
273	shift
274	while	:
275	do	case $1 in
276		++NOOUTPUT)	output= ;;
277		++NOERROR)	error= ;;
278		++NOEXIT)	exitcode= ;;
279		++*)		print -u2 -r -- $command: $0: $1: unknown option; exit 1 ;;
280		*)		break ;;
281		esac
282		shift
283	done
284	if	[[ $op == PROG ]]
285	then	unit=$1
286		shift
287	elif	(( ! ${#UNIT[@]} ))
288	then	print -u2 -r -- $command: $SCRIPT: UNIT statement or operand expected
289		exit 1
290	fi
291	LINE
292	case $STYLE in
293	regress)if	[[ $op == PROG ]]
294		then	print -u$stdout -r -f $'\t'"$op"$'\t'"$unit"
295			sep=$' '
296		else	print -u$stdout -r -f $'\t'"$op"
297			sep=$'\t'
298		fi
299		for ARG in "$@"
300		do	LC_CTYPE=C print -u$stdout -r -f "$sep$QUOTE" -- "$ARG"
301			sep=$' '
302		done
303		print -u$stdout
304		[[ ${DATA[-]} || /dev/fd/0 -ef /dev/fd/$stdin ]] || cat > $TEMP.in
305		IO=$(cat $TEMP.in; print :)
306		if	[[ $IO == ?*$'\n:' ]]
307		then	IO=${IO%??}
308			n=
309		else	IO=${IO%?}
310			n=-n
311		fi
312		{
313			[[ $UMASK != $UMASK_ORIG ]] && umask $UMASK
314			cd $WORK
315			if	[[ $op == PROG ]]
316			then	"$unit" "$@"
317				code=$?
318			else	"${UNIT[@]}" "$@"
319				code=$?
320			fi
321			cd ..
322			[[ $UMASK != $UMASK_ORIG ]] && umask $UMASK_ORIG
323		} < $TEMP.in > $TEMP.out 2> $TEMP.err
324		if	[[ $IO != "$INPUT" || $n != "$INPUT_N" ]]
325		then	INPUT=$IO
326			INPUT_N=$n
327			if	[[ ${FORMAT[-]} ]]
328			then	print -u$stdout -n -r -- $'\t\tINPUT'
329				print -u$stdout -r -f " $QUOTE" -- "${FORMAT[-]}"
330				print -u$stdout -r -f " $QUOTE" -- -
331				unset FORMAT[-]
332			else	print -u$stdout -n -r -- $'\t\tINPUT' $n -
333				[[ $IO ]] && LC_CTYPE=C print -u$stdout -r -f " $QUOTE" -- "$IO"
334			fi
335			print -u$stdout
336			unset DATA[-]
337		fi
338		for i in ${!DATA[@]}
339		do	if	[[ ${FORMAT[$i]} ]]
340			then	print -u$stdout -n -r -- $'\t\tINPUT'
341				print -u$stdout -r -f " $QUOTE" -- "${FORMAT[$i]}"
342				print -u$stdout -r -f " $QUOTE" -- "$i"
343				unset FORMAT[$i]
344			else	case $i in
345				-)	p=$TEMP.in ;;
346				*)	p=$WORK/$i ;;
347				esac
348				IO=$(cat $p; print :)
349				if	[[ $IO == ?*$'\n:' ]]
350				then	IO=${IO%??}
351					n=
352				else	IO=${IO%?}
353					n=-n
354				fi
355				print -u$stdout -n -r -- $'\t\tINPUT' $n
356				print -u$stdout -r -f " $QUOTE" -- "$i"
357				[[ $IO ]] && LC_CTYPE=C print -u$stdout -r -f " $QUOTE" -- "$IO"
358			fi
359			print -u$stdout
360			unset DATA[$i]
361		done
362		IO=$(cat $TEMP.out; print :)
363		if	[[ $IO == ?*$'\n:' ]]
364		then	IO=${IO%??}
365			n=
366		else	IO=${IO%?}
367			n=-n
368		fi
369		if	[[ $IO != "$OUTPUT" || $n != "$OUTPUT_N" ]]
370		then	OUTPUT=$IO
371			OUTPUT_N=$n
372			if	[[ $output ]]
373			then	if	[[ ! -s $TEMP.out ]]
374				then	print -u$stdout -n -r -- $'\t\tOUTPUT' -
375				elif	cmp -s $TEMP.in $TEMP.out
376				then	OUTPUT=not-$OUTPUT
377					print -u$stdout -n -r -- $'\t\tSAME OUTPUT INPUT'
378				else	print -u$stdout -n -r -- $'\t\tOUTPUT' $n -
379					[[ $IO ]] && LC_CTYPE=C print -u$stdout -r -f " $QUOTE" -- "$IO"
380				fi
381				print -u$stdout
382			fi
383		fi
384		IO=$(cat $TEMP.err; print :)
385		IO=${IO//$command\[*([0-9])\]:\ .\[*([0-9])\]:\ @(EXEC|PROG)\[*([0-9])\]:\ /}
386		if	[[ $IO == ?*$'\n:' ]]
387		then	IO=${IO%??}
388			n=
389		else	IO=${IO%?}
390			n=-n
391		fi
392		if	[[ $IO != "$ERROR" || $n != "$ERROR_N" ]]
393		then	ERROR=$IO
394			ERROR_N=$n
395			if	[[ $error ]]
396			then	print -u$stdout -n -r -- $'\t\tERROR' $n -
397				[[ $IO ]] && LC_CTYPE=C print -u$stdout -r -f " $QUOTE" -- "$IO"
398				print -u$stdout
399			fi
400		fi
401		case $output:$error in
402		:)	OUTPUT=
403			OUTPUT_N=
404			ERROR=
405			ERROR_N=
406			print -u$stdout -r -- $'\t\tIGNORE OUTPUT ERROR'
407			;;
408		:1)	OUTPUT=
409			OUTPUT_N=
410			print -u$stdout -r -- $'\t\tIGNORE OUTPUT'
411			;;
412		1:)	ERROR=
413			ERROR_N=
414			print -u$stdout -r -- $'\t\tIGNORE ERROR'
415			;;
416		esac
417		if	[[ $UMASK_DONE != $UMASK ]]
418		then	UMASK_DONE=$UMASK
419			print -u$stdout -r -f $'\t\tUMASK %s\n' $UMASK
420		fi
421		if	(( code != CODE ))
422		then	(( CODE=code ))
423			if	[[ $exitcode ]]
424			then	print -u$stdout -r -f $'\t\tEXIT %d\n' $CODE
425			fi
426		fi
427		;;
428	shell)	[[ $UMASK != $UMASK_ORIG ]] && print -u$stdout -r -f "{ umask $UMASK; "
429		if	[[ $op == PROG ]]
430		then	print -u$stdout -r -f $'"'"$unit"$'"'
431		else	print -u$stdout -r -f $'"$@"'
432		fi
433		for ARG in "$@"
434		do	print -u$stdout -r -f " $QUOTE" -- "$ARG"
435		done
436		[[ $UMASK != $UMASK_ORIG ]] && print -u$stdout -r -f "umask $UMASK_ORIG; } "
437		if	[[ ! $output ]]
438		then	print -u$stdout -r -f " >/dev/null"
439		fi
440		if	[[ ! $error ]]
441		then	if	[[ ! $output ]]
442			then	print -u$stdout -r -f " 2>&1"
443			else	print -u$stdout -r -f " 2>/dev/null"
444			fi
445		fi
446		IO=$(cat)
447		if	[[ $IO ]]
448		then	print -u$stdout -r -- "<<'!TEST-INPUT!'"
449			print -u$stdout -r -- "$IO"
450			print -u$stdout -r -- !TEST-INPUT!
451		else	print -u$stdout
452		fi
453		if	[[ $exitcode ]]
454		then	print -u$stdout -r -- $'CODE=$?\ncase $CODE in\n0) ;;\n*) echo exit status $CODE ;;\nesac'
455		fi
456		;;
457	esac
458}
459
460function DO
461{
462	LINE
463	print -r $'\t'DO "$@"
464}
465
466function EXEC
467{
468	RUN EXEC "$@"
469}
470
471function DATA
472{
473	typeset f p o
474	f=$1
475	shift
476	case $f in
477	-)	p=$TEMP.in ;;
478	*)	p=$WORK/$f ;;
479	esac
480	case $1 in
481	'')	cat ;;
482	-)	;;
483	*)	print -r "$@" ;;
484	esac > $p
485	DATA[$f]=1
486	if	(( $# == 1 )) && [[ $1 == -?* ]]
487	then	FORMAT[$f]=$1
488	else	FORMAT[$f]=
489	fi
490	if	[[ $f != $KEEP ]]
491	then	REMOVE[$f]=1
492	fi
493	if	[[ $STYLE == shell ]]
494	then	{
495		print -r -f "cat > $QUOTE <<'!TEST-INPUT!'"$'\n' -- "$f"
496		cat "$p"
497		print -r -- !TEST-INPUT!
498		} >&$stdout
499	fi
500}
501
502function KEEP
503{
504	typeset p
505	for p
506	do	if	[[ $KEEP ]]
507		then	KEEP=$KEEP'|'
508		fi
509		KEEP=$KEEP$p
510	done
511}
512
513function DIAGNOSTICS
514{
515	LINE
516	case $STYLE in
517	regress)	print -u$stdout -r $'DIAGNOSTICS' ;;
518	shell)		DIAGNOSTICS=1 ;;
519	esac
520}
521
522function EXPORT
523{
524	typeset x n v
525	LINE
526	case $STYLE in
527	regress)	print -u$stdout -r -f $'EXPORT' ;;
528	shell)		print -u$stdout -r -f $'export' ;;
529	esac
530	for x
531	do	n=${x%%=*}
532		v=${x#*=}
533		export "$x"
534		print -u$stdout -r -f " %s=$QUOTE" "$n" "$v"
535		(( TEST )) && STATE.RESET["$n"]=1
536	done
537	print -u$stdout
538}
539
540function PROG
541{
542	RUN PROG "$@"
543}
544
545function WIDTH
546{
547	WIDTH=${1:-80}
548	eval QUOTE='"'$quote'"'
549}
550
551function IGNORESPACE
552{
553	IGNORESPACE=-b
554	LINE
555	print -u$stdout -r IGNORESPACE
556}
557
558function UMASK # [ mask ]
559{
560	[[ $UMASK_ORIG ]] || UMASK_ORIG=$(umask)
561	UMASK=$1
562	[[ $UMASK ]] || UMASK=$UMASK_ORIG
563}
564
565trap 'CODE=$?; rm -rf $TEMP.* $WORK; exit $CODE' 0 1 2 3 15
566
567typeset IGNORESPACE UMASK UMASK_ORIG UMASK_DONE
568UMASK_ORIG=$(umask)
569IFS=$IFS$'\n'
570
571print -u$stdout -r "# : : generated from $SCRIPT by $command : : #"
572case $STYLE in
573shell)	cat <<!
574ACCEPT=0
575while	:
576do	case \$1 in
577	-a|--accept)
578		ACCEPT=1
579		;;
580	--help|--man)
581		cat 1>&2 <<!!
582Usage: \\\$SHELL $PREFIX${UNIT[0]}.sh [ --accept ] [ unit ... ]
583
584${UNIT[0]} regression test script.  Run this script to generate new
585results in $PREFIX${UNIT[0]}.tmp and compare with expected results in
586$PREFIX${UNIT[0]}.out.  The --accept option generates $PREFIX${UNIT[0]}.tmp
587and moves it to $PREFIX${UNIT[0]}.out.
588!!
589		exit 2
590		;;
591	-*)	echo \$0: \$1: invalid option >&2
592		exit 1
593		;;
594	*)	break
595		;;
596	esac
597	shift
598done
599export COLUMNS=80
600{
601!
602	;;
603esac
604
605export COLUMNS=80
606
607case $STYLE in
608shell)	SINGLE='#'
609	eval QUOTE='"'$quote'"'
610	. $SCRIPT < /dev/null | sed -e $'s,\\\\n,\n,g' -e $'s,\\\\t,\t,g' -e $'s,\\$\',\',g'
611	;;
612*)	eval QUOTE='"'$quote'"'
613	: > $TEMP.INPUT > $TEMP.in
614	eval "exec $stdin<$TEMP.INPUT"
615	. $SCRIPT <&$stdin
616	;;
617esac
618
619case $STYLE in
620shell)	cat <<!
621} > $PREFIX${UNIT[0]}.tmp 2>&1 < /dev/null
622case \$ACCEPT in
6230)	if	grep '
624$' $PREFIX${UNIT[0]}.tmp >/dev/null
625	then	mv $PREFIX${UNIT[0]}.tmp $PREFIX${UNIT[0]}.junk
626		sed 's/
627$//' < $PREFIX${UNIT[0]}.junk > $PREFIX${UNIT[0]}.tmp
628		rm -f $PREFIX${UNIT[0]}.junk
629	fi
630	if	cmp -s $PREFIX${UNIT[0]}.tmp $PREFIX${UNIT[0]}.out
631	then	echo ${UNIT[0]} tests PASSED
632		rm -f $PREFIX${UNIT[0]}.tmp
633	else	echo ${UNIT[0]} tests FAILED
634		diff $IGNORESPACE $PREFIX${UNIT[0]}.tmp $PREFIX${UNIT[0]}.out
635	fi
636	;;
637
638*)	mv $PREFIX${UNIT[0]}.tmp $PREFIX${UNIT[0]}.out
639	;;
640esac
641!
642	;;
643esac
644