1########################################################################
2#                                                                      #
3#               This software is part of the ast package               #
4#          Copyright (c) 1994-2012 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: regress - run regression tests in command.tst
21
22command=regress
23case $(getopts '[-][123:xyz]' opt --xyz 2>/dev/null; echo 0$opt) in
240123)	USAGE=$'
25[-?
26@(#)$Id: regress (AT&T Research) 2012-02-02 $
27]
28'$USAGE_LICENSE$'
29[+NAME?regress - run regression tests]
30[+DESCRIPTION?\bregress\b runs the tests in \aunit\a, or
31    \aunit\a\b.tst\b if \aunit\a does not exist. If \acommand\a is omitted
32    then it is assumed to be the base name of \aunit\a. All testing is done
33    in the temporary directory \aunit\a\b.tmp\b.]
34[+?Default test output lists the \anumber\a and \adescription\a for
35    each active \bTEST\b group and the \anumber\a:\aline\a for each
36    individual \bEXEC\b test. Each test that fails results in a diagnostic
37    that contains the word \bFAILED\b; no other diagnostics contain this
38    word.]
39[b:ignore-space?Ignore space differences when comparing expected
40    output.]
41[i:pipe-input?Repeat each test with the standard input redirected through a
42    pipe.]
43[k:keep?Enable \bcore\b dumps, exit after the first test that fails,
44    and do not remove the temporary directory \aunit\a\b.tmp\b.]
45[l:local-fs?Force \aunit\a\b.tmp\b to be in a local filesystem.]
46[o:pipe-output?Repeat each test with the standard output redirected through
47    a pipe.]
48[p:pipe-io?Repeat each test with the standard input and standard output
49    redirected through pipes.]
50[q:quiet?Output information on \bFAILED\b tests only.]
51[r!:regular?Run each test with the standard input and standard output
52    redirected through regular files.]
53[t:test?Run only tests matching \apattern\a. Tests are numbered and
54    consist of at least two digits (0 filled if necessary.) Tests matching
55    \b+(0)\b are always run.]:[pattern]
56[x:trace?Enable debug tracing.]
57[v:verbose?List differences between actual (<) and expected (>) output,
58    errors and exit codes. Also disable long output line truncation.]
59
60unit [ command [ arg ... ] ]
61
62[+INPUT FILES?The regression test file \aunit\a\b.tst\b is a \bksh\b(1)
63    script that is executed in an environment with the following functions
64    defined:]
65    {
66        [+BODY \b{ ... }?Defines the test body; used for complex tests.]
67        [+CD \b\adirectory\a?Create and change to working directory for
68            one test.]
69        [+CLEANUP \b\astatus\a?Called at exit time to remove the
70            temporary directory \aunit\a\b.tmp\b, list the tests totals via
71            \bTALLY\b, and exit with status \astatus\a.]
72        [+COMMAND \b\aarg\a ...?Runs the current command under test with
73            \aarg\a ... appended to the default args.]
74        [+CONTINUE?The background job must be running.]
75        [+COPY \b\afrom to\a?Copy file \afrom\a to \ato\a. \afrom\a may
76            be a regular file or \bINPUT\b, \bOUTPUT\b or \bERROR\b. Post
77            test comparisons are still done for \afrom\a.]
78        [+DIAGNOSTICS \b[ \b1\b | \b0\b | \apattern\a ]]?No argument or an
79	    argument of \b1\b declares that diagnostics are to expected for
80	    the remainder of the current \bTEST\b; \b0\b reverts to the default
81            state that diagnostics are not expected; otherwise the argument
82	    is a \bksh\b(1) pattern that must match the non-empty contents
83	    of the standard error.]
84        [+DO \b\astatement\a?Defines additional statements to be executed
85            for the current test. \astatement\a may be a { ... } group.]
86        [+EMPTY \bINPUT|OUTPUT|ERROR|SAME?The corresponding file is
87            expected to be empty.]
88        [+ERROR \b[ \b-e\b \afilter\a ]] [ \b-n\b ]] \afile\a | - \adata\a ...?The
89	    standard error is expected to match either the contents
90	    of \afile\a or the line \adata\a. \bERROR -n\b does not
91	    append a newline to \adata\a. \afilter\a is a shell command
92	    or pipeline that reads standard input and writes standard
93	    output that is applied to ERROR before comparison with the
94	    expected contents.]
95        [+EXEC \b[ \aarg\a ... ]]?Runs the command under test with
96            optional arguments. \bINPUT\b, \bOUTPUT\b, \bERROR\b, \bEXIT\b
97            and \bSAME\b calls following this \bEXEC\b up until the next
98            \bEXEC\b or the end of the script provide details for the
99            expected results. If no arguments are specified then the
100            arguments from the previious \bEXEC\b in the current \bTEST\b
101            group are used, or no arguments if this is the first \bEXEC\b
102            in the group.]
103        [+EXIT \b\astatus\a?The command exit status is expected to match
104            the pattern \astatus\a.]
105        [+EXITED?The background job must have exited.]
106        [+EXPORT \b[-]] \aname\a=\avalue\a ...?Export environment
107            variables for one test.]
108        [+FATAL \b\amessage\a ...?\amessage\a is printed on the standard
109            error and \bregress\b exits with status \b1\b.]
110        [+FIFO \bINPUT|OUTPUT|ERROR\b [ \b-n\b ]] \afile\a | - \adata\a ...?The
111	    \bIO\B file is a fifo.]
112        [+IF \b\acommand\a [\anote\a]]?If the \bsh\b(1) \acommand\a exits
113            0 then tests until the next \bELIF\b, \bELSE\b or \bFI\b are
114            enabled. Otherwise those tests are skipped. \bIF\b ... \bFI\b
115            may be nested, but must not cross \bTEST\b boundaries. \anote\a
116            is listed on the standard error if the correspoding test block
117            is enabled; \bIF\b, \bELIF\b, \bELSE\b may nave a \anote\a
118            operand.]
119        [+IGNORE \b\afile\a ...?\afile\a is ignored for subsequent result
120            comparisons. \afile\a may be \bOUTPUT\b or \bERROR\b.]
121        [+IGNORESPACE?Ignore space differences when comparing expected
122            output.]
123        [+INCLUDE \b\afile\a ...?One or more \afile\a operands are read
124            via the \bksh\b(1) \b.\b(1) command. \bVIEW\b is used to locate
125            the files.]
126        [+INFO \b\adescription\a?\adescription\a is printed on the
127            standard error.]
128        [+INITIALIZE?Called by \bregress\b to initialize a each
129            \bTEST\b group.]
130        [+INPUT \b[ \b-e\b \afilter\a ]] [ \b-n\b ]] \afile\a | - \adata\a ...?The
131	    standard input is set to either the contents of \afile\a
132	    or the line \adata\a. \bINPUT -n\b does not append a newline
133	    to \adata\a. \afilter\a is a shell command or pipeline that
134	    reads standard input and writes standard output that is
135	    applied to OUTPUT before comparison with the expected contents.]
136        [+INTRO?Called by \bregress\b to introduce all \bTEST\b
137            groups.]
138        [+IO \b[ \bFIFO\b | \bPIPE\b ]] \bINPUT|OUTPUT|ERROR\b [ \b-e\b \afilter\a ]] [ \b-n\b ]] \afile\a | - \adata\a ...?Internal
139            support for the \bINPUT\b, \bOUTPUT\b and \bERROR\b functions.]
140        [+JOB \b\aop\a [ ... ]]?Like \bEXEC\b except the command is run
141            as a background job for the duration of the group or until it
142            is killed via \bKILL\b.]
143        [+KEEP \b\apattern\a ...?The temporary directory is cleared for
144            each test. Files matching \apattern\a are retained between
145            tests.]
146        [+KILL \b[ \asignal\a ]]?Kill the background job with \asignal\a
147        [ \bSIGKILL\b ]].]
148        [+MOVE \b\afrom to\a?Rename file \afrom\a to \ato\a. \afrom\a may
149            be a regular file or \bINPUT\b, \bOUTPUT\b or \bERROR\b. Post
150            test comparisons are ignored for \afrom\a.]
151        [+NOTE \b\acomment\a?\acomment\a is added to the current test
152            trace output.]
153        [+OUTPUT \b[ \b-e\b \afilter\a ]] [ \b-n\b ]] \afile\a | - \adata\a ...?The
154	    standard output is expected to match either the contents
155	    of \afile\a or the line \adata\a. \bOUTPUT -n\b does not
156	    append a newline to \adata\a. \afilter\a is a shell command
157	    or pipeline that reads standard input and writes standard
158	    output that is applied to ERROR before comparison with the
159	    expected contents.]
160        [+PIPE \bINPUT|OUTPUT|ERROR\b [ \b-n\b ]] \afile\a | - \adata\a ...?The
161	    \bIO\B file is a pipe.]
162        [+PROG \b\acommand\a [ \aarg\a ... ]]?\acommand\a is run with
163            optional arguments.]
164        [+REMOVE \b\afile\a ...?\afile\a ... are removed after the
165            current test is done.]
166        [+RUN?Called by \bregress\b to run the current test.]
167        [+SAME \b\anew old\a?\anew\a is expected to be the same as
168            \aold\a after the current test completes.]
169        [+SET \b[\bno\b]]\aname\a[=\avalue\a]]?Set the command line
170            option --\aname\a. The setting is in effect for all tests until
171            the next explicit \bSET\b.]
172        [+TALLY?Called by \bregress\b display the \bTEST\b results.]
173        [+TEST \b\anumber\a [ \adescription\a ... ]]?Define a new test
174            group labelled \anumber\a with optional \adescripion\a.]
175        [+TITLE \b[+]] \atext\a?Set the \bTEST\b output title to
176            \atext\a. If \b+\b is specified then \atext\a is appended to
177            the default title. The default title is the test file base
178            name, and, if different from the test file base name, the test
179            unit base name.]
180        [+TWD \b[ \adir\a ... ]]?Set the temporary test dir to \adir\a.
181            The default is \aunit\a\b.tmp\b, where \aunit\a is the test
182            input file sans directory and suffix. If \adir\a matches \b/*\b
183            then it is the directory name; if \adir\a is non-null then the
184            prefix \b${TMPDIR:-/tmp}\b is added; otherwise if \adir\a is
185            omitted then
186            \b${TMPDIR:-/tmp}/tst-\b\aunit\a-$$-$RANDOM.\b\aunit\a is
187            used.]
188        [+UMASK \b[ \amask\a ]]?Run subsequent tests with \bumask\b(1)
189            \amask\a. If \amask\a is omitted then the original \bumask\b is
190            used.]
191        [+UNIT \b\acommand\a [ \aarg\a ... ]]?Define the command and
192            optional default arguments to be tested. \bUNIT\b explicitly
193            overrides the default command name derived from the test script
194            file name. A \acommand\a operand with optional arguments
195            overrides the \bUNIT\b \acommand\a and arguments, with the
196            exception that if the \bUNIT\b \acommand\a is \b-\b or \b+\b
197            the \bUNIT\b arguments are appended to the operand or default
198            unit command and arguments.]
199        [+VIEW \b\avar\a [ \afile\a ]]?\avar\a is set to the full
200            pathname of \avar\a [ \afile\a ]] in the current \b$VPATH\b
201            view if defined.]
202    }
203[+SEE ALSO?\bnmake\b(1), \bksh\b(1)]
204'
205	;;
206*)	USAGE='ko:[[no]name[=value]]t:[test]v unit [path [arg ...]]'
207	;;
208esac
209
210function FATAL # message
211{
212	print -r -u2 "$command: $*"
213	GROUP=FINI
214	exit 1
215}
216
217function EMPTY
218{
219	typeset i
220	typeset -n ARRAY=$1
221	for i in ${!ARRAY[@]}
222	do	unset ARRAY[$i]
223	done
224}
225
226function INITIALIZE # void
227{
228	typeset i j
229	cd "$TWD"
230	case $KEEP in
231	"")	RM *
232		;;
233	*)	for i in *
234		do	case $i in
235			!($KEEP))	j="$j $i" ;;
236			esac
237		done
238		case $j in
239		?*)	RM $j ;;
240		esac
241		;;
242	esac
243	: >INPUT >OUTPUT.ex >ERROR.ex
244	BODY=""
245	COPY=""
246	DIAGNOSTICS=""
247	DONE=""
248	ERROR=""
249	EXIT=0
250	IGNORE=""
251	INIT=""
252	INPUT=""
253	MOVE=""
254	OUTPUT=""
255	EMPTY FILE
256	EMPTY FILTER
257	EMPTY SAME
258	EMPTY TYPE
259}
260
261function INTRO
262{
263	typeset base command
264
265	if	[[ ! $TEST_quiet ]]
266	then	base=${REGRESS##*/}
267		base=${base%.tst}
268		command=${COMMAND##*/}
269		command=${command%' '*}
270		set -- $TITLE
271		TITLE=
272		case $1 in
273		''|+)	if	[[ $command == $base ]]
274			then	TITLE=$COMMAND
275			else	TITLE="$COMMAND, $base"
276			fi
277			if	(( $# ))
278			then	shift
279			fi
280			;;
281		esac
282		while	(( $# ))
283		do	if	[[ $TITLE ]]
284			then	TITLE="$TITLE, $1"
285			else	TITLE="$1"
286			fi
287			shift
288		done
289		print -u2 "TEST	$TITLE"
290	fi
291}
292
293function TALLY # extra message text
294{
295	typeset msg
296	case $GROUP in
297	INIT)	;;
298	*)	msg="TEST	$TITLE, $TESTS test"
299		case $TESTS in
300		1)	;;
301		*)	msg=${msg}s ;;
302		esac
303		msg="$msg, $ERRORS error"
304		case $ERRORS in
305		1)	;;
306		*)	msg=${msg}s ;;
307		esac
308		if	(( $# ))
309		then	msg="$msg, $*"
310		fi
311		print -u2 "$msg"
312		GROUP=INIT
313		TESTS=0
314		ERRORS=0
315		;;
316	esac
317}
318
319function TITLE # text
320{
321	TITLE=$@
322}
323
324function UNWIND
325{
326	while	(( COND > 1 ))
327	do	print -r -u2 "$command: line $LINE: no matching FI for IF on line ${COND_LINE[COND]}"
328		(( COND-- ))
329	done
330	if	(( COND > 0 ))
331	then	(( COND = 0 ))
332		FATAL "line $LINE: no matching FI for IF on line ${COND_LINE[COND+1]}"
333	fi
334	if	[[ $JOBPID ]]
335	then	if	[[ $JOBPID != 0 ]]
336		then	kill -KILL $JOBPID 2>/dev/null
337			wait
338		fi
339		JOBPID=
340	fi
341	JOBSTATUS=
342	JOBOP=
343	wait
344}
345
346function CLEANUP # status
347{
348	typeset note
349
350	if	[[ $GROUP != INIT ]]
351	then	if	[[ ! $TEST_keep ]]
352		then	cd $SOURCE
353			if	[[ $TEST_local ]]
354			then	RM ${TEST_local}
355			fi
356			RM "$TWD"
357		fi
358		if	(( $1 )) && [[ $GROUP != FINI ]]
359		then	note=terminated
360		fi
361	fi
362	TALLY $note
363	[[ $TEST_keep ]] || UNWIND
364	exit $1
365}
366
367function RUN # [ op ]
368{
369	typeset i r=1
370	[[ $UMASK != $UMASK_ORIG ]] && umask $UMASK_ORIG
371#print -u2 AHA#$LINENO $0 GROUP=$GROUP ITEM=$ITEM FLUSHED=$FLUSHED JOBOP=$JOBOP
372	case $GROUP in
373	INIT)	RM "$TWD"
374		if	[[ $TEST_local ]]
375		then	TEST_local=${TMPDIR:-/tmp}/rt-$$/${TWD##*/}
376			mkdir -p "$TEST_local" && ln -s "$TEST_local" "$TWD" || FATAL "$TWD": cannot create directory
377			TEST_local=${TEST_local%/*}
378		else	mkdir "$TWD" || FATAL "$TWD": cannot create directory
379		fi
380		cd "$TWD"
381		TWD=$PWD
382		: > rmu
383		if	rm -u rmu >/dev/null 2>&1
384		then	TEST_rmu=-u
385		else	rm rmu
386		fi
387		if	[[ $UNIT ]]
388		then	set -- "${ARGV[@]}"
389			case $1 in
390			""|[-+]*)
391				UNIT $UNIT "${ARGV[@]}"
392				;;
393			*)	UNIT "${ARGV[@]}"
394				;;
395			esac
396		fi
397		INTRO
398		;;
399	FINI)	;;
400	$TEST_select)
401		if	[[ $ITEM == $FLUSHED ]]
402		then	return 0
403		fi
404		FLUSHED=$ITEM
405		if	(( COND_SKIP[COND] ))
406		then	return 1
407		fi
408		((COUNT++))
409		if	(( $ITEM <= $LASTITEM ))
410		then	LABEL=$TEST#$COUNT
411		else	LASTITEM=$ITEM
412			LABEL=$TEST:$ITEM
413		fi
414		TEST_file=""
415		exec >/dev/null
416		for i in $INPUT
417		do	case " $OUTPUT " in
418			*" $i "*)
419				if	[[ -f $i.sav ]]
420				then	cp $i.sav $i
421					COMPARE="$COMPARE $i"
422				elif	[[ -f $i ]]
423				then	cp $i $i.sav
424					COMPARE="$COMPARE $i"
425				fi
426				;;
427			esac
428		done
429		for i in $OUTPUT
430		do	case " $COMPARE " in
431			*" $i "*)
432				;;
433			*)	COMPARE="$COMPARE $i"
434				;;
435			esac
436		done
437		for i in $INIT
438		do	$i $TEST INIT
439		done
440#print -u2 AHA#$LINENO $0 GROUP=$GROUP ITEM=$ITEM JOBOP=$JOBOP JOBPID=$JOBPID JOBSTATUS=$JOBSTATUS
441		if	[[ $JOBPID != 0 && ( $JOBPID || $JOBSTATUS ) ]]
442		then	if	[[ ! $TEST_quiet ]]
443			then	print -nu2 "$LABEL"
444			fi
445			RESULTS
446		elif	[[ $BODY ]]
447		then	SHOW=$NOTE
448			if	[[ ! $TEST_quiet ]]
449			then	print -r -u2 "	$SHOW"
450			fi
451			for i in $BODY
452			do	$i $TEST BODY
453			done
454		else	SHOW=
455			if	[[ ${TYPE[INPUT]} == PIPE ]]
456			then	if	[[ ${TYPE[OUTPUT]} == PIPE ]]
457				then	if	[[ ! $TEST_quiet ]]
458					then	print -nu2 "$LABEL"
459					fi
460					cat <$TWD/INPUT | COMMAND "${ARGS[@]}" | cat >$TWD/OUTPUT
461					RESULTS 'pipe input'
462				else	if	[[ ! $TEST_quiet ]]
463					then	print -nu2 "$LABEL"
464					fi
465					cat <$TWD/INPUT | COMMAND "${ARGS[@]}" >$TWD/OUTPUT
466					RESULTS 'pipe io'
467				fi
468			elif	[[ ${TYPE[OUTPUT]} == PIPE ]]
469			then	if	[[ ! $TEST_quiet ]]
470				then	print -nu2 "$LABEL"
471				fi
472				COMMAND "${ARGS[@]}" <$TWD/INPUT | cat >$TWD/OUTPUT
473				RESULTS 'pipe output'
474			else	if	[[ $TEST_regular ]]
475				then	if	[[ ! $TEST_quiet ]]
476					then	print -nu2 "$LABEL"
477					fi
478					if	[[ ${TYPE[INPUT]} == FIFO ]]
479					then	COMMAND "${ARGS[@]}" >$TWD/OUTPUT
480					else	COMMAND "${ARGS[@]}" <$TWD/INPUT >$TWD/OUTPUT
481					fi
482					RESULTS
483				fi
484				if	[[ $TEST_pipe_input ]]
485				then	if	[[ ! $TEST_quiet ]]
486					then	print -nu2 "$LABEL"
487					fi
488					(trap '' PIPE; cat <$TWD/INPUT 2>/dev/null; exit 0) | COMMAND "${ARGS[@]}" >$TWD/OUTPUT
489					STATUS=$?
490					RESULTS 'pipe input'
491				fi
492				if	[[ $TEST_pipe_output ]]
493				then	if	[[ ! $TEST_quiet ]]
494					then	print -nu2 "$LABEL"
495					fi
496					COMMAND "${ARGS[@]}" <$TWD/INPUT | cat >$TWD/OUTPUT
497					STATUS=$?
498					RESULTS 'pipe output'
499				fi
500				if	[[ $TEST_pipe_io ]]
501				then	if	[[ ! $TEST_quiet ]]
502					then	print -nu2 "$LABEL"
503					fi
504					(trap '' PIPE; cat <$TWD/INPUT 2>/dev/null; exit 0) | COMMAND "${ARGS[@]}" | cat >$TWD/OUTPUT
505					STATUS=$?
506					RESULTS 'pipe io'
507				fi
508			fi
509			set -- $COPY
510			COPY=""
511			while	:
512			do	case $# in
513				0|1)	break ;;
514				*)	cp $1 $2 ;;
515				esac
516				shift 2
517			done
518			set -- $MOVE
519			MOVE=""
520			while	(( $# > 1 ))
521			do	mv $1 $2
522				shift 2
523			done
524		fi
525		for i in $DONE
526		do	$i $TEST DONE $STATUS
527		done
528		COMPARE=""
529		r=0
530		;;
531	esac
532	if	[[ $COMMAND_ORIG ]]
533	then	COMMAND=$COMMAND_ORIG
534		COMMAND_ORIG=
535		ARGS=(${ARGS_ORIG[@]})
536	fi
537	return $r
538}
539
540function DO # cmd ...
541{
542	[[ $GROUP == $TEST_select ]] || return 1
543	(( COND_SKIP[COND] )) && return 1
544	[[ $UMASK != $UMASK_ORIG ]] && umask $UMASK
545	return 0
546}
547
548function UNIT # cmd arg ...
549{
550	typeset cmd=$1
551	case $cmd in
552	[-+])	shift
553		if	(( UNIT_READONLY ))
554		then	COMMAND="$COMMAND $*"
555		else	#BUG# ARGV=("${ARGV[@]}" "$@")
556			set -- "${ARGV[@]}" "$@"
557			ARGV=("$@")
558		fi
559		return
560		;;
561	esac
562	(( UNIT_READONLY )) && return
563	if	[[ $UNIT ]] && (( $# <= 1 ))
564	then	set -- "${ARGV[@]}"
565		case $1 in
566		"")	set -- "$cmd" ;;
567		[-+]*)	set -- "$cmd" "${ARGV[@]}" ;;
568		*)	cmd=$1 ;;
569		esac
570	fi
571	UNIT=
572	COMMAND=$cmd
573	shift
574	typeset cmd=$(whence $COMMAND)
575	if	[[ ! $cmd ]]
576	then	FATAL $COMMAND: not found
577	elif	[[ ! $cmd ]]
578	then	FATAL $cmd: not found
579	fi
580	case $# in
581	0)	;;
582	*)	COMMAND="$COMMAND $*" ;;
583	esac
584}
585
586function TWD # [ dir ]
587{
588	case $1 in
589	'')	TWD=${TWD##*/}; TWD=${TMPDIR:-/tmp}/tst-${TWD%.*}-$$-$RANDOM ;;
590	/*)	TWD=$1 ;;
591	*)	TWD=${TMPDIR:-/tmp}/$1 ;;
592	esac
593}
594
595function TEST # number description arg ...
596{
597	RUN
598	LINE=$TESTLINE
599	UNWIND
600	COUNT=0
601	LASTITEM=0
602	case $1 in
603	-)		((LAST++)); TEST=$LAST ;;
604	+([0123456789]))	LAST=$1 TEST=$1 ;;
605	*)		LAST=0${1/[!0123456789]/} TEST=$1 ;;
606	esac
607	NOTE=
608	if	[[ ! $TEST_quiet && $TEST == $TEST_select ]] && (( ! COND_SKIP[COND] ))
609	then	print -r -u2 "$TEST	$2"
610	fi
611	unset ARGS
612	unset EXPORT
613	EXPORTS=0
614	TEST_file=""
615	if	[[ $TEST != ${GROUP}* ]]
616	then	GROUP=${TEST%%+([abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ])}
617		if	[[ $GROUP == $TEST_select ]] && (( ! COND_SKIP[COND] ))
618		then	INITIALIZE
619		fi
620	fi
621	((SUBTESTS=0))
622	[[ $TEST == $TEST_select ]] && (( ! COND_SKIP[COND] ))
623}
624
625function EXEC # arg ...
626{
627	if	[[ $GROUP != $TEST_select ]] || (( COND_SKIP[COND] ))
628	then	return
629	fi
630	if	((SUBTESTS++))
631	then	RUN
632	fi
633	case $# in
634	0)	set -- "${ARGS[@]}" ;;
635	esac
636	ITEM=$LINE
637	NOTE="$(print -r -f '%q ' -- $COMMAND_ORIG "$@")${JOBPID:+&}"
638	ARGS=("$@")
639}
640
641function JOB # arg ...
642{
643	JOBPID=0
644	EXEC "$@"
645}
646
647function CONTINUE
648{
649	RUN || return
650	JOBOP=CONTINUE
651	ITEM=$LINE
652	NOTE="$(print -r -f '%q ' -- $JOBOP)"
653#print -u2 AHA#$LINENO JOBOP=$JOBOP ITEM=$ITEM NOTE=$NOTE
654}
655
656function EXITED
657{
658	RUN || return
659	JOBOP=EXITED
660	ITEM=$LINE
661	NOTE="$(print -r -f '%q ' -- $JOBOP)"
662#print -u2 AHA#$LINENO JOBOP=$JOBOP ITEM=$ITEM NOTE=$NOTE
663}
664
665function KILL # [ signal ]
666{
667	RUN || return
668	JOBOP=$2
669	[[ $JOBOP ]] || JOBOP=KILL
670	ITEM=$LINE
671	NOTE="$(print -r -f '%q ' -- $JOBOP)"
672}
673
674function CD
675{
676	RUN
677	if	[[ $GROUP == $TEST_select ]] && (( ! COND_SKIP[COND] ))
678	then	mkdir -p "$@" && cd "$@" || FATAL cannot initialize working directory "$@"
679	fi
680}
681
682function EXPORT
683{
684	typeset x n v
685	if	[[ $GROUP == INIT ]]
686	then	for x
687		do	n=${x%%=*}
688			v=${x#*=}
689			ENVIRON[ENVIRONS++]=$n="'$v'"
690		done
691	else	RUN
692		if	[[ $GROUP != $TEST_select ]] || (( COND_SKIP[COND] ))
693		then	return
694		fi
695		for x
696		do	n=${x%%=*}
697			v=${x#*=}
698			EXPORT[EXPORTS++]=$n="'$v'"
699		done
700	fi
701}
702
703function FLUSH
704{
705	if	[[ $GROUP != $TEST_select ]] || (( COND_SKIP[COND] ))
706	then	return
707	fi
708	if	((SUBTESTS++))
709	then	RUN
710	fi
711}
712
713function PROG # cmd arg ...
714{
715	typeset command args
716	if	[[ $GROUP != $TEST_select ]] || (( COND_SKIP[COND] ))
717	then	return
718	fi
719	ITEM=$LINE
720	NOTE="$(print -r -f '%q ' -- "$@")"
721	COMMAND_ORIG=$COMMAND
722	COMMAND=$1
723	shift
724	ARGS_ORIG=(${ARGS[@]})
725	ARGS=("$@")
726}
727
728function NOTE # description
729{
730	NOTE=$*
731}
732
733function IO # [ PIPE ] INPUT|OUTPUT|ERROR [-f*|-n] file|- data ...
734{
735	typeset op i v f file type x
736	if	[[ $GROUP != $TEST_select ]] || (( COND_SKIP[COND] ))
737	then	return
738	fi
739	[[ $UMASK != $UMASK_ORIG ]] && umask $UMASK_ORIG
740	while	:
741	do	case $1 in
742		FIFO|PIPE)	type=$1; shift ;;
743		*)		break ;;
744		esac
745	done
746	op=$1
747	shift
748	[[ $type ]] && TYPE[$op]=$type
749	FILTER[$op]=
750	file=$TWD/$op
751	while	:
752	do	case $1 in
753		-x)	x=1
754			shift
755			;;
756		-e)	(( $# > 1 )) && shift
757			FILTER[$op]=$1
758			shift
759			;;
760		-e*)	FILTER[$op]=${1#-e}
761			shift
762			;;
763		-f*|-n)	f=$1
764			shift
765			;;
766		*)	break
767			;;
768		esac
769	done
770	case $# in
771	0)	;;
772	*)	case $1 in
773		-)	;;
774		*)	file=$1
775			eval i='$'$op
776			case " $i " in
777			*" $file "*)
778				;;
779			*)	eval $op='"$'$op' $file"'
780				;;
781			esac
782			;;
783		esac
784		shift
785		;;
786	esac
787	case " $IGNORE " in
788	*" $file "*)
789		for i in $IGNORE
790		do	case $i in
791			$file)	;;
792			*)	v="$v $i" ;;
793			esac
794		done
795		IGNORE=$v
796		;;
797	esac
798	FILE[$op]=$file
799	case $op in
800	OUTPUT|ERROR)
801		file=$file.ex
802		if	[[ $file != /* ]]
803		then	file=$TWD/$file
804		fi
805		;;
806	esac
807	#unset SAME[$op]
808	SAME[$op]=
809	if	[[ $file == /* ]]
810	then	RM $file.sav
811	else	RM $TWD/$file.sav
812	fi
813	if	[[ $file == */* ]]
814	then	mkdir -p ${file%/*}
815	fi
816	if	[[ $file != */ ]]
817	then	if	[[ $type == FIFO ]]
818		then	rm -f $file
819			mkfifo $file
820		fi
821		if	[[ ${TYPE[$op]} != FIFO ]]
822		then	if	[[ $JOBOP ]]
823			then	case $#:$f in
824				0:)	;;
825				*:-f)	printf -- "$@" ;;
826				*:-f*)	printf -- "${f#-f}""$@" ;;
827				*)	print $f -r -- "$@" ;;
828				esac >> $file
829			else	case $#:$f in
830				0:)	;;
831				*:-f)	printf -- "$@" ;;
832				*:-f*)	printf -- "${f#-f}""$@" ;;
833				*)	print $f -r -- "$@" ;;
834				esac > $file
835			fi
836		elif	[[ $#:$f != 0: ]]
837		then	case $#:$f in
838			*:-f)	printf -- "$@" ;;
839			*:-f*)	printf -- "${f#-f}""$@" ;;
840			*)	print $f -r -- "$@" ;;
841			esac >> $file &
842		fi
843		if	[[ $x ]]
844		then	chmod +x $file
845		fi
846	fi
847}
848
849function INPUT # file|- data ...
850{
851	IO $0 "$@"
852}
853
854function COPY # from to
855{
856	if	[[ $GROUP != $TEST_select ]] || (( COND_SKIP[COND] ))
857	then	return
858	fi
859	COPY="$COPY $@"
860}
861
862function MOVE # from to
863{
864	typeset f
865	if	[[ $GROUP != $TEST_select ]] || (( COND_SKIP[COND] ))
866	then	return
867	fi
868	for f
869	do	case $f in
870		INPUT|OUTPUT|ERROR)
871			f=$TWD/$f
872			;;
873		/*)	;;
874		*)	f=$PWD/$f
875			;;
876		esac
877		MOVE="$MOVE $f"
878	done
879}
880
881function SAME # new old
882{
883	typeset i file v
884	if	[[ $GROUP != $TEST_select ]] || (( COND_SKIP[COND] ))
885	then	return
886	fi
887	case $# in
888	2)	case $1 in
889		INPUT)	cat $2 > $1; return ;;
890		esac
891		SAME[$1]=$2
892		file=$1
893		COMPARE="$COMPARE $1"
894		;;
895	3)	SAME[$2]=$3
896		file=$2
897		eval i='$'$1
898		case " $i " in
899		*" $2 "*)
900			;;
901		*)	eval $1='"$'$1' $2"'
902			;;
903		esac
904		COMPARE="$COMPARE $2"
905		;;
906	esac
907	case " $IGNORE " in
908	*" $file "*)
909		for i in $IGNORE
910		do	case $i in
911			$file)	;;
912			*)	v="$v $i" ;;
913			esac
914		done
915		IGNORE=$v
916		;;
917	esac
918}
919
920function OUTPUT # file|- data ...
921{
922	IO $0 "$@"
923}
924
925function ERROR # file|- data ...
926{
927	IO $0 "$@"
928}
929
930function RM # rm(1) args
931{
932	if	[[ ! $TEST_rmu ]]
933	then	chmod -R u+rwx "$@" >/dev/null 2>&1
934	fi
935	rm $TEST_rmu $TEST_rmflags "$@"
936}
937
938function REMOVE # file ...
939{
940	typeset i
941	for i
942	do	RM $i $i.sav
943	done
944}
945
946function IGNORE # file ...
947{
948	typeset i
949	for i
950	do	case $i in
951		INPUT|OUTPUT|ERROR)
952			i=$TWD/$i
953			;;
954		esac
955		case " $IGNORE " in
956		*" $i "*)
957			;;
958		*)	IGNORE="$IGNORE $i"
959			;;
960		esac
961	done
962}
963
964function KEEP # pattern ...
965{
966	typeset i
967	for i
968	do	case $KEEP in
969		"")	KEEP="$i" ;;
970		*)	KEEP="$KEEP|$i" ;;
971		esac
972	done
973}
974
975function DIAGNOSTICS # [ 1 | 0 ]
976{
977	case $#:$1 in
978	0:|1:1)	DIAGNOSTICS=1
979		EXIT='*'
980		;;
981	1:|1:0)	DIAGNOSTICS=""
982		EXIT=0
983		;;
984	*)	DIAGNOSTICS=$1
985		EXIT='*'
986		;;
987	esac
988}
989
990function IGNORESPACE
991{
992	: ${IGNORESPACE=-b}
993}
994
995function EXIT # status
996{
997	EXIT=$1
998}
999
1000function INFO # info description
1001{
1002	typeset -R15 info=$1
1003	if	[[ ! $1 ]]
1004	then	info=no
1005	fi
1006	shift
1007	if	[[ ! $TEST_quiet ]]
1008	then	print -r -u2 "$info " "$@"
1009	fi
1010}
1011
1012function COMMAND # arg ...
1013{
1014	typeset input
1015	((TESTS++))
1016	case " ${ENVIRON[*]} ${EXPORT[*]}" in
1017	*' 'LC_ALL=*)
1018		;;
1019	*' 'LC_+([A-Z])=*)
1020		EXPORT[EXPORTS++]="LC_ALL="
1021		;;
1022	esac
1023	if	[[ $TEST_keep ]]
1024	then	(
1025		PS4=''
1026		set -x
1027		print -r -- "${ENVIRON[@]}" "${EXPORT[@]}" "PATH=$PATH" $COMMAND "$@"
1028		) 2>&1 >/dev/null |
1029		sed -e 's,^print -r -- ,,' -e 's,$, "$@",' >$TWD/COMMAND
1030		chmod +x $TWD/COMMAND
1031	fi
1032	if	[[ $UMASK != $UMASK_ORIG ]]
1033	then	: >$TWD/ERROR
1034		umask $UMASK
1035	fi
1036	if	[[ ${TYPE[INPUT]} == FIFO && ${FILE[INPUT]} == */INPUT ]]
1037	then	input="< ${FILE[INPUT]}"
1038	fi
1039	if	[[ $TEST_trace ]]
1040	then	set +x
1041		eval print -u2 "$PS4" "${ENVIRON[@]}" "${EXPORT[@]}" PATH='$PATH' '$'COMMAND '"$@"' '$input' '"2>$TWD/ERROR"' '"${JOBPID:+&}"'
1042	fi
1043	eval "${ENVIRON[@]}" "${EXPORT[@]}" PATH='$PATH' '$'COMMAND '"$@"' $input "2>$TWD/ERROR" "${JOBPID:+&}"
1044	STATUS=$?
1045	[[ $TEST_trace ]] && set -x
1046	if	[[ $JOBPID ]]
1047	then	JOBPID=$!
1048	fi
1049	[[ $UMASK != $UMASK_ORIG ]] && umask $UMASK_ORIG
1050	return $STATUS
1051}
1052
1053function RESULTS # pipe*
1054{
1055	typeset i j k s failed ignore io op
1056	if	[[ $1 ]]
1057	then	io="$1 "
1058	fi
1059	[[ $JOBOP || $JOBPID || $JOBSTATUS ]] && sleep 1
1060	for i in $COMPARE $TWD/OUTPUT $TWD/ERROR
1061	do	case " $IGNORE $ignore $MOVE " in
1062		*" $i "*)	continue ;;
1063		esac
1064		ignore="$ignore $i"
1065		op=${i##*/}
1066		if	[[ ${FILTER[$op]} ]]
1067		then	eval "{ ${FILTER[$op]} ;} < $i > $i.fi"
1068			mv $i.fi $i
1069		fi
1070		j=${SAME[$op]}
1071		if	[[ ! $j ]]
1072		then	if	[[ $i == /* ]]
1073			then	k=$i
1074			else	k=$TWD/$i
1075			fi
1076			for s in ex sav err
1077			do	[[ -f $k.$s ]] && break
1078			done
1079			j=$k.$s
1080		fi
1081		if	[[ "$DIAGNOSTICS" && $i == */ERROR ]]
1082		then	if	[[ $STATUS == 0 && ! -s $TWD/ERROR || $DIAGNOSTICS != 1 && $(<$i) != $DIAGNOSTICS ]]
1083			then	failed=$failed${failed:+,}DIAGNOSTICS
1084				if	[[ $TEST_verbose && $DIAGNOSTICS != 1 ]]
1085				then	print -u2 "	===" "diagnostic pattern '$DIAGNOSTICS' did not match" ${i#$TWD/} "==="
1086					cat $i >&2
1087				fi
1088			fi
1089			continue
1090		fi
1091		diff $IGNORESPACE $i $j >$i.diff 2>&1
1092		if	[[ -s $i.diff ]]
1093		then	failed=$failed${failed:+,}${i#$TWD/}
1094			if	[[ $TEST_verbose ]]
1095			then	print -u2 "	===" diff $IGNORESPACE ${i#$TWD/} "<actual >expected ==="
1096				cat $i.diff >&2
1097			fi
1098		fi
1099	done
1100	if	[[ $JOBOP ]]
1101	then	if	[[ $JOBPID ]] && ! kill -0 $JOBPID 2>/dev/null
1102		then	wait $JOBPID
1103			JOBSTATUS=$?
1104			JOBPID=
1105		fi
1106#print -u2 AHA#$LINENO JOBOP=$JOBOP JOBPID=$JOBPID JOBSTATUS=$JOBSTATUS
1107		case $JOBOP in
1108		CONTINUE)
1109			if	[[ ! $JOBPID ]]
1110			then	failed=$failed${failed:+,}EXITED
1111			fi
1112			;;
1113		EXITED) if	[[ $JOBPID ]]
1114			then	failed=$failed${failed:+,}RUNNING
1115			fi
1116			;;
1117		*)	if	[[ ! $JOBPID ]]
1118			then	failed=$failed${failed:+,}EXITED
1119			fi
1120			if	! kill -$JOBOP $JOBPID 2>/dev/null
1121			then	failed=$failed${failed:+,}KILL-$JOBOP
1122			fi
1123			;;
1124		esac
1125		JOBOP=
1126	fi
1127	if	[[ ! $failed && $STATUS != $EXIT ]]
1128	then	failed="exit code $EXIT expected -- got $STATUS"
1129	fi
1130	if	[[ $failed ]]
1131	then	((ERRORS++))
1132		if	[[ ! $TEST_quiet ]]
1133		then	SHOW="FAILED ${io}[ $failed ] $NOTE"
1134			print -r -u2 "	$SHOW"
1135		fi
1136		if	[[ $TEST_keep ]]
1137		then	GROUP=FINI
1138			exit
1139		fi
1140	elif	[[ ! $TEST_quiet ]]
1141	then	SHOW=$NOTE
1142		print -r -u2 "	$SHOW"
1143	fi
1144}
1145
1146function SET # [no]name[=value]
1147{
1148	typeset i r
1149	if	[[ $TEST ]]
1150	then	RUN
1151	fi
1152	for i
1153	do	if	[[ $i == - ]]
1154		then	r=1
1155		elif	[[ $i == + ]]
1156		then	r=
1157		else	if	[[ $i == no?* ]]
1158			then	i=${i#no}
1159				v=
1160			elif	[[ $i == *=* ]]
1161			then	v=${i#*=}
1162				if	[[ $v == 0 ]]
1163				then	v=
1164				fi
1165				i=${i%%=*}
1166			else	v=1
1167			fi
1168			i=${i//-/_}
1169			if	[[ $r ]]
1170			then	READONLY[$i]=1
1171			elif	[[ ${READONLY[$i]} ]]
1172			then	continue
1173			fi
1174			eval TEST_$i=$v
1175		fi
1176	done
1177}
1178
1179function VIEW # var [ file ]
1180{
1181	nameref var=$1
1182	typeset i bwd file pwd view root offset
1183	if	[[ $var ]]
1184	then	return 0
1185	fi
1186	case $# in
1187	1)	file=$1 ;;
1188	*)	file=$2 ;;
1189	esac
1190	pwd=${TWD%/*}
1191	bwd=${PMP%/*}
1192	if	[[ -r $file ]]
1193	then	if	[[ ! -d $file ]]
1194		then	var=$PWD/$file
1195			return 0
1196		fi
1197		for i in $file/*
1198		do	if	[[ -r $i ]]
1199			then	var=$PWD/$file
1200				return 0
1201			fi
1202			break
1203		done
1204	fi
1205	for view in ${VIEWS[@]}
1206	do	case $view in
1207		/*)	;;
1208		*)	view=$pwd/$view ;;
1209		esac
1210		case $offset in
1211		'')	case $pwd in
1212			$view/*)	offset=${pwd#$view} ;;
1213			*)		offset=${bwd#$view} ;;
1214			esac
1215			;;
1216		esac
1217		if	[[ -r $view$offset/$file ]]
1218		then	if	[[ ! -d $view$offset/$file ]]
1219			then	var=$view$offset/$file
1220				return 0
1221			fi
1222			for i in $view$offset/$file/*
1223			do	if	[[ -f $i ]]
1224				then	var=$view$offset/$file
1225					return 0
1226				fi
1227				break
1228			done
1229		fi
1230	done
1231	var=
1232	return 1
1233}
1234
1235function INCLUDE # file ...
1236{
1237	typeset f v x
1238	for f
1239	do	if	VIEW v $f || [[ $PREFIX && $f != /* ]] && VIEW v $PREFIX$f
1240		then	x=$x$'\n'". $v"
1241		else	FATAL $f: not found
1242		fi
1243	done
1244	[[ $x ]] && trap "$x" 0
1245}
1246
1247function UMASK # [ mask ]
1248{
1249	if	(( $# ))
1250	then	UMASK=$1
1251	else	UMASK=$UMASK_ORIG
1252	fi
1253}
1254
1255function PIPE # INPUT|OUTPUT|ERROR file|- data ...
1256{
1257	IO $0 "$@"
1258}
1259
1260function FIFO # INPUT|OUTPUT|ERROR file|- data ...
1261{
1262	IO $0 "$@"
1263}
1264
1265function IF # command(s) [note]
1266{
1267	[[ $GROUP == $TEST_select ]] || return
1268	RUN
1269	(( COND++ ))
1270	COND_LINE[COND]=$LINE
1271	if	(( COND > 1 && COND_SKIP[COND-1] ))
1272	then	(( COND_KEPT[COND] = 1 ))
1273		(( COND_SKIP[COND] = 1 ))
1274	elif	eval "{ $1 ;} >/dev/null 2>&1"
1275	then	(( COND_KEPT[COND] = 1 ))
1276		(( COND_SKIP[COND] = 0 ))
1277		[[ $2 && ! $TEST_quiet ]] && print -u2 "NOTE	$2"
1278	else	(( COND_KEPT[COND] = 0 ))
1279		(( COND_SKIP[COND] = 1 ))
1280	fi
1281}
1282
1283function ELIF # command(s) [note]
1284{
1285	[[ $GROUP == $TEST_select ]] || return
1286	RUN
1287	if	(( COND <= 0 ))
1288	then	FATAL line $LINE: no matching IF for ELIF
1289	fi
1290	if	(( COND_KEPT[COND] ))
1291	then	(( COND_SKIP[COND] = 0 ))
1292	elif	eval "$* > /dev/null 2>&1"
1293	then	(( COND_KEPT[COND] = 1 ))
1294		(( COND_SKIP[COND] = 0 ))
1295		[[ $2 && ! $TEST_quiet ]] && print -u2 "NOTE	$2"
1296	else	(( COND_SKIP[COND] = 1 ))
1297	fi
1298}
1299
1300function ELSE # [note]
1301{
1302	[[ $GROUP == $TEST_select ]] || return
1303	RUN
1304	if	(( COND <= 0 ))
1305	then	FATAL line $LINE: no matching IF for ELSE
1306	fi
1307	if	(( COND_KEPT[COND] ))
1308	then	(( COND_SKIP[COND] = 1 ))
1309	else	(( COND_KEPT[COND] = 1 ))
1310		(( COND_SKIP[COND] = 0 ))
1311		[[ $1 && ! $TEST_quiet ]] && print -u2 "NOTE	$1"
1312	fi
1313}
1314
1315function FI
1316{
1317	[[ $GROUP == $TEST_select ]] || return
1318	RUN
1319	if	(( COND <= 0 ))
1320	then	FATAL line $LINE: no matching IF for FI on line $LINE
1321	fi
1322	(( ! COND_KEPT[COND] )) && [[ $1 && ! $TEST_quiet ]] && print -u2 "NOTE	$1"
1323	(( COND-- ))
1324}
1325
1326# main
1327
1328integer ERRORS=0 ENVIRONS=0 EXPORTS=0 TESTS=0 SUBTESTS=0 LINE=0 TESTLINE=0
1329integer ITEM=0 LASTITEM=0 COND=0 UNIT_READONLY=0 COUNT
1330typeset ARGS COMMAND COPY DIAGNOSTICS ERROR EXEC FLUSHED=0 GROUP=INIT
1331typeset IGNORE INPUT KEEP OUTPUT TEST SOURCE MOVE NOTE UMASK UMASK_ORIG
1332typeset ARGS_ORIG COMMAND_ORIG TITLE UNIT ARGV PREFIX OFFSET IGNORESPACE
1333typeset COMPARE MAIN JOBPID='' JOBSTATUS=''
1334typeset TEST_file TEST_keep TEST_pipe_input TEST_pipe_io TEST_pipe_output TEST_local
1335typeset TEST_quiet TEST_regular=1 TEST_rmflags='-rf --' TEST_rmu TEST_select
1336
1337typeset -A SAME VIEWS FILE TYPE READONLY FILTER
1338typeset -a COND_LINE COND_SKIP COND_KEPT ENVIRON EXPORT
1339typeset -Z LAST=00
1340
1341unset FIGNORE
1342
1343while	getopts -a $command "$USAGE" OPT
1344do	case $OPT in
1345	b)	(( $OPTARG )) && IGNORESPACE=-b
1346		;;
1347	i)	SET - pipe-input=$OPTARG
1348		;;
1349	k)	SET - keep=$OPTARG
1350		;;
1351	l)	SET - local
1352		;;
1353	o)	SET - pipe-output=$OPTARG
1354		;;
1355	p)	SET - pipe-io=$OPTARG
1356		;;
1357	q)	SET - quiet=$OPTARG
1358		;;
1359	r)	SET - regular=$OPTARG
1360		;;
1361	t)	if	[[ $TEST_select ]]
1362		then	TEST_select="$TEST_select|${OPTARG//,/\|}"
1363		else	TEST_select="${OPTARG//,/\|}"
1364		fi
1365		;;
1366	x)	SET - trace=$OPTARG
1367		;;
1368	v)	SET - verbose=$OPTARG
1369		;;
1370	*)	GROUP=FINI
1371		exit 2
1372		;;
1373	esac
1374done
1375shift $OPTIND-1
1376case $# in
13770)	FATAL test unit name omitted ;;
1378esac
1379export COLUMNS=80
1380SOURCE=$PWD
1381PATH=$SOURCE:${PATH#?(.):}
1382PATH=${PATH%%:?(.)}:/bin:/usr/bin
1383UNIT=$1
1384shift
1385if	[[ -f $UNIT && ! -x $UNIT ]]
1386then	REGRESS=$UNIT
1387else	REGRESS=${UNIT%.tst}
1388	REGRESS=$REGRESS.tst
1389	[[ -f $REGRESS ]] || FATAL $REGRESS: regression tests not found
1390fi
1391UNIT=${UNIT##*/}
1392UNIT=${UNIT%.tst}
1393MAIN=$UNIT
1394if	[[ $VPATH ]]
1395then	set -A VIEWS ${VPATH//:/' '}
1396	OFFSET=${SOURCE#${VIEWS[0]}}
1397	if	[[ $OFFSET ]]
1398	then	OFFSET=${OFFSET#/}/
1399	fi
1400fi
1401if	[[ $REGRESS == */* ]]
1402then	PREFIX=${REGRESS%/*}
1403	if	[[ ${#VIEWS[@]} ]]
1404	then	for i in ${VIEWS[@]}
1405		do	PREFIX=${PREFIX#$i/}
1406		done
1407	fi
1408	PREFIX=${PREFIX#$OFFSET}
1409	if	[[ $PREFIX ]]
1410	then	PREFIX=$PREFIX/
1411	fi
1412fi
1413TWD=$PWD/$UNIT.tmp
1414PMP=$(pwd -P)/$UNIT.tmp
1415UMASK_ORIG=$(umask)
1416UMASK=$UMASK_ORIG
1417ARGV=("$@")
1418if	[[ ${ARGV[0]} && ${ARGV[0]} != [-+]* ]]
1419then	UNIT "${ARGV[@]}"
1420	UNIT_READONLY=1
1421fi
1422trap 'code=$?; CLEANUP $code' EXIT
1423if	[[ ! $TEST_select ]]
1424then	TEST_select="[0123456789]*"
1425fi
1426TEST_select="@($TEST_select|+(0))"
1427if	[[ $TEST_trace ]]
1428then	export PS4=':$LINENO: '
1429	typeset -ft $(typeset +f)
1430	set -x
1431fi
1432if	[[ $TEST_verbose ]]
1433then	typeset SHOW
1434else	typeset -L70 SHOW
1435fi
1436if	[[ ! $TEST_keep ]] && (ulimit -c 0) >/dev/null 2>&1
1437then	ulimit -c 0
1438fi
1439set --pipefail
1440
1441# some last minute shenanigans
1442
1443alias BODY='BODY=BODY; function BODY'
1444alias CONTINUE='LINE=$LINENO; CONTINUE'
1445alias DO='(( $ITEM != $FLUSHED )) && RUN DO; DO &&'
1446alias DONE='DONE=DONE; function DONE'
1447alias EXEC='LINE=$LINENO; EXEC'
1448alias EXITED='LINE=$LINENO; EXITED'
1449alias INIT='INIT=INIT; function INIT'
1450alias JOB='LINE=$LINENO; JOB'
1451alias KILL='LINE=$LINENO; KILL'
1452alias PROG='LINE=$LINENO; FLUSH; PROG'
1453alias TEST='TESTLINE=$LINENO; TEST'
1454alias IF='LINE=$LINENO; FLUSH; IF'
1455alias ELIF='LINE=$LINENO; FLUSH; ELIF'
1456alias ELSE='LINE=$LINENO; FLUSH; ELSE'
1457alias FI='LINE=$LINENO; FLUSH; FI'
1458
1459# do the tests
1460
1461. $REGRESS
1462RUN
1463GROUP=FINI
1464