1# tcltest.tcl --
2#
3#	This file contains support code for the Tcl test suite.  It
4#       defines the tcltest namespace and finds and defines the output
5#       directory, constraints available, output and error channels,
6#	etc. used by Tcl tests.  See the tcltest man page for more
7#	details.
8#
9#       This design was based on the Tcl testing approach designed and
10#       initially implemented by Mary Ann May-Pumphrey of Sun
11#	Microsystems.
12#
13# Copyright (c) 1994-1997 Sun Microsystems, Inc.
14# Copyright (c) 1998-1999 Scriptics Corporation.
15# Copyright (c) 2000 Ajuba Solutions
16# Contributions from Don Porter, NIST, 2002.  (not subject to US copyright)
17# All rights reserved.
18
19package require Tcl 8.5-		;# -verbose line uses [info frame]
20namespace eval tcltest {
21
22    # When the version number changes, be sure to update the pkgIndex.tcl file,
23    # and the install directory in the Makefiles.  When the minor version
24    # changes (new feature) be sure to update the man page as well.
25    variable Version 2.5.3
26
27    # Compatibility support for dumb variables defined in tcltest 1
28    # Do not use these.  Call [package provide Tcl] and [info patchlevel]
29    # yourself.  You don't need tcltest to wrap it for you.
30    variable version [package provide Tcl]
31    variable patchLevel [info patchlevel]
32
33##### Export the public tcltest procs; several categories
34    #
35    # Export the main functional commands that do useful things
36    namespace export cleanupTests loadTestedCommands makeDirectory \
37	makeFile removeDirectory removeFile runAllTests test
38
39    # Export configuration commands that control the functional commands
40    namespace export configure customMatch errorChannel interpreter \
41	    outputChannel testConstraint
42
43    # Export commands that are duplication (candidates for deprecation)
44    if {![package vsatisfies [package provide Tcl] 8.7-]} {
45	namespace export bytestring	;# dups [encoding convertfrom identity]
46    }
47    namespace export debug		;#	[configure -debug]
48    namespace export errorFile		;#	[configure -errfile]
49    namespace export limitConstraints	;#	[configure -limitconstraints]
50    namespace export loadFile		;#	[configure -loadfile]
51    namespace export loadScript		;#	[configure -load]
52    namespace export match		;#	[configure -match]
53    namespace export matchFiles		;#	[configure -file]
54    namespace export matchDirectories	;#	[configure -relateddir]
55    namespace export normalizeMsg	;#	application of [customMatch]
56    namespace export normalizePath	;#	[file normalize] (8.4)
57    namespace export outputFile		;#	[configure -outfile]
58    namespace export preserveCore	;#	[configure -preservecore]
59    namespace export singleProcess	;#	[configure -singleproc]
60    namespace export skip		;#	[configure -skip]
61    namespace export skipFiles		;#	[configure -notfile]
62    namespace export skipDirectories	;#	[configure -asidefromdir]
63    namespace export temporaryDirectory	;#	[configure -tmpdir]
64    namespace export testsDirectory	;#	[configure -testdir]
65    namespace export verbose		;#	[configure -verbose]
66    namespace export viewFile		;#	binary encoding [read]
67    namespace export workingDirectory	;#	[cd] [pwd]
68
69    # Export deprecated commands for tcltest 1 compatibility
70    namespace export getMatchingFiles mainThread restoreState saveState \
71	    threadReap
72
73    # tcltest::normalizePath --
74    #
75    #     This procedure resolves any symlinks in the path thus creating
76    #     a path without internal redirection. It assumes that the
77    #     incoming path is absolute.
78    #
79    # Arguments
80    #     pathVar - name of variable containing path to modify.
81    #
82    # Results
83    #     The path is modified in place.
84    #
85    # Side Effects:
86    #     None.
87    #
88    proc normalizePath {pathVar} {
89	upvar 1 $pathVar path
90	set oldpwd [pwd]
91	catch {cd $path}
92	set path [pwd]
93	cd $oldpwd
94	return $path
95    }
96
97##### Verification commands used to test values of variables and options
98    #
99    # Verification command that accepts everything
100    proc AcceptAll {value} {
101	return $value
102    }
103
104    # Verification command that accepts valid Tcl lists
105    proc AcceptList { list } {
106	return [lrange $list 0 end]
107    }
108
109    # Verification command that accepts a glob pattern
110    proc AcceptPattern { pattern } {
111	return [AcceptAll $pattern]
112    }
113
114    # Verification command that accepts integers
115    proc AcceptInteger { level } {
116	return [incr level 0]
117    }
118
119    # Verification command that accepts boolean values
120    proc AcceptBoolean { boolean } {
121	return [expr {$boolean && $boolean}]
122    }
123
124    # Verification command that accepts (syntactically) valid Tcl scripts
125    proc AcceptScript { script } {
126	if {![info complete $script]} {
127	    return -code error "invalid Tcl script: $script"
128	}
129	return $script
130    }
131
132    # Verification command that accepts (converts to) absolute pathnames
133    proc AcceptAbsolutePath { path } {
134	return [file join [pwd] $path]
135    }
136
137    # Verification command that accepts existing readable directories
138    proc AcceptReadable { path } {
139	if {![file readable $path]} {
140	    return -code error "\"$path\" is not readable"
141	}
142	return $path
143    }
144    proc AcceptDirectory { directory } {
145	set directory [AcceptAbsolutePath $directory]
146	if {![file exists $directory]} {
147	    return -code error "\"$directory\" does not exist"
148	}
149	if {![file isdir $directory]} {
150	    return -code error "\"$directory\" is not a directory"
151	}
152	return [AcceptReadable $directory]
153    }
154
155##### Initialize internal arrays of tcltest, but only if the caller
156    # has not already pre-initialized them.  This is done to support
157    # compatibility with older tests that directly access internals
158    # rather than go through command interfaces.
159    #
160    proc ArrayDefault {varName value} {
161	variable $varName
162	if {[array exists $varName]} {
163	    return
164	}
165	if {[info exists $varName]} {
166	    # Pre-initialized value is a scalar: destroy it!
167	    unset $varName
168	}
169	array set $varName $value
170    }
171
172    # save the original environment so that it can be restored later
173    ArrayDefault originalEnv [array get ::env]
174
175    # initialize numTests array to keep track of the number of tests
176    # that pass, fail, and are skipped.
177    ArrayDefault numTests [list Total 0 Passed 0 Skipped 0 Failed 0]
178
179    # createdNewFiles will store test files as indices and the list of
180    # files (that should not have been) left behind by the test files
181    # as values.
182    ArrayDefault createdNewFiles {}
183
184    # initialize skippedBecause array to keep track of constraints that
185    # kept tests from running; a constraint name of "userSpecifiedSkip"
186    # means that the test appeared on the list of tests that matched the
187    # -skip value given to the flag; "userSpecifiedNonMatch" means that
188    # the test didn't match the argument given to the -match flag; both
189    # of these constraints are counted only if tcltest::debug is set to
190    # true.
191    ArrayDefault skippedBecause {}
192
193    # initialize the testConstraints array to keep track of valid
194    # predefined constraints (see the explanation for the
195    # InitConstraints proc for more details).
196    ArrayDefault testConstraints {}
197
198##### Initialize internal variables of tcltest, but only if the caller
199    # has not already pre-initialized them.  This is done to support
200    # compatibility with older tests that directly access internals
201    # rather than go through command interfaces.
202    #
203    proc Default {varName value {verify AcceptAll}} {
204	variable $varName
205	if {![info exists $varName]} {
206	    variable $varName [$verify $value]
207	} else {
208	    variable $varName [$verify [set $varName]]
209	}
210    }
211
212    # Save any arguments that we might want to pass through to other
213    # programs.  This is used by the -args flag.
214    # FINDUSER
215    Default parameters {}
216
217    # Count the number of files tested (0 if runAllTests wasn't called).
218    # runAllTests will set testSingleFile to false, so stats will
219    # not be printed until runAllTests calls the cleanupTests proc.
220    # The currentFailure var stores the boolean value of whether the
221    # current test file has had any failures.  The failFiles list
222    # stores the names of test files that had failures.
223    Default numTestFiles 0 AcceptInteger
224    Default testSingleFile true AcceptBoolean
225    Default currentFailure false AcceptBoolean
226    Default failFiles {} AcceptList
227
228    # Tests should remove all files they create.  The test suite will
229    # check the current working dir for files created by the tests.
230    # filesMade keeps track of such files created using the makeFile and
231    # makeDirectory procedures.  filesExisted stores the names of
232    # pre-existing files.
233    #
234    # Note that $filesExisted lists only those files that exist in
235    # the original [temporaryDirectory].
236    Default filesMade {} AcceptList
237    Default filesExisted {} AcceptList
238    proc FillFilesExisted {} {
239	variable filesExisted
240
241	# Save the names of files that already exist in the scratch directory.
242	foreach file [glob -nocomplain -directory [temporaryDirectory] *] {
243	    lappend filesExisted [file tail $file]
244	}
245
246	# After successful filling, turn this into a no-op.
247	proc FillFilesExisted args {}
248    }
249
250    # Kept only for compatibility
251    Default constraintsSpecified {} AcceptList
252    trace add variable constraintsSpecified read [namespace code {
253	    set constraintsSpecified [array names testConstraints] ;#}]
254
255    # tests that use threads need to know which is the main thread
256    Default mainThread 1
257    variable mainThread
258    if {[info commands thread::id] ne {}} {
259	set mainThread [thread::id]
260    } elseif {[info commands testthread] ne {}} {
261	set mainThread [testthread id]
262    }
263
264    # Set workingDirectory to [pwd]. The default output directory for
265    # Tcl tests is the working directory.  Whenever this value changes
266    # change to that directory.
267    variable workingDirectory
268    trace add variable workingDirectory write \
269	    [namespace code {cd $workingDirectory ;#}]
270
271    Default workingDirectory [pwd] AcceptAbsolutePath
272    proc workingDirectory { {dir ""} } {
273	variable workingDirectory
274	if {[llength [info level 0]] == 1} {
275	    return $workingDirectory
276	}
277	set workingDirectory [AcceptAbsolutePath $dir]
278    }
279
280    # Set the location of the execuatble
281    Default tcltest [info nameofexecutable]
282    trace add variable tcltest write [namespace code {testConstraint stdio \
283	    [eval [ConstraintInitializer stdio]] ;#}]
284
285    # save the platform information so it can be restored later
286    Default originalTclPlatform [array get ::tcl_platform]
287
288    # If a core file exists, save its modification time.
289    if {[file exists [file join [workingDirectory] core]]} {
290	Default coreModTime \
291		[file mtime [file join [workingDirectory] core]]
292    }
293
294    # stdout and stderr buffers for use when we want to store them
295    Default outData {}
296    Default errData {}
297
298    # keep track of test level for nested test commands
299    variable testLevel 0
300
301    # the variables and procs that existed when saveState was called are
302    # stored in a variable of the same name
303    Default saveState {}
304
305    # Internationalization support -- used in [SetIso8859_1_Locale] and
306    # [RestoreLocale]. Those commands are used in cmdIL.test.
307
308    if {![info exists [namespace current]::isoLocale]} {
309	variable isoLocale fr
310	switch -- $::tcl_platform(platform) {
311	    "unix" {
312
313		# Try some 'known' values for some platforms:
314
315		switch -exact -- $::tcl_platform(os) {
316		    "FreeBSD" {
317			set isoLocale fr_FR.ISO_8859-1
318		    }
319		    HP-UX {
320			set isoLocale fr_FR.iso88591
321		    }
322		    Linux -
323		    IRIX {
324			set isoLocale fr
325		    }
326		    default {
327
328			# Works on SunOS 4 and Solaris, and maybe
329			# others...  Define it to something else on your
330			# system if you want to test those.
331
332			set isoLocale iso_8859_1
333		    }
334		}
335	    }
336	    "windows" {
337		set isoLocale French
338	    }
339	}
340    }
341
342    variable ChannelsWeOpened; array set ChannelsWeOpened {}
343    # output goes to stdout by default
344    Default outputChannel stdout
345    proc outputChannel { {filename ""} } {
346	variable outputChannel
347	variable ChannelsWeOpened
348
349	# This is very subtle and tricky, so let me try to explain.
350	# (Hopefully this longer comment will be clear when I come
351	# back in a few months, unlike its predecessor :) )
352	#
353	# The [outputChannel] command (and underlying variable) have to
354	# be kept in sync with the [configure -outfile] configuration
355	# option ( and underlying variable Option(-outfile) ).  This is
356	# accomplished with a write trace on Option(-outfile) that will
357	# update [outputChannel] whenver a new value is written.  That
358	# much is easy.
359	#
360	# The trick is that in order to maintain compatibility with
361	# version 1 of tcltest, we must allow every configuration option
362	# to get its inital value from command line arguments.  This is
363	# accomplished by setting initial read traces on all the
364	# configuration options to parse the command line option the first
365	# time they are read.  These traces are cancelled whenever the
366	# program itself calls [configure].
367	#
368	# OK, then so to support tcltest 1 compatibility, it seems we want
369	# to get the return from [outputFile] to trigger the read traces,
370	# just in case.
371	#
372	# BUT!  A little known feature of Tcl variable traces is that
373	# traces are disabled during the handling of other traces.  So,
374	# if we trigger read traces on Option(-outfile) and that triggers
375	# command line parsing which turns around and sets an initial
376	# value for Option(-outfile) -- <whew!> -- the write trace that
377	# would keep [outputChannel] in sync with that new initial value
378	# would not fire!
379	#
380	# SO, finally, as a workaround, instead of triggering read traces
381	# by invoking [outputFile], we instead trigger the same set of
382	# read traces by invoking [debug].  Any command that reads a
383	# configuration option would do.  [debug] is just a handy one.
384	# The end result is that we support tcltest 1 compatibility and
385	# keep outputChannel and -outfile in sync in all cases.
386	debug
387
388	if {[llength [info level 0]] == 1} {
389	    return $outputChannel
390	}
391	if {[info exists ChannelsWeOpened($outputChannel)]} {
392	    close $outputChannel
393	    unset ChannelsWeOpened($outputChannel)
394	}
395	switch -exact -- $filename {
396	    stderr -
397	    stdout {
398		set outputChannel $filename
399	    }
400	    default {
401		set outputChannel [open $filename a]
402		set ChannelsWeOpened($outputChannel) 1
403
404		# If we created the file in [temporaryDirectory], then
405		# [cleanupTests] will delete it, unless we claim it was
406		# already there.
407		set outdir [normalizePath [file dirname \
408			[file join [pwd] $filename]]]
409		if {$outdir eq [temporaryDirectory]} {
410		    variable filesExisted
411		    FillFilesExisted
412		    set filename [file tail $filename]
413		    if {$filename ni $filesExisted} {
414			lappend filesExisted $filename
415		    }
416		}
417	    }
418	}
419	return $outputChannel
420    }
421
422    # errors go to stderr by default
423    Default errorChannel stderr
424    proc errorChannel { {filename ""} } {
425	variable errorChannel
426	variable ChannelsWeOpened
427
428	# This is subtle and tricky.  See the comment above in
429	# [outputChannel] for a detailed explanation.
430	debug
431
432	if {[llength [info level 0]] == 1} {
433	    return $errorChannel
434	}
435	if {[info exists ChannelsWeOpened($errorChannel)]} {
436	    close $errorChannel
437	    unset ChannelsWeOpened($errorChannel)
438	}
439	switch -exact -- $filename {
440	    stderr -
441	    stdout {
442		set errorChannel $filename
443	    }
444	    default {
445		set errorChannel [open $filename a]
446		set ChannelsWeOpened($errorChannel) 1
447
448		# If we created the file in [temporaryDirectory], then
449		# [cleanupTests] will delete it, unless we claim it was
450		# already there.
451		set outdir [normalizePath [file dirname \
452			[file join [pwd] $filename]]]
453		if {$outdir eq [temporaryDirectory]} {
454		    variable filesExisted
455		    FillFilesExisted
456		    set filename [file tail $filename]
457		    if {$filename ni $filesExisted} {
458			lappend filesExisted $filename
459		    }
460		}
461	    }
462	}
463	return $errorChannel
464    }
465
466##### Set up the configurable options
467    #
468    # The configurable options of the package
469    variable Option; array set Option {}
470
471    # Usage strings for those options
472    variable Usage; array set Usage {}
473
474    # Verification commands for those options
475    variable Verify; array set Verify {}
476
477    # Initialize the default values of the configurable options that are
478    # historically associated with an exported variable.  If that variable
479    # is already set, support compatibility by accepting its pre-set value.
480    # Use [trace] to establish ongoing connection between the deprecated
481    # exported variable and the modern option kept as a true internal var.
482    # Also set up usage string and value testing for the option.
483    proc Option {option value usage {verify AcceptAll} {varName {}}} {
484	variable Option
485	variable Verify
486	variable Usage
487	variable OptionControlledVariables
488	variable DefaultValue
489	set Usage($option) $usage
490	set Verify($option) $verify
491	set DefaultValue($option) $value
492	if {[catch {$verify $value} msg]} {
493	    return -code error $msg
494	} else {
495	    set Option($option) $msg
496	}
497	if {[string length $varName]} {
498	    variable $varName
499	    if {[info exists $varName]} {
500		if {[catch {$verify [set $varName]} msg]} {
501		    return -code error $msg
502		} else {
503		    set Option($option) $msg
504		}
505		unset $varName
506	    }
507	    namespace eval [namespace current] \
508	    	    [list upvar 0 Option($option) $varName]
509	    # Workaround for Bug (now Feature Request) 572889.  Grrrr....
510	    # Track all the variables tied to options
511	    lappend OptionControlledVariables $varName
512	    # Later, set auto-configure read traces on all
513	    # of them, since a single trace on Option does not work.
514	    proc $varName {{value {}}} [subst -nocommands {
515		if {[llength [info level 0]] == 2} {
516		    Configure $option [set value]
517		}
518		return [Configure $option]
519	    }]
520	}
521    }
522
523    proc MatchingOption {option} {
524	variable Option
525	set match [array names Option $option*]
526	switch -- [llength $match] {
527	    0 {
528		set sorted [lsort [array names Option]]
529		set values [join [lrange $sorted 0 end-1] ", "]
530		append values ", or [lindex $sorted end]"
531		return -code error "unknown option $option: should be\
532			one of $values"
533	    }
534	    1 {
535		return [lindex $match 0]
536	    }
537	    default {
538		# Exact match trumps ambiguity
539		if {$option in $match} {
540		    return $option
541		}
542		set values [join [lrange $match 0 end-1] ", "]
543		append values ", or [lindex $match end]"
544		return -code error "ambiguous option $option:\
545			could match $values"
546	    }
547	}
548    }
549
550    proc EstablishAutoConfigureTraces {} {
551	variable OptionControlledVariables
552	foreach varName [concat $OptionControlledVariables Option] {
553	    variable $varName
554	    trace add variable $varName read [namespace code {
555		    ProcessCmdLineArgs ;#}]
556	}
557    }
558
559    proc RemoveAutoConfigureTraces {} {
560	variable OptionControlledVariables
561	foreach varName [concat $OptionControlledVariables Option] {
562	    variable $varName
563	    foreach pair [trace info variable $varName] {
564		lassign $pair op cmd
565		if {($op eq "read") &&
566			[string match *ProcessCmdLineArgs* $cmd]} {
567		    trace remove variable $varName $op $cmd
568		}
569	    }
570	}
571	# Once the traces are removed, this can become a no-op
572	proc RemoveAutoConfigureTraces {} {}
573    }
574
575    proc Configure args {
576	variable Option
577	variable Verify
578	set n [llength $args]
579	if {$n == 0} {
580	    return [lsort [array names Option]]
581	}
582	if {$n == 1} {
583	    if {[catch {MatchingOption [lindex $args 0]} option]} {
584		return -code error $option
585	    }
586	    return $Option($option)
587	}
588	while {[llength $args] > 1} {
589	    if {[catch {MatchingOption [lindex $args 0]} option]} {
590		return -code error $option
591	    }
592	    if {[catch {$Verify($option) [lindex $args 1]} value]} {
593		return -code error "invalid $option\
594			value \"[lindex $args 1]\": $value"
595	    }
596	    set Option($option) $value
597	    set args [lrange $args 2 end]
598	}
599	if {[llength $args]} {
600	    if {[catch {MatchingOption [lindex $args 0]} option]} {
601		return -code error $option
602	    }
603	    return -code error "missing value for option $option"
604	}
605    }
606    proc configure args {
607	if {[llength $args] > 1} {
608	    RemoveAutoConfigureTraces
609	}
610	set code [catch {Configure {*}$args} msg]
611	return -code $code $msg
612    }
613
614    proc AcceptVerbose { level } {
615	set level [AcceptList $level]
616	set levelMap {
617	    l list
618	    p pass
619	    b body
620	    s skip
621	    t start
622	    e error
623	    l line
624	    m msec
625	    u usec
626	}
627	set levelRegexp "^([join [dict values $levelMap] |])\$"
628	if {[llength $level] == 1} {
629	    if {![regexp $levelRegexp $level]} {
630		# translate single characters abbreviations to expanded list
631		set level [string map $levelMap [split $level {}]]
632	    }
633	}
634	set valid [list]
635	foreach v $level {
636	    if {[regexp $levelRegexp $v]} {
637		lappend valid $v
638	    }
639	}
640	return $valid
641    }
642
643    proc IsVerbose {level} {
644	variable Option
645	return [expr {$level in $Option(-verbose)}]
646    }
647
648    # Default verbosity is to show bodies of failed tests
649    Option -verbose {body error} {
650	Takes any combination of the values 'p', 's', 'b', 't', 'e' and 'l'.
651	Test suite will display all passed tests if 'p' is specified, all
652	skipped tests if 's' is specified, the bodies of failed tests if
653	'b' is specified, and when tests start if 't' is specified.
654	ErrorInfo is displayed if 'e' is specified. Source file line
655	information of failed tests is displayed if 'l' is specified.
656    } AcceptVerbose verbose
657
658    # Match and skip patterns default to the empty list, except for
659    # matchFiles, which defaults to all .test files in the
660    # testsDirectory and matchDirectories, which defaults to all
661    # directories.
662    Option -match * {
663	Run all tests within the specified files that match one of the
664	list of glob patterns given.
665    } AcceptList match
666
667    Option -skip {} {
668	Skip all tests within the specified tests (via -match) and files
669	that match one of the list of glob patterns given.
670    } AcceptList skip
671
672    Option -file *.test {
673	Run tests in all test files that match the glob pattern given.
674    } AcceptPattern matchFiles
675
676    # By default, skip files that appear to be SCCS lock files.
677    Option -notfile l.*.test {
678	Skip all test files that match the glob pattern given.
679    } AcceptPattern skipFiles
680
681    Option -relateddir * {
682	Run tests in directories that match the glob pattern given.
683    } AcceptPattern matchDirectories
684
685    Option -asidefromdir {} {
686	Skip tests in directories that match the glob pattern given.
687    } AcceptPattern skipDirectories
688
689    # By default, don't save core files
690    Option -preservecore 0 {
691	If 2, save any core files produced during testing in the directory
692	specified by -tmpdir. If 1, notify the user if core files are
693	created.
694    } AcceptInteger preserveCore
695
696    # debug output doesn't get printed by default; debug level 1 spits
697    # up only the tests that were skipped because they didn't match or
698    # were specifically skipped.  A debug level of 2 would spit up the
699    # tcltest variables and flags provided; a debug level of 3 causes
700    # some additional output regarding operations of the test harness.
701    # The tcltest package currently implements only up to debug level 3.
702    Option -debug 0 {
703	Internal debug level
704    } AcceptInteger debug
705
706    proc SetSelectedConstraints args {
707	variable Option
708	foreach c $Option(-constraints) {
709	    testConstraint $c 1
710	}
711    }
712    Option -constraints {} {
713	Do not skip the listed constraints listed in -constraints.
714    } AcceptList
715    trace add variable Option(-constraints) write \
716	    [namespace code {SetSelectedConstraints ;#}]
717
718    # Don't run only the "-constraint" specified tests by default
719    proc ClearUnselectedConstraints args {
720	variable Option
721	variable testConstraints
722	if {!$Option(-limitconstraints)} {return}
723	foreach c [array names testConstraints] {
724	    if {$c ni $Option(-constraints)} {
725		testConstraint $c 0
726	    }
727	}
728    }
729    Option -limitconstraints 0 {
730	whether to run only tests with the constraints
731    } AcceptBoolean limitConstraints
732    trace add variable Option(-limitconstraints) write \
733	    [namespace code {ClearUnselectedConstraints ;#}]
734
735    # A test application has to know how to load the tested commands
736    # into the interpreter.
737    Option -load {} {
738	Specifies the script to load the tested commands.
739    } AcceptScript loadScript
740
741    # Default is to run each test file in a separate process
742    Option -singleproc 0 {
743	whether to run all tests in one process
744    } AcceptBoolean singleProcess
745
746    proc AcceptTemporaryDirectory { directory } {
747	set directory [AcceptAbsolutePath $directory]
748	if {![file exists $directory]} {
749	    file mkdir $directory
750	}
751	set directory [AcceptDirectory $directory]
752	if {![file writable $directory]} {
753	    if {[workingDirectory] eq $directory} {
754		# Special exception: accept the default value
755		# even if the directory is not writable
756		return $directory
757	    }
758	    return -code error "\"$directory\" is not writeable"
759	}
760	return $directory
761    }
762
763    # Directory where files should be created
764    Option -tmpdir [workingDirectory] {
765	Save temporary files in the specified directory.
766    } AcceptTemporaryDirectory temporaryDirectory
767    trace add variable Option(-tmpdir) write \
768	    [namespace code {normalizePath Option(-tmpdir) ;#}]
769
770    # Tests should not rely on the current working directory.
771    # Files that are part of the test suite should be accessed relative
772    # to [testsDirectory]
773    Option -testdir [workingDirectory] {
774	Search tests in the specified directory.
775    } AcceptDirectory testsDirectory
776    trace add variable Option(-testdir) write \
777	    [namespace code {normalizePath Option(-testdir) ;#}]
778
779    proc AcceptLoadFile { file } {
780	if {$file eq {}} {return $file}
781	set file [file join [temporaryDirectory] $file]
782	return [AcceptReadable $file]
783    }
784    proc ReadLoadScript {args} {
785	variable Option
786	if {$Option(-loadfile) eq {}} {return}
787	set tmp [open $Option(-loadfile) r]
788	loadScript [read $tmp]
789	close $tmp
790    }
791    Option -loadfile {} {
792	Read the script to load the tested commands from the specified file.
793    } AcceptLoadFile loadFile
794    trace add variable Option(-loadfile) write [namespace code ReadLoadScript]
795
796    proc AcceptOutFile { file } {
797	if {[string equal stderr $file]} {return $file}
798	if {[string equal stdout $file]} {return $file}
799	return [file join [temporaryDirectory] $file]
800    }
801
802    # output goes to stdout by default
803    Option -outfile stdout {
804	Send output from test runs to the specified file.
805    } AcceptOutFile outputFile
806    trace add variable Option(-outfile) write \
807	    [namespace code {outputChannel $Option(-outfile) ;#}]
808
809    # errors go to stderr by default
810    Option -errfile stderr {
811	Send errors from test runs to the specified file.
812    } AcceptOutFile errorFile
813    trace add variable Option(-errfile) write \
814	    [namespace code {errorChannel $Option(-errfile) ;#}]
815
816    proc loadIntoChildInterpreter {child args} {
817	variable Version
818	interp eval $child [package ifneeded tcltest $Version]
819	interp eval $child "tcltest::configure {*}{$args}"
820	interp alias $child ::tcltest::ReportToParent \
821	    {} ::tcltest::ReportedFromChild
822    }
823    proc ReportedFromChild {total passed skipped failed because newfiles} {
824	variable numTests
825	variable skippedBecause
826	variable createdNewFiles
827	incr numTests(Total)   $total
828	incr numTests(Passed)  $passed
829	incr numTests(Skipped) $skipped
830	incr numTests(Failed)  $failed
831	foreach {constraint count} $because {
832	    incr skippedBecause($constraint) $count
833	}
834	foreach {testfile created} $newfiles {
835	    lappend createdNewFiles($testfile) {*}$created
836	}
837	return
838    }
839}
840
841#####################################################################
842
843# tcltest::Debug* --
844#
845#     Internal helper procedures to write out debug information
846#     dependent on the chosen level. A test shell may overide
847#     them, f.e. to redirect the output into a different
848#     channel, or even into a GUI.
849
850# tcltest::DebugPuts --
851#
852#     Prints the specified string if the current debug level is
853#     higher than the provided level argument.
854#
855# Arguments:
856#     level   The lowest debug level triggering the output
857#     string  The string to print out.
858#
859# Results:
860#     Prints the string. Nothing else is allowed.
861#
862# Side Effects:
863#     None.
864#
865
866proc tcltest::DebugPuts {level string} {
867    variable debug
868    if {$debug >= $level} {
869	puts $string
870    }
871    return
872}
873
874# tcltest::DebugPArray --
875#
876#     Prints the contents of the specified array if the current
877#       debug level is higher than the provided level argument
878#
879# Arguments:
880#     level           The lowest debug level triggering the output
881#     arrayvar        The name of the array to print out.
882#
883# Results:
884#     Prints the contents of the array. Nothing else is allowed.
885#
886# Side Effects:
887#     None.
888#
889
890proc tcltest::DebugPArray {level arrayvar} {
891    variable debug
892
893    if {$debug >= $level} {
894	catch {upvar 1 $arrayvar $arrayvar}
895	parray $arrayvar
896    }
897    return
898}
899
900# Define our own [parray] in ::tcltest that will inherit use of the [puts]
901# defined in ::tcltest.  NOTE: Ought to construct with [info args] and
902# [info default], but can't be bothered now.  If [parray] changes, then
903# this will need changing too.
904auto_load ::parray
905proc tcltest::parray {a {pattern *}} [info body ::parray]
906
907# tcltest::DebugDo --
908#
909#     Executes the script if the current debug level is greater than
910#       the provided level argument
911#
912# Arguments:
913#     level   The lowest debug level triggering the execution.
914#     script  The tcl script executed upon a debug level high enough.
915#
916# Results:
917#     Arbitrary side effects, dependent on the executed script.
918#
919# Side Effects:
920#     None.
921#
922
923proc tcltest::DebugDo {level script} {
924    variable debug
925
926    if {$debug >= $level} {
927	uplevel 1 $script
928    }
929    return
930}
931
932#####################################################################
933
934proc tcltest::Warn {msg} {
935    puts [outputChannel] "WARNING: $msg"
936}
937
938# tcltest::mainThread
939#
940#     Accessor command for tcltest variable mainThread.
941#
942proc tcltest::mainThread { {new ""} } {
943    variable mainThread
944    if {[llength [info level 0]] == 1} {
945	return $mainThread
946    }
947    set mainThread $new
948}
949
950# tcltest::testConstraint --
951#
952#	sets a test constraint to a value; to do multiple constraints,
953#       call this proc multiple times.  also returns the value of the
954#       named constraint if no value was supplied.
955#
956# Arguments:
957#	constraint - name of the constraint
958#       value - new value for constraint (should be boolean) - if not
959#               supplied, this is a query
960#
961# Results:
962#	content of tcltest::testConstraints($constraint)
963#
964# Side effects:
965#	none
966
967proc tcltest::testConstraint {constraint {value ""}} {
968    variable testConstraints
969    variable Option
970    DebugPuts 3 "entering testConstraint $constraint $value"
971    if {[llength [info level 0]] == 2} {
972	return $testConstraints($constraint)
973    }
974    # Check for boolean values
975    if {[catch {expr {$value && 1}} msg]} {
976	return -code error $msg
977    }
978    if {[limitConstraints] && ($constraint ni $Option(-constraints))} {
979	set value 0
980    }
981    set testConstraints($constraint) $value
982}
983
984# tcltest::interpreter --
985#
986#	the interpreter name stored in tcltest::tcltest
987#
988# Arguments:
989#	executable name
990#
991# Results:
992#	content of tcltest::tcltest
993#
994# Side effects:
995#	None.
996
997proc tcltest::interpreter { {interp ""} } {
998    variable tcltest
999    if {[llength [info level 0]] == 1} {
1000	return $tcltest
1001    }
1002    set tcltest $interp
1003}
1004
1005#####################################################################
1006
1007# tcltest::AddToSkippedBecause --
1008#
1009#	Increments the variable used to track how many tests were
1010#       skipped because of a particular constraint.
1011#
1012# Arguments:
1013#	constraint     The name of the constraint to be modified
1014#
1015# Results:
1016#	Modifies tcltest::skippedBecause; sets the variable to 1 if
1017#       didn't previously exist - otherwise, it just increments it.
1018#
1019# Side effects:
1020#	None.
1021
1022proc tcltest::AddToSkippedBecause { constraint {value 1}} {
1023    # add the constraint to the list of constraints that kept tests
1024    # from running
1025    variable skippedBecause
1026
1027    if {[info exists skippedBecause($constraint)]} {
1028	incr skippedBecause($constraint) $value
1029    } else {
1030	set skippedBecause($constraint) $value
1031    }
1032    return
1033}
1034
1035# tcltest::PrintError --
1036#
1037#	Prints errors to tcltest::errorChannel and then flushes that
1038#       channel, making sure that all messages are < 80 characters per
1039#       line.
1040#
1041# Arguments:
1042#	errorMsg     String containing the error to be printed
1043#
1044# Results:
1045#	None.
1046#
1047# Side effects:
1048#	None.
1049
1050proc tcltest::PrintError {errorMsg} {
1051    set InitialMessage "Error:  "
1052    set InitialMsgLen  [string length $InitialMessage]
1053    puts -nonewline [errorChannel] $InitialMessage
1054
1055    # Keep track of where the end of the string is.
1056    set endingIndex [string length $errorMsg]
1057
1058    if {$endingIndex < (80 - $InitialMsgLen)} {
1059	puts [errorChannel] $errorMsg
1060    } else {
1061	# Print up to 80 characters on the first line, including the
1062	# InitialMessage.
1063	set beginningIndex [string last " " [string range $errorMsg 0 \
1064		[expr {80 - $InitialMsgLen}]]]
1065	puts [errorChannel] [string range $errorMsg 0 $beginningIndex]
1066
1067	while {$beginningIndex ne "end"} {
1068	    puts -nonewline [errorChannel] \
1069		    [string repeat " " $InitialMsgLen]
1070	    if {($endingIndex - $beginningIndex)
1071		    < (80 - $InitialMsgLen)} {
1072		puts [errorChannel] [string trim \
1073			[string range $errorMsg $beginningIndex end]]
1074		break
1075	    } else {
1076		set newEndingIndex [expr {[string last " " \
1077			[string range $errorMsg $beginningIndex \
1078				[expr {$beginningIndex
1079					+ (80 - $InitialMsgLen)}]
1080		]] + $beginningIndex}]
1081		if {($newEndingIndex <= 0)
1082			|| ($newEndingIndex <= $beginningIndex)} {
1083		    set newEndingIndex end
1084		}
1085		puts [errorChannel] [string trim \
1086			[string range $errorMsg \
1087			    $beginningIndex $newEndingIndex]]
1088		set beginningIndex $newEndingIndex
1089	    }
1090	}
1091    }
1092    flush [errorChannel]
1093    return
1094}
1095
1096# tcltest::SafeFetch --
1097#
1098#	 The following trace procedure makes it so that we can safely
1099#        refer to non-existent members of the testConstraints array
1100#        without causing an error.  Instead, reading a non-existent
1101#        member will return 0. This is necessary because tests are
1102#        allowed to use constraint "X" without ensuring that
1103#        testConstraints("X") is defined.
1104#
1105# Arguments:
1106#	n1 - name of the array (testConstraints)
1107#       n2 - array key value (constraint name)
1108#       op - operation performed on testConstraints (generally r)
1109#
1110# Results:
1111#	none
1112#
1113# Side effects:
1114#	sets testConstraints($n2) to 0 if it's referenced but never
1115#       before used
1116
1117proc tcltest::SafeFetch {n1 n2 op} {
1118    variable testConstraints
1119    DebugPuts 3 "entering SafeFetch $n1 $n2 $op"
1120    if {$n2 eq {}} {return}
1121    if {![info exists testConstraints($n2)]} {
1122	if {[catch {testConstraint $n2 [eval [ConstraintInitializer $n2]]}]} {
1123	    testConstraint $n2 0
1124	}
1125    }
1126}
1127
1128# tcltest::ConstraintInitializer --
1129#
1130#	Get or set a script that when evaluated in the tcltest namespace
1131#	will return a boolean value with which to initialize the
1132#	associated constraint.
1133#
1134# Arguments:
1135#	constraint - name of the constraint initialized by the script
1136#	script - the initializer script
1137#
1138# Results
1139#	boolean value of the constraint - enabled or disabled
1140#
1141# Side effects:
1142#	Constraint is initialized for future reference by [test]
1143proc tcltest::ConstraintInitializer {constraint {script ""}} {
1144    variable ConstraintInitializer
1145    DebugPuts 3 "entering ConstraintInitializer $constraint $script"
1146    if {[llength [info level 0]] == 2} {
1147	return $ConstraintInitializer($constraint)
1148    }
1149    # Check for boolean values
1150    if {![info complete $script]} {
1151	return -code error "ConstraintInitializer must be complete script"
1152    }
1153    set ConstraintInitializer($constraint) $script
1154}
1155
1156# tcltest::InitConstraints --
1157#
1158# Call all registered constraint initializers to force initialization
1159# of all known constraints.
1160# See the tcltest man page for the list of built-in constraints defined
1161# in this procedure.
1162#
1163# Arguments:
1164#	none
1165#
1166# Results:
1167#	The testConstraints array is reset to have an index for each
1168#	built-in test constraint.
1169#
1170# Side Effects:
1171#       None.
1172#
1173
1174proc tcltest::InitConstraints {} {
1175    variable ConstraintInitializer
1176    initConstraintsHook
1177    foreach constraint [array names ConstraintInitializer] {
1178	testConstraint $constraint
1179    }
1180}
1181
1182proc tcltest::DefineConstraintInitializers {} {
1183    ConstraintInitializer singleTestInterp {singleProcess}
1184
1185    # All the 'pc' constraints are here for backward compatibility and
1186    # are not documented.  They have been replaced with equivalent 'win'
1187    # constraints.
1188
1189    ConstraintInitializer unixOnly \
1190	    {string equal $::tcl_platform(platform) unix}
1191    ConstraintInitializer macOnly \
1192	    {string equal $::tcl_platform(platform) macintosh}
1193    ConstraintInitializer pcOnly \
1194	    {string equal $::tcl_platform(platform) windows}
1195    ConstraintInitializer winOnly \
1196	    {string equal $::tcl_platform(platform) windows}
1197
1198    ConstraintInitializer unix {testConstraint unixOnly}
1199    ConstraintInitializer mac {testConstraint macOnly}
1200    ConstraintInitializer pc {testConstraint pcOnly}
1201    ConstraintInitializer win {testConstraint winOnly}
1202
1203    ConstraintInitializer unixOrPc \
1204	    {expr {[testConstraint unix] || [testConstraint pc]}}
1205    ConstraintInitializer macOrPc \
1206	    {expr {[testConstraint mac] || [testConstraint pc]}}
1207    ConstraintInitializer unixOrWin \
1208	    {expr {[testConstraint unix] || [testConstraint win]}}
1209    ConstraintInitializer macOrWin \
1210	    {expr {[testConstraint mac] || [testConstraint win]}}
1211    ConstraintInitializer macOrUnix \
1212	    {expr {[testConstraint mac] || [testConstraint unix]}}
1213
1214    ConstraintInitializer nt {string equal $::tcl_platform(os) "Windows NT"}
1215    ConstraintInitializer 95 {string equal $::tcl_platform(os) "Windows 95"}
1216    ConstraintInitializer 98 {string equal $::tcl_platform(os) "Windows 98"}
1217
1218    # The following Constraints switches are used to mark tests that
1219    # should work, but have been temporarily disabled on certain
1220    # platforms because they don't and we haven't gotten around to
1221    # fixing the underlying problem.
1222
1223    ConstraintInitializer tempNotPc {expr {![testConstraint pc]}}
1224    ConstraintInitializer tempNotWin {expr {![testConstraint win]}}
1225    ConstraintInitializer tempNotMac {expr {![testConstraint mac]}}
1226    ConstraintInitializer tempNotUnix {expr {![testConstraint unix]}}
1227
1228    # The following Constraints switches are used to mark tests that
1229    # crash on certain platforms, so that they can be reactivated again
1230    # when the underlying problem is fixed.
1231
1232    ConstraintInitializer pcCrash {expr {![testConstraint pc]}}
1233    ConstraintInitializer winCrash {expr {![testConstraint win]}}
1234    ConstraintInitializer macCrash {expr {![testConstraint mac]}}
1235    ConstraintInitializer unixCrash {expr {![testConstraint unix]}}
1236
1237    # Skip empty tests
1238
1239    ConstraintInitializer emptyTest {format 0}
1240
1241    # By default, tests that expose known bugs are skipped.
1242
1243    ConstraintInitializer knownBug {format 0}
1244
1245    # By default, non-portable tests are skipped.
1246
1247    ConstraintInitializer nonPortable {format 0}
1248
1249    # Some tests require user interaction.
1250
1251    ConstraintInitializer userInteraction {format 0}
1252
1253    # Some tests must be skipped if the interpreter is not in
1254    # interactive mode
1255
1256    ConstraintInitializer interactive \
1257	    {expr {[info exists ::tcl_interactive] && $::tcl_interactive}}
1258
1259    # Some tests can only be run if the installation came from a CD
1260    # image instead of a web image.  Some tests must be skipped if you
1261    # are running as root on Unix.  Other tests can only be run if you
1262    # are running as root on Unix.
1263
1264    ConstraintInitializer root {expr \
1265	    {($::tcl_platform(platform) eq "unix") &&
1266		    ($::tcl_platform(user) in {root {}})}}
1267    ConstraintInitializer notRoot {expr {![testConstraint root]}}
1268
1269    # Set nonBlockFiles constraint: 1 means this platform supports
1270    # setting files into nonblocking mode.
1271
1272    ConstraintInitializer nonBlockFiles {
1273	    set code [expr {[catch {set f [open defs r]}]
1274		    || [catch {fconfigure $f -blocking off}]}]
1275	    catch {close $f}
1276	    set code
1277    }
1278
1279    # Set asyncPipeClose constraint: 1 means this platform supports
1280    # async flush and async close on a pipe.
1281    #
1282    # Test for SCO Unix - cannot run async flushing tests because a
1283    # potential problem with select is apparently interfering.
1284    # (Mark Diekhans).
1285
1286    ConstraintInitializer asyncPipeClose {expr {
1287	    !([string equal unix $::tcl_platform(platform)]
1288	    && ([catch {exec uname -X | fgrep {Release = 3.2v}}] == 0))}}
1289
1290    # Test to see if we have a broken version of sprintf with respect
1291    # to the "e" format of floating-point numbers.
1292
1293    ConstraintInitializer eformat {string equal [format %g 5e-5] 5e-05}
1294
1295    # Test to see if execed commands such as cat, echo, rm and so forth
1296    # are present on this machine.
1297
1298    ConstraintInitializer unixExecs {
1299	set code 1
1300        if {$::tcl_platform(platform) eq "macintosh"} {
1301	    set code 0
1302        }
1303        if {$::tcl_platform(platform) eq "windows"} {
1304	    if {[catch {
1305	        set file _tcl_test_remove_me.txt
1306	        makeFile {hello} $file
1307	    }]} {
1308	        set code 0
1309	    } elseif {
1310	        [catch {exec cat $file}] ||
1311	        [catch {exec echo hello}] ||
1312	        [catch {exec sh -c echo hello}] ||
1313	        [catch {exec wc $file}] ||
1314	        [catch {exec sleep 1}] ||
1315	        [catch {exec echo abc > $file}] ||
1316	        [catch {exec chmod 644 $file}] ||
1317	        [catch {exec rm $file}] ||
1318	        [llength [auto_execok mkdir]] == 0 ||
1319	        [llength [auto_execok fgrep]] == 0 ||
1320	        [llength [auto_execok grep]] == 0 ||
1321	        [llength [auto_execok ps]] == 0
1322	    } {
1323	        set code 0
1324	    }
1325	    removeFile $file
1326        }
1327	set code
1328    }
1329
1330    ConstraintInitializer stdio {
1331	set code 0
1332	if {![catch {set f [open "|[list [interpreter]]" w]}]} {
1333	    if {![catch {puts $f exit}]} {
1334		if {![catch {close $f}]} {
1335		    set code 1
1336		}
1337	    }
1338	}
1339	set code
1340    }
1341
1342    # Deliberately call socket with the wrong number of arguments.  The
1343    # error message you get will indicate whether sockets are available
1344    # on this system.
1345
1346    ConstraintInitializer socket {
1347	catch {socket} msg
1348	string compare $msg "sockets are not available on this system"
1349    }
1350
1351    # Check for internationalization
1352    ConstraintInitializer hasIsoLocale {
1353	if {[llength [info commands testlocale]] == 0} {
1354	    set code 0
1355	} else {
1356	    set code [string length [SetIso8859_1_Locale]]
1357	    RestoreLocale
1358	}
1359	set code
1360    }
1361
1362}
1363#####################################################################
1364
1365# Usage and command line arguments processing.
1366
1367# tcltest::PrintUsageInfo
1368#
1369#	Prints out the usage information for package tcltest.  This can
1370#	be customized with the redefinition of [PrintUsageInfoHook].
1371#
1372# Arguments:
1373#	none
1374#
1375# Results:
1376#       none
1377#
1378# Side Effects:
1379#       none
1380proc tcltest::PrintUsageInfo {} {
1381    puts [Usage]
1382    PrintUsageInfoHook
1383}
1384
1385proc tcltest::Usage { {option ""} } {
1386    variable Usage
1387    variable Verify
1388    if {[llength [info level 0]] == 1} {
1389	set msg "Usage: [file tail [info nameofexecutable]] script "
1390	append msg "?-help? ?flag value? ... \n"
1391	append msg "Available flags (and valid input values) are:"
1392
1393	set max 0
1394	set allOpts [concat -help [Configure]]
1395	foreach opt $allOpts {
1396	    set foo [Usage $opt]
1397	    lassign $foo x type($opt) usage($opt)
1398	    set line($opt) "  $opt $type($opt)  "
1399	    set length($opt) [string length $line($opt)]
1400	    if {$length($opt) > $max} {set max $length($opt)}
1401	}
1402	set rest [expr {72 - $max}]
1403	foreach opt $allOpts {
1404	    append msg \n$line($opt)
1405	    append msg [string repeat " " [expr {$max - $length($opt)}]]
1406	    set u [string trim $usage($opt)]
1407	    catch {append u "  (default: \[[Configure $opt]])"}
1408	    regsub -all {\s*\n\s*} $u " " u
1409	    while {[string length $u] > $rest} {
1410		set break [string wordstart $u $rest]
1411		if {$break == 0} {
1412		    set break [string wordend $u 0]
1413		}
1414		append msg [string range $u 0 [expr {$break - 1}]]
1415		set u [string trim [string range $u $break end]]
1416		append msg \n[string repeat " " $max]
1417	    }
1418	    append msg $u
1419	}
1420	return $msg\n
1421    } elseif {$option eq "-help"} {
1422	return [list -help "" "Display this usage information."]
1423    } else {
1424	set type [lindex [info args $Verify($option)] 0]
1425	return [list $option $type $Usage($option)]
1426    }
1427}
1428
1429# tcltest::ProcessFlags --
1430#
1431#	process command line arguments supplied in the flagArray - this
1432#	is called by processCmdLineArgs.  Modifies tcltest variables
1433#	according to the content of the flagArray.
1434#
1435# Arguments:
1436#	flagArray - array containing name/value pairs of flags
1437#
1438# Results:
1439#	sets tcltest variables according to their values as defined by
1440#       flagArray
1441#
1442# Side effects:
1443#	None.
1444
1445proc tcltest::ProcessFlags {flagArray} {
1446    # Process -help first
1447    if {"-help" in $flagArray} {
1448	PrintUsageInfo
1449	exit 1
1450    }
1451
1452    if {[llength $flagArray] == 0} {
1453	RemoveAutoConfigureTraces
1454    } else {
1455	set args $flagArray
1456	while {[llength $args] > 1 && [catch {configure {*}$args} msg]} {
1457
1458	    # Something went wrong parsing $args for tcltest options
1459	    # Check whether the problem is "unknown option"
1460	    if {[regexp {^unknown option (\S+):} $msg -> option]} {
1461		# Could be this is an option the Hook knows about
1462		set moreOptions [processCmdLineArgsAddFlagsHook]
1463		if {$option ni $moreOptions} {
1464		    # Nope.  Report the error, including additional options,
1465		    # but keep going
1466		    if {[llength $moreOptions]} {
1467			append msg ", "
1468			append msg [join [lrange $moreOptions 0 end-1] ", "]
1469			append msg "or [lindex $moreOptions end]"
1470		    }
1471		    Warn $msg
1472		}
1473	    } else {
1474		# error is something other than "unknown option"
1475		# notify user of the error; and exit
1476		puts [errorChannel] $msg
1477		exit 1
1478	    }
1479
1480	    # To recover, find that unknown option and remove up to it.
1481	    # then retry
1482	    while {[lindex $args 0] ne $option} {
1483		set args [lrange $args 2 end]
1484	    }
1485	    set args [lrange $args 2 end]
1486	}
1487	if {[llength $args] == 1} {
1488	    puts [errorChannel] \
1489		    "missing value for option [lindex $args 0]"
1490	    exit 1
1491	}
1492    }
1493
1494    # Call the hook
1495    catch {
1496        array set flag $flagArray
1497        processCmdLineArgsHook [array get flag]
1498    }
1499    return
1500}
1501
1502# tcltest::ProcessCmdLineArgs --
1503#
1504#       This procedure must be run after constraint initialization is
1505#	set up (by [DefineConstraintInitializers]) because some constraints
1506#	can be overridden.
1507#
1508#       Perform configuration according to the command-line options.
1509#
1510# Arguments:
1511#	none
1512#
1513# Results:
1514#	Sets the above-named variables in the tcltest namespace.
1515#
1516# Side Effects:
1517#       None.
1518#
1519
1520proc tcltest::ProcessCmdLineArgs {} {
1521    variable originalEnv
1522    variable testConstraints
1523
1524    # The "argv" var doesn't exist in some cases, so use {}.
1525    if {![info exists ::argv]} {
1526	ProcessFlags {}
1527    } else {
1528	ProcessFlags $::argv
1529    }
1530
1531    # Spit out everything you know if we're at a debug level 2 or
1532    # greater
1533    DebugPuts 2 "Flags passed into tcltest:"
1534    if {[info exists ::env(TCLTEST_OPTIONS)]} {
1535	DebugPuts 2 \
1536		"    ::env(TCLTEST_OPTIONS): $::env(TCLTEST_OPTIONS)"
1537    }
1538    if {[info exists ::argv]} {
1539	DebugPuts 2 "    argv: $::argv"
1540    }
1541    DebugPuts    2 "tcltest::debug              = [debug]"
1542    DebugPuts    2 "tcltest::testsDirectory     = [testsDirectory]"
1543    DebugPuts    2 "tcltest::workingDirectory   = [workingDirectory]"
1544    DebugPuts    2 "tcltest::temporaryDirectory = [temporaryDirectory]"
1545    DebugPuts    2 "tcltest::outputChannel      = [outputChannel]"
1546    DebugPuts    2 "tcltest::errorChannel       = [errorChannel]"
1547    DebugPuts    2 "Original environment (tcltest::originalEnv):"
1548    DebugPArray  2 originalEnv
1549    DebugPuts    2 "Constraints:"
1550    DebugPArray  2 testConstraints
1551}
1552
1553#####################################################################
1554
1555# Code to run the tests goes here.
1556
1557# tcltest::TestPuts --
1558#
1559#	Used to redefine puts in test environment.  Stores whatever goes
1560#	out on stdout in tcltest::outData and stderr in errData before
1561#	sending it on to the regular puts.
1562#
1563# Arguments:
1564#	same as standard puts
1565#
1566# Results:
1567#	none
1568#
1569# Side effects:
1570#       Intercepts puts; data that would otherwise go to stdout, stderr,
1571#	or file channels specified in outputChannel and errorChannel
1572#	does not get sent to the normal puts function.
1573namespace eval tcltest::Replace {
1574    namespace export puts
1575}
1576proc tcltest::Replace::puts {args} {
1577    variable [namespace parent]::outData
1578    variable [namespace parent]::errData
1579    switch [llength $args] {
1580	1 {
1581	    # Only the string to be printed is specified
1582	    append outData [lindex $args 0]\n
1583	    return
1584	    # return [Puts [lindex $args 0]]
1585	}
1586	2 {
1587	    # Either -nonewline or channelId has been specified
1588	    if {[lindex $args 0] eq "-nonewline"} {
1589		append outData [lindex $args end]
1590		return
1591		# return [Puts -nonewline [lindex $args end]]
1592	    } else {
1593		set channel [lindex $args 0]
1594		set newline \n
1595	    }
1596	}
1597	3 {
1598	    if {[lindex $args 0] eq "-nonewline"} {
1599		# Both -nonewline and channelId are specified, unless
1600		# it's an error.  -nonewline is supposed to be argv[0].
1601		set channel [lindex $args 1]
1602		set newline ""
1603	    }
1604	}
1605    }
1606
1607    if {[info exists channel]} {
1608	if {$channel in [list [[namespace parent]::outputChannel] stdout]} {
1609	    append outData [lindex $args end]$newline
1610	    return
1611	} elseif {$channel in [list [[namespace parent]::errorChannel] stderr]} {
1612	    append errData [lindex $args end]$newline
1613	    return
1614	}
1615    }
1616
1617    # If we haven't returned by now, we don't know how to handle the
1618    # input.  Let puts handle it.
1619    return [Puts {*}$args]
1620}
1621
1622# tcltest::Eval --
1623#
1624#	Evaluate the script in the test environment.  If ignoreOutput is
1625#       false, store data sent to stderr and stdout in outData and
1626#       errData.  Otherwise, ignore this output altogether.
1627#
1628# Arguments:
1629#	script             Script to evaluate
1630#       ?ignoreOutput?     Indicates whether or not to ignore output
1631#			   sent to stdout & stderr
1632#
1633# Results:
1634#	result from running the script
1635#
1636# Side effects:
1637#	Empties the contents of outData and errData before running a
1638#	test if ignoreOutput is set to 0.
1639
1640proc tcltest::Eval {script {ignoreOutput 1}} {
1641    variable outData
1642    variable errData
1643    DebugPuts 3 "[lindex [info level 0] 0] called"
1644    if {!$ignoreOutput} {
1645	set outData {}
1646	set errData {}
1647	rename ::puts [namespace current]::Replace::Puts
1648	namespace eval :: [list namespace import [namespace origin Replace::puts]]
1649	namespace import Replace::puts
1650    }
1651    set result [uplevel 1 $script]
1652    if {!$ignoreOutput} {
1653	namespace forget puts
1654	namespace eval :: namespace forget puts
1655	rename [namespace current]::Replace::Puts ::puts
1656    }
1657    return $result
1658}
1659
1660# tcltest::CompareStrings --
1661#
1662#	compares the expected answer to the actual answer, depending on
1663#	the mode provided.  Mode determines whether a regexp, exact,
1664#	glob or custom comparison is done.
1665#
1666# Arguments:
1667#	actual - string containing the actual result
1668#       expected - pattern to be matched against
1669#       mode - type of comparison to be done
1670#
1671# Results:
1672#	result of the match
1673#
1674# Side effects:
1675#	None.
1676
1677proc tcltest::CompareStrings {actual expected mode} {
1678    variable CustomMatch
1679    if {![info exists CustomMatch($mode)]} {
1680        return -code error "No matching command registered for `-match $mode'"
1681    }
1682    set match [namespace eval :: $CustomMatch($mode) [list $expected $actual]]
1683    if {[catch {expr {$match && $match}} result]} {
1684	return -code error "Invalid result from `-match $mode' command: $result"
1685    }
1686    return $match
1687}
1688
1689# tcltest::customMatch --
1690#
1691#	registers a command to be called when a particular type of
1692#	matching is required.
1693#
1694# Arguments:
1695#	nickname - Keyword for the type of matching
1696#	cmd - Incomplete command that implements that type of matching
1697#		when completed with expected string and actual string
1698#		and then evaluated.
1699#
1700# Results:
1701#	None.
1702#
1703# Side effects:
1704#	Sets the variable tcltest::CustomMatch
1705
1706proc tcltest::customMatch {mode script} {
1707    variable CustomMatch
1708    if {![info complete $script]} {
1709	return -code error \
1710		"invalid customMatch script; can't evaluate after completion"
1711    }
1712    set CustomMatch($mode) $script
1713}
1714
1715# tcltest::SubstArguments list
1716#
1717# This helper function takes in a list of words, then perform a
1718# substitution on the list as though each word in the list is a separate
1719# argument to the Tcl function.  For example, if this function is
1720# invoked as:
1721#
1722#      SubstArguments {$a {$a}}
1723#
1724# Then it is as though the function is invoked as:
1725#
1726#      SubstArguments $a {$a}
1727#
1728# This code is adapted from Paul Duffin's function "SplitIntoWords".
1729# The original function can be found  on:
1730#
1731#      http://purl.org/thecliff/tcl/wiki/858.html
1732#
1733# Results:
1734#     a list containing the result of the substitution
1735#
1736# Exceptions:
1737#     An error may occur if the list containing unbalanced quote or
1738#     unknown variable.
1739#
1740# Side Effects:
1741#     None.
1742#
1743
1744proc tcltest::SubstArguments {argList} {
1745
1746    # We need to split the argList up into tokens but cannot use list
1747    # operations as they throw away some significant quoting, and
1748    # [split] ignores braces as it should.  Therefore what we do is
1749    # gradually build up a string out of whitespace seperated strings.
1750    # We cannot use [split] to split the argList into whitespace
1751    # separated strings as it throws away the whitespace which maybe
1752    # important so we have to do it all by hand.
1753
1754    set result {}
1755    set token ""
1756
1757    while {[string length $argList]} {
1758        # Look for the next word containing a quote: " { }
1759        if {[regexp -indices {[^ \t\n]*[\"\{\}]+[^ \t\n]*} \
1760		$argList all]} {
1761            # Get the text leading up to this word, but not including
1762	    # this word, from the argList.
1763            set text [string range $argList 0 \
1764		    [expr {[lindex $all 0] - 1}]]
1765            # Get the word with the quote
1766            set word [string range $argList \
1767                    [lindex $all 0] [lindex $all 1]]
1768
1769            # Remove all text up to and including the word from the
1770            # argList.
1771            set argList [string range $argList \
1772                    [expr {[lindex $all 1] + 1}] end]
1773        } else {
1774            # Take everything up to the end of the argList.
1775            set text $argList
1776            set word {}
1777            set argList {}
1778        }
1779
1780        if {$token ne {}} {
1781            # If we saw a word with quote before, then there is a
1782            # multi-word token starting with that word.  In this case,
1783            # add the text and the current word to this token.
1784            append token $text $word
1785        } else {
1786            # Add the text to the result.  There is no need to parse
1787            # the text because it couldn't be a part of any multi-word
1788            # token.  Then start a new multi-word token with the word
1789            # because we need to pass this token to the Tcl parser to
1790            # check for balancing quotes
1791            append result $text
1792            set token $word
1793        }
1794
1795        if { [catch {llength $token} length] == 0 && $length == 1} {
1796            # The token is a valid list so add it to the result.
1797            # lappend result [string trim $token]
1798            append result \{$token\}
1799            set token {}
1800        }
1801    }
1802
1803    # If the last token has not been added to the list then there
1804    # is a problem.
1805    if { [string length $token] } {
1806        error "incomplete token \"$token\""
1807    }
1808
1809    return $result
1810}
1811
1812
1813# tcltest::test --
1814#
1815# This procedure runs a test and prints an error message if the test
1816# fails.  If verbose has been set, it also prints a message even if the
1817# test succeeds.  The test will be skipped if it doesn't match the
1818# match variable, if it matches an element in skip, or if one of the
1819# elements of "constraints" turns out not to be true.
1820#
1821# If testLevel is 1, then this is a top level test, and we record
1822# pass/fail information; otherwise, this information is not logged and
1823# is not added to running totals.
1824#
1825# Attributes:
1826#   Only description is a required attribute.  All others are optional.
1827#   Default values are indicated.
1828#
1829#   constraints -	A list of one or more keywords, each of which
1830#			must be the name of an element in the array
1831#			"testConstraints".  If any of these elements is
1832#			zero, the test is skipped. This attribute is
1833#			optional; default is {}
1834#   body -	        Script to run to carry out the test.  It must
1835#		        return a result that can be checked for
1836#		        correctness.  This attribute is optional;
1837#                       default is {}
1838#   result -	        Expected result from script.  This attribute is
1839#                       optional; default is {}.
1840#   output -            Expected output sent to stdout.  This attribute
1841#                       is optional; default is {}.
1842#   errorOutput -       Expected output sent to stderr.  This attribute
1843#                       is optional; default is {}.
1844#   returnCodes -       Expected return codes.  This attribute is
1845#                       optional; default is {0 2}.
1846#   errorCode -         Expected error code.  This attribute is
1847#                       optional; default is {*}. It is a glob pattern.
1848#                       If given, returnCodes defaults to {1}.
1849#   setup -             Code to run before $script (above).  This
1850#                       attribute is optional; default is {}.
1851#   cleanup -           Code to run after $script (above).  This
1852#                       attribute is optional; default is {}.
1853#   match -             specifies type of matching to do on result,
1854#                       output, errorOutput; this must be a string
1855#			previously registered by a call to [customMatch].
1856#			The strings exact, glob, and regexp are pre-registered
1857#			by the tcltest package.  Default value is exact.
1858#
1859# Arguments:
1860#   name -		Name of test, in the form foo-1.2.
1861#   description -	Short textual description of the test, to
1862#  		  	help humans understand what it does.
1863#
1864# Results:
1865#	None.
1866#
1867# Side effects:
1868#       Just about anything is possible depending on the test.
1869#
1870
1871proc tcltest::test {name description args} {
1872    global tcl_platform
1873    variable testLevel
1874    variable coreModTime
1875    DebugPuts 3 "test $name $args"
1876    DebugDo 1 {
1877	variable TestNames
1878	catch {
1879	    puts "test name '$name' re-used; prior use in $TestNames($name)"
1880	}
1881	set TestNames($name) [info script]
1882    }
1883
1884    FillFilesExisted
1885    incr testLevel
1886
1887    # Pre-define everything to null except output and errorOutput.  We
1888    # determine whether or not to trap output based on whether or not
1889    # these variables (output & errorOutput) are defined.
1890    lassign {} constraints setup cleanup body result returnCodes errorCode match
1891
1892    # Set the default match mode
1893    set match exact
1894
1895    # Set the default match values for return codes (0 is the standard
1896    # expected return value if everything went well; 2 represents
1897    # 'return' being used in the test script).
1898    set returnCodes [list 0 2]
1899
1900    # Set the default error code pattern
1901    set errorCode "*"
1902
1903    # The old test format can't have a 3rd argument (constraints or
1904    # script) that starts with '-'.
1905    if {[string match -* [lindex $args 0]] || ([llength $args] <= 1)} {
1906	if {[llength $args] == 1} {
1907	    set list [SubstArguments [lindex $args 0]]
1908	    foreach {element value} $list {
1909		set testAttributes($element) $value
1910	    }
1911	    foreach item {constraints match setup body cleanup \
1912		    result returnCodes errorCode output errorOutput} {
1913		if {[info exists testAttributes(-$item)]} {
1914		    set testAttributes(-$item) [uplevel 1 \
1915			    ::concat $testAttributes(-$item)]
1916		}
1917	    }
1918	} else {
1919	    array set testAttributes $args
1920	}
1921
1922	set validFlags {-setup -cleanup -body -result -returnCodes \
1923		-errorCode -match -output -errorOutput -constraints}
1924
1925	foreach flag [array names testAttributes] {
1926	    if {$flag ni $validFlags} {
1927		incr testLevel -1
1928		set sorted [lsort $validFlags]
1929		set options [join [lrange $sorted 0 end-1] ", "]
1930		append options ", or [lindex $sorted end]"
1931		return -code error "bad option \"$flag\": must be $options"
1932	    }
1933	}
1934
1935	# store whatever the user gave us
1936	foreach item [array names testAttributes] {
1937	    set [string trimleft $item "-"] $testAttributes($item)
1938	}
1939
1940	# Check the values supplied for -match
1941	variable CustomMatch
1942	if {$match ni [array names CustomMatch]} {
1943	    incr testLevel -1
1944	    set sorted [lsort [array names CustomMatch]]
1945	    set values [join [lrange $sorted 0 end-1] ", "]
1946	    append values ", or [lindex $sorted end]"
1947	    return -code error "bad -match value \"$match\":\
1948		    must be $values"
1949	}
1950
1951	# Replace symbolic valies supplied for -returnCodes
1952	foreach {strcode numcode} {ok 0 normal 0 error 1 return 2 break 3 continue 4} {
1953	    set returnCodes [string map -nocase [list $strcode $numcode] $returnCodes]
1954	}
1955        # errorCode without returnCode 1 is meaningless
1956        if {$errorCode ne "*" && 1 ni $returnCodes} {
1957            set returnCodes 1
1958        }
1959    } else {
1960	# This is parsing for the old test command format; it is here
1961	# for backward compatibility.
1962	set result [lindex $args end]
1963	if {[llength $args] == 2} {
1964	    set body [lindex $args 0]
1965	} elseif {[llength $args] == 3} {
1966	    set constraints [lindex $args 0]
1967	    set body [lindex $args 1]
1968	} else {
1969	    incr testLevel -1
1970	    return -code error "wrong # args:\
1971		    should be \"test name desc ?options?\""
1972	}
1973    }
1974
1975    if {[Skipped $name $constraints]} {
1976	incr testLevel -1
1977	return
1978    }
1979
1980    # Save information about the core file.
1981    if {[preserveCore]} {
1982	if {[file exists [file join [workingDirectory] core]]} {
1983	    set coreModTime [file mtime [file join [workingDirectory] core]]
1984	}
1985    }
1986
1987    # First, run the setup script (or a hook if it presents):
1988    if {[set cmd [namespace which -command [namespace current]::SetupTest]] ne ""} {
1989	set setup [list $cmd $setup]
1990    }
1991    set processTest 1
1992    set code [catch {uplevel 1 $setup} setupMsg]
1993    if {$code == 1} {
1994	set errorInfo(setup) $::errorInfo
1995	set errorCodeRes(setup) $::errorCode
1996	if {$errorCodeRes(setup) eq "BYPASS-SKIPPED-TEST"} {
1997	    _noticeSkipped $name $setupMsg
1998	    set processTest [set code 0]
1999	}
2000    }
2001    set setupFailure [expr {$code != 0}]
2002
2003    # Only run the test body if the setup was successful
2004    if {$processTest && !$setupFailure} {
2005
2006	# Register startup time
2007	if {[IsVerbose msec] || [IsVerbose usec]} {
2008	    set timeStart [clock microseconds]
2009	}
2010
2011	# Verbose notification of $body start
2012	if {[IsVerbose start]} {
2013	    puts [outputChannel] "---- $name start"
2014	    flush [outputChannel]
2015	}
2016
2017	set command [list [namespace origin RunTest] $name $body]
2018	if {[info exists output] || [info exists errorOutput]} {
2019	    set testResult [uplevel 1 [list [namespace origin Eval] $command 0]]
2020	} else {
2021	    set testResult [uplevel 1 [list [namespace origin Eval] $command 1]]
2022	}
2023	lassign $testResult actualAnswer returnCode
2024	if {$returnCode == 1} {
2025	    set errorInfo(body) $::errorInfo
2026	    set errorCodeRes(body) $::errorCode
2027	    if {$errorCodeRes(body) eq "BYPASS-SKIPPED-TEST"} {
2028		_noticeSkipped $name $actualAnswer
2029		set processTest [set returnCode 0]
2030	    }
2031	}
2032    }
2033
2034    # check if the return code matched the expected return code
2035    set codeFailure 0
2036    if {$processTest && !$setupFailure && ($returnCode ni $returnCodes)} {
2037	set codeFailure 1
2038    }
2039    set errorCodeFailure 0
2040    if {$processTest && !$setupFailure && !$codeFailure && $returnCode == 1 && \
2041                ![string match $errorCode $errorCodeRes(body)]} {
2042	set errorCodeFailure 1
2043    }
2044
2045    # If expected output/error strings exist, we have to compare
2046    # them.  If the comparison fails, then so did the test.
2047    set outputFailure 0
2048    variable outData
2049    if {$processTest && [info exists output] && !$codeFailure} {
2050	if {[set outputCompare [catch {
2051	    CompareStrings $outData $output $match
2052	} outputMatch]] == 0} {
2053	    set outputFailure [expr {!$outputMatch}]
2054	} else {
2055	    set outputFailure 1
2056	}
2057    }
2058
2059    set errorFailure 0
2060    variable errData
2061    if {$processTest && [info exists errorOutput] && !$codeFailure} {
2062	if {[set errorCompare [catch {
2063	    CompareStrings $errData $errorOutput $match
2064	} errorMatch]] == 0} {
2065	    set errorFailure [expr {!$errorMatch}]
2066	} else {
2067	    set errorFailure 1
2068	}
2069    }
2070
2071    # check if the answer matched the expected answer
2072    # Only check if we ran the body of the test (no setup failure)
2073    if {!$processTest} {
2074    	set scriptFailure 0
2075    } elseif {$setupFailure || $codeFailure} {
2076	set scriptFailure 0
2077    } elseif {[set scriptCompare [catch {
2078	CompareStrings $actualAnswer $result $match
2079    } scriptMatch]] == 0} {
2080	set scriptFailure [expr {!$scriptMatch}]
2081    } else {
2082	set scriptFailure 1
2083    }
2084
2085    # Always run the cleanup script (or a hook if it presents):
2086    if {[set cmd [namespace which -command [namespace current]::CleanupTest]] ne ""} {
2087	set cleanup [list $cmd $cleanup]
2088    }
2089    set code [catch {uplevel 1 $cleanup} cleanupMsg]
2090    if {$code == 1} {
2091	set errorInfo(cleanup) $::errorInfo
2092	set errorCodeRes(cleanup) $::errorCode
2093    }
2094    set cleanupFailure [expr {$code != 0}]
2095
2096    set coreFailure 0
2097    set coreMsg ""
2098    # check for a core file first - if one was created by the test,
2099    # then the test failed
2100    if {[preserveCore]} {
2101	if {[file exists [file join [workingDirectory] core]]} {
2102	    # There's only a test failure if there is a core file
2103	    # and (1) there previously wasn't one or (2) the new
2104	    # one is different from the old one.
2105	    if {[info exists coreModTime]} {
2106		if {$coreModTime != [file mtime \
2107			[file join [workingDirectory] core]]} {
2108		    set coreFailure 1
2109		}
2110	    } else {
2111		set coreFailure 1
2112	    }
2113
2114	    if {([preserveCore] > 1) && ($coreFailure)} {
2115		append coreMsg "\nMoving file to:\
2116		    [file join [temporaryDirectory] core-$name]"
2117		catch {file rename -force -- \
2118		    [file join [workingDirectory] core] \
2119		    [file join [temporaryDirectory] core-$name]
2120		} msg
2121		if {$msg ne {}} {
2122		    append coreMsg "\nError:\
2123			Problem renaming core file: $msg"
2124		}
2125	    }
2126	}
2127    }
2128
2129    if {[IsVerbose msec] || [IsVerbose usec]} {
2130	set t [expr {[clock microseconds] - $timeStart}]
2131	if {[IsVerbose usec]} {
2132	    puts [outputChannel] "++++ $name took $t μs"
2133	}
2134	if {[IsVerbose msec]} {
2135	    puts [outputChannel] "++++ $name took [expr {round($t/1000.)}] ms"
2136	}
2137    }
2138
2139    # if skipped, it is safe to return here
2140    if {!$processTest} {
2141	incr testLevel -1
2142	return
2143    }
2144
2145    # if we didn't experience any failures, then we passed
2146    variable numTests
2147    if {!($setupFailure || $cleanupFailure || $coreFailure
2148	    || $outputFailure || $errorFailure || $codeFailure
2149	    || $errorCodeFailure || $scriptFailure)} {
2150	if {$testLevel == 1} {
2151	    incr numTests(Passed)
2152	    if {[IsVerbose pass]} {
2153		puts [outputChannel] "++++ $name PASSED"
2154	    }
2155	}
2156	incr testLevel -1
2157	return
2158    }
2159
2160    # We know the test failed, tally it...
2161    if {$testLevel == 1} {
2162	incr numTests(Failed)
2163    }
2164
2165    # ... then report according to the type of failure
2166    variable currentFailure true
2167    if {![IsVerbose body]} {
2168	set body ""
2169    }
2170    puts [outputChannel] "\n"
2171    if {[IsVerbose line]} {
2172	if {![catch {set testFrame [info frame -1]}] &&
2173		[dict get $testFrame type] eq "source"} {
2174	    set testFile [dict get $testFrame file]
2175	    set testLine [dict get $testFrame line]
2176	} else {
2177	    set testFile [file normalize [uplevel 1 {info script}]]
2178	    if {[file readable $testFile]} {
2179		set testFd [open $testFile r]
2180		set testLine [expr {[lsearch -regexp \
2181			[split [read $testFd] "\n"] \
2182			"^\[ \t\]*test [string map {. \\.} $name] "] + 1}]
2183		close $testFd
2184	    }
2185	}
2186	if {[info exists testLine]} {
2187	    puts [outputChannel] "$testFile:$testLine: error: test failed:\
2188		    $name [string trim $description]"
2189	}
2190    }
2191    puts [outputChannel] "==== $name\
2192	    [string trim $description] FAILED"
2193    if {[string length $body]} {
2194	puts [outputChannel] "==== Contents of test case:"
2195	puts [outputChannel] $body
2196    }
2197    if {$setupFailure} {
2198	puts [outputChannel] "---- Test setup\
2199		failed:\n$setupMsg"
2200	if {[info exists errorInfo(setup)]} {
2201	    puts [outputChannel] "---- errorInfo(setup): $errorInfo(setup)"
2202	    puts [outputChannel] "---- errorCode(setup): $errorCodeRes(setup)"
2203	}
2204    }
2205    if {$processTest && $scriptFailure} {
2206	if {$scriptCompare} {
2207	    puts [outputChannel] "---- Error testing result: $scriptMatch"
2208	} else {
2209	    puts [outputChannel] "---- Result was:\n$actualAnswer"
2210	    puts [outputChannel] "---- Result should have been\
2211		    ($match matching):\n$result"
2212	}
2213    }
2214    if {$errorCodeFailure} {
2215	puts [outputChannel] "---- Error code was: '$errorCodeRes(body)'"
2216	puts [outputChannel] "---- Error code should have been: '$errorCode'"
2217    }
2218    if {$codeFailure} {
2219	switch -- $returnCode {
2220	    0 { set msg "Test completed normally" }
2221	    1 { set msg "Test generated error" }
2222	    2 { set msg "Test generated return exception" }
2223	    3 { set msg "Test generated break exception" }
2224	    4 { set msg "Test generated continue exception" }
2225	    default { set msg "Test generated exception" }
2226	}
2227	puts [outputChannel] "---- $msg; Return code was: $returnCode"
2228	puts [outputChannel] "---- Return code should have been\
2229		one of: $returnCodes"
2230	if {[IsVerbose error]} {
2231	    if {[info exists errorInfo(body)] && (1 ni $returnCodes)} {
2232		puts [outputChannel] "---- errorInfo: $errorInfo(body)"
2233		puts [outputChannel] "---- errorCode: $errorCodeRes(body)"
2234	    }
2235	}
2236    }
2237    if {$outputFailure} {
2238	if {$outputCompare} {
2239	    puts [outputChannel] "---- Error testing output: $outputMatch"
2240	} else {
2241	    puts [outputChannel] "---- Output was:\n$outData"
2242	    puts [outputChannel] "---- Output should have been\
2243		    ($match matching):\n$output"
2244	}
2245    }
2246    if {$errorFailure} {
2247	if {$errorCompare} {
2248	    puts [outputChannel] "---- Error testing errorOutput: $errorMatch"
2249	} else {
2250	    puts [outputChannel] "---- Error output was:\n$errData"
2251	    puts [outputChannel] "---- Error output should have\
2252		    been ($match matching):\n$errorOutput"
2253	}
2254    }
2255    if {$cleanupFailure} {
2256	puts [outputChannel] "---- Test cleanup failed:\n$cleanupMsg"
2257	if {[info exists errorInfo(cleanup)]} {
2258	    puts [outputChannel] "---- errorInfo(cleanup): $errorInfo(cleanup)"
2259	    puts [outputChannel] "---- errorCode(cleanup): $errorCodeRes(cleanup)"
2260	}
2261    }
2262    if {$coreFailure} {
2263	puts [outputChannel] "---- Core file produced while running\
2264		test!  $coreMsg"
2265    }
2266    puts [outputChannel] "==== $name FAILED\n"
2267
2268    incr testLevel -1
2269    return
2270}
2271
2272# Skip --
2273#
2274# Skips a running test and add a reason to skipped "constraints". Can be used
2275# to conditional intended abort of the test.
2276#
2277# Side Effects:  Maintains tally of total tests seen and tests skipped.
2278#
2279proc tcltest::Skip {reason} {
2280    return -code error -errorcode BYPASS-SKIPPED-TEST $reason
2281}
2282
2283proc tcltest::_noticeSkipped {name reason} {
2284    variable testLevel
2285    variable numTests
2286
2287    if {[IsVerbose skip]} {
2288	puts [outputChannel] "++++ $name SKIPPED: $reason"
2289    }
2290
2291    if {$testLevel == 1} {
2292	incr numTests(Skipped)
2293	AddToSkippedBecause $reason
2294    }
2295}
2296
2297
2298# Skipped --
2299#
2300# Given a test name and it constraints, returns a boolean indicating
2301# whether the current configuration says the test should be skipped.
2302#
2303# Side Effects:  Maintains tally of total tests seen and tests skipped.
2304#
2305proc tcltest::Skipped {name constraints} {
2306    variable testLevel
2307    variable numTests
2308    variable testConstraints
2309
2310    if {$testLevel == 1} {
2311	incr numTests(Total)
2312    }
2313    # skip the test if it's name matches an element of skip
2314    foreach pattern [skip] {
2315	if {[string match $pattern $name]} {
2316	    if {$testLevel == 1} {
2317		incr numTests(Skipped)
2318		DebugDo 1 {AddToSkippedBecause userSpecifiedSkip}
2319	    }
2320	    return 1
2321	}
2322    }
2323    # skip the test if it's name doesn't match any element of match
2324    set ok 0
2325    foreach pattern [match] {
2326	if {[string match $pattern $name]} {
2327	    set ok 1
2328	    break
2329	}
2330    }
2331    if {!$ok} {
2332	if {$testLevel == 1} {
2333	    incr numTests(Skipped)
2334	    DebugDo 1 {AddToSkippedBecause userSpecifiedNonMatch}
2335	}
2336	return 1
2337    }
2338    if {$constraints eq {}} {
2339	# If we're limited to the listed constraints and there aren't
2340	# any listed, then we shouldn't run the test.
2341	if {[limitConstraints]} {
2342	    AddToSkippedBecause userSpecifiedLimitConstraint
2343	    if {$testLevel == 1} {
2344		incr numTests(Skipped)
2345	    }
2346	    return 1
2347	}
2348    } else {
2349	# "constraints" argument exists;
2350	# make sure that the constraints are satisfied.
2351
2352	set doTest 0
2353	if {[string match {*[$\[]*} $constraints] != 0} {
2354	    # full expression, e.g. {$foo > [info tclversion]}
2355	    catch {set doTest [uplevel #0 [list expr $constraints]]}
2356	} elseif {[regexp {[^.:_a-zA-Z0-9 \n\r\t]+} $constraints] != 0} {
2357	    # something like {a || b} should be turned into
2358	    # $testConstraints(a) || $testConstraints(b).
2359	    regsub -all {[.\w]+} $constraints {$testConstraints(&)} c
2360	    catch {set doTest [eval [list expr $c]]}
2361	} elseif {![catch {llength $constraints}]} {
2362	    # just simple constraints such as {unixOnly fonts}.
2363	    set doTest 1
2364	    foreach constraint $constraints {
2365		if {(![info exists testConstraints($constraint)]) \
2366			|| (!$testConstraints($constraint))} {
2367		    set doTest 0
2368
2369		    # store the constraint that kept the test from
2370		    # running
2371		    set constraints $constraint
2372		    break
2373		}
2374	    }
2375	}
2376
2377	if {!$doTest} {
2378	    _noticeSkipped $name $constraints
2379	    return 1
2380	}
2381    }
2382    return 0
2383}
2384
2385# RunTest --
2386#
2387# This is where the body of a test is evaluated.  The combination of
2388# [RunTest] and [Eval] allows the output and error output of the test
2389# body to be captured for comparison against the expected values.
2390
2391proc tcltest::RunTest {name script} {
2392    DebugPuts 3 "Running $name {$script}"
2393
2394    # If there is no "memory" command (because memory debugging isn't
2395    # enabled), then don't attempt to use the command.
2396
2397    if {[llength [info commands memory]] == 1} {
2398	memory tag $name
2399    }
2400
2401    # run the test script (or a hook if it presents):
2402    if {[set cmd [namespace which -command [namespace current]::EvalTest]] ne ""} {
2403	set script [list $cmd $script]
2404    }
2405    set code [catch {uplevel 1 $script} actualAnswer]
2406
2407    return [list $actualAnswer $code]
2408}
2409
2410#####################################################################
2411
2412# tcltest::cleanupTestsHook --
2413#
2414#	This hook allows a harness that builds upon tcltest to specify
2415#       additional things that should be done at cleanup.
2416#
2417
2418if {[llength [info commands tcltest::cleanupTestsHook]] == 0} {
2419    proc tcltest::cleanupTestsHook {} {}
2420}
2421
2422# tcltest::cleanupTests --
2423#
2424# Remove files and dirs created using the makeFile and makeDirectory
2425# commands since the last time this proc was invoked.
2426#
2427# Print the names of the files created without the makeFile command
2428# since the tests were invoked.
2429#
2430# Print the number tests (total, passed, failed, and skipped) since the
2431# tests were invoked.
2432#
2433# Restore original environment (as reported by special variable env).
2434#
2435# Arguments:
2436#      calledFromAllFile - if 0, behave as if we are running a single
2437#      test file within an entire suite of tests.  if we aren't running
2438#      a single test file, then don't report status.  check for new
2439#      files created during the test run and report on them.  if 1,
2440#      report collated status from all the test file runs.
2441#
2442# Results:
2443#      None.
2444#
2445# Side Effects:
2446#      None
2447#
2448
2449proc tcltest::cleanupTests {{calledFromAllFile 0}} {
2450    variable filesMade
2451    variable filesExisted
2452    variable createdNewFiles
2453    variable testSingleFile
2454    variable numTests
2455    variable numTestFiles
2456    variable failFiles
2457    variable skippedBecause
2458    variable currentFailure
2459    variable originalEnv
2460    variable originalTclPlatform
2461    variable coreModTime
2462
2463    FillFilesExisted
2464    set testFileName [file tail [info script]]
2465
2466    # Hook to handle reporting to a parent interpreter
2467    if {[llength [info commands [namespace current]::ReportToParent]]} {
2468	ReportToParent $numTests(Total) $numTests(Passed) $numTests(Skipped) \
2469	    $numTests(Failed) [array get skippedBecause] \
2470	    [array get createdNewFiles]
2471	set testSingleFile false
2472    }
2473
2474    # Call the cleanup hook
2475    cleanupTestsHook
2476
2477    # Remove files and directories created by the makeFile and
2478    # makeDirectory procedures.  Record the names of files in
2479    # workingDirectory that were not pre-existing, and associate them
2480    # with the test file that created them.
2481
2482    if {!$calledFromAllFile} {
2483	foreach file $filesMade {
2484	    if {[file exists $file]} {
2485		DebugDo 1 {Warn "cleanupTests deleting $file..."}
2486		catch {file delete -force -- $file}
2487	    }
2488	}
2489	set currentFiles {}
2490	foreach file [glob -nocomplain \
2491		-directory [temporaryDirectory] *] {
2492	    lappend currentFiles [file tail $file]
2493	}
2494	set newFiles {}
2495	foreach file $currentFiles {
2496	    if {$file ni $filesExisted} {
2497		lappend newFiles $file
2498	    }
2499	}
2500	set filesExisted $currentFiles
2501	if {[llength $newFiles] > 0} {
2502	    set createdNewFiles($testFileName) $newFiles
2503	}
2504    }
2505
2506    if {$calledFromAllFile || $testSingleFile} {
2507
2508	# print stats
2509
2510	puts -nonewline [outputChannel] "$testFileName:"
2511	foreach index [list "Total" "Passed" "Skipped" "Failed"] {
2512	    puts -nonewline [outputChannel] \
2513		    "\t$index\t$numTests($index)"
2514	}
2515	puts [outputChannel] ""
2516
2517	# print number test files sourced
2518	# print names of files that ran tests which failed
2519
2520	if {$calledFromAllFile} {
2521	    puts [outputChannel] \
2522		    "Sourced $numTestFiles Test Files."
2523	    set numTestFiles 0
2524	    if {[llength $failFiles] > 0} {
2525		puts [outputChannel] \
2526			"Files with failing tests: $failFiles"
2527		set failFiles {}
2528	    }
2529	}
2530
2531	# if any tests were skipped, print the constraints that kept
2532	# them from running.
2533
2534	set constraintList [array names skippedBecause]
2535	if {[llength $constraintList] > 0} {
2536	    puts [outputChannel] \
2537		    "Number of tests skipped for each constraint:"
2538	    foreach constraint [lsort $constraintList] {
2539		puts [outputChannel] \
2540			"\t$skippedBecause($constraint)\t$constraint"
2541		unset skippedBecause($constraint)
2542	    }
2543	}
2544
2545	# report the names of test files in createdNewFiles, and reset
2546	# the array to be empty.
2547
2548	set testFilesThatTurded [lsort [array names createdNewFiles]]
2549	if {[llength $testFilesThatTurded] > 0} {
2550	    puts [outputChannel] "Warning: files left behind:"
2551	    foreach testFile $testFilesThatTurded {
2552		puts [outputChannel] \
2553			"\t$testFile:\t$createdNewFiles($testFile)"
2554		unset createdNewFiles($testFile)
2555	    }
2556	}
2557
2558	# reset filesMade, filesExisted, and numTests
2559
2560	set filesMade {}
2561	foreach index [list "Total" "Passed" "Skipped" "Failed"] {
2562	    set numTests($index) 0
2563	}
2564
2565	# exit only if running Tk in non-interactive mode
2566	# This should be changed to determine if an event
2567	# loop is running, which is the real issue.
2568	# Actually, this doesn't belong here at all.  A package
2569	# really has no business [exit]-ing an application.
2570	if {![catch {package present Tk}] && ![testConstraint interactive]} {
2571	    exit
2572	}
2573    } else {
2574
2575	# if we're deferring stat-reporting until all files are sourced,
2576	# then add current file to failFile list if any tests in this
2577	# file failed
2578
2579	if {$currentFailure && ($testFileName ni $failFiles)} {
2580	    lappend failFiles $testFileName
2581	}
2582	set currentFailure false
2583
2584	# restore the environment to the state it was in before this package
2585	# was loaded
2586
2587	set newEnv {}
2588	set changedEnv {}
2589	set removedEnv {}
2590	foreach index [array names ::env] {
2591	    if {![info exists originalEnv($index)]} {
2592		lappend newEnv $index
2593		unset ::env($index)
2594	    }
2595	}
2596	foreach index [array names originalEnv] {
2597	    if {![info exists ::env($index)]} {
2598		lappend removedEnv $index
2599		set ::env($index) $originalEnv($index)
2600	    } elseif {$::env($index) ne $originalEnv($index)} {
2601		lappend changedEnv $index
2602		set ::env($index) $originalEnv($index)
2603	    }
2604	}
2605	if {[llength $newEnv] > 0} {
2606	    puts [outputChannel] \
2607		    "env array elements created:\t$newEnv"
2608	}
2609	if {[llength $changedEnv] > 0} {
2610	    puts [outputChannel] \
2611		    "env array elements changed:\t$changedEnv"
2612	}
2613	if {[llength $removedEnv] > 0} {
2614	    puts [outputChannel] \
2615		    "env array elements removed:\t$removedEnv"
2616	}
2617
2618	set changedTclPlatform {}
2619	foreach index [array names originalTclPlatform] {
2620	    if {$::tcl_platform($index) \
2621		    != $originalTclPlatform($index)} {
2622		lappend changedTclPlatform $index
2623		set ::tcl_platform($index) $originalTclPlatform($index)
2624	    }
2625	}
2626	if {[llength $changedTclPlatform] > 0} {
2627	    puts [outputChannel] "tcl_platform array elements\
2628		    changed:\t$changedTclPlatform"
2629	}
2630
2631	if {[file exists [file join [workingDirectory] core]]} {
2632	    if {[preserveCore] > 1} {
2633		puts "rename core file (> 1)"
2634		puts [outputChannel] "produced core file! \
2635			Moving file to: \
2636			[file join [temporaryDirectory] core-$testFileName]"
2637		catch {file rename -force -- \
2638			[file join [workingDirectory] core] \
2639			[file join [temporaryDirectory] core-$testFileName]
2640		} msg
2641		if {$msg ne {}} {
2642		    PrintError "Problem renaming file: $msg"
2643		}
2644	    } else {
2645		# Print a message if there is a core file and (1) there
2646		# previously wasn't one or (2) the new one is different
2647		# from the old one.
2648
2649		if {[info exists coreModTime]} {
2650		    if {$coreModTime != [file mtime \
2651			    [file join [workingDirectory] core]]} {
2652			puts [outputChannel] "A core file was created!"
2653		    }
2654		} else {
2655		    puts [outputChannel] "A core file was created!"
2656		}
2657	    }
2658	}
2659    }
2660    flush [outputChannel]
2661    flush [errorChannel]
2662    return
2663}
2664
2665#####################################################################
2666
2667# Procs that determine which tests/test files to run
2668
2669# tcltest::GetMatchingFiles
2670#
2671#       Looks at the patterns given to match and skip files and uses
2672#	them to put together a list of the tests that will be run.
2673#
2674# Arguments:
2675#       directory to search
2676#
2677# Results:
2678#       The constructed list is returned to the user.  This will
2679#	primarily be used in 'all.tcl' files.  It is used in
2680#	runAllTests.
2681#
2682# Side Effects:
2683#       None
2684
2685# a lower case version is needed for compatibility with tcltest 1.0
2686proc tcltest::getMatchingFiles args {GetMatchingFiles {*}$args}
2687
2688proc tcltest::GetMatchingFiles { args } {
2689    if {[llength $args]} {
2690	set dirList $args
2691    } else {
2692	# Finding tests only in [testsDirectory] is normal operation.
2693	# This procedure is written to accept multiple directory arguments
2694	# only to satisfy version 1 compatibility.
2695	set dirList [list [testsDirectory]]
2696    }
2697
2698    set matchingFiles [list]
2699    foreach directory $dirList {
2700
2701	# List files in $directory that match patterns to run.
2702	set matchFileList [list]
2703	foreach match [matchFiles] {
2704	    set matchFileList [concat $matchFileList \
2705		    [glob -directory $directory -types {b c f p s} \
2706		    -nocomplain -- $match]]
2707	}
2708
2709	# List files in $directory that match patterns to skip.
2710	set skipFileList [list]
2711	foreach skip [skipFiles] {
2712	    set skipFileList [concat $skipFileList \
2713		    [glob -directory $directory -types {b c f p s} \
2714		    -nocomplain -- $skip]]
2715	}
2716
2717	# Add to result list all files in match list and not in skip list
2718	foreach file $matchFileList {
2719	    if {$file ni $skipFileList} {
2720		lappend matchingFiles $file
2721	    }
2722	}
2723    }
2724
2725    if {[llength $matchingFiles] == 0} {
2726	PrintError "No test files remain after applying your match and\
2727		skip patterns!"
2728    }
2729    return $matchingFiles
2730}
2731
2732# tcltest::GetMatchingDirectories --
2733#
2734#	Looks at the patterns given to match and skip directories and
2735#	uses them to put together a list of the test directories that we
2736#	should attempt to run.  (Only subdirectories containing an
2737#	"all.tcl" file are put into the list.)
2738#
2739# Arguments:
2740#	root directory from which to search
2741#
2742# Results:
2743#	The constructed list is returned to the user.  This is used in
2744#	the primary all.tcl file.
2745#
2746# Side Effects:
2747#       None.
2748
2749proc tcltest::GetMatchingDirectories {rootdir} {
2750
2751    # Determine the skip list first, to avoid [glob]-ing over subdirectories
2752    # we're going to throw away anyway.  Be sure we skip the $rootdir if it
2753    # comes up to avoid infinite loops.
2754    set skipDirs [list $rootdir]
2755    foreach pattern [skipDirectories] {
2756	set skipDirs [concat $skipDirs [glob -directory $rootdir -types d \
2757		-nocomplain -- $pattern]]
2758    }
2759
2760    # Now step through the matching directories, prune out the skipped ones
2761    # as you go.
2762    set matchDirs [list]
2763    foreach pattern [matchDirectories] {
2764	foreach path [glob -directory $rootdir -types d -nocomplain -- \
2765		$pattern] {
2766	    if {$path ni $skipDirs} {
2767		set matchDirs [concat $matchDirs [GetMatchingDirectories $path]]
2768		if {[file exists [file join $path all.tcl]]} {
2769		    lappend matchDirs $path
2770		}
2771	    }
2772	}
2773    }
2774
2775    if {[llength $matchDirs] == 0} {
2776	DebugPuts 1 "No test directories remain after applying match\
2777		and skip patterns!"
2778    }
2779    return [lsort $matchDirs]
2780}
2781
2782# tcltest::runAllTests --
2783#
2784#	prints output and sources test files according to the match and
2785#	skip patterns provided.  after sourcing test files, it goes on
2786#	to source all.tcl files in matching test subdirectories.
2787#
2788# Arguments:
2789#	shell being tested
2790#
2791# Results:
2792#	Whether there were any failures.
2793#
2794# Side effects:
2795#	None.
2796
2797proc tcltest::runAllTests { {shell ""} } {
2798    variable testSingleFile
2799    variable numTestFiles
2800    variable numTests
2801    variable failFiles
2802    variable DefaultValue
2803
2804    FillFilesExisted
2805    if {[llength [info level 0]] == 1} {
2806	set shell [interpreter]
2807    }
2808
2809    set testSingleFile false
2810
2811    puts [outputChannel] "Tests running in interp:  $shell"
2812    puts [outputChannel] "Tests located in:  [testsDirectory]"
2813    puts [outputChannel] "Tests running in:  [workingDirectory]"
2814    puts [outputChannel] "Temporary files stored in\
2815	    [temporaryDirectory]"
2816
2817    # [file system] first available in Tcl 8.4
2818    if {![catch {file system [testsDirectory]} result]
2819	    && ([lindex $result 0] ne "native")} {
2820	# If we aren't running in the native filesystem, then we must
2821	# run the tests in a single process (via 'source'), because
2822	# trying to run then via a pipe will fail since the files don't
2823	# really exist.
2824	singleProcess 1
2825    }
2826
2827    if {[singleProcess]} {
2828	puts [outputChannel] \
2829		"Test files sourced into current interpreter"
2830    } else {
2831	puts [outputChannel] \
2832		"Test files run in separate interpreters"
2833    }
2834    if {[llength [skip]] > 0} {
2835	puts [outputChannel] "Skipping tests that match:  [skip]"
2836    }
2837    puts [outputChannel] "Running tests that match:  [match]"
2838
2839    if {[llength [skipFiles]] > 0} {
2840	puts [outputChannel] \
2841		"Skipping test files that match:  [skipFiles]"
2842    }
2843    if {[llength [matchFiles]] > 0} {
2844	puts [outputChannel] \
2845		"Only running test files that match:  [matchFiles]"
2846    }
2847
2848    set timeCmd {clock format [clock seconds]}
2849    puts [outputChannel] "Tests began at [eval $timeCmd]"
2850
2851    # Run each of the specified tests
2852    foreach file [lsort [GetMatchingFiles]] {
2853	set tail [file tail $file]
2854	puts [outputChannel] $tail
2855	flush [outputChannel]
2856
2857	if {[singleProcess]} {
2858	    if {[catch {
2859		incr numTestFiles
2860		uplevel 1 [list ::source $file]
2861	    } msg]} {
2862		puts [outputChannel] "Test file error: $msg"
2863		# append the name of the test to a list to be reported
2864		# later
2865		lappend testFileFailures $file
2866	    }
2867	    if {$numTests(Failed) > 0} {
2868		set failFilesSet 1
2869	    }
2870	} else {
2871	    # Pass along our configuration to the child processes.
2872	    # EXCEPT for the -outfile, because the parent process
2873	    # needs to read and process output of children.
2874	    set childargv [list]
2875	    foreach opt [Configure] {
2876		if {$opt eq "-outfile"} {continue}
2877		set value [Configure $opt]
2878		# Don't bother passing default configuration options
2879		if {$value eq $DefaultValue($opt)} {
2880			continue
2881		}
2882		lappend childargv $opt $value
2883	    }
2884	    set cmd [linsert $childargv 0 | $shell $file]
2885	    if {[catch {
2886		incr numTestFiles
2887		set pipeFd [open $cmd "r"]
2888		while {[gets $pipeFd line] >= 0} {
2889		    if {[regexp [join {
2890			    {^([^:]+):\t}
2891			    {Total\t([0-9]+)\t}
2892			    {Passed\t([0-9]+)\t}
2893			    {Skipped\t([0-9]+)\t}
2894			    {Failed\t([0-9]+)}
2895			    } ""] $line null testFile \
2896			    Total Passed Skipped Failed]} {
2897			foreach index {Total Passed Skipped Failed} {
2898			    incr numTests($index) [set $index]
2899			}
2900			if {$Failed > 0} {
2901			    lappend failFiles $testFile
2902			    set failFilesSet 1
2903			}
2904		    } elseif {[regexp [join {
2905			    {^Number of tests skipped }
2906			    {for each constraint:}
2907			    {|^\t(\d+)\t(.+)$}
2908			    } ""] $line match skipped constraint]} {
2909			if {[string match \t* $match]} {
2910			    AddToSkippedBecause $constraint $skipped
2911			}
2912		    } else {
2913			puts [outputChannel] $line
2914		    }
2915		}
2916		close $pipeFd
2917	    } msg]} {
2918		puts [outputChannel] "Test file error: $msg"
2919		# append the name of the test to a list to be reported
2920		# later
2921		lappend testFileFailures $file
2922	    }
2923	}
2924    }
2925
2926    # cleanup
2927    puts [outputChannel] "\nTests ended at [eval $timeCmd]"
2928    cleanupTests 1
2929    if {[info exists testFileFailures]} {
2930	puts [outputChannel] "\nTest files exiting with errors:  \n"
2931	foreach file $testFileFailures {
2932	    puts [outputChannel] "  [file tail $file]\n"
2933	}
2934    }
2935
2936    # Checking for subdirectories in which to run tests
2937    foreach directory [GetMatchingDirectories [testsDirectory]] {
2938	set dir [file tail $directory]
2939	puts [outputChannel] [string repeat ~ 44]
2940	puts [outputChannel] "$dir test began at [eval $timeCmd]\n"
2941
2942	uplevel 1 [list ::source [file join $directory all.tcl]]
2943
2944	set endTime [eval $timeCmd]
2945	puts [outputChannel] "\n$dir test ended at $endTime"
2946	puts [outputChannel] ""
2947	puts [outputChannel] [string repeat ~ 44]
2948    }
2949    return [expr {[info exists testFileFailures] || [info exists failFilesSet]}]
2950}
2951
2952#####################################################################
2953
2954# Test utility procs - not used in tcltest, but may be useful for
2955# testing.
2956
2957# tcltest::loadTestedCommands --
2958#
2959#     Uses the specified script to load the commands to test. Allowed to
2960#     be empty, as the tested commands could have been compiled into the
2961#     interpreter.
2962#
2963# Arguments
2964#     none
2965#
2966# Results
2967#     none
2968#
2969# Side Effects:
2970#     none.
2971
2972proc tcltest::loadTestedCommands {} {
2973    return [uplevel 1 [loadScript]]
2974}
2975
2976# tcltest::saveState --
2977#
2978#	Save information regarding what procs and variables exist.
2979#
2980# Arguments:
2981#	none
2982#
2983# Results:
2984#	Modifies the variable saveState
2985#
2986# Side effects:
2987#	None.
2988
2989proc tcltest::saveState {} {
2990    variable saveState
2991    uplevel 1 [list ::set [namespace which -variable saveState]] \
2992	    {[::list [::info procs] [::info vars]]}
2993    DebugPuts  2 "[lindex [info level 0] 0]: $saveState"
2994    return
2995}
2996
2997# tcltest::restoreState --
2998#
2999#	Remove procs and variables that didn't exist before the call to
3000#       [saveState].
3001#
3002# Arguments:
3003#	none
3004#
3005# Results:
3006#	Removes procs and variables from your environment if they don't
3007#	exist in the saveState variable.
3008#
3009# Side effects:
3010#	None.
3011
3012proc tcltest::restoreState {} {
3013    variable saveState
3014    foreach p [uplevel 1 {::info procs}] {
3015	if {($p ni [lindex $saveState 0]) && ("[namespace current]::$p" ne
3016		[uplevel 1 [list ::namespace origin $p]])} {
3017
3018	    DebugPuts 2 "[lindex [info level 0] 0]: Removing proc $p"
3019	    uplevel 1 [list ::catch [list ::rename $p {}]]
3020	}
3021    }
3022    foreach p [uplevel 1 {::info vars}] {
3023	if {$p ni [lindex $saveState 1]} {
3024	    DebugPuts 2 "[lindex [info level 0] 0]:\
3025		    Removing variable $p"
3026	    uplevel 1 [list ::catch [list ::unset $p]]
3027	}
3028    }
3029    return
3030}
3031
3032# tcltest::normalizeMsg --
3033#
3034#	Removes "extra" newlines from a string.
3035#
3036# Arguments:
3037#	msg        String to be modified
3038#
3039# Results:
3040#	string with extra newlines removed
3041#
3042# Side effects:
3043#	None.
3044
3045proc tcltest::normalizeMsg {msg} {
3046    regsub "\n$" [string tolower $msg] "" msg
3047    set msg [string map [list "\n\n" "\n"] $msg]
3048    return [string map [list "\n\}" "\}"] $msg]
3049}
3050
3051# tcltest::makeFile --
3052#
3053# Create a new file with the name <name>, and write <contents> to it.
3054#
3055# If this file hasn't been created via makeFile since the last time
3056# cleanupTests was called, add it to the $filesMade list, so it will be
3057# removed by the next call to cleanupTests.
3058#
3059# Arguments:
3060#	contents        content of the new file
3061#       name            name of the new file
3062#       directory       directory name for new file
3063#
3064# Results:
3065#	absolute path to the file created
3066#
3067# Side effects:
3068#	None.
3069
3070proc tcltest::makeFile {contents name {directory ""}} {
3071    variable filesMade
3072    FillFilesExisted
3073
3074    if {[llength [info level 0]] == 3} {
3075	set directory [temporaryDirectory]
3076    }
3077
3078    set fullName [file join $directory $name]
3079
3080    DebugPuts 3 "[lindex [info level 0] 0]:\
3081	     putting ``$contents'' into $fullName"
3082
3083    set fd [open $fullName w]
3084    fconfigure $fd -translation lf
3085    if {[package vsatisfies [package provide Tcl] 8.7-]} {
3086	fconfigure $fd -encoding utf-8
3087    }
3088    if {[string index $contents end] eq "\n"} {
3089	puts -nonewline $fd $contents
3090    } else {
3091	puts $fd $contents
3092    }
3093    close $fd
3094
3095    if {$fullName ni $filesMade} {
3096	lappend filesMade $fullName
3097    }
3098    return $fullName
3099}
3100
3101# tcltest::removeFile --
3102#
3103#	Removes the named file from the filesystem
3104#
3105# Arguments:
3106#	name          file to be removed
3107#       directory     directory from which to remove file
3108#
3109# Results:
3110#	return value from [file delete]
3111#
3112# Side effects:
3113#	None.
3114
3115proc tcltest::removeFile {name {directory ""}} {
3116    variable filesMade
3117    FillFilesExisted
3118    if {[llength [info level 0]] == 2} {
3119	set directory [temporaryDirectory]
3120    }
3121    set fullName [file join $directory $name]
3122    DebugPuts 3 "[lindex [info level 0] 0]: removing $fullName"
3123    set idx [lsearch -exact $filesMade $fullName]
3124    if {$idx < 0} {
3125	DebugDo 1 {
3126	    Warn "removeFile removing \"$fullName\":\n  not created by makeFile"
3127	}
3128    } else {
3129	set filesMade [lreplace $filesMade $idx $idx]
3130    }
3131    if {![file isfile $fullName]} {
3132	DebugDo 1 {
3133	    Warn "removeFile removing \"$fullName\":\n  not a file"
3134	}
3135    }
3136    if {[catch {file delete -- $fullName} msg ]} {
3137	DebugDo 1 {
3138	    Warn "removeFile removing \"$fullName\":\n  failed: $msg"
3139	}
3140    }
3141    return
3142}
3143
3144# tcltest::makeDirectory --
3145#
3146# Create a new dir with the name <name>.
3147#
3148# If this dir hasn't been created via makeDirectory since the last time
3149# cleanupTests was called, add it to the $directoriesMade list, so it
3150# will be removed by the next call to cleanupTests.
3151#
3152# Arguments:
3153#       name            name of the new directory
3154#       directory       directory in which to create new dir
3155#
3156# Results:
3157#	absolute path to the directory created
3158#
3159# Side effects:
3160#	None.
3161
3162proc tcltest::makeDirectory {name {directory ""}} {
3163    variable filesMade
3164    FillFilesExisted
3165    if {[llength [info level 0]] == 2} {
3166	set directory [temporaryDirectory]
3167    }
3168    set fullName [file join $directory $name]
3169    DebugPuts 3 "[lindex [info level 0] 0]: creating $fullName"
3170    file mkdir $fullName
3171    if {$fullName ni $filesMade} {
3172	lappend filesMade $fullName
3173    }
3174    return $fullName
3175}
3176
3177# tcltest::removeDirectory --
3178#
3179#	Removes a named directory from the file system.
3180#
3181# Arguments:
3182#	name          Name of the directory to remove
3183#       directory     Directory from which to remove
3184#
3185# Results:
3186#	return value from [file delete]
3187#
3188# Side effects:
3189#	None
3190
3191proc tcltest::removeDirectory {name {directory ""}} {
3192    variable filesMade
3193    FillFilesExisted
3194    if {[llength [info level 0]] == 2} {
3195	set directory [temporaryDirectory]
3196    }
3197    set fullName [file join $directory $name]
3198    DebugPuts 3 "[lindex [info level 0] 0]: deleting $fullName"
3199    set idx [lsearch -exact $filesMade $fullName]
3200    set filesMade [lreplace $filesMade $idx $idx]
3201    if {$idx < 0} {
3202	DebugDo 1 {
3203	    Warn "removeDirectory removing \"$fullName\":\n  not created\
3204		    by makeDirectory"
3205	}
3206    }
3207    if {![file isdirectory $fullName]} {
3208	DebugDo 1 {
3209	    Warn "removeDirectory removing \"$fullName\":\n  not a directory"
3210	}
3211    }
3212    return [file delete -force -- $fullName]
3213}
3214
3215# tcltest::viewFile --
3216#
3217#	reads the content of a file and returns it
3218#
3219# Arguments:
3220#	name of the file to read
3221#       directory in which file is located
3222#
3223# Results:
3224#	content of the named file
3225#
3226# Side effects:
3227#	None.
3228
3229proc tcltest::viewFile {name {directory ""}} {
3230    FillFilesExisted
3231    if {[llength [info level 0]] == 2} {
3232	set directory [temporaryDirectory]
3233    }
3234    set fullName [file join $directory $name]
3235    set f [open $fullName]
3236    if {[package vsatisfies [package provide Tcl] 8.7-]} {
3237	fconfigure $f -encoding utf-8
3238    }
3239    set data [read -nonewline $f]
3240    close $f
3241    return $data
3242}
3243
3244# tcltest::bytestring --
3245#
3246# Construct a string that consists of the requested sequence of bytes,
3247# as opposed to a string of properly formed UTF-8 characters.
3248# This allows the tester to
3249# 1. Create denormalized or improperly formed strings to pass to C
3250#    procedures that are supposed to accept strings with embedded NULL
3251#    bytes.
3252# 2. Confirm that a string result has a certain pattern of bytes, for
3253#    instance to confirm that "\xE0\0" in a Tcl script is stored
3254#    internally in UTF-8 as the sequence of bytes "\xC3\xA0\xC0\x80".
3255#
3256# Generally, it's a bad idea to examine the bytes in a Tcl string or to
3257# construct improperly formed strings in this manner, because it involves
3258# exposing that Tcl uses UTF-8 internally.
3259#
3260# This function doesn't work any more in Tcl 8.7, since the 'identity'
3261# is gone (TIP #345)
3262#
3263# Arguments:
3264#	string being converted
3265#
3266# Results:
3267#	result fom encoding
3268#
3269# Side effects:
3270#	None
3271
3272if {![package vsatisfies [package provide Tcl] 8.7-]} {
3273    proc tcltest::bytestring {string} {
3274	return [encoding convertfrom identity $string]
3275    }
3276}
3277
3278# tcltest::OpenFiles --
3279#
3280#	used in io tests, uses testchannel
3281#
3282# Arguments:
3283#	None.
3284#
3285# Results:
3286#	???
3287#
3288# Side effects:
3289#	None.
3290
3291proc tcltest::OpenFiles {} {
3292    if {[catch {testchannel open} result]} {
3293	return {}
3294    }
3295    return $result
3296}
3297
3298# tcltest::LeakFiles --
3299#
3300#	used in io tests, uses testchannel
3301#
3302# Arguments:
3303#	None.
3304#
3305# Results:
3306#	???
3307#
3308# Side effects:
3309#	None.
3310
3311proc tcltest::LeakFiles {old} {
3312    if {[catch {testchannel open} new]} {
3313	return {}
3314    }
3315    set leak {}
3316    foreach p $new {
3317	if {$p ni $old} {
3318	    lappend leak $p
3319	}
3320    }
3321    return $leak
3322}
3323
3324#
3325# Internationalization / ISO support procs     -- dl
3326#
3327
3328# tcltest::SetIso8859_1_Locale --
3329#
3330#	used in cmdIL.test, uses testlocale
3331#
3332# Arguments:
3333#	None.
3334#
3335# Results:
3336#	None.
3337#
3338# Side effects:
3339#	None.
3340
3341proc tcltest::SetIso8859_1_Locale {} {
3342    variable previousLocale
3343    variable isoLocale
3344    if {[info commands testlocale] != ""} {
3345	set previousLocale [testlocale ctype]
3346	testlocale ctype $isoLocale
3347    }
3348    return
3349}
3350
3351# tcltest::RestoreLocale --
3352#
3353#	used in cmdIL.test, uses testlocale
3354#
3355# Arguments:
3356#	None.
3357#
3358# Results:
3359#	None.
3360#
3361# Side effects:
3362#	None.
3363
3364proc tcltest::RestoreLocale {} {
3365    variable previousLocale
3366    if {[info commands testlocale] != ""} {
3367	testlocale ctype $previousLocale
3368    }
3369    return
3370}
3371
3372# tcltest::threadReap --
3373#
3374#	Kill all threads except for the main thread.
3375#	Do nothing if testthread is not defined.
3376#
3377# Arguments:
3378#	none.
3379#
3380# Results:
3381#	Returns the number of existing threads.
3382#
3383# Side Effects:
3384#       none.
3385#
3386
3387proc tcltest::threadReap {} {
3388    if {[info commands testthread] ne {}} {
3389
3390	# testthread built into tcltest
3391
3392	testthread errorproc ThreadNullError
3393	while {[llength [testthread names]] > 1} {
3394	    foreach tid [testthread names] {
3395		if {$tid != [mainThread]} {
3396		    catch {
3397			testthread send -async $tid {testthread exit}
3398		    }
3399		}
3400	    }
3401	    ## Enter a bit a sleep to give the threads enough breathing
3402	    ## room to kill themselves off, otherwise the end up with a
3403	    ## massive queue of repeated events
3404	    after 1
3405	}
3406	testthread errorproc ThreadError
3407	return [llength [testthread names]]
3408    } elseif {[info commands thread::id] ne {}} {
3409
3410	# Thread extension
3411
3412	thread::errorproc ThreadNullError
3413	while {[llength [thread::names]] > 1} {
3414	    foreach tid [thread::names] {
3415		if {$tid != [mainThread]} {
3416		    catch {thread::send -async $tid {thread::exit}}
3417		}
3418	    }
3419	    ## Enter a bit a sleep to give the threads enough breathing
3420	    ## room to kill themselves off, otherwise the end up with a
3421	    ## massive queue of repeated events
3422	    after 1
3423	}
3424	thread::errorproc ThreadError
3425	return [llength [thread::names]]
3426    } else {
3427	return 1
3428    }
3429    return 0
3430}
3431
3432# Initialize the constraints and set up command line arguments
3433namespace eval tcltest {
3434    # Define initializers for all the built-in contraint definitions
3435    DefineConstraintInitializers
3436
3437    # Set up the constraints in the testConstraints array to be lazily
3438    # initialized by a registered initializer, or by "false" if no
3439    # initializer is registered.
3440    trace add variable testConstraints read [namespace code SafeFetch]
3441
3442    # Only initialize constraints at package load time if an
3443    # [initConstraintsHook] has been pre-defined.  This is only
3444    # for compatibility support.  The modern way to add a custom
3445    # test constraint is to just call the [testConstraint] command
3446    # straight away, without all this "hook" nonsense.
3447    if {[namespace current] eq
3448	    [namespace qualifiers [namespace which initConstraintsHook]]} {
3449	InitConstraints
3450    } else {
3451	proc initConstraintsHook {} {}
3452    }
3453
3454    # Define the standard match commands
3455    customMatch exact	[list string equal]
3456    customMatch glob	[list string match]
3457    customMatch regexp	[list regexp --]
3458
3459    # If the TCLTEST_OPTIONS environment variable exists, configure
3460    # tcltest according to the option values it specifies.  This has
3461    # the effect of resetting tcltest's default configuration.
3462    proc ConfigureFromEnvironment {} {
3463	upvar #0 env(TCLTEST_OPTIONS) options
3464	if {[catch {llength $options} msg]} {
3465	    Warn "invalid TCLTEST_OPTIONS \"$options\":\n  invalid\
3466		    Tcl list: $msg"
3467	    return
3468	}
3469	if {[llength $options] % 2} {
3470	    Warn "invalid TCLTEST_OPTIONS: \"$options\":\n  should be\
3471		    -option value ?-option value ...?"
3472	    return
3473	}
3474	if {[catch {Configure {*}$options} msg]} {
3475	    Warn "invalid TCLTEST_OPTIONS: \"$options\":\n  $msg"
3476	    return
3477	}
3478    }
3479    if {[info exists ::env(TCLTEST_OPTIONS)]} {
3480	ConfigureFromEnvironment
3481    }
3482
3483    proc LoadTimeCmdLineArgParsingRequired {} {
3484	set required false
3485	if {[info exists ::argv] && ("-help" in $::argv)} {
3486	    # The command line asks for -help, so give it (and exit)
3487	    # right now.  ([configure] does not process -help)
3488	    set required true
3489	}
3490	foreach hook { PrintUsageInfoHook processCmdLineArgsHook
3491			processCmdLineArgsAddFlagsHook } {
3492	    if {[namespace current] eq
3493		    [namespace qualifiers [namespace which $hook]]} {
3494		set required true
3495	    } else {
3496		proc $hook args {}
3497	    }
3498	}
3499	return $required
3500    }
3501
3502    # Only initialize configurable options from the command line arguments
3503    # at package load time if necessary for backward compatibility.  This
3504    # lets the tcltest user call [configure] for themselves if they wish.
3505    # Traces are established for auto-configuration from the command line
3506    # if any configurable options are accessed before the user calls
3507    # [configure].
3508    if {[LoadTimeCmdLineArgParsingRequired]} {
3509	ProcessCmdLineArgs
3510    } else {
3511	EstablishAutoConfigureTraces
3512    }
3513
3514    package provide [namespace tail [namespace current]] $Version
3515}
3516