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
9 # This design was based on the Tcl testing approach designed and
10 # initially implemented by Mary Ann May-Pumphrey of Sun
13 # Copyright (c) 1994-1997 Sun Microsystems, Inc.
14 # Copyright (c) 1998-1999 by Scriptics Corporation.
15 # Copyright (c) 2000 by Ajuba Solutions
16 # Contributions from Don Porter, NIST, 2002. (not subject to US copyright)
17 # All rights reserved.
19 # RCS: @(#) $Id: tcltest.tcl,v 1.103.2.3 2009/09/01 14:13:02 dgp Exp $
21 package require Tcl 8.5 ;# -verbose line uses [info frame]
22 namespace eval tcltest {
24 # When the version number changes, be sure to update the pkgIndex.tcl file,
25 # and the install directory in the Makefiles. When the minor version
26 # changes (new feature) be sure to update the man page as well.
27 variable Version 2.3.2
29 # Compatibility support for dumb variables defined in tcltest 1
30 # Do not use these. Call [package provide Tcl] and [info patchlevel]
31 # yourself. You don't need tcltest to wrap it for you.
32 variable version [package provide Tcl]
33 variable patchLevel [info patchlevel]
35 ##### Export the public tcltest procs; several categories
37 # Export the main functional commands that do useful things
38 namespace export cleanupTests loadTestedCommands makeDirectory \
39 makeFile removeDirectory removeFile runAllTests test
41 # Export configuration commands that control the functional commands
42 namespace export configure customMatch errorChannel interpreter \
43 outputChannel testConstraint
45 # Export commands that are duplication (candidates for deprecation)
46 namespace export bytestring ;# dups [encoding convertfrom identity]
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]
69 # Export deprecated commands for tcltest 1 compatibility
70 namespace export getMatchingFiles mainThread restoreState saveState \
73 # tcltest::normalizePath --
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.
80 # pathVar - name of variable containing path to modify.
83 # The path is modified in place.
88 proc normalizePath {pathVar} {
97 ##### Verification commands used to test values of variables and options
99 # Verification command that accepts everything
100 proc AcceptAll {value} {
104 # Verification command that accepts valid Tcl lists
105 proc AcceptList { list } {
106 return [lrange $list 0 end]
109 # Verification command that accepts a glob pattern
110 proc AcceptPattern { pattern } {
111 return [AcceptAll $pattern]
114 # Verification command that accepts integers
115 proc AcceptInteger { level } {
116 return [incr level 0]
119 # Verification command that accepts boolean values
120 proc AcceptBoolean { boolean } {
121 return [expr {$boolean && $boolean}]
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"
132 # Verification command that accepts (converts to) absolute pathnames
133 proc AcceptAbsolutePath { path } {
134 return [file join [pwd] $path]
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"
144 proc AcceptDirectory { directory } {
145 set directory [AcceptAbsolutePath $directory]
146 if {![file exists $directory]} {
147 return -code error "\"$directory\" does not exist"
149 if {![file isdir $directory]} {
150 return -code error "\"$directory\" is not a directory"
152 return [AcceptReadable $directory]
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.
160 proc ArrayDefault {varName value} {
162 if {[array exists $varName]} {
165 if {[info exists $varName]} {
166 # Pre-initialized value is a scalar: destroy it!
169 array set $varName $value
172 # save the original environment so that it can be restored later
173 ArrayDefault originalEnv [array get ::env]
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]
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
182 ArrayDefault createdNewFiles {}
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
191 ArrayDefault skippedBecause {}
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 {}
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.
203 proc Default {varName value {verify AcceptAll}} {
205 if {![info exists $varName]} {
206 variable $varName [$verify $value]
208 variable $varName [$verify [set $varName]]
212 # Save any arguments that we might want to pass through to other
213 # programs. This is used by the -args flag.
215 Default parameters {}
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
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.
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
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]
246 # After successful filling, turn this into a no-op.
247 proc FillFilesExisted args {}
250 # Kept only for compatibility
251 Default constraintsSpecified {} AcceptList
252 trace variable constraintsSpecified r {set ::tcltest::constraintsSpecified \
253 [array names ::tcltest::testConstraints] ;# }
255 # tests that use threads need to know which is the main thread
258 if {[info commands thread::id] != {}} {
259 set mainThread [thread::id]
260 } elseif {[info commands testthread] != {}} {
261 set mainThread [testthread id]
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 variable workingDirectory w \
269 [namespace code {cd $workingDirectory ;#}]
271 Default workingDirectory [pwd] AcceptAbsolutePath
272 proc workingDirectory { {dir ""} } {
273 variable workingDirectory
274 if {[llength [info level 0]] == 1} {
275 return $workingDirectory
277 set workingDirectory [AcceptAbsolutePath $dir]
280 # Set the location of the execuatble
281 Default tcltest [info nameofexecutable]
282 trace variable tcltest w [namespace code {testConstraint stdio \
283 [eval [ConstraintInitializer stdio]] ;#}]
285 # save the platform information so it can be restored later
286 Default originalTclPlatform [array get ::tcl_platform]
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]]
294 # stdout and stderr buffers for use when we want to store them
298 # keep track of test level for nested test commands
301 # the variables and procs that existed when saveState was called are
302 # stored in a variable of the same name
305 # Internationalization support -- used in [SetIso8859_1_Locale] and
306 # [RestoreLocale]. Those commands are used in cmdIL.test.
308 if {![info exists [namespace current]::isoLocale]} {
309 variable isoLocale fr
310 switch -- $::tcl_platform(platform) {
313 # Try some 'known' values for some platforms:
315 switch -exact -- $::tcl_platform(os) {
317 set isoLocale fr_FR.ISO_8859-1
320 set isoLocale fr_FR.iso88591
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.
332 set isoLocale iso_8859_1
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
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 :) )
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
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].
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,
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
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.
388 if {[llength [info level 0]] == 1} {
389 return $outputChannel
391 if {[info exists ChannelsWeOpened($outputChannel)]} {
393 unset ChannelsWeOpened($outputChannel)
395 switch -exact -- $filename {
398 set outputChannel $filename
401 set outputChannel [open $filename a]
402 set ChannelsWeOpened($outputChannel) 1
404 # If we created the file in [temporaryDirectory], then
405 # [cleanupTests] will delete it, unless we claim it was
407 set outdir [normalizePath [file dirname \
408 [file join [pwd] $filename]]]
409 if {[string equal $outdir [temporaryDirectory]]} {
410 variable filesExisted
412 set filename [file tail $filename]
413 if {[lsearch -exact $filesExisted $filename] == -1} {
414 lappend filesExisted $filename
419 return $outputChannel
422 # errors go to stderr by default
423 Default errorChannel stderr
424 proc errorChannel { {filename ""} } {
425 variable errorChannel
426 variable ChannelsWeOpened
428 # This is subtle and tricky. See the comment above in
429 # [outputChannel] for a detailed explanation.
432 if {[llength [info level 0]] == 1} {
435 if {[info exists ChannelsWeOpened($errorChannel)]} {
437 unset ChannelsWeOpened($errorChannel)
439 switch -exact -- $filename {
442 set errorChannel $filename
445 set errorChannel [open $filename a]
446 set ChannelsWeOpened($errorChannel) 1
448 # If we created the file in [temporaryDirectory], then
449 # [cleanupTests] will delete it, unless we claim it was
451 set outdir [normalizePath [file dirname \
452 [file join [pwd] $filename]]]
453 if {[string equal $outdir [temporaryDirectory]]} {
454 variable filesExisted
456 set filename [file tail $filename]
457 if {[lsearch -exact $filesExisted $filename] == -1} {
458 lappend filesExisted $filename
466 ##### Set up the configurable options
468 # The configurable options of the package
469 variable Option; array set Option {}
471 # Usage strings for those options
472 variable Usage; array set Usage {}
474 # Verification commands for those options
475 variable Verify; array set Verify {}
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 {}}} {
487 variable OptionControlledVariables
488 set Usage($option) $usage
489 set Verify($option) $verify
490 if {[catch {$verify $value} msg]} {
491 return -code error $msg
493 set Option($option) $msg
495 if {[string length $varName]} {
497 if {[info exists $varName]} {
498 if {[catch {$verify [set $varName]} msg]} {
499 return -code error $msg
501 set Option($option) $msg
505 namespace eval [namespace current] \
506 [list upvar 0 Option($option) $varName]
507 # Workaround for Bug (now Feature Request) 572889. Grrrr....
508 # Track all the variables tied to options
509 lappend OptionControlledVariables $varName
510 # Later, set auto-configure read traces on all
511 # of them, since a single trace on Option does not work.
512 proc $varName {{value {}}} [subst -nocommands {
513 if {[llength [info level 0]] == 2} {
514 Configure $option [set value]
516 return [Configure $option]
521 proc MatchingOption {option} {
523 set match [array names Option $option*]
524 switch -- [llength $match] {
526 set sorted [lsort [array names Option]]
527 set values [join [lrange $sorted 0 end-1] ", "]
528 append values ", or [lindex $sorted end]"
529 return -code error "unknown option $option: should be\
533 return [lindex $match 0]
536 # Exact match trumps ambiguity
537 if {[lsearch -exact $match $option] >= 0} {
540 set values [join [lrange $match 0 end-1] ", "]
541 append values ", or [lindex $match end]"
542 return -code error "ambiguous option $option:\
548 proc EstablishAutoConfigureTraces {} {
549 variable OptionControlledVariables
550 foreach varName [concat $OptionControlledVariables Option] {
552 trace variable $varName r [namespace code {ProcessCmdLineArgs ;#}]
556 proc RemoveAutoConfigureTraces {} {
557 variable OptionControlledVariables
558 foreach varName [concat $OptionControlledVariables Option] {
560 foreach pair [trace vinfo $varName] {
561 foreach {op cmd} $pair break
562 if {[string equal r $op]
563 && [string match *ProcessCmdLineArgs* $cmd]} {
564 trace vdelete $varName $op $cmd
568 # Once the traces are removed, this can become a no-op
569 proc RemoveAutoConfigureTraces {} {}
572 proc Configure args {
575 set n [llength $args]
577 return [lsort [array names Option]]
580 if {[catch {MatchingOption [lindex $args 0]} option]} {
581 return -code error $option
583 return $Option($option)
585 while {[llength $args] > 1} {
586 if {[catch {MatchingOption [lindex $args 0]} option]} {
587 return -code error $option
589 if {[catch {$Verify($option) [lindex $args 1]} value]} {
590 return -code error "invalid $option\
591 value \"[lindex $args 1]\": $value"
593 set Option($option) $value
594 set args [lrange $args 2 end]
596 if {[llength $args]} {
597 if {[catch {MatchingOption [lindex $args 0]} option]} {
598 return -code error $option
600 return -code error "missing value for option $option"
603 proc configure args {
604 RemoveAutoConfigureTraces
605 set code [catch {Configure {*}$args} msg]
606 return -code $code $msg
609 proc AcceptVerbose { level } {
610 set level [AcceptList $level]
611 if {[llength $level] == 1} {
612 if {![regexp {^(pass|body|skip|start|error|line)$} $level]} {
613 # translate single characters abbreviations to expanded list
614 set level [string map {p pass b body s skip t start e error l line} \
620 if {[regexp {^(pass|body|skip|start|error|line)$} $v]} {
627 proc IsVerbose {level} {
629 return [expr {[lsearch -exact $Option(-verbose) $level] != -1}]
632 # Default verbosity is to show bodies of failed tests
633 Option -verbose {body error} {
634 Takes any combination of the values 'p', 's', 'b', 't', 'e' and 'l'.
635 Test suite will display all passed tests if 'p' is specified, all
636 skipped tests if 's' is specified, the bodies of failed tests if
637 'b' is specified, and when tests start if 't' is specified.
638 ErrorInfo is displayed if 'e' is specified. Source file line
639 information of failed tests is displayed if 'l' is specified.
640 } AcceptVerbose verbose
642 # Match and skip patterns default to the empty list, except for
643 # matchFiles, which defaults to all .test files in the
644 # testsDirectory and matchDirectories, which defaults to all
647 Run all tests within the specified files that match one of the
648 list of glob patterns given.
652 Skip all tests within the specified tests (via -match) and files
653 that match one of the list of glob patterns given.
656 Option -file *.test {
657 Run tests in all test files that match the glob pattern given.
658 } AcceptPattern matchFiles
660 # By default, skip files that appear to be SCCS lock files.
661 Option -notfile l.*.test {
662 Skip all test files that match the glob pattern given.
663 } AcceptPattern skipFiles
665 Option -relateddir * {
666 Run tests in directories that match the glob pattern given.
667 } AcceptPattern matchDirectories
669 Option -asidefromdir {} {
670 Skip tests in directories that match the glob pattern given.
671 } AcceptPattern skipDirectories
673 # By default, don't save core files
674 Option -preservecore 0 {
675 If 2, save any core files produced during testing in the directory
676 specified by -tmpdir. If 1, notify the user if core files are
678 } AcceptInteger preserveCore
680 # debug output doesn't get printed by default; debug level 1 spits
681 # up only the tests that were skipped because they didn't match or
682 # were specifically skipped. A debug level of 2 would spit up the
683 # tcltest variables and flags provided; a debug level of 3 causes
684 # some additional output regarding operations of the test harness.
685 # The tcltest package currently implements only up to debug level 3.
688 } AcceptInteger debug
690 proc SetSelectedConstraints args {
692 foreach c $Option(-constraints) {
696 Option -constraints {} {
697 Do not skip the listed constraints listed in -constraints.
699 trace variable Option(-constraints) w \
700 [namespace code {SetSelectedConstraints ;#}]
702 # Don't run only the "-constraint" specified tests by default
703 proc ClearUnselectedConstraints args {
705 variable testConstraints
706 if {!$Option(-limitconstraints)} {return}
707 foreach c [array names testConstraints] {
708 if {[lsearch -exact $Option(-constraints) $c] == -1} {
713 Option -limitconstraints false {
714 whether to run only tests with the constraints
715 } AcceptBoolean limitConstraints
716 trace variable Option(-limitconstraints) w \
717 [namespace code {ClearUnselectedConstraints ;#}]
719 # A test application has to know how to load the tested commands
720 # into the interpreter.
722 Specifies the script to load the tested commands.
723 } AcceptScript loadScript
725 # Default is to run each test file in a separate process
726 Option -singleproc 0 {
727 whether to run all tests in one process
728 } AcceptBoolean singleProcess
730 proc AcceptTemporaryDirectory { directory } {
731 set directory [AcceptAbsolutePath $directory]
732 if {![file exists $directory]} {
733 file mkdir $directory
735 set directory [AcceptDirectory $directory]
736 if {![file writable $directory]} {
737 if {[string equal [workingDirectory] $directory]} {
738 # Special exception: accept the default value
739 # even if the directory is not writable
742 return -code error "\"$directory\" is not writeable"
747 # Directory where files should be created
748 Option -tmpdir [workingDirectory] {
749 Save temporary files in the specified directory.
750 } AcceptTemporaryDirectory temporaryDirectory
751 trace variable Option(-tmpdir) w \
752 [namespace code {normalizePath Option(-tmpdir) ;#}]
754 # Tests should not rely on the current working directory.
755 # Files that are part of the test suite should be accessed relative
756 # to [testsDirectory]
757 Option -testdir [workingDirectory] {
758 Search tests in the specified directory.
759 } AcceptDirectory testsDirectory
760 trace variable Option(-testdir) w \
761 [namespace code {normalizePath Option(-testdir) ;#}]
763 proc AcceptLoadFile { file } {
764 if {[string equal "" $file]} {return $file}
765 set file [file join [temporaryDirectory] $file]
766 return [AcceptReadable $file]
768 proc ReadLoadScript {args} {
770 if {[string equal "" $Option(-loadfile)]} {return}
771 set tmp [open $Option(-loadfile) r]
772 loadScript [read $tmp]
775 Option -loadfile {} {
776 Read the script to load the tested commands from the specified file.
777 } AcceptLoadFile loadFile
778 trace variable Option(-loadfile) w [namespace code ReadLoadScript]
780 proc AcceptOutFile { file } {
781 if {[string equal stderr $file]} {return $file}
782 if {[string equal stdout $file]} {return $file}
783 return [file join [temporaryDirectory] $file]
786 # output goes to stdout by default
787 Option -outfile stdout {
788 Send output from test runs to the specified file.
789 } AcceptOutFile outputFile
790 trace variable Option(-outfile) w \
791 [namespace code {outputChannel $Option(-outfile) ;#}]
793 # errors go to stderr by default
794 Option -errfile stderr {
795 Send errors from test runs to the specified file.
796 } AcceptOutFile errorFile
797 trace variable Option(-errfile) w \
798 [namespace code {errorChannel $Option(-errfile) ;#}]
802 #####################################################################
806 # Internal helper procedures to write out debug information
807 # dependent on the chosen level. A test shell may overide
808 # them, f.e. to redirect the output into a different
809 # channel, or even into a GUI.
811 # tcltest::DebugPuts --
813 # Prints the specified string if the current debug level is
814 # higher than the provided level argument.
817 # level The lowest debug level triggering the output
818 # string The string to print out.
821 # Prints the string. Nothing else is allowed.
827 proc tcltest::DebugPuts {level string} {
829 if {$debug >= $level} {
835 # tcltest::DebugPArray --
837 # Prints the contents of the specified array if the current
838 # debug level is higher than the provided level argument
841 # level The lowest debug level triggering the output
842 # arrayvar The name of the array to print out.
845 # Prints the contents of the array. Nothing else is allowed.
851 proc tcltest::DebugPArray {level arrayvar} {
854 if {$debug >= $level} {
855 catch {upvar $arrayvar $arrayvar}
861 # Define our own [parray] in ::tcltest that will inherit use of the [puts]
862 # defined in ::tcltest. NOTE: Ought to construct with [info args] and
863 # [info default], but can't be bothered now. If [parray] changes, then
864 # this will need changing too.
866 proc tcltest::parray {a {pattern *}} [info body ::parray]
868 # tcltest::DebugDo --
870 # Executes the script if the current debug level is greater than
871 # the provided level argument
874 # level The lowest debug level triggering the execution.
875 # script The tcl script executed upon a debug level high enough.
878 # Arbitrary side effects, dependent on the executed script.
884 proc tcltest::DebugDo {level script} {
887 if {$debug >= $level} {
893 #####################################################################
895 proc tcltest::Warn {msg} {
896 puts [outputChannel] "WARNING: $msg"
899 # tcltest::mainThread
901 # Accessor command for tcltest variable mainThread.
903 proc tcltest::mainThread { {new ""} } {
905 if {[llength [info level 0]] == 1} {
911 # tcltest::testConstraint --
913 # sets a test constraint to a value; to do multiple constraints,
914 # call this proc multiple times. also returns the value of the
915 # named constraint if no value was supplied.
918 # constraint - name of the constraint
919 # value - new value for constraint (should be boolean) - if not
920 # supplied, this is a query
923 # content of tcltest::testConstraints($constraint)
928 proc tcltest::testConstraint {constraint {value ""}} {
929 variable testConstraints
931 DebugPuts 3 "entering testConstraint $constraint $value"
932 if {[llength [info level 0]] == 2} {
933 return $testConstraints($constraint)
935 # Check for boolean values
936 if {[catch {expr {$value && $value}} msg]} {
937 return -code error $msg
939 if {[limitConstraints]
940 && [lsearch -exact $Option(-constraints) $constraint] == -1} {
943 set testConstraints($constraint) $value
946 # tcltest::interpreter --
948 # the interpreter name stored in tcltest::tcltest
954 # content of tcltest::tcltest
959 proc tcltest::interpreter { {interp ""} } {
961 if {[llength [info level 0]] == 1} {
964 if {[string equal {} $interp]} {
971 #####################################################################
973 # tcltest::AddToSkippedBecause --
975 # Increments the variable used to track how many tests were
976 # skipped because of a particular constraint.
979 # constraint The name of the constraint to be modified
982 # Modifies tcltest::skippedBecause; sets the variable to 1 if
983 # didn't previously exist - otherwise, it just increments it.
988 proc tcltest::AddToSkippedBecause { constraint {value 1}} {
989 # add the constraint to the list of constraints that kept tests
991 variable skippedBecause
993 if {[info exists skippedBecause($constraint)]} {
994 incr skippedBecause($constraint) $value
996 set skippedBecause($constraint) $value
1001 # tcltest::PrintError --
1003 # Prints errors to tcltest::errorChannel and then flushes that
1004 # channel, making sure that all messages are < 80 characters per
1008 # errorMsg String containing the error to be printed
1016 proc tcltest::PrintError {errorMsg} {
1017 set InitialMessage "Error: "
1018 set InitialMsgLen [string length $InitialMessage]
1019 puts -nonewline [errorChannel] $InitialMessage
1021 # Keep track of where the end of the string is.
1022 set endingIndex [string length $errorMsg]
1024 if {$endingIndex < (80 - $InitialMsgLen)} {
1025 puts [errorChannel] $errorMsg
1027 # Print up to 80 characters on the first line, including the
1029 set beginningIndex [string last " " [string range $errorMsg 0 \
1030 [expr {80 - $InitialMsgLen}]]]
1031 puts [errorChannel] [string range $errorMsg 0 $beginningIndex]
1033 while {![string equal end $beginningIndex]} {
1034 puts -nonewline [errorChannel] \
1035 [string repeat " " $InitialMsgLen]
1036 if {($endingIndex - $beginningIndex)
1037 < (80 - $InitialMsgLen)} {
1038 puts [errorChannel] [string trim \
1039 [string range $errorMsg $beginningIndex end]]
1042 set newEndingIndex [expr {[string last " " \
1043 [string range $errorMsg $beginningIndex \
1044 [expr {$beginningIndex
1045 + (80 - $InitialMsgLen)}]
1046 ]] + $beginningIndex}]
1047 if {($newEndingIndex <= 0)
1048 || ($newEndingIndex <= $beginningIndex)} {
1049 set newEndingIndex end
1051 puts [errorChannel] [string trim \
1052 [string range $errorMsg \
1053 $beginningIndex $newEndingIndex]]
1054 set beginningIndex $newEndingIndex
1058 flush [errorChannel]
1062 # tcltest::SafeFetch --
1064 # The following trace procedure makes it so that we can safely
1065 # refer to non-existent members of the testConstraints array
1066 # without causing an error. Instead, reading a non-existent
1067 # member will return 0. This is necessary because tests are
1068 # allowed to use constraint "X" without ensuring that
1069 # testConstraints("X") is defined.
1072 # n1 - name of the array (testConstraints)
1073 # n2 - array key value (constraint name)
1074 # op - operation performed on testConstraints (generally r)
1080 # sets testConstraints($n2) to 0 if it's referenced but never
1083 proc tcltest::SafeFetch {n1 n2 op} {
1084 variable testConstraints
1085 DebugPuts 3 "entering SafeFetch $n1 $n2 $op"
1086 if {[string equal {} $n2]} {return}
1087 if {![info exists testConstraints($n2)]} {
1088 if {[catch {testConstraint $n2 [eval [ConstraintInitializer $n2]]}]} {
1089 testConstraint $n2 0
1094 # tcltest::ConstraintInitializer --
1096 # Get or set a script that when evaluated in the tcltest namespace
1097 # will return a boolean value with which to initialize the
1098 # associated constraint.
1101 # constraint - name of the constraint initialized by the script
1102 # script - the initializer script
1105 # boolean value of the constraint - enabled or disabled
1108 # Constraint is initialized for future reference by [test]
1109 proc tcltest::ConstraintInitializer {constraint {script ""}} {
1110 variable ConstraintInitializer
1111 DebugPuts 3 "entering ConstraintInitializer $constraint $script"
1112 if {[llength [info level 0]] == 2} {
1113 return $ConstraintInitializer($constraint)
1115 # Check for boolean values
1116 if {![info complete $script]} {
1117 return -code error "ConstraintInitializer must be complete script"
1119 set ConstraintInitializer($constraint) $script
1122 # tcltest::InitConstraints --
1124 # Call all registered constraint initializers to force initialization
1125 # of all known constraints.
1126 # See the tcltest man page for the list of built-in constraints defined
1127 # in this procedure.
1133 # The testConstraints array is reset to have an index for each
1134 # built-in test constraint.
1140 proc tcltest::InitConstraints {} {
1141 variable ConstraintInitializer
1143 foreach constraint [array names ConstraintInitializer] {
1144 testConstraint $constraint
1148 proc tcltest::DefineConstraintInitializers {} {
1149 ConstraintInitializer singleTestInterp {singleProcess}
1151 # All the 'pc' constraints are here for backward compatibility and
1152 # are not documented. They have been replaced with equivalent 'win'
1155 ConstraintInitializer unixOnly \
1156 {string equal $::tcl_platform(platform) unix}
1157 ConstraintInitializer macOnly \
1158 {string equal $::tcl_platform(platform) macintosh}
1159 ConstraintInitializer pcOnly \
1160 {string equal $::tcl_platform(platform) windows}
1161 ConstraintInitializer winOnly \
1162 {string equal $::tcl_platform(platform) windows}
1164 ConstraintInitializer unix {testConstraint unixOnly}
1165 ConstraintInitializer mac {testConstraint macOnly}
1166 ConstraintInitializer pc {testConstraint pcOnly}
1167 ConstraintInitializer win {testConstraint winOnly}
1169 ConstraintInitializer unixOrPc \
1170 {expr {[testConstraint unix] || [testConstraint pc]}}
1171 ConstraintInitializer macOrPc \
1172 {expr {[testConstraint mac] || [testConstraint pc]}}
1173 ConstraintInitializer unixOrWin \
1174 {expr {[testConstraint unix] || [testConstraint win]}}
1175 ConstraintInitializer macOrWin \
1176 {expr {[testConstraint mac] || [testConstraint win]}}
1177 ConstraintInitializer macOrUnix \
1178 {expr {[testConstraint mac] || [testConstraint unix]}}
1180 ConstraintInitializer nt {string equal $::tcl_platform(os) "Windows NT"}
1181 ConstraintInitializer 95 {string equal $::tcl_platform(os) "Windows 95"}
1182 ConstraintInitializer 98 {string equal $::tcl_platform(os) "Windows 98"}
1184 # The following Constraints switches are used to mark tests that
1185 # should work, but have been temporarily disabled on certain
1186 # platforms because they don't and we haven't gotten around to
1187 # fixing the underlying problem.
1189 ConstraintInitializer tempNotPc {expr {![testConstraint pc]}}
1190 ConstraintInitializer tempNotWin {expr {![testConstraint win]}}
1191 ConstraintInitializer tempNotMac {expr {![testConstraint mac]}}
1192 ConstraintInitializer tempNotUnix {expr {![testConstraint unix]}}
1194 # The following Constraints switches are used to mark tests that
1195 # crash on certain platforms, so that they can be reactivated again
1196 # when the underlying problem is fixed.
1198 ConstraintInitializer pcCrash {expr {![testConstraint pc]}}
1199 ConstraintInitializer winCrash {expr {![testConstraint win]}}
1200 ConstraintInitializer macCrash {expr {![testConstraint mac]}}
1201 ConstraintInitializer unixCrash {expr {![testConstraint unix]}}
1205 ConstraintInitializer emptyTest {format 0}
1207 # By default, tests that expose known bugs are skipped.
1209 ConstraintInitializer knownBug {format 0}
1211 # By default, non-portable tests are skipped.
1213 ConstraintInitializer nonPortable {format 0}
1215 # Some tests require user interaction.
1217 ConstraintInitializer userInteraction {format 0}
1219 # Some tests must be skipped if the interpreter is not in
1222 ConstraintInitializer interactive \
1223 {expr {[info exists ::tcl_interactive] && $::tcl_interactive}}
1225 # Some tests can only be run if the installation came from a CD
1226 # image instead of a web image. Some tests must be skipped if you
1227 # are running as root on Unix. Other tests can only be run if you
1228 # are running as root on Unix.
1230 ConstraintInitializer root {expr \
1231 {[string equal unix $::tcl_platform(platform)]
1232 && ([string equal root $::tcl_platform(user)]
1233 || [string equal "" $::tcl_platform(user)])}}
1234 ConstraintInitializer notRoot {expr {![testConstraint root]}}
1236 # Set nonBlockFiles constraint: 1 means this platform supports
1237 # setting files into nonblocking mode.
1239 ConstraintInitializer nonBlockFiles {
1240 set code [expr {[catch {set f [open defs r]}]
1241 || [catch {fconfigure $f -blocking off}]}]
1246 # Set asyncPipeClose constraint: 1 means this platform supports
1247 # async flush and async close on a pipe.
1249 # Test for SCO Unix - cannot run async flushing tests because a
1250 # potential problem with select is apparently interfering.
1253 ConstraintInitializer asyncPipeClose {expr {
1254 !([string equal unix $::tcl_platform(platform)]
1255 && ([catch {exec uname -X | fgrep {Release = 3.2v}}] == 0))}}
1257 # Test to see if we have a broken version of sprintf with respect
1258 # to the "e" format of floating-point numbers.
1260 ConstraintInitializer eformat {string equal [format %g 5e-5] 5e-05}
1262 # Test to see if execed commands such as cat, echo, rm and so forth
1263 # are present on this machine.
1265 ConstraintInitializer unixExecs {
1267 if {[string equal macintosh $::tcl_platform(platform)]} {
1270 if {[string equal windows $::tcl_platform(platform)]} {
1272 set file _tcl_test_remove_me.txt
1273 makeFile {hello} $file
1277 [catch {exec cat $file}] ||
1278 [catch {exec echo hello}] ||
1279 [catch {exec sh -c echo hello}] ||
1280 [catch {exec wc $file}] ||
1281 [catch {exec sleep 1}] ||
1282 [catch {exec echo abc > $file}] ||
1283 [catch {exec chmod 644 $file}] ||
1284 [catch {exec rm $file}] ||
1285 [llength [auto_execok mkdir]] == 0 ||
1286 [llength [auto_execok fgrep]] == 0 ||
1287 [llength [auto_execok grep]] == 0 ||
1288 [llength [auto_execok ps]] == 0
1297 ConstraintInitializer stdio {
1299 if {![catch {set f [open "|[list [interpreter]]" w]}]} {
1300 if {![catch {puts $f exit}]} {
1301 if {![catch {close $f}]} {
1309 # Deliberately call socket with the wrong number of arguments. The
1310 # error message you get will indicate whether sockets are available
1313 ConstraintInitializer socket {
1315 string compare $msg "sockets are not available on this system"
1318 # Check for internationalization
1319 ConstraintInitializer hasIsoLocale {
1320 if {[llength [info commands testlocale]] == 0} {
1323 set code [string length [SetIso8859_1_Locale]]
1330 #####################################################################
1332 # Usage and command line arguments processing.
1334 # tcltest::PrintUsageInfo
1336 # Prints out the usage information for package tcltest. This can
1337 # be customized with the redefinition of [PrintUsageInfoHook].
1347 proc tcltest::PrintUsageInfo {} {
1352 proc tcltest::Usage { {option ""} } {
1355 if {[llength [info level 0]] == 1} {
1356 set msg "Usage: [file tail [info nameofexecutable]] script "
1357 append msg "?-help? ?flag value? ... \n"
1358 append msg "Available flags (and valid input values) are:"
1361 set allOpts [concat -help [Configure]]
1362 foreach opt $allOpts {
1363 set foo [Usage $opt]
1364 foreach [list x type($opt) usage($opt)] $foo break
1365 set line($opt) " $opt $type($opt) "
1366 set length($opt) [string length $line($opt)]
1367 if {$length($opt) > $max} {set max $length($opt)}
1369 set rest [expr {72 - $max}]
1370 foreach opt $allOpts {
1371 append msg \n$line($opt)
1372 append msg [string repeat " " [expr {$max - $length($opt)}]]
1373 set u [string trim $usage($opt)]
1374 catch {append u " (default: \[[Configure $opt]])"}
1375 regsub -all {\s*\n\s*} $u " " u
1376 while {[string length $u] > $rest} {
1377 set break [string wordstart $u $rest]
1379 set break [string wordend $u 0]
1381 append msg [string range $u 0 [expr {$break - 1}]]
1382 set u [string trim [string range $u $break end]]
1383 append msg \n[string repeat " " $max]
1388 } elseif {[string equal -help $option]} {
1389 return [list -help "" "Display this usage information."]
1391 set type [lindex [info args $Verify($option)] 0]
1392 return [list $option $type $Usage($option)]
1396 # tcltest::ProcessFlags --
1398 # process command line arguments supplied in the flagArray - this
1399 # is called by processCmdLineArgs. Modifies tcltest variables
1400 # according to the content of the flagArray.
1403 # flagArray - array containing name/value pairs of flags
1406 # sets tcltest variables according to their values as defined by
1412 proc tcltest::ProcessFlags {flagArray} {
1413 # Process -help first
1414 if {[lsearch -exact $flagArray {-help}] != -1} {
1419 if {[llength $flagArray] == 0} {
1420 RemoveAutoConfigureTraces
1423 while {[llength $args]>1 && [catch {configure {*}$args} msg]} {
1425 # Something went wrong parsing $args for tcltest options
1426 # Check whether the problem is "unknown option"
1427 if {[regexp {^unknown option (\S+):} $msg -> option]} {
1428 # Could be this is an option the Hook knows about
1429 set moreOptions [processCmdLineArgsAddFlagsHook]
1430 if {[lsearch -exact $moreOptions $option] == -1} {
1431 # Nope. Report the error, including additional options,
1433 if {[llength $moreOptions]} {
1435 append msg [join [lrange $moreOptions 0 end-1] ", "]
1436 append msg "or [lindex $moreOptions end]"
1441 # error is something other than "unknown option"
1442 # notify user of the error; and exit
1443 puts [errorChannel] $msg
1447 # To recover, find that unknown option and remove up to it.
1449 while {![string equal [lindex $args 0] $option]} {
1450 set args [lrange $args 2 end]
1452 set args [lrange $args 2 end]
1454 if {[llength $args] == 1} {
1455 puts [errorChannel] \
1456 "missing value for option [lindex $args 0]"
1463 array set flag $flagArray
1464 processCmdLineArgsHook [array get flag]
1469 # tcltest::ProcessCmdLineArgs --
1471 # This procedure must be run after constraint initialization is
1472 # set up (by [DefineConstraintInitializers]) because some constraints
1473 # can be overridden.
1475 # Perform configuration according to the command-line options.
1481 # Sets the above-named variables in the tcltest namespace.
1487 proc tcltest::ProcessCmdLineArgs {} {
1488 variable originalEnv
1489 variable testConstraints
1491 # The "argv" var doesn't exist in some cases, so use {}.
1492 if {![info exists ::argv]} {
1495 ProcessFlags $::argv
1498 # Spit out everything you know if we're at a debug level 2 or
1500 DebugPuts 2 "Flags passed into tcltest:"
1501 if {[info exists ::env(TCLTEST_OPTIONS)]} {
1503 " ::env(TCLTEST_OPTIONS): $::env(TCLTEST_OPTIONS)"
1505 if {[info exists ::argv]} {
1506 DebugPuts 2 " argv: $::argv"
1508 DebugPuts 2 "tcltest::debug = [debug]"
1509 DebugPuts 2 "tcltest::testsDirectory = [testsDirectory]"
1510 DebugPuts 2 "tcltest::workingDirectory = [workingDirectory]"
1511 DebugPuts 2 "tcltest::temporaryDirectory = [temporaryDirectory]"
1512 DebugPuts 2 "tcltest::outputChannel = [outputChannel]"
1513 DebugPuts 2 "tcltest::errorChannel = [errorChannel]"
1514 DebugPuts 2 "Original environment (tcltest::originalEnv):"
1515 DebugPArray 2 originalEnv
1516 DebugPuts 2 "Constraints:"
1517 DebugPArray 2 testConstraints
1520 #####################################################################
1522 # Code to run the tests goes here.
1524 # tcltest::TestPuts --
1526 # Used to redefine puts in test environment. Stores whatever goes
1527 # out on stdout in tcltest::outData and stderr in errData before
1528 # sending it on to the regular puts.
1531 # same as standard puts
1537 # Intercepts puts; data that would otherwise go to stdout, stderr,
1538 # or file channels specified in outputChannel and errorChannel
1539 # does not get sent to the normal puts function.
1540 namespace eval tcltest::Replace {
1541 namespace export puts
1543 proc tcltest::Replace::puts {args} {
1544 variable [namespace parent]::outData
1545 variable [namespace parent]::errData
1546 switch [llength $args] {
1548 # Only the string to be printed is specified
1549 append outData [lindex $args 0]\n
1551 # return [Puts [lindex $args 0]]
1554 # Either -nonewline or channelId has been specified
1555 if {[string equal -nonewline [lindex $args 0]]} {
1556 append outData [lindex $args end]
1558 # return [Puts -nonewline [lindex $args end]]
1560 set channel [lindex $args 0]
1565 if {[string equal -nonewline [lindex $args 0]]} {
1566 # Both -nonewline and channelId are specified, unless
1567 # it's an error. -nonewline is supposed to be argv[0].
1568 set channel [lindex $args 1]
1574 if {[info exists channel]} {
1575 if {[string equal $channel [[namespace parent]::outputChannel]]
1576 || [string equal $channel stdout]} {
1577 append outData [lindex $args end]$newline
1579 } elseif {[string equal $channel [[namespace parent]::errorChannel]]
1580 || [string equal $channel stderr]} {
1581 append errData [lindex $args end]$newline
1586 # If we haven't returned by now, we don't know how to handle the
1587 # input. Let puts handle it.
1588 return [Puts {*}$args]
1593 # Evaluate the script in the test environment. If ignoreOutput is
1594 # false, store data sent to stderr and stdout in outData and
1595 # errData. Otherwise, ignore this output altogether.
1598 # script Script to evaluate
1599 # ?ignoreOutput? Indicates whether or not to ignore output
1600 # sent to stdout & stderr
1603 # result from running the script
1606 # Empties the contents of outData and errData before running a
1607 # test if ignoreOutput is set to 0.
1609 proc tcltest::Eval {script {ignoreOutput 1}} {
1612 DebugPuts 3 "[lindex [info level 0] 0] called"
1613 if {!$ignoreOutput} {
1616 rename ::puts [namespace current]::Replace::Puts
1617 namespace eval :: [list namespace import [namespace origin Replace::puts]]
1618 namespace import Replace::puts
1620 set result [uplevel 1 $script]
1621 if {!$ignoreOutput} {
1622 namespace forget puts
1623 namespace eval :: namespace forget puts
1624 rename [namespace current]::Replace::Puts ::puts
1629 # tcltest::CompareStrings --
1631 # compares the expected answer to the actual answer, depending on
1632 # the mode provided. Mode determines whether a regexp, exact,
1633 # glob or custom comparison is done.
1636 # actual - string containing the actual result
1637 # expected - pattern to be matched against
1638 # mode - type of comparison to be done
1641 # result of the match
1646 proc tcltest::CompareStrings {actual expected mode} {
1647 variable CustomMatch
1648 if {![info exists CustomMatch($mode)]} {
1649 return -code error "No matching command registered for `-match $mode'"
1651 set match [namespace eval :: $CustomMatch($mode) [list $expected $actual]]
1652 if {[catch {expr {$match && $match}} result]} {
1653 return -code error "Invalid result from `-match $mode' command: $result"
1658 # tcltest::customMatch --
1660 # registers a command to be called when a particular type of
1661 # matching is required.
1664 # nickname - Keyword for the type of matching
1665 # cmd - Incomplete command that implements that type of matching
1666 # when completed with expected string and actual string
1667 # and then evaluated.
1673 # Sets the variable tcltest::CustomMatch
1675 proc tcltest::customMatch {mode script} {
1676 variable CustomMatch
1677 if {![info complete $script]} {
1678 return -code error \
1679 "invalid customMatch script; can't evaluate after completion"
1681 set CustomMatch($mode) $script
1684 # tcltest::SubstArguments list
1686 # This helper function takes in a list of words, then perform a
1687 # substitution on the list as though each word in the list is a separate
1688 # argument to the Tcl function. For example, if this function is
1691 # SubstArguments {$a {$a}}
1693 # Then it is as though the function is invoked as:
1695 # SubstArguments $a {$a}
1697 # This code is adapted from Paul Duffin's function "SplitIntoWords".
1698 # The original function can be found on:
1700 # http://purl.org/thecliff/tcl/wiki/858.html
1703 # a list containing the result of the substitution
1706 # An error may occur if the list containing unbalanced quote or
1713 proc tcltest::SubstArguments {argList} {
1715 # We need to split the argList up into tokens but cannot use list
1716 # operations as they throw away some significant quoting, and
1717 # [split] ignores braces as it should. Therefore what we do is
1718 # gradually build up a string out of whitespace seperated strings.
1719 # We cannot use [split] to split the argList into whitespace
1720 # separated strings as it throws away the whitespace which maybe
1721 # important so we have to do it all by hand.
1726 while {[string length $argList]} {
1727 # Look for the next word containing a quote: " { }
1728 if {[regexp -indices {[^ \t\n]*[\"\{\}]+[^ \t\n]*} \
1730 # Get the text leading up to this word, but not including
1731 # this word, from the argList.
1732 set text [string range $argList 0 \
1733 [expr {[lindex $all 0] - 1}]]
1734 # Get the word with the quote
1735 set word [string range $argList \
1736 [lindex $all 0] [lindex $all 1]]
1738 # Remove all text up to and including the word from the
1740 set argList [string range $argList \
1741 [expr {[lindex $all 1] + 1}] end]
1743 # Take everything up to the end of the argList.
1750 # If we saw a word with quote before, then there is a
1751 # multi-word token starting with that word. In this case,
1752 # add the text and the current word to this token.
1753 append token $text $word
1755 # Add the text to the result. There is no need to parse
1756 # the text because it couldn't be a part of any multi-word
1757 # token. Then start a new multi-word token with the word
1758 # because we need to pass this token to the Tcl parser to
1759 # check for balancing quotes
1764 if { [catch {llength $token} length] == 0 && $length == 1} {
1765 # The token is a valid list so add it to the result.
1766 # lappend result [string trim $token]
1767 append result \{$token\}
1772 # If the last token has not been added to the list then there
1774 if { [string length $token] } {
1775 error "incomplete token \"$token\""
1784 # This procedure runs a test and prints an error message if the test
1785 # fails. If verbose has been set, it also prints a message even if the
1786 # test succeeds. The test will be skipped if it doesn't match the
1787 # match variable, if it matches an element in skip, or if one of the
1788 # elements of "constraints" turns out not to be true.
1790 # If testLevel is 1, then this is a top level test, and we record
1791 # pass/fail information; otherwise, this information is not logged and
1792 # is not added to running totals.
1795 # Only description is a required attribute. All others are optional.
1796 # Default values are indicated.
1798 # constraints - A list of one or more keywords, each of which
1799 # must be the name of an element in the array
1800 # "testConstraints". If any of these elements is
1801 # zero, the test is skipped. This attribute is
1802 # optional; default is {}
1803 # body - Script to run to carry out the test. It must
1804 # return a result that can be checked for
1805 # correctness. This attribute is optional;
1807 # result - Expected result from script. This attribute is
1808 # optional; default is {}.
1809 # output - Expected output sent to stdout. This attribute
1810 # is optional; default is {}.
1811 # errorOutput - Expected output sent to stderr. This attribute
1812 # is optional; default is {}.
1813 # returnCodes - Expected return codes. This attribute is
1814 # optional; default is {0 2}.
1815 # setup - Code to run before $script (above). This
1816 # attribute is optional; default is {}.
1817 # cleanup - Code to run after $script (above). This
1818 # attribute is optional; default is {}.
1819 # match - specifies type of matching to do on result,
1820 # output, errorOutput; this must be a string
1821 # previously registered by a call to [customMatch].
1822 # The strings exact, glob, and regexp are pre-registered
1823 # by the tcltest package. Default value is exact.
1826 # name - Name of test, in the form foo-1.2.
1827 # description - Short textual description of the test, to
1828 # help humans understand what it does.
1834 # Just about anything is possible depending on the test.
1837 proc tcltest::test {name description args} {
1840 variable coreModTime
1841 DebugPuts 3 "test $name $args"
1845 puts "test name '$name' re-used; prior use in $TestNames($name)"
1847 set TestNames($name) [info script]
1853 # Pre-define everything to null except output and errorOutput. We
1854 # determine whether or not to trap output based on whether or not
1855 # these variables (output & errorOutput) are defined.
1856 foreach item {constraints setup cleanup body result returnCodes
1861 # Set the default match mode
1864 # Set the default match values for return codes (0 is the standard
1865 # expected return value if everything went well; 2 represents
1866 # 'return' being used in the test script).
1867 set returnCodes [list 0 2]
1869 # The old test format can't have a 3rd argument (constraints or
1870 # script) that starts with '-'.
1871 if {[string match -* [lindex $args 0]]
1872 || ([llength $args] <= 1)} {
1873 if {[llength $args] == 1} {
1874 set list [SubstArguments [lindex $args 0]]
1875 foreach {element value} $list {
1876 set testAttributes($element) $value
1878 foreach item {constraints match setup body cleanup \
1879 result returnCodes output errorOutput} {
1880 if {[info exists testAttributes(-$item)]} {
1881 set testAttributes(-$item) [uplevel 1 \
1882 ::concat $testAttributes(-$item)]
1886 array set testAttributes $args
1889 set validFlags {-setup -cleanup -body -result -returnCodes \
1890 -match -output -errorOutput -constraints}
1892 foreach flag [array names testAttributes] {
1893 if {[lsearch -exact $validFlags $flag] == -1} {
1895 set sorted [lsort $validFlags]
1896 set options [join [lrange $sorted 0 end-1] ", "]
1897 append options ", or [lindex $sorted end]"
1898 return -code error "bad option \"$flag\": must be $options"
1902 # store whatever the user gave us
1903 foreach item [array names testAttributes] {
1904 set [string trimleft $item "-"] $testAttributes($item)
1907 # Check the values supplied for -match
1908 variable CustomMatch
1909 if {[lsearch [array names CustomMatch] $match] == -1} {
1911 set sorted [lsort [array names CustomMatch]]
1912 set values [join [lrange $sorted 0 end-1] ", "]
1913 append values ", or [lindex $sorted end]"
1914 return -code error "bad -match value \"$match\":\
1918 # Replace symbolic valies supplied for -returnCodes
1919 foreach {strcode numcode} {ok 0 normal 0 error 1 return 2 break 3 continue 4} {
1920 set returnCodes [string map -nocase [list $strcode $numcode] $returnCodes]
1923 # This is parsing for the old test command format; it is here
1924 # for backward compatibility.
1925 set result [lindex $args end]
1926 if {[llength $args] == 2} {
1927 set body [lindex $args 0]
1928 } elseif {[llength $args] == 3} {
1929 set constraints [lindex $args 0]
1930 set body [lindex $args 1]
1933 return -code error "wrong # args:\
1934 should be \"test name desc ?options?\""
1938 if {[Skipped $name $constraints]} {
1943 # Save information about the core file.
1944 if {[preserveCore]} {
1945 if {[file exists [file join [workingDirectory] core]]} {
1946 set coreModTime [file mtime [file join [workingDirectory] core]]
1950 # First, run the setup script
1951 set code [catch {uplevel 1 $setup} setupMsg]
1953 set errorInfo(setup) $::errorInfo
1954 set errorCode(setup) $::errorCode
1956 set setupFailure [expr {$code != 0}]
1958 # Only run the test body if the setup was successful
1959 if {!$setupFailure} {
1961 # Verbose notification of $body start
1962 if {[IsVerbose start]} {
1963 puts [outputChannel] "---- $name start"
1964 flush [outputChannel]
1967 set command [list [namespace origin RunTest] $name $body]
1968 if {[info exists output] || [info exists errorOutput]} {
1969 set testResult [uplevel 1 [list [namespace origin Eval] $command 0]]
1971 set testResult [uplevel 1 [list [namespace origin Eval] $command 1]]
1973 foreach {actualAnswer returnCode} $testResult break
1974 if {$returnCode == 1} {
1975 set errorInfo(body) $::errorInfo
1976 set errorCode(body) $::errorCode
1980 # Always run the cleanup script
1981 set code [catch {uplevel 1 $cleanup} cleanupMsg]
1983 set errorInfo(cleanup) $::errorInfo
1984 set errorCode(cleanup) $::errorCode
1986 set cleanupFailure [expr {$code != 0}]
1990 # check for a core file first - if one was created by the test,
1991 # then the test failed
1992 if {[preserveCore]} {
1993 if {[file exists [file join [workingDirectory] core]]} {
1994 # There's only a test failure if there is a core file
1995 # and (1) there previously wasn't one or (2) the new
1996 # one is different from the old one.
1997 if {[info exists coreModTime]} {
1998 if {$coreModTime != [file mtime \
1999 [file join [workingDirectory] core]]} {
2006 if {([preserveCore] > 1) && ($coreFailure)} {
2007 append coreMsg "\nMoving file to:\
2008 [file join [temporaryDirectory] core-$name]"
2009 catch {file rename -force \
2010 [file join [workingDirectory] core] \
2011 [file join [temporaryDirectory] core-$name]
2013 if {[string length $msg] > 0} {
2014 append coreMsg "\nError:\
2015 Problem renaming core file: $msg"
2021 # check if the return code matched the expected return code
2023 if {!$setupFailure && [lsearch -exact $returnCodes $returnCode] == -1} {
2027 # If expected output/error strings exist, we have to compare
2028 # them. If the comparison fails, then so did the test.
2031 if {[info exists output] && !$codeFailure} {
2032 if {[set outputCompare [catch {
2033 CompareStrings $outData $output $match
2034 } outputMatch]] == 0} {
2035 set outputFailure [expr {!$outputMatch}]
2043 if {[info exists errorOutput] && !$codeFailure} {
2044 if {[set errorCompare [catch {
2045 CompareStrings $errData $errorOutput $match
2046 } errorMatch]] == 0} {
2047 set errorFailure [expr {!$errorMatch}]
2053 # check if the answer matched the expected answer
2054 # Only check if we ran the body of the test (no setup failure)
2055 if {$setupFailure || $codeFailure} {
2057 } elseif {[set scriptCompare [catch {
2058 CompareStrings $actualAnswer $result $match
2059 } scriptMatch]] == 0} {
2060 set scriptFailure [expr {!$scriptMatch}]
2065 # if we didn't experience any failures, then we passed
2067 if {!($setupFailure || $cleanupFailure || $coreFailure
2068 || $outputFailure || $errorFailure || $codeFailure
2069 || $scriptFailure)} {
2070 if {$testLevel == 1} {
2071 incr numTests(Passed)
2072 if {[IsVerbose pass]} {
2073 puts [outputChannel] "++++ $name PASSED"
2080 # We know the test failed, tally it...
2081 if {$testLevel == 1} {
2082 incr numTests(Failed)
2085 # ... then report according to the type of failure
2086 variable currentFailure true
2087 if {![IsVerbose body]} {
2090 puts [outputChannel] "\n"
2091 if {[IsVerbose line]} {
2092 if {![catch {set testFrame [info frame -1]}] &&
2093 [dict get $testFrame type] eq "source"} {
2094 set testFile [dict get $testFrame file]
2095 set testLine [dict get $testFrame line]
2097 set testFile [file normalize [uplevel 1 {info script}]]
2098 if {[file readable $testFile]} {
2099 set testFd [open $testFile r]
2100 set testLine [expr {[lsearch -regexp \
2101 [split [read $testFd] "\n"] \
2102 "^\[ \t\]*test [string map {. \\.} $name] "]+1}]
2106 if {[info exists testLine]} {
2107 puts [outputChannel] "$testFile:$testLine: error: test failed:\
2108 $name [string trim $description]"
2111 puts [outputChannel] "==== $name\
2112 [string trim $description] FAILED"
2113 if {[string length $body]} {
2114 puts [outputChannel] "==== Contents of test case:"
2115 puts [outputChannel] $body
2117 if {$setupFailure} {
2118 puts [outputChannel] "---- Test setup\
2120 if {[info exists errorInfo(setup)]} {
2121 puts [outputChannel] "---- errorInfo(setup): $errorInfo(setup)"
2122 puts [outputChannel] "---- errorCode(setup): $errorCode(setup)"
2125 if {$scriptFailure} {
2126 if {$scriptCompare} {
2127 puts [outputChannel] "---- Error testing result: $scriptMatch"
2129 puts [outputChannel] "---- Result was:\n$actualAnswer"
2130 puts [outputChannel] "---- Result should have been\
2131 ($match matching):\n$result"
2135 switch -- $returnCode {
2136 0 { set msg "Test completed normally" }
2137 1 { set msg "Test generated error" }
2138 2 { set msg "Test generated return exception" }
2139 3 { set msg "Test generated break exception" }
2140 4 { set msg "Test generated continue exception" }
2141 default { set msg "Test generated exception" }
2143 puts [outputChannel] "---- $msg; Return code was: $returnCode"
2144 puts [outputChannel] "---- Return code should have been\
2145 one of: $returnCodes"
2146 if {[IsVerbose error]} {
2147 if {[info exists errorInfo(body)] && ([lsearch $returnCodes 1]<0)} {
2148 puts [outputChannel] "---- errorInfo: $errorInfo(body)"
2149 puts [outputChannel] "---- errorCode: $errorCode(body)"
2153 if {$outputFailure} {
2154 if {$outputCompare} {
2155 puts [outputChannel] "---- Error testing output: $outputMatch"
2157 puts [outputChannel] "---- Output was:\n$outData"
2158 puts [outputChannel] "---- Output should have been\
2159 ($match matching):\n$output"
2162 if {$errorFailure} {
2163 if {$errorCompare} {
2164 puts [outputChannel] "---- Error testing errorOutput: $errorMatch"
2166 puts [outputChannel] "---- Error output was:\n$errData"
2167 puts [outputChannel] "---- Error output should have\
2168 been ($match matching):\n$errorOutput"
2171 if {$cleanupFailure} {
2172 puts [outputChannel] "---- Test cleanup failed:\n$cleanupMsg"
2173 if {[info exists errorInfo(cleanup)]} {
2174 puts [outputChannel] "---- errorInfo(cleanup): $errorInfo(cleanup)"
2175 puts [outputChannel] "---- errorCode(cleanup): $errorCode(cleanup)"
2179 puts [outputChannel] "---- Core file produced while running\
2182 puts [outputChannel] "==== $name FAILED\n"
2190 # Given a test name and it constraints, returns a boolean indicating
2191 # whether the current configuration says the test should be skipped.
2193 # Side Effects: Maintains tally of total tests seen and tests skipped.
2195 proc tcltest::Skipped {name constraints} {
2198 variable testConstraints
2200 if {$testLevel == 1} {
2201 incr numTests(Total)
2203 # skip the test if it's name matches an element of skip
2204 foreach pattern [skip] {
2205 if {[string match $pattern $name]} {
2206 if {$testLevel == 1} {
2207 incr numTests(Skipped)
2208 DebugDo 1 {AddToSkippedBecause userSpecifiedSkip}
2213 # skip the test if it's name doesn't match any element of match
2215 foreach pattern [match] {
2216 if {[string match $pattern $name]} {
2222 if {$testLevel == 1} {
2223 incr numTests(Skipped)
2224 DebugDo 1 {AddToSkippedBecause userSpecifiedNonMatch}
2228 if {[string equal {} $constraints]} {
2229 # If we're limited to the listed constraints and there aren't
2230 # any listed, then we shouldn't run the test.
2231 if {[limitConstraints]} {
2232 AddToSkippedBecause userSpecifiedLimitConstraint
2233 if {$testLevel == 1} {
2234 incr numTests(Skipped)
2239 # "constraints" argument exists;
2240 # make sure that the constraints are satisfied.
2243 if {[string match {*[$\[]*} $constraints] != 0} {
2244 # full expression, e.g. {$foo > [info tclversion]}
2245 catch {set doTest [uplevel #0 [list expr $constraints]]}
2246 } elseif {[regexp {[^.:_a-zA-Z0-9 \n\r\t]+} $constraints] != 0} {
2247 # something like {a || b} should be turned into
2248 # $testConstraints(a) || $testConstraints(b).
2249 regsub -all {[.\w]+} $constraints {$testConstraints(&)} c
2250 catch {set doTest [eval [list expr $c]]}
2251 } elseif {![catch {llength $constraints}]} {
2252 # just simple constraints such as {unixOnly fonts}.
2254 foreach constraint $constraints {
2255 if {(![info exists testConstraints($constraint)]) \
2256 || (!$testConstraints($constraint))} {
2259 # store the constraint that kept the test from
2261 set constraints $constraint
2268 if {[IsVerbose skip]} {
2269 puts [outputChannel] "++++ $name SKIPPED: $constraints"
2272 if {$testLevel == 1} {
2273 incr numTests(Skipped)
2274 AddToSkippedBecause $constraints
2284 # This is where the body of a test is evaluated. The combination of
2285 # [RunTest] and [Eval] allows the output and error output of the test
2286 # body to be captured for comparison against the expected values.
2288 proc tcltest::RunTest {name script} {
2289 DebugPuts 3 "Running $name {$script}"
2291 # If there is no "memory" command (because memory debugging isn't
2292 # enabled), then don't attempt to use the command.
2294 if {[llength [info commands memory]] == 1} {
2298 set code [catch {uplevel 1 $script} actualAnswer]
2300 return [list $actualAnswer $code]
2303 #####################################################################
2305 # tcltest::cleanupTestsHook --
2307 # This hook allows a harness that builds upon tcltest to specify
2308 # additional things that should be done at cleanup.
2311 if {[llength [info commands tcltest::cleanupTestsHook]] == 0} {
2312 proc tcltest::cleanupTestsHook {} {}
2315 # tcltest::cleanupTests --
2317 # Remove files and dirs created using the makeFile and makeDirectory
2318 # commands since the last time this proc was invoked.
2320 # Print the names of the files created without the makeFile command
2321 # since the tests were invoked.
2323 # Print the number tests (total, passed, failed, and skipped) since the
2324 # tests were invoked.
2326 # Restore original environment (as reported by special variable env).
2329 # calledFromAllFile - if 0, behave as if we are running a single
2330 # test file within an entire suite of tests. if we aren't running
2331 # a single test file, then don't report status. check for new
2332 # files created during the test run and report on them. if 1,
2333 # report collated status from all the test file runs.
2342 proc tcltest::cleanupTests {{calledFromAllFile 0}} {
2344 variable filesExisted
2345 variable createdNewFiles
2346 variable testSingleFile
2348 variable numTestFiles
2350 variable skippedBecause
2351 variable currentFailure
2352 variable originalEnv
2353 variable originalTclPlatform
2354 variable coreModTime
2357 set testFileName [file tail [info script]]
2359 # Call the cleanup hook
2362 # Remove files and directories created by the makeFile and
2363 # makeDirectory procedures. Record the names of files in
2364 # workingDirectory that were not pre-existing, and associate them
2365 # with the test file that created them.
2367 if {!$calledFromAllFile} {
2368 foreach file $filesMade {
2369 if {[file exists $file]} {
2370 DebugDo 1 {Warn "cleanupTests deleting $file..."}
2371 catch {file delete -force $file}
2375 foreach file [glob -nocomplain \
2376 -directory [temporaryDirectory] *] {
2377 lappend currentFiles [file tail $file]
2380 foreach file $currentFiles {
2381 if {[lsearch -exact $filesExisted $file] == -1} {
2382 lappend newFiles $file
2385 set filesExisted $currentFiles
2386 if {[llength $newFiles] > 0} {
2387 set createdNewFiles($testFileName) $newFiles
2391 if {$calledFromAllFile || $testSingleFile} {
2395 puts -nonewline [outputChannel] "$testFileName:"
2396 foreach index [list "Total" "Passed" "Skipped" "Failed"] {
2397 puts -nonewline [outputChannel] \
2398 "\t$index\t$numTests($index)"
2400 puts [outputChannel] ""
2402 # print number test files sourced
2403 # print names of files that ran tests which failed
2405 if {$calledFromAllFile} {
2406 puts [outputChannel] \
2407 "Sourced $numTestFiles Test Files."
2409 if {[llength $failFiles] > 0} {
2410 puts [outputChannel] \
2411 "Files with failing tests: $failFiles"
2416 # if any tests were skipped, print the constraints that kept
2417 # them from running.
2419 set constraintList [array names skippedBecause]
2420 if {[llength $constraintList] > 0} {
2421 puts [outputChannel] \
2422 "Number of tests skipped for each constraint:"
2423 foreach constraint [lsort $constraintList] {
2424 puts [outputChannel] \
2425 "\t$skippedBecause($constraint)\t$constraint"
2426 unset skippedBecause($constraint)
2430 # report the names of test files in createdNewFiles, and reset
2431 # the array to be empty.
2433 set testFilesThatTurded [lsort [array names createdNewFiles]]
2434 if {[llength $testFilesThatTurded] > 0} {
2435 puts [outputChannel] "Warning: files left behind:"
2436 foreach testFile $testFilesThatTurded {
2437 puts [outputChannel] \
2438 "\t$testFile:\t$createdNewFiles($testFile)"
2439 unset createdNewFiles($testFile)
2443 # reset filesMade, filesExisted, and numTests
2446 foreach index [list "Total" "Passed" "Skipped" "Failed"] {
2447 set numTests($index) 0
2450 # exit only if running Tk in non-interactive mode
2451 # This should be changed to determine if an event
2452 # loop is running, which is the real issue.
2453 # Actually, this doesn't belong here at all. A package
2454 # really has no business [exit]-ing an application.
2455 if {![catch {package present Tk}] && ![testConstraint interactive]} {
2460 # if we're deferring stat-reporting until all files are sourced,
2461 # then add current file to failFile list if any tests in this
2464 if {$currentFailure \
2465 && ([lsearch -exact $failFiles $testFileName] == -1)} {
2466 lappend failFiles $testFileName
2468 set currentFailure false
2470 # restore the environment to the state it was in before this package
2476 foreach index [array names ::env] {
2477 if {![info exists originalEnv($index)]} {
2478 lappend newEnv $index
2481 if {$::env($index) != $originalEnv($index)} {
2482 lappend changedEnv $index
2483 set ::env($index) $originalEnv($index)
2487 foreach index [array names originalEnv] {
2488 if {![info exists ::env($index)]} {
2489 lappend removedEnv $index
2490 set ::env($index) $originalEnv($index)
2493 if {[llength $newEnv] > 0} {
2494 puts [outputChannel] \
2495 "env array elements created:\t$newEnv"
2497 if {[llength $changedEnv] > 0} {
2498 puts [outputChannel] \
2499 "env array elements changed:\t$changedEnv"
2501 if {[llength $removedEnv] > 0} {
2502 puts [outputChannel] \
2503 "env array elements removed:\t$removedEnv"
2506 set changedTclPlatform {}
2507 foreach index [array names originalTclPlatform] {
2508 if {$::tcl_platform($index) \
2509 != $originalTclPlatform($index)} {
2510 lappend changedTclPlatform $index
2511 set ::tcl_platform($index) $originalTclPlatform($index)
2514 if {[llength $changedTclPlatform] > 0} {
2515 puts [outputChannel] "tcl_platform array elements\
2516 changed:\t$changedTclPlatform"
2519 if {[file exists [file join [workingDirectory] core]]} {
2520 if {[preserveCore] > 1} {
2521 puts "rename core file (> 1)"
2522 puts [outputChannel] "produced core file! \
2524 [file join [temporaryDirectory] core-$testFileName]"
2525 catch {file rename -force \
2526 [file join [workingDirectory] core] \
2527 [file join [temporaryDirectory] core-$testFileName]
2529 if {[string length $msg] > 0} {
2530 PrintError "Problem renaming file: $msg"
2533 # Print a message if there is a core file and (1) there
2534 # previously wasn't one or (2) the new one is different
2537 if {[info exists coreModTime]} {
2538 if {$coreModTime != [file mtime \
2539 [file join [workingDirectory] core]]} {
2540 puts [outputChannel] "A core file was created!"
2543 puts [outputChannel] "A core file was created!"
2548 flush [outputChannel]
2549 flush [errorChannel]
2553 #####################################################################
2555 # Procs that determine which tests/test files to run
2557 # tcltest::GetMatchingFiles
2559 # Looks at the patterns given to match and skip files and uses
2560 # them to put together a list of the tests that will be run.
2563 # directory to search
2566 # The constructed list is returned to the user. This will
2567 # primarily be used in 'all.tcl' files. It is used in
2573 # a lower case version is needed for compatibility with tcltest 1.0
2574 proc tcltest::getMatchingFiles args {GetMatchingFiles {*}$args}
2576 proc tcltest::GetMatchingFiles { args } {
2577 if {[llength $args]} {
2580 # Finding tests only in [testsDirectory] is normal operation.
2581 # This procedure is written to accept multiple directory arguments
2582 # only to satisfy version 1 compatibility.
2583 set dirList [list [testsDirectory]]
2586 set matchingFiles [list]
2587 foreach directory $dirList {
2589 # List files in $directory that match patterns to run.
2590 set matchFileList [list]
2591 foreach match [matchFiles] {
2592 set matchFileList [concat $matchFileList \
2593 [glob -directory $directory -types {b c f p s} \
2594 -nocomplain -- $match]]
2597 # List files in $directory that match patterns to skip.
2598 set skipFileList [list]
2599 foreach skip [skipFiles] {
2600 set skipFileList [concat $skipFileList \
2601 [glob -directory $directory -types {b c f p s} \
2602 -nocomplain -- $skip]]
2605 # Add to result list all files in match list and not in skip list
2606 foreach file $matchFileList {
2607 if {[lsearch -exact $skipFileList $file] == -1} {
2608 lappend matchingFiles $file
2613 if {[llength $matchingFiles] == 0} {
2614 PrintError "No test files remain after applying your match and\
2617 return $matchingFiles
2620 # tcltest::GetMatchingDirectories --
2622 # Looks at the patterns given to match and skip directories and
2623 # uses them to put together a list of the test directories that we
2624 # should attempt to run. (Only subdirectories containing an
2625 # "all.tcl" file are put into the list.)
2628 # root directory from which to search
2631 # The constructed list is returned to the user. This is used in
2632 # the primary all.tcl file.
2637 proc tcltest::GetMatchingDirectories {rootdir} {
2639 # Determine the skip list first, to avoid [glob]-ing over subdirectories
2640 # we're going to throw away anyway. Be sure we skip the $rootdir if it
2641 # comes up to avoid infinite loops.
2642 set skipDirs [list $rootdir]
2643 foreach pattern [skipDirectories] {
2644 set skipDirs [concat $skipDirs [glob -directory $rootdir -types d \
2645 -nocomplain -- $pattern]]
2648 # Now step through the matching directories, prune out the skipped ones
2650 set matchDirs [list]
2651 foreach pattern [matchDirectories] {
2652 foreach path [glob -directory $rootdir -types d -nocomplain -- \
2654 if {[lsearch -exact $skipDirs $path] == -1} {
2655 set matchDirs [concat $matchDirs [GetMatchingDirectories $path]]
2656 if {[file exists [file join $path all.tcl]]} {
2657 lappend matchDirs $path
2663 if {[llength $matchDirs] == 0} {
2664 DebugPuts 1 "No test directories remain after applying match\
2670 # tcltest::runAllTests --
2672 # prints output and sources test files according to the match and
2673 # skip patterns provided. after sourcing test files, it goes on
2674 # to source all.tcl files in matching test subdirectories.
2677 # shell being tested
2685 proc tcltest::runAllTests { {shell ""} } {
2686 variable testSingleFile
2687 variable numTestFiles
2692 if {[llength [info level 0]] == 1} {
2693 set shell [interpreter]
2696 set testSingleFile false
2698 puts [outputChannel] "Tests running in interp: $shell"
2699 puts [outputChannel] "Tests located in: [testsDirectory]"
2700 puts [outputChannel] "Tests running in: [workingDirectory]"
2701 puts [outputChannel] "Temporary files stored in\
2702 [temporaryDirectory]"
2704 # [file system] first available in Tcl 8.4
2705 if {![catch {file system [testsDirectory]} result]
2706 && ![string equal native [lindex $result 0]]} {
2707 # If we aren't running in the native filesystem, then we must
2708 # run the tests in a single process (via 'source'), because
2709 # trying to run then via a pipe will fail since the files don't
2714 if {[singleProcess]} {
2715 puts [outputChannel] \
2716 "Test files sourced into current interpreter"
2718 puts [outputChannel] \
2719 "Test files run in separate interpreters"
2721 if {[llength [skip]] > 0} {
2722 puts [outputChannel] "Skipping tests that match: [skip]"
2724 puts [outputChannel] "Running tests that match: [match]"
2726 if {[llength [skipFiles]] > 0} {
2727 puts [outputChannel] \
2728 "Skipping test files that match: [skipFiles]"
2730 if {[llength [matchFiles]] > 0} {
2731 puts [outputChannel] \
2732 "Only running test files that match: [matchFiles]"
2735 set timeCmd {clock format [clock seconds]}
2736 puts [outputChannel] "Tests began at [eval $timeCmd]"
2738 # Run each of the specified tests
2739 foreach file [lsort [GetMatchingFiles]] {
2740 set tail [file tail $file]
2741 puts [outputChannel] $tail
2742 flush [outputChannel]
2744 if {[singleProcess]} {
2746 uplevel 1 [list ::source $file]
2748 # Pass along our configuration to the child processes.
2749 # EXCEPT for the -outfile, because the parent process
2750 # needs to read and process output of children.
2751 set childargv [list]
2752 foreach opt [Configure] {
2753 if {[string equal $opt -outfile]} {continue}
2754 lappend childargv $opt [Configure $opt]
2756 set cmd [linsert $childargv 0 | $shell $file]
2759 set pipeFd [open $cmd "r"]
2760 while {[gets $pipeFd line] >= 0} {
2764 {Passed\t([0-9]+)\t}
2765 {Skipped\t([0-9]+)\t}
2767 } ""] $line null testFile \
2768 Total Passed Skipped Failed]} {
2769 foreach index {Total Passed Skipped Failed} {
2770 incr numTests($index) [set $index]
2773 lappend failFiles $testFile
2775 } elseif {[regexp [join {
2776 {^Number of tests skipped }
2777 {for each constraint:}
2779 } ""] $line match skipped constraint]} {
2780 if {[string match \t* $match]} {
2781 AddToSkippedBecause $constraint $skipped
2784 puts [outputChannel] $line
2789 puts [outputChannel] "Test file error: $msg"
2790 # append the name of the test to a list to be reported
2792 lappend testFileFailures $file
2798 puts [outputChannel] "\nTests ended at [eval $timeCmd]"
2800 if {[info exists testFileFailures]} {
2801 puts [outputChannel] "\nTest files exiting with errors: \n"
2802 foreach file $testFileFailures {
2803 puts [outputChannel] " [file tail $file]\n"
2807 # Checking for subdirectories in which to run tests
2808 foreach directory [GetMatchingDirectories [testsDirectory]] {
2809 set dir [file tail $directory]
2810 puts [outputChannel] [string repeat ~ 44]
2811 puts [outputChannel] "$dir test began at [eval $timeCmd]\n"
2813 uplevel 1 [list ::source [file join $directory all.tcl]]
2815 set endTime [eval $timeCmd]
2816 puts [outputChannel] "\n$dir test ended at $endTime"
2817 puts [outputChannel] ""
2818 puts [outputChannel] [string repeat ~ 44]
2823 #####################################################################
2825 # Test utility procs - not used in tcltest, but may be useful for
2828 # tcltest::loadTestedCommands --
2830 # Uses the specified script to load the commands to test. Allowed to
2831 # be empty, as the tested commands could have been compiled into the
2843 proc tcltest::loadTestedCommands {} {
2845 if {[string equal {} [loadScript]]} {
2849 return [uplevel 1 [loadScript]]
2852 # tcltest::saveState --
2854 # Save information regarding what procs and variables exist.
2860 # Modifies the variable saveState
2865 proc tcltest::saveState {} {
2867 uplevel 1 [list ::set [namespace which -variable saveState]] \
2868 {[::list [::info procs] [::info vars]]}
2869 DebugPuts 2 "[lindex [info level 0] 0]: $saveState"
2873 # tcltest::restoreState --
2875 # Remove procs and variables that didn't exist before the call to
2882 # Removes procs and variables from your environment if they don't
2883 # exist in the saveState variable.
2888 proc tcltest::restoreState {} {
2890 foreach p [uplevel 1 {::info procs}] {
2891 if {([lsearch [lindex $saveState 0] $p] < 0)
2892 && ![string equal [namespace current]::$p \
2893 [uplevel 1 [list ::namespace origin $p]]]} {
2895 DebugPuts 2 "[lindex [info level 0] 0]: Removing proc $p"
2896 uplevel 1 [list ::catch [list ::rename $p {}]]
2899 foreach p [uplevel 1 {::info vars}] {
2900 if {[lsearch [lindex $saveState 1] $p] < 0} {
2901 DebugPuts 2 "[lindex [info level 0] 0]:\
2902 Removing variable $p"
2903 uplevel 1 [list ::catch [list ::unset $p]]
2909 # tcltest::normalizeMsg --
2911 # Removes "extra" newlines from a string.
2914 # msg String to be modified
2917 # string with extra newlines removed
2922 proc tcltest::normalizeMsg {msg} {
2923 regsub "\n$" [string tolower $msg] "" msg
2924 set msg [string map [list "\n\n" "\n"] $msg]
2925 return [string map [list "\n\}" "\}"] $msg]
2928 # tcltest::makeFile --
2930 # Create a new file with the name <name>, and write <contents> to it.
2932 # If this file hasn't been created via makeFile since the last time
2933 # cleanupTests was called, add it to the $filesMade list, so it will be
2934 # removed by the next call to cleanupTests.
2937 # contents content of the new file
2938 # name name of the new file
2939 # directory directory name for new file
2942 # absolute path to the file created
2947 proc tcltest::makeFile {contents name {directory ""}} {
2951 if {[llength [info level 0]] == 3} {
2952 set directory [temporaryDirectory]
2955 set fullName [file join $directory $name]
2957 DebugPuts 3 "[lindex [info level 0] 0]:\
2958 putting ``$contents'' into $fullName"
2960 set fd [open $fullName w]
2961 fconfigure $fd -translation lf
2962 if {[string equal [string index $contents end] \n]} {
2963 puts -nonewline $fd $contents
2969 if {[lsearch -exact $filesMade $fullName] == -1} {
2970 lappend filesMade $fullName
2975 # tcltest::removeFile --
2977 # Removes the named file from the filesystem
2980 # name file to be removed
2981 # directory directory from which to remove file
2984 # return value from [file delete]
2989 proc tcltest::removeFile {name {directory ""}} {
2992 if {[llength [info level 0]] == 2} {
2993 set directory [temporaryDirectory]
2995 set fullName [file join $directory $name]
2996 DebugPuts 3 "[lindex [info level 0] 0]: removing $fullName"
2997 set idx [lsearch -exact $filesMade $fullName]
2998 set filesMade [lreplace $filesMade $idx $idx]
3001 Warn "removeFile removing \"$fullName\":\n not created by makeFile"
3004 if {![file isfile $fullName]} {
3006 Warn "removeFile removing \"$fullName\":\n not a file"
3009 return [file delete $fullName]
3012 # tcltest::makeDirectory --
3014 # Create a new dir with the name <name>.
3016 # If this dir hasn't been created via makeDirectory since the last time
3017 # cleanupTests was called, add it to the $directoriesMade list, so it
3018 # will be removed by the next call to cleanupTests.
3021 # name name of the new directory
3022 # directory directory in which to create new dir
3025 # absolute path to the directory created
3030 proc tcltest::makeDirectory {name {directory ""}} {
3033 if {[llength [info level 0]] == 2} {
3034 set directory [temporaryDirectory]
3036 set fullName [file join $directory $name]
3037 DebugPuts 3 "[lindex [info level 0] 0]: creating $fullName"
3038 file mkdir $fullName
3039 if {[lsearch -exact $filesMade $fullName] == -1} {
3040 lappend filesMade $fullName
3045 # tcltest::removeDirectory --
3047 # Removes a named directory from the file system.
3050 # name Name of the directory to remove
3051 # directory Directory from which to remove
3054 # return value from [file delete]
3059 proc tcltest::removeDirectory {name {directory ""}} {
3062 if {[llength [info level 0]] == 2} {
3063 set directory [temporaryDirectory]
3065 set fullName [file join $directory $name]
3066 DebugPuts 3 "[lindex [info level 0] 0]: deleting $fullName"
3067 set idx [lsearch -exact $filesMade $fullName]
3068 set filesMade [lreplace $filesMade $idx $idx]
3071 Warn "removeDirectory removing \"$fullName\":\n not created\
3075 if {![file isdirectory $fullName]} {
3077 Warn "removeDirectory removing \"$fullName\":\n not a directory"
3080 return [file delete -force $fullName]
3083 # tcltest::viewFile --
3085 # reads the content of a file and returns it
3088 # name of the file to read
3089 # directory in which file is located
3092 # content of the named file
3097 proc tcltest::viewFile {name {directory ""}} {
3099 if {[llength [info level 0]] == 2} {
3100 set directory [temporaryDirectory]
3102 set fullName [file join $directory $name]
3103 set f [open $fullName]
3104 set data [read -nonewline $f]
3109 # tcltest::bytestring --
3111 # Construct a string that consists of the requested sequence of bytes,
3112 # as opposed to a string of properly formed UTF-8 characters.
3113 # This allows the tester to
3114 # 1. Create denormalized or improperly formed strings to pass to C
3115 # procedures that are supposed to accept strings with embedded NULL
3117 # 2. Confirm that a string result has a certain pattern of bytes, for
3118 # instance to confirm that "\xe0\0" in a Tcl script is stored
3119 # internally in UTF-8 as the sequence of bytes "\xc3\xa0\xc0\x80".
3121 # Generally, it's a bad idea to examine the bytes in a Tcl string or to
3122 # construct improperly formed strings in this manner, because it involves
3123 # exposing that Tcl uses UTF-8 internally.
3126 # string being converted
3129 # result fom encoding
3134 proc tcltest::bytestring {string} {
3135 return [encoding convertfrom identity $string]
3138 # tcltest::OpenFiles --
3140 # used in io tests, uses testchannel
3151 proc tcltest::OpenFiles {} {
3152 if {[catch {testchannel open} result]} {
3158 # tcltest::LeakFiles --
3160 # used in io tests, uses testchannel
3171 proc tcltest::LeakFiles {old} {
3172 if {[catch {testchannel open} new]} {
3177 if {[lsearch $old $p] < 0} {
3185 # Internationalization / ISO support procs -- dl
3188 # tcltest::SetIso8859_1_Locale --
3190 # used in cmdIL.test, uses testlocale
3201 proc tcltest::SetIso8859_1_Locale {} {
3202 variable previousLocale
3204 if {[info commands testlocale] != ""} {
3205 set previousLocale [testlocale ctype]
3206 testlocale ctype $isoLocale
3211 # tcltest::RestoreLocale --
3213 # used in cmdIL.test, uses testlocale
3224 proc tcltest::RestoreLocale {} {
3225 variable previousLocale
3226 if {[info commands testlocale] != ""} {
3227 testlocale ctype $previousLocale
3232 # tcltest::threadReap --
3234 # Kill all threads except for the main thread.
3235 # Do nothing if testthread is not defined.
3241 # Returns the number of existing threads.
3247 proc tcltest::threadReap {} {
3248 if {[info commands testthread] != {}} {
3250 # testthread built into tcltest
3252 testthread errorproc ThreadNullError
3253 while {[llength [testthread names]] > 1} {
3254 foreach tid [testthread names] {
3255 if {$tid != [mainThread]} {
3257 testthread send -async $tid {testthread exit}
3261 ## Enter a bit a sleep to give the threads enough breathing
3262 ## room to kill themselves off, otherwise the end up with a
3263 ## massive queue of repeated events
3266 testthread errorproc ThreadError
3267 return [llength [testthread names]]
3268 } elseif {[info commands thread::id] != {}} {
3272 thread::errorproc ThreadNullError
3273 while {[llength [thread::names]] > 1} {
3274 foreach tid [thread::names] {
3275 if {$tid != [mainThread]} {
3276 catch {thread::send -async $tid {thread::exit}}
3279 ## Enter a bit a sleep to give the threads enough breathing
3280 ## room to kill themselves off, otherwise the end up with a
3281 ## massive queue of repeated events
3284 thread::errorproc ThreadError
3285 return [llength [thread::names]]
3292 # Initialize the constraints and set up command line arguments
3293 namespace eval tcltest {
3294 # Define initializers for all the built-in contraint definitions
3295 DefineConstraintInitializers
3297 # Set up the constraints in the testConstraints array to be lazily
3298 # initialized by a registered initializer, or by "false" if no
3299 # initializer is registered.
3300 trace variable testConstraints r [namespace code SafeFetch]
3302 # Only initialize constraints at package load time if an
3303 # [initConstraintsHook] has been pre-defined. This is only
3304 # for compatibility support. The modern way to add a custom
3305 # test constraint is to just call the [testConstraint] command
3306 # straight away, without all this "hook" nonsense.
3307 if {[string equal [namespace current] \
3308 [namespace qualifiers [namespace which initConstraintsHook]]]} {
3311 proc initConstraintsHook {} {}
3314 # Define the standard match commands
3315 customMatch exact [list string equal]
3316 customMatch glob [list string match]
3317 customMatch regexp [list regexp --]
3319 # If the TCLTEST_OPTIONS environment variable exists, configure
3320 # tcltest according to the option values it specifies. This has
3321 # the effect of resetting tcltest's default configuration.
3322 proc ConfigureFromEnvironment {} {
3323 upvar #0 env(TCLTEST_OPTIONS) options
3324 if {[catch {llength $options} msg]} {
3325 Warn "invalid TCLTEST_OPTIONS \"$options\":\n invalid\
3329 if {[llength $options] % 2} {
3330 Warn "invalid TCLTEST_OPTIONS: \"$options\":\n should be\
3331 -option value ?-option value ...?"
3334 if {[catch {Configure {*}$options} msg]} {
3335 Warn "invalid TCLTEST_OPTIONS: \"$options\":\n $msg"
3339 if {[info exists ::env(TCLTEST_OPTIONS)]} {
3340 ConfigureFromEnvironment
3343 proc LoadTimeCmdLineArgParsingRequired {} {
3345 if {[info exists ::argv] && [lsearch -exact $::argv -help] != -1} {
3346 # The command line asks for -help, so give it (and exit)
3347 # right now. ([configure] does not process -help)
3350 foreach hook { PrintUsageInfoHook processCmdLineArgsHook
3351 processCmdLineArgsAddFlagsHook } {
3352 if {[string equal [namespace current] [namespace qualifiers \
3353 [namespace which $hook]]]} {
3362 # Only initialize configurable options from the command line arguments
3363 # at package load time if necessary for backward compatibility. This
3364 # lets the tcltest user call [configure] for themselves if they wish.
3365 # Traces are established for auto-configuration from the command line
3366 # if any configurable options are accessed before the user calls
3368 if {[LoadTimeCmdLineArgParsingRequired]} {
3371 EstablishAutoConfigureTraces
3374 package provide [namespace tail [namespace current]] $Version