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 package require Tcl 8.5 ;# -verbose line uses [info frame]
20 namespace eval tcltest {
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.3.8
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]
33 ##### Export the public tcltest procs; several categories
35 # Export the main functional commands that do useful things
36 namespace export cleanupTests loadTestedCommands makeDirectory \
37 makeFile removeDirectory removeFile runAllTests test
39 # Export configuration commands that control the functional commands
40 namespace export configure customMatch errorChannel interpreter \
41 outputChannel testConstraint
43 # Export commands that are duplication (candidates for deprecation)
44 namespace export bytestring ;# dups [encoding convertfrom identity]
45 namespace export debug ;# [configure -debug]
46 namespace export errorFile ;# [configure -errfile]
47 namespace export limitConstraints ;# [configure -limitconstraints]
48 namespace export loadFile ;# [configure -loadfile]
49 namespace export loadScript ;# [configure -load]
50 namespace export match ;# [configure -match]
51 namespace export matchFiles ;# [configure -file]
52 namespace export matchDirectories ;# [configure -relateddir]
53 namespace export normalizeMsg ;# application of [customMatch]
54 namespace export normalizePath ;# [file normalize] (8.4)
55 namespace export outputFile ;# [configure -outfile]
56 namespace export preserveCore ;# [configure -preservecore]
57 namespace export singleProcess ;# [configure -singleproc]
58 namespace export skip ;# [configure -skip]
59 namespace export skipFiles ;# [configure -notfile]
60 namespace export skipDirectories ;# [configure -asidefromdir]
61 namespace export temporaryDirectory ;# [configure -tmpdir]
62 namespace export testsDirectory ;# [configure -testdir]
63 namespace export verbose ;# [configure -verbose]
64 namespace export viewFile ;# binary encoding [read]
65 namespace export workingDirectory ;# [cd] [pwd]
67 # Export deprecated commands for tcltest 1 compatibility
68 namespace export getMatchingFiles mainThread restoreState saveState \
71 # tcltest::normalizePath --
73 # This procedure resolves any symlinks in the path thus creating
74 # a path without internal redirection. It assumes that the
75 # incoming path is absolute.
78 # pathVar - name of variable containing path to modify.
81 # The path is modified in place.
86 proc normalizePath {pathVar} {
95 ##### Verification commands used to test values of variables and options
97 # Verification command that accepts everything
98 proc AcceptAll {value} {
102 # Verification command that accepts valid Tcl lists
103 proc AcceptList { list } {
104 return [lrange $list 0 end]
107 # Verification command that accepts a glob pattern
108 proc AcceptPattern { pattern } {
109 return [AcceptAll $pattern]
112 # Verification command that accepts integers
113 proc AcceptInteger { level } {
114 return [incr level 0]
117 # Verification command that accepts boolean values
118 proc AcceptBoolean { boolean } {
119 return [expr {$boolean && $boolean}]
122 # Verification command that accepts (syntactically) valid Tcl scripts
123 proc AcceptScript { script } {
124 if {![info complete $script]} {
125 return -code error "invalid Tcl script: $script"
130 # Verification command that accepts (converts to) absolute pathnames
131 proc AcceptAbsolutePath { path } {
132 return [file join [pwd] $path]
135 # Verification command that accepts existing readable directories
136 proc AcceptReadable { path } {
137 if {![file readable $path]} {
138 return -code error "\"$path\" is not readable"
142 proc AcceptDirectory { directory } {
143 set directory [AcceptAbsolutePath $directory]
144 if {![file exists $directory]} {
145 return -code error "\"$directory\" does not exist"
147 if {![file isdir $directory]} {
148 return -code error "\"$directory\" is not a directory"
150 return [AcceptReadable $directory]
153 ##### Initialize internal arrays of tcltest, but only if the caller
154 # has not already pre-initialized them. This is done to support
155 # compatibility with older tests that directly access internals
156 # rather than go through command interfaces.
158 proc ArrayDefault {varName value} {
160 if {[array exists $varName]} {
163 if {[info exists $varName]} {
164 # Pre-initialized value is a scalar: destroy it!
167 array set $varName $value
170 # save the original environment so that it can be restored later
171 ArrayDefault originalEnv [array get ::env]
173 # initialize numTests array to keep track of the number of tests
174 # that pass, fail, and are skipped.
175 ArrayDefault numTests [list Total 0 Passed 0 Skipped 0 Failed 0]
177 # createdNewFiles will store test files as indices and the list of
178 # files (that should not have been) left behind by the test files
180 ArrayDefault createdNewFiles {}
182 # initialize skippedBecause array to keep track of constraints that
183 # kept tests from running; a constraint name of "userSpecifiedSkip"
184 # means that the test appeared on the list of tests that matched the
185 # -skip value given to the flag; "userSpecifiedNonMatch" means that
186 # the test didn't match the argument given to the -match flag; both
187 # of these constraints are counted only if tcltest::debug is set to
189 ArrayDefault skippedBecause {}
191 # initialize the testConstraints array to keep track of valid
192 # predefined constraints (see the explanation for the
193 # InitConstraints proc for more details).
194 ArrayDefault testConstraints {}
196 ##### Initialize internal variables of tcltest, but only if the caller
197 # has not already pre-initialized them. This is done to support
198 # compatibility with older tests that directly access internals
199 # rather than go through command interfaces.
201 proc Default {varName value {verify AcceptAll}} {
203 if {![info exists $varName]} {
204 variable $varName [$verify $value]
206 variable $varName [$verify [set $varName]]
210 # Save any arguments that we might want to pass through to other
211 # programs. This is used by the -args flag.
213 Default parameters {}
215 # Count the number of files tested (0 if runAllTests wasn't called).
216 # runAllTests will set testSingleFile to false, so stats will
217 # not be printed until runAllTests calls the cleanupTests proc.
218 # The currentFailure var stores the boolean value of whether the
219 # current test file has had any failures. The failFiles list
220 # stores the names of test files that had failures.
221 Default numTestFiles 0 AcceptInteger
222 Default testSingleFile true AcceptBoolean
223 Default currentFailure false AcceptBoolean
224 Default failFiles {} AcceptList
226 # Tests should remove all files they create. The test suite will
227 # check the current working dir for files created by the tests.
228 # filesMade keeps track of such files created using the makeFile and
229 # makeDirectory procedures. filesExisted stores the names of
230 # pre-existing files.
232 # Note that $filesExisted lists only those files that exist in
233 # the original [temporaryDirectory].
234 Default filesMade {} AcceptList
235 Default filesExisted {} AcceptList
236 proc FillFilesExisted {} {
237 variable filesExisted
239 # Save the names of files that already exist in the scratch directory.
240 foreach file [glob -nocomplain -directory [temporaryDirectory] *] {
241 lappend filesExisted [file tail $file]
244 # After successful filling, turn this into a no-op.
245 proc FillFilesExisted args {}
248 # Kept only for compatibility
249 Default constraintsSpecified {} AcceptList
250 trace add variable constraintsSpecified read [namespace code {
251 set constraintsSpecified [array names testConstraints] ;#}]
253 # tests that use threads need to know which is the main thread
256 if {[info commands thread::id] ne {}} {
257 set mainThread [thread::id]
258 } elseif {[info commands testthread] ne {}} {
259 set mainThread [testthread id]
262 # Set workingDirectory to [pwd]. The default output directory for
263 # Tcl tests is the working directory. Whenever this value changes
264 # change to that directory.
265 variable workingDirectory
266 trace add variable workingDirectory write \
267 [namespace code {cd $workingDirectory ;#}]
269 Default workingDirectory [pwd] AcceptAbsolutePath
270 proc workingDirectory { {dir ""} } {
271 variable workingDirectory
272 if {[llength [info level 0]] == 1} {
273 return $workingDirectory
275 set workingDirectory [AcceptAbsolutePath $dir]
278 # Set the location of the execuatble
279 Default tcltest [info nameofexecutable]
280 trace add variable tcltest write [namespace code {testConstraint stdio \
281 [eval [ConstraintInitializer stdio]] ;#}]
283 # save the platform information so it can be restored later
284 Default originalTclPlatform [array get ::tcl_platform]
286 # If a core file exists, save its modification time.
287 if {[file exists [file join [workingDirectory] core]]} {
288 Default coreModTime \
289 [file mtime [file join [workingDirectory] core]]
292 # stdout and stderr buffers for use when we want to store them
296 # keep track of test level for nested test commands
299 # the variables and procs that existed when saveState was called are
300 # stored in a variable of the same name
303 # Internationalization support -- used in [SetIso8859_1_Locale] and
304 # [RestoreLocale]. Those commands are used in cmdIL.test.
306 if {![info exists [namespace current]::isoLocale]} {
307 variable isoLocale fr
308 switch -- $::tcl_platform(platform) {
311 # Try some 'known' values for some platforms:
313 switch -exact -- $::tcl_platform(os) {
315 set isoLocale fr_FR.ISO_8859-1
318 set isoLocale fr_FR.iso88591
326 # Works on SunOS 4 and Solaris, and maybe
327 # others... Define it to something else on your
328 # system if you want to test those.
330 set isoLocale iso_8859_1
340 variable ChannelsWeOpened; array set ChannelsWeOpened {}
341 # output goes to stdout by default
342 Default outputChannel stdout
343 proc outputChannel { {filename ""} } {
344 variable outputChannel
345 variable ChannelsWeOpened
347 # This is very subtle and tricky, so let me try to explain.
348 # (Hopefully this longer comment will be clear when I come
349 # back in a few months, unlike its predecessor :) )
351 # The [outputChannel] command (and underlying variable) have to
352 # be kept in sync with the [configure -outfile] configuration
353 # option ( and underlying variable Option(-outfile) ). This is
354 # accomplished with a write trace on Option(-outfile) that will
355 # update [outputChannel] whenver a new value is written. That
358 # The trick is that in order to maintain compatibility with
359 # version 1 of tcltest, we must allow every configuration option
360 # to get its inital value from command line arguments. This is
361 # accomplished by setting initial read traces on all the
362 # configuration options to parse the command line option the first
363 # time they are read. These traces are cancelled whenever the
364 # program itself calls [configure].
366 # OK, then so to support tcltest 1 compatibility, it seems we want
367 # to get the return from [outputFile] to trigger the read traces,
370 # BUT! A little known feature of Tcl variable traces is that
371 # traces are disabled during the handling of other traces. So,
372 # if we trigger read traces on Option(-outfile) and that triggers
373 # command line parsing which turns around and sets an initial
374 # value for Option(-outfile) -- <whew!> -- the write trace that
375 # would keep [outputChannel] in sync with that new initial value
378 # SO, finally, as a workaround, instead of triggering read traces
379 # by invoking [outputFile], we instead trigger the same set of
380 # read traces by invoking [debug]. Any command that reads a
381 # configuration option would do. [debug] is just a handy one.
382 # The end result is that we support tcltest 1 compatibility and
383 # keep outputChannel and -outfile in sync in all cases.
386 if {[llength [info level 0]] == 1} {
387 return $outputChannel
389 if {[info exists ChannelsWeOpened($outputChannel)]} {
391 unset ChannelsWeOpened($outputChannel)
393 switch -exact -- $filename {
396 set outputChannel $filename
399 set outputChannel [open $filename a]
400 set ChannelsWeOpened($outputChannel) 1
402 # If we created the file in [temporaryDirectory], then
403 # [cleanupTests] will delete it, unless we claim it was
405 set outdir [normalizePath [file dirname \
406 [file join [pwd] $filename]]]
407 if {$outdir eq [temporaryDirectory]} {
408 variable filesExisted
410 set filename [file tail $filename]
411 if {$filename ni $filesExisted} {
412 lappend filesExisted $filename
417 return $outputChannel
420 # errors go to stderr by default
421 Default errorChannel stderr
422 proc errorChannel { {filename ""} } {
423 variable errorChannel
424 variable ChannelsWeOpened
426 # This is subtle and tricky. See the comment above in
427 # [outputChannel] for a detailed explanation.
430 if {[llength [info level 0]] == 1} {
433 if {[info exists ChannelsWeOpened($errorChannel)]} {
435 unset ChannelsWeOpened($errorChannel)
437 switch -exact -- $filename {
440 set errorChannel $filename
443 set errorChannel [open $filename a]
444 set ChannelsWeOpened($errorChannel) 1
446 # If we created the file in [temporaryDirectory], then
447 # [cleanupTests] will delete it, unless we claim it was
449 set outdir [normalizePath [file dirname \
450 [file join [pwd] $filename]]]
451 if {$outdir eq [temporaryDirectory]} {
452 variable filesExisted
454 set filename [file tail $filename]
455 if {$filename ni $filesExisted} {
456 lappend filesExisted $filename
464 ##### Set up the configurable options
466 # The configurable options of the package
467 variable Option; array set Option {}
469 # Usage strings for those options
470 variable Usage; array set Usage {}
472 # Verification commands for those options
473 variable Verify; array set Verify {}
475 # Initialize the default values of the configurable options that are
476 # historically associated with an exported variable. If that variable
477 # is already set, support compatibility by accepting its pre-set value.
478 # Use [trace] to establish ongoing connection between the deprecated
479 # exported variable and the modern option kept as a true internal var.
480 # Also set up usage string and value testing for the option.
481 proc Option {option value usage {verify AcceptAll} {varName {}}} {
485 variable OptionControlledVariables
486 variable DefaultValue
487 set Usage($option) $usage
488 set Verify($option) $verify
489 set DefaultValue($option) $value
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 {$option in $match} {
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 add variable $varName read [namespace code {
553 ProcessCmdLineArgs ;#}]
557 proc RemoveAutoConfigureTraces {} {
558 variable OptionControlledVariables
559 foreach varName [concat $OptionControlledVariables Option] {
561 foreach pair [trace info variable $varName] {
563 if {($op eq "read") &&
564 [string match *ProcessCmdLineArgs* $cmd]} {
565 trace remove variable $varName $op $cmd
569 # Once the traces are removed, this can become a no-op
570 proc RemoveAutoConfigureTraces {} {}
573 proc Configure args {
576 set n [llength $args]
578 return [lsort [array names Option]]
581 if {[catch {MatchingOption [lindex $args 0]} option]} {
582 return -code error $option
584 return $Option($option)
586 while {[llength $args] > 1} {
587 if {[catch {MatchingOption [lindex $args 0]} option]} {
588 return -code error $option
590 if {[catch {$Verify($option) [lindex $args 1]} value]} {
591 return -code error "invalid $option\
592 value \"[lindex $args 1]\": $value"
594 set Option($option) $value
595 set args [lrange $args 2 end]
597 if {[llength $args]} {
598 if {[catch {MatchingOption [lindex $args 0]} option]} {
599 return -code error $option
601 return -code error "missing value for option $option"
604 proc configure args {
605 if {[llength $args] > 1} {
606 RemoveAutoConfigureTraces
608 set code [catch {Configure {*}$args} msg]
609 return -code $code $msg
612 proc AcceptVerbose { level } {
613 set level [AcceptList $level]
614 if {[llength $level] == 1} {
615 if {![regexp {^(pass|body|skip|start|error|line)$} $level]} {
616 # translate single characters abbreviations to expanded list
617 set level [string map {p pass b body s skip t start e error l line} \
623 if {[regexp {^(pass|body|skip|start|error|line)$} $v]} {
630 proc IsVerbose {level} {
632 return [expr {[lsearch -exact $Option(-verbose) $level] != -1}]
635 # Default verbosity is to show bodies of failed tests
636 Option -verbose {body error} {
637 Takes any combination of the values 'p', 's', 'b', 't', 'e' and 'l'.
638 Test suite will display all passed tests if 'p' is specified, all
639 skipped tests if 's' is specified, the bodies of failed tests if
640 'b' is specified, and when tests start if 't' is specified.
641 ErrorInfo is displayed if 'e' is specified. Source file line
642 information of failed tests is displayed if 'l' is specified.
643 } AcceptVerbose verbose
645 # Match and skip patterns default to the empty list, except for
646 # matchFiles, which defaults to all .test files in the
647 # testsDirectory and matchDirectories, which defaults to all
650 Run all tests within the specified files that match one of the
651 list of glob patterns given.
655 Skip all tests within the specified tests (via -match) and files
656 that match one of the list of glob patterns given.
659 Option -file *.test {
660 Run tests in all test files that match the glob pattern given.
661 } AcceptPattern matchFiles
663 # By default, skip files that appear to be SCCS lock files.
664 Option -notfile l.*.test {
665 Skip all test files that match the glob pattern given.
666 } AcceptPattern skipFiles
668 Option -relateddir * {
669 Run tests in directories that match the glob pattern given.
670 } AcceptPattern matchDirectories
672 Option -asidefromdir {} {
673 Skip tests in directories that match the glob pattern given.
674 } AcceptPattern skipDirectories
676 # By default, don't save core files
677 Option -preservecore 0 {
678 If 2, save any core files produced during testing in the directory
679 specified by -tmpdir. If 1, notify the user if core files are
681 } AcceptInteger preserveCore
683 # debug output doesn't get printed by default; debug level 1 spits
684 # up only the tests that were skipped because they didn't match or
685 # were specifically skipped. A debug level of 2 would spit up the
686 # tcltest variables and flags provided; a debug level of 3 causes
687 # some additional output regarding operations of the test harness.
688 # The tcltest package currently implements only up to debug level 3.
691 } AcceptInteger debug
693 proc SetSelectedConstraints args {
695 foreach c $Option(-constraints) {
699 Option -constraints {} {
700 Do not skip the listed constraints listed in -constraints.
702 trace add variable Option(-constraints) write \
703 [namespace code {SetSelectedConstraints ;#}]
705 # Don't run only the "-constraint" specified tests by default
706 proc ClearUnselectedConstraints args {
708 variable testConstraints
709 if {!$Option(-limitconstraints)} {return}
710 foreach c [array names testConstraints] {
711 if {$c ni $Option(-constraints)} {
716 Option -limitconstraints 0 {
717 whether to run only tests with the constraints
718 } AcceptBoolean limitConstraints
719 trace add variable Option(-limitconstraints) write \
720 [namespace code {ClearUnselectedConstraints ;#}]
722 # A test application has to know how to load the tested commands
723 # into the interpreter.
725 Specifies the script to load the tested commands.
726 } AcceptScript loadScript
728 # Default is to run each test file in a separate process
729 Option -singleproc 0 {
730 whether to run all tests in one process
731 } AcceptBoolean singleProcess
733 proc AcceptTemporaryDirectory { directory } {
734 set directory [AcceptAbsolutePath $directory]
735 if {![file exists $directory]} {
736 file mkdir $directory
738 set directory [AcceptDirectory $directory]
739 if {![file writable $directory]} {
740 if {[workingDirectory] eq $directory} {
741 # Special exception: accept the default value
742 # even if the directory is not writable
745 return -code error "\"$directory\" is not writeable"
750 # Directory where files should be created
751 Option -tmpdir [workingDirectory] {
752 Save temporary files in the specified directory.
753 } AcceptTemporaryDirectory temporaryDirectory
754 trace add variable Option(-tmpdir) write \
755 [namespace code {normalizePath Option(-tmpdir) ;#}]
757 # Tests should not rely on the current working directory.
758 # Files that are part of the test suite should be accessed relative
759 # to [testsDirectory]
760 Option -testdir [workingDirectory] {
761 Search tests in the specified directory.
762 } AcceptDirectory testsDirectory
763 trace add variable Option(-testdir) write \
764 [namespace code {normalizePath Option(-testdir) ;#}]
766 proc AcceptLoadFile { file } {
767 if {$file eq {}} {return $file}
768 set file [file join [temporaryDirectory] $file]
769 return [AcceptReadable $file]
771 proc ReadLoadScript {args} {
773 if {$Option(-loadfile) eq {}} {return}
774 set tmp [open $Option(-loadfile) r]
775 loadScript [read $tmp]
778 Option -loadfile {} {
779 Read the script to load the tested commands from the specified file.
780 } AcceptLoadFile loadFile
781 trace add variable Option(-loadfile) write [namespace code ReadLoadScript]
783 proc AcceptOutFile { file } {
784 if {[string equal stderr $file]} {return $file}
785 if {[string equal stdout $file]} {return $file}
786 return [file join [temporaryDirectory] $file]
789 # output goes to stdout by default
790 Option -outfile stdout {
791 Send output from test runs to the specified file.
792 } AcceptOutFile outputFile
793 trace add variable Option(-outfile) write \
794 [namespace code {outputChannel $Option(-outfile) ;#}]
796 # errors go to stderr by default
797 Option -errfile stderr {
798 Send errors from test runs to the specified file.
799 } AcceptOutFile errorFile
800 trace add variable Option(-errfile) write \
801 [namespace code {errorChannel $Option(-errfile) ;#}]
803 proc loadIntoSlaveInterpreter {slave args} {
805 interp eval $slave [package ifneeded tcltest $Version]
806 interp eval $slave "tcltest::configure {*}{$args}"
807 interp alias $slave ::tcltest::ReportToMaster \
808 {} ::tcltest::ReportedFromSlave
810 proc ReportedFromSlave {total passed skipped failed because newfiles} {
812 variable skippedBecause
813 variable createdNewFiles
814 incr numTests(Total) $total
815 incr numTests(Passed) $passed
816 incr numTests(Skipped) $skipped
817 incr numTests(Failed) $failed
818 foreach {constraint count} $because {
819 incr skippedBecause($constraint) $count
821 foreach {testfile created} $newfiles {
822 lappend createdNewFiles($testfile) {*}$created
828 #####################################################################
832 # Internal helper procedures to write out debug information
833 # dependent on the chosen level. A test shell may overide
834 # them, f.e. to redirect the output into a different
835 # channel, or even into a GUI.
837 # tcltest::DebugPuts --
839 # Prints the specified string if the current debug level is
840 # higher than the provided level argument.
843 # level The lowest debug level triggering the output
844 # string The string to print out.
847 # Prints the string. Nothing else is allowed.
853 proc tcltest::DebugPuts {level string} {
855 if {$debug >= $level} {
861 # tcltest::DebugPArray --
863 # Prints the contents of the specified array if the current
864 # debug level is higher than the provided level argument
867 # level The lowest debug level triggering the output
868 # arrayvar The name of the array to print out.
871 # Prints the contents of the array. Nothing else is allowed.
877 proc tcltest::DebugPArray {level arrayvar} {
880 if {$debug >= $level} {
881 catch {upvar 1 $arrayvar $arrayvar}
887 # Define our own [parray] in ::tcltest that will inherit use of the [puts]
888 # defined in ::tcltest. NOTE: Ought to construct with [info args] and
889 # [info default], but can't be bothered now. If [parray] changes, then
890 # this will need changing too.
892 proc tcltest::parray {a {pattern *}} [info body ::parray]
894 # tcltest::DebugDo --
896 # Executes the script if the current debug level is greater than
897 # the provided level argument
900 # level The lowest debug level triggering the execution.
901 # script The tcl script executed upon a debug level high enough.
904 # Arbitrary side effects, dependent on the executed script.
910 proc tcltest::DebugDo {level script} {
913 if {$debug >= $level} {
919 #####################################################################
921 proc tcltest::Warn {msg} {
922 puts [outputChannel] "WARNING: $msg"
925 # tcltest::mainThread
927 # Accessor command for tcltest variable mainThread.
929 proc tcltest::mainThread { {new ""} } {
931 if {[llength [info level 0]] == 1} {
937 # tcltest::testConstraint --
939 # sets a test constraint to a value; to do multiple constraints,
940 # call this proc multiple times. also returns the value of the
941 # named constraint if no value was supplied.
944 # constraint - name of the constraint
945 # value - new value for constraint (should be boolean) - if not
946 # supplied, this is a query
949 # content of tcltest::testConstraints($constraint)
954 proc tcltest::testConstraint {constraint {value ""}} {
955 variable testConstraints
957 DebugPuts 3 "entering testConstraint $constraint $value"
958 if {[llength [info level 0]] == 2} {
959 return $testConstraints($constraint)
961 # Check for boolean values
962 if {[catch {expr {$value && $value}} msg]} {
963 return -code error $msg
965 if {[limitConstraints] && ($constraint ni $Option(-constraints))} {
968 set testConstraints($constraint) $value
971 # tcltest::interpreter --
973 # the interpreter name stored in tcltest::tcltest
979 # content of tcltest::tcltest
984 proc tcltest::interpreter { {interp ""} } {
986 if {[llength [info level 0]] == 1} {
992 #####################################################################
994 # tcltest::AddToSkippedBecause --
996 # Increments the variable used to track how many tests were
997 # skipped because of a particular constraint.
1000 # constraint The name of the constraint to be modified
1003 # Modifies tcltest::skippedBecause; sets the variable to 1 if
1004 # didn't previously exist - otherwise, it just increments it.
1009 proc tcltest::AddToSkippedBecause { constraint {value 1}} {
1010 # add the constraint to the list of constraints that kept tests
1012 variable skippedBecause
1014 if {[info exists skippedBecause($constraint)]} {
1015 incr skippedBecause($constraint) $value
1017 set skippedBecause($constraint) $value
1022 # tcltest::PrintError --
1024 # Prints errors to tcltest::errorChannel and then flushes that
1025 # channel, making sure that all messages are < 80 characters per
1029 # errorMsg String containing the error to be printed
1037 proc tcltest::PrintError {errorMsg} {
1038 set InitialMessage "Error: "
1039 set InitialMsgLen [string length $InitialMessage]
1040 puts -nonewline [errorChannel] $InitialMessage
1042 # Keep track of where the end of the string is.
1043 set endingIndex [string length $errorMsg]
1045 if {$endingIndex < (80 - $InitialMsgLen)} {
1046 puts [errorChannel] $errorMsg
1048 # Print up to 80 characters on the first line, including the
1050 set beginningIndex [string last " " [string range $errorMsg 0 \
1051 [expr {80 - $InitialMsgLen}]]]
1052 puts [errorChannel] [string range $errorMsg 0 $beginningIndex]
1054 while {$beginningIndex ne "end"} {
1055 puts -nonewline [errorChannel] \
1056 [string repeat " " $InitialMsgLen]
1057 if {($endingIndex - $beginningIndex)
1058 < (80 - $InitialMsgLen)} {
1059 puts [errorChannel] [string trim \
1060 [string range $errorMsg $beginningIndex end]]
1063 set newEndingIndex [expr {[string last " " \
1064 [string range $errorMsg $beginningIndex \
1065 [expr {$beginningIndex
1066 + (80 - $InitialMsgLen)}]
1067 ]] + $beginningIndex}]
1068 if {($newEndingIndex <= 0)
1069 || ($newEndingIndex <= $beginningIndex)} {
1070 set newEndingIndex end
1072 puts [errorChannel] [string trim \
1073 [string range $errorMsg \
1074 $beginningIndex $newEndingIndex]]
1075 set beginningIndex $newEndingIndex
1079 flush [errorChannel]
1083 # tcltest::SafeFetch --
1085 # The following trace procedure makes it so that we can safely
1086 # refer to non-existent members of the testConstraints array
1087 # without causing an error. Instead, reading a non-existent
1088 # member will return 0. This is necessary because tests are
1089 # allowed to use constraint "X" without ensuring that
1090 # testConstraints("X") is defined.
1093 # n1 - name of the array (testConstraints)
1094 # n2 - array key value (constraint name)
1095 # op - operation performed on testConstraints (generally r)
1101 # sets testConstraints($n2) to 0 if it's referenced but never
1104 proc tcltest::SafeFetch {n1 n2 op} {
1105 variable testConstraints
1106 DebugPuts 3 "entering SafeFetch $n1 $n2 $op"
1107 if {$n2 eq {}} {return}
1108 if {![info exists testConstraints($n2)]} {
1109 if {[catch {testConstraint $n2 [eval [ConstraintInitializer $n2]]}]} {
1110 testConstraint $n2 0
1115 # tcltest::ConstraintInitializer --
1117 # Get or set a script that when evaluated in the tcltest namespace
1118 # will return a boolean value with which to initialize the
1119 # associated constraint.
1122 # constraint - name of the constraint initialized by the script
1123 # script - the initializer script
1126 # boolean value of the constraint - enabled or disabled
1129 # Constraint is initialized for future reference by [test]
1130 proc tcltest::ConstraintInitializer {constraint {script ""}} {
1131 variable ConstraintInitializer
1132 DebugPuts 3 "entering ConstraintInitializer $constraint $script"
1133 if {[llength [info level 0]] == 2} {
1134 return $ConstraintInitializer($constraint)
1136 # Check for boolean values
1137 if {![info complete $script]} {
1138 return -code error "ConstraintInitializer must be complete script"
1140 set ConstraintInitializer($constraint) $script
1143 # tcltest::InitConstraints --
1145 # Call all registered constraint initializers to force initialization
1146 # of all known constraints.
1147 # See the tcltest man page for the list of built-in constraints defined
1148 # in this procedure.
1154 # The testConstraints array is reset to have an index for each
1155 # built-in test constraint.
1161 proc tcltest::InitConstraints {} {
1162 variable ConstraintInitializer
1164 foreach constraint [array names ConstraintInitializer] {
1165 testConstraint $constraint
1169 proc tcltest::DefineConstraintInitializers {} {
1170 ConstraintInitializer singleTestInterp {singleProcess}
1172 # All the 'pc' constraints are here for backward compatibility and
1173 # are not documented. They have been replaced with equivalent 'win'
1176 ConstraintInitializer unixOnly \
1177 {string equal $::tcl_platform(platform) unix}
1178 ConstraintInitializer macOnly \
1179 {string equal $::tcl_platform(platform) macintosh}
1180 ConstraintInitializer pcOnly \
1181 {string equal $::tcl_platform(platform) windows}
1182 ConstraintInitializer winOnly \
1183 {string equal $::tcl_platform(platform) windows}
1185 ConstraintInitializer unix {testConstraint unixOnly}
1186 ConstraintInitializer mac {testConstraint macOnly}
1187 ConstraintInitializer pc {testConstraint pcOnly}
1188 ConstraintInitializer win {testConstraint winOnly}
1190 ConstraintInitializer unixOrPc \
1191 {expr {[testConstraint unix] || [testConstraint pc]}}
1192 ConstraintInitializer macOrPc \
1193 {expr {[testConstraint mac] || [testConstraint pc]}}
1194 ConstraintInitializer unixOrWin \
1195 {expr {[testConstraint unix] || [testConstraint win]}}
1196 ConstraintInitializer macOrWin \
1197 {expr {[testConstraint mac] || [testConstraint win]}}
1198 ConstraintInitializer macOrUnix \
1199 {expr {[testConstraint mac] || [testConstraint unix]}}
1201 ConstraintInitializer nt {string equal $::tcl_platform(os) "Windows NT"}
1202 ConstraintInitializer 95 {string equal $::tcl_platform(os) "Windows 95"}
1203 ConstraintInitializer 98 {string equal $::tcl_platform(os) "Windows 98"}
1205 # The following Constraints switches are used to mark tests that
1206 # should work, but have been temporarily disabled on certain
1207 # platforms because they don't and we haven't gotten around to
1208 # fixing the underlying problem.
1210 ConstraintInitializer tempNotPc {expr {![testConstraint pc]}}
1211 ConstraintInitializer tempNotWin {expr {![testConstraint win]}}
1212 ConstraintInitializer tempNotMac {expr {![testConstraint mac]}}
1213 ConstraintInitializer tempNotUnix {expr {![testConstraint unix]}}
1215 # The following Constraints switches are used to mark tests that
1216 # crash on certain platforms, so that they can be reactivated again
1217 # when the underlying problem is fixed.
1219 ConstraintInitializer pcCrash {expr {![testConstraint pc]}}
1220 ConstraintInitializer winCrash {expr {![testConstraint win]}}
1221 ConstraintInitializer macCrash {expr {![testConstraint mac]}}
1222 ConstraintInitializer unixCrash {expr {![testConstraint unix]}}
1226 ConstraintInitializer emptyTest {format 0}
1228 # By default, tests that expose known bugs are skipped.
1230 ConstraintInitializer knownBug {format 0}
1232 # By default, non-portable tests are skipped.
1234 ConstraintInitializer nonPortable {format 0}
1236 # Some tests require user interaction.
1238 ConstraintInitializer userInteraction {format 0}
1240 # Some tests must be skipped if the interpreter is not in
1243 ConstraintInitializer interactive \
1244 {expr {[info exists ::tcl_interactive] && $::tcl_interactive}}
1246 # Some tests can only be run if the installation came from a CD
1247 # image instead of a web image. Some tests must be skipped if you
1248 # are running as root on Unix. Other tests can only be run if you
1249 # are running as root on Unix.
1251 ConstraintInitializer root {expr \
1252 {($::tcl_platform(platform) eq "unix") &&
1253 ($::tcl_platform(user) in {root {}})}}
1254 ConstraintInitializer notRoot {expr {![testConstraint root]}}
1256 # Set nonBlockFiles constraint: 1 means this platform supports
1257 # setting files into nonblocking mode.
1259 ConstraintInitializer nonBlockFiles {
1260 set code [expr {[catch {set f [open defs r]}]
1261 || [catch {chan configure $f -blocking off}]}]
1266 # Set asyncPipeClose constraint: 1 means this platform supports
1267 # async flush and async close on a pipe.
1269 # Test for SCO Unix - cannot run async flushing tests because a
1270 # potential problem with select is apparently interfering.
1273 ConstraintInitializer asyncPipeClose {expr {
1274 !([string equal unix $::tcl_platform(platform)]
1275 && ([catch {exec uname -X | fgrep {Release = 3.2v}}] == 0))}}
1277 # Test to see if we have a broken version of sprintf with respect
1278 # to the "e" format of floating-point numbers.
1280 ConstraintInitializer eformat {string equal [format %g 5e-5] 5e-05}
1282 # Test to see if execed commands such as cat, echo, rm and so forth
1283 # are present on this machine.
1285 ConstraintInitializer unixExecs {
1287 if {$::tcl_platform(platform) eq "macintosh"} {
1290 if {$::tcl_platform(platform) eq "windows"} {
1292 set file _tcl_test_remove_me.txt
1293 makeFile {hello} $file
1297 [catch {exec cat $file}] ||
1298 [catch {exec echo hello}] ||
1299 [catch {exec sh -c echo hello}] ||
1300 [catch {exec wc $file}] ||
1301 [catch {exec sleep 1}] ||
1302 [catch {exec echo abc > $file}] ||
1303 [catch {exec chmod 644 $file}] ||
1304 [catch {exec rm $file}] ||
1305 [llength [auto_execok mkdir]] == 0 ||
1306 [llength [auto_execok fgrep]] == 0 ||
1307 [llength [auto_execok grep]] == 0 ||
1308 [llength [auto_execok ps]] == 0
1317 ConstraintInitializer stdio {
1319 if {![catch {set f [open "|[list [interpreter]]" w]}]} {
1320 if {![catch {puts $f exit}]} {
1321 if {![catch {close $f}]} {
1329 # Deliberately call socket with the wrong number of arguments. The
1330 # error message you get will indicate whether sockets are available
1333 ConstraintInitializer socket {
1335 string compare $msg "sockets are not available on this system"
1338 # Check for internationalization
1339 ConstraintInitializer hasIsoLocale {
1340 if {[llength [info commands testlocale]] == 0} {
1343 set code [string length [SetIso8859_1_Locale]]
1350 #####################################################################
1352 # Usage and command line arguments processing.
1354 # tcltest::PrintUsageInfo
1356 # Prints out the usage information for package tcltest. This can
1357 # be customized with the redefinition of [PrintUsageInfoHook].
1367 proc tcltest::PrintUsageInfo {} {
1372 proc tcltest::Usage { {option ""} } {
1375 if {[llength [info level 0]] == 1} {
1376 set msg "Usage: [file tail [info nameofexecutable]] script "
1377 append msg "?-help? ?flag value? ... \n"
1378 append msg "Available flags (and valid input values) are:"
1381 set allOpts [concat -help [Configure]]
1382 foreach opt $allOpts {
1383 set foo [Usage $opt]
1384 lassign $foo x type($opt) usage($opt)
1385 set line($opt) " $opt $type($opt) "
1386 set length($opt) [string length $line($opt)]
1387 if {$length($opt) > $max} {set max $length($opt)}
1389 set rest [expr {72 - $max}]
1390 foreach opt $allOpts {
1391 append msg \n$line($opt)
1392 append msg [string repeat " " [expr {$max - $length($opt)}]]
1393 set u [string trim $usage($opt)]
1394 catch {append u " (default: \[[Configure $opt]])"}
1395 regsub -all {\s*\n\s*} $u " " u
1396 while {[string length $u] > $rest} {
1397 set break [string wordstart $u $rest]
1399 set break [string wordend $u 0]
1401 append msg [string range $u 0 [expr {$break - 1}]]
1402 set u [string trim [string range $u $break end]]
1403 append msg \n[string repeat " " $max]
1408 } elseif {$option eq "-help"} {
1409 return [list -help "" "Display this usage information."]
1411 set type [lindex [info args $Verify($option)] 0]
1412 return [list $option $type $Usage($option)]
1416 # tcltest::ProcessFlags --
1418 # process command line arguments supplied in the flagArray - this
1419 # is called by processCmdLineArgs. Modifies tcltest variables
1420 # according to the content of the flagArray.
1423 # flagArray - array containing name/value pairs of flags
1426 # sets tcltest variables according to their values as defined by
1432 proc tcltest::ProcessFlags {flagArray} {
1433 # Process -help first
1434 if {"-help" in $flagArray} {
1439 if {[llength $flagArray] == 0} {
1440 RemoveAutoConfigureTraces
1443 while {[llength $args] > 1 && [catch {configure {*}$args} msg]} {
1445 # Something went wrong parsing $args for tcltest options
1446 # Check whether the problem is "unknown option"
1447 if {[regexp {^unknown option (\S+):} $msg -> option]} {
1448 # Could be this is an option the Hook knows about
1449 set moreOptions [processCmdLineArgsAddFlagsHook]
1450 if {$option ni $moreOptions} {
1451 # Nope. Report the error, including additional options,
1453 if {[llength $moreOptions]} {
1455 append msg [join [lrange $moreOptions 0 end-1] ", "]
1456 append msg "or [lindex $moreOptions end]"
1461 # error is something other than "unknown option"
1462 # notify user of the error; and exit
1463 puts [errorChannel] $msg
1467 # To recover, find that unknown option and remove up to it.
1469 while {[lindex $args 0] ne $option} {
1470 set args [lrange $args 2 end]
1472 set args [lrange $args 2 end]
1474 if {[llength $args] == 1} {
1475 puts [errorChannel] \
1476 "missing value for option [lindex $args 0]"
1483 array set flag $flagArray
1484 processCmdLineArgsHook [array get flag]
1489 # tcltest::ProcessCmdLineArgs --
1491 # This procedure must be run after constraint initialization is
1492 # set up (by [DefineConstraintInitializers]) because some constraints
1493 # can be overridden.
1495 # Perform configuration according to the command-line options.
1501 # Sets the above-named variables in the tcltest namespace.
1507 proc tcltest::ProcessCmdLineArgs {} {
1508 variable originalEnv
1509 variable testConstraints
1511 # The "argv" var doesn't exist in some cases, so use {}.
1512 if {![info exists ::argv]} {
1515 ProcessFlags $::argv
1518 # Spit out everything you know if we're at a debug level 2 or
1520 DebugPuts 2 "Flags passed into tcltest:"
1521 if {[info exists ::env(TCLTEST_OPTIONS)]} {
1523 " ::env(TCLTEST_OPTIONS): $::env(TCLTEST_OPTIONS)"
1525 if {[info exists ::argv]} {
1526 DebugPuts 2 " argv: $::argv"
1528 DebugPuts 2 "tcltest::debug = [debug]"
1529 DebugPuts 2 "tcltest::testsDirectory = [testsDirectory]"
1530 DebugPuts 2 "tcltest::workingDirectory = [workingDirectory]"
1531 DebugPuts 2 "tcltest::temporaryDirectory = [temporaryDirectory]"
1532 DebugPuts 2 "tcltest::outputChannel = [outputChannel]"
1533 DebugPuts 2 "tcltest::errorChannel = [errorChannel]"
1534 DebugPuts 2 "Original environment (tcltest::originalEnv):"
1535 DebugPArray 2 originalEnv
1536 DebugPuts 2 "Constraints:"
1537 DebugPArray 2 testConstraints
1540 #####################################################################
1542 # Code to run the tests goes here.
1544 # tcltest::TestPuts --
1546 # Used to redefine puts in test environment. Stores whatever goes
1547 # out on stdout in tcltest::outData and stderr in errData before
1548 # sending it on to the regular puts.
1551 # same as standard puts
1557 # Intercepts puts; data that would otherwise go to stdout, stderr,
1558 # or file channels specified in outputChannel and errorChannel
1559 # does not get sent to the normal puts function.
1560 namespace eval tcltest::Replace {
1561 namespace export puts
1563 proc tcltest::Replace::puts {args} {
1564 variable [namespace parent]::outData
1565 variable [namespace parent]::errData
1566 switch [llength $args] {
1568 # Only the string to be printed is specified
1569 append outData [lindex $args 0]\n
1571 # return [Puts [lindex $args 0]]
1574 # Either -nonewline or channelId has been specified
1575 if {[lindex $args 0] eq "-nonewline"} {
1576 append outData [lindex $args end]
1578 # return [Puts -nonewline [lindex $args end]]
1580 set channel [lindex $args 0]
1585 if {[lindex $args 0] eq "-nonewline"} {
1586 # Both -nonewline and channelId are specified, unless
1587 # it's an error. -nonewline is supposed to be argv[0].
1588 set channel [lindex $args 1]
1594 if {[info exists channel]} {
1595 if {$channel in [list [[namespace parent]::outputChannel] stdout]} {
1596 append outData [lindex $args end]$newline
1598 } elseif {$channel in [list [[namespace parent]::errorChannel] stderr]} {
1599 append errData [lindex $args end]$newline
1604 # If we haven't returned by now, we don't know how to handle the
1605 # input. Let puts handle it.
1606 return [Puts {*}$args]
1611 # Evaluate the script in the test environment. If ignoreOutput is
1612 # false, store data sent to stderr and stdout in outData and
1613 # errData. Otherwise, ignore this output altogether.
1616 # script Script to evaluate
1617 # ?ignoreOutput? Indicates whether or not to ignore output
1618 # sent to stdout & stderr
1621 # result from running the script
1624 # Empties the contents of outData and errData before running a
1625 # test if ignoreOutput is set to 0.
1627 proc tcltest::Eval {script {ignoreOutput 1}} {
1630 DebugPuts 3 "[lindex [info level 0] 0] called"
1631 if {!$ignoreOutput} {
1634 rename ::puts [namespace current]::Replace::Puts
1635 namespace eval :: [list namespace import [namespace origin Replace::puts]]
1636 namespace import Replace::puts
1638 set result [uplevel 1 $script]
1639 if {!$ignoreOutput} {
1640 namespace forget puts
1641 namespace eval :: namespace forget puts
1642 rename [namespace current]::Replace::Puts ::puts
1647 # tcltest::CompareStrings --
1649 # compares the expected answer to the actual answer, depending on
1650 # the mode provided. Mode determines whether a regexp, exact,
1651 # glob or custom comparison is done.
1654 # actual - string containing the actual result
1655 # expected - pattern to be matched against
1656 # mode - type of comparison to be done
1659 # result of the match
1664 proc tcltest::CompareStrings {actual expected mode} {
1665 variable CustomMatch
1666 if {![info exists CustomMatch($mode)]} {
1667 return -code error "No matching command registered for `-match $mode'"
1669 set match [namespace eval :: $CustomMatch($mode) [list $expected $actual]]
1670 if {[catch {expr {$match && $match}} result]} {
1671 return -code error "Invalid result from `-match $mode' command: $result"
1676 # tcltest::customMatch --
1678 # registers a command to be called when a particular type of
1679 # matching is required.
1682 # nickname - Keyword for the type of matching
1683 # cmd - Incomplete command that implements that type of matching
1684 # when completed with expected string and actual string
1685 # and then evaluated.
1691 # Sets the variable tcltest::CustomMatch
1693 proc tcltest::customMatch {mode script} {
1694 variable CustomMatch
1695 if {![info complete $script]} {
1696 return -code error \
1697 "invalid customMatch script; can't evaluate after completion"
1699 set CustomMatch($mode) $script
1702 # tcltest::SubstArguments list
1704 # This helper function takes in a list of words, then perform a
1705 # substitution on the list as though each word in the list is a separate
1706 # argument to the Tcl function. For example, if this function is
1709 # SubstArguments {$a {$a}}
1711 # Then it is as though the function is invoked as:
1713 # SubstArguments $a {$a}
1715 # This code is adapted from Paul Duffin's function "SplitIntoWords".
1716 # The original function can be found on:
1718 # http://purl.org/thecliff/tcl/wiki/858.html
1721 # a list containing the result of the substitution
1724 # An error may occur if the list containing unbalanced quote or
1731 proc tcltest::SubstArguments {argList} {
1733 # We need to split the argList up into tokens but cannot use list
1734 # operations as they throw away some significant quoting, and
1735 # [split] ignores braces as it should. Therefore what we do is
1736 # gradually build up a string out of whitespace seperated strings.
1737 # We cannot use [split] to split the argList into whitespace
1738 # separated strings as it throws away the whitespace which maybe
1739 # important so we have to do it all by hand.
1744 while {[string length $argList]} {
1745 # Look for the next word containing a quote: " { }
1746 if {[regexp -indices {[^ \t\n]*[\"\{\}]+[^ \t\n]*} \
1748 # Get the text leading up to this word, but not including
1749 # this word, from the argList.
1750 set text [string range $argList 0 \
1751 [expr {[lindex $all 0] - 1}]]
1752 # Get the word with the quote
1753 set word [string range $argList \
1754 [lindex $all 0] [lindex $all 1]]
1756 # Remove all text up to and including the word from the
1758 set argList [string range $argList \
1759 [expr {[lindex $all 1] + 1}] end]
1761 # Take everything up to the end of the argList.
1768 # If we saw a word with quote before, then there is a
1769 # multi-word token starting with that word. In this case,
1770 # add the text and the current word to this token.
1771 append token $text $word
1773 # Add the text to the result. There is no need to parse
1774 # the text because it couldn't be a part of any multi-word
1775 # token. Then start a new multi-word token with the word
1776 # because we need to pass this token to the Tcl parser to
1777 # check for balancing quotes
1782 if { [catch {llength $token} length] == 0 && $length == 1} {
1783 # The token is a valid list so add it to the result.
1784 # lappend result [string trim $token]
1785 append result \{$token\}
1790 # If the last token has not been added to the list then there
1792 if { [string length $token] } {
1793 error "incomplete token \"$token\""
1802 # This procedure runs a test and prints an error message if the test
1803 # fails. If verbose has been set, it also prints a message even if the
1804 # test succeeds. The test will be skipped if it doesn't match the
1805 # match variable, if it matches an element in skip, or if one of the
1806 # elements of "constraints" turns out not to be true.
1808 # If testLevel is 1, then this is a top level test, and we record
1809 # pass/fail information; otherwise, this information is not logged and
1810 # is not added to running totals.
1813 # Only description is a required attribute. All others are optional.
1814 # Default values are indicated.
1816 # constraints - A list of one or more keywords, each of which
1817 # must be the name of an element in the array
1818 # "testConstraints". If any of these elements is
1819 # zero, the test is skipped. This attribute is
1820 # optional; default is {}
1821 # body - Script to run to carry out the test. It must
1822 # return a result that can be checked for
1823 # correctness. This attribute is optional;
1825 # result - Expected result from script. This attribute is
1826 # optional; default is {}.
1827 # output - Expected output sent to stdout. This attribute
1828 # is optional; default is {}.
1829 # errorOutput - Expected output sent to stderr. This attribute
1830 # is optional; default is {}.
1831 # returnCodes - Expected return codes. This attribute is
1832 # optional; default is {0 2}.
1833 # setup - Code to run before $script (above). This
1834 # attribute is optional; default is {}.
1835 # cleanup - Code to run after $script (above). This
1836 # attribute is optional; default is {}.
1837 # match - specifies type of matching to do on result,
1838 # output, errorOutput; this must be a string
1839 # previously registered by a call to [customMatch].
1840 # The strings exact, glob, and regexp are pre-registered
1841 # by the tcltest package. Default value is exact.
1844 # name - Name of test, in the form foo-1.2.
1845 # description - Short textual description of the test, to
1846 # help humans understand what it does.
1852 # Just about anything is possible depending on the test.
1855 proc tcltest::test {name description args} {
1858 variable coreModTime
1859 DebugPuts 3 "test $name $args"
1863 puts "test name '$name' re-used; prior use in $TestNames($name)"
1865 set TestNames($name) [info script]
1871 # Pre-define everything to null except output and errorOutput. We
1872 # determine whether or not to trap output based on whether or not
1873 # these variables (output & errorOutput) are defined.
1874 lassign {} constraints setup cleanup body result returnCodes match
1876 # Set the default match mode
1879 # Set the default match values for return codes (0 is the standard
1880 # expected return value if everything went well; 2 represents
1881 # 'return' being used in the test script).
1882 set returnCodes [list 0 2]
1884 # The old test format can't have a 3rd argument (constraints or
1885 # script) that starts with '-'.
1886 if {[string match -* [lindex $args 0]] || ([llength $args] <= 1)} {
1887 if {[llength $args] == 1} {
1888 set list [SubstArguments [lindex $args 0]]
1889 foreach {element value} $list {
1890 set testAttributes($element) $value
1892 foreach item {constraints match setup body cleanup \
1893 result returnCodes output errorOutput} {
1894 if {[info exists testAttributes(-$item)]} {
1895 set testAttributes(-$item) [uplevel 1 \
1896 ::concat $testAttributes(-$item)]
1900 array set testAttributes $args
1903 set validFlags {-setup -cleanup -body -result -returnCodes \
1904 -match -output -errorOutput -constraints}
1906 foreach flag [array names testAttributes] {
1907 if {$flag ni $validFlags} {
1909 set sorted [lsort $validFlags]
1910 set options [join [lrange $sorted 0 end-1] ", "]
1911 append options ", or [lindex $sorted end]"
1912 return -code error "bad option \"$flag\": must be $options"
1916 # store whatever the user gave us
1917 foreach item [array names testAttributes] {
1918 set [string trimleft $item "-"] $testAttributes($item)
1921 # Check the values supplied for -match
1922 variable CustomMatch
1923 if {$match ni [array names CustomMatch]} {
1925 set sorted [lsort [array names CustomMatch]]
1926 set values [join [lrange $sorted 0 end-1] ", "]
1927 append values ", or [lindex $sorted end]"
1928 return -code error "bad -match value \"$match\":\
1932 # Replace symbolic valies supplied for -returnCodes
1933 foreach {strcode numcode} {ok 0 normal 0 error 1 return 2 break 3 continue 4} {
1934 set returnCodes [string map -nocase [list $strcode $numcode] $returnCodes]
1937 # This is parsing for the old test command format; it is here
1938 # for backward compatibility.
1939 set result [lindex $args end]
1940 if {[llength $args] == 2} {
1941 set body [lindex $args 0]
1942 } elseif {[llength $args] == 3} {
1943 set constraints [lindex $args 0]
1944 set body [lindex $args 1]
1947 return -code error "wrong # args:\
1948 should be \"test name desc ?options?\""
1952 if {[Skipped $name $constraints]} {
1957 # Save information about the core file.
1958 if {[preserveCore]} {
1959 if {[file exists [file join [workingDirectory] core]]} {
1960 set coreModTime [file mtime [file join [workingDirectory] core]]
1964 # First, run the setup script
1965 set code [catch {uplevel 1 $setup} setupMsg]
1967 set errorInfo(setup) $::errorInfo
1968 set errorCode(setup) $::errorCode
1970 set setupFailure [expr {$code != 0}]
1972 # Only run the test body if the setup was successful
1973 if {!$setupFailure} {
1975 # Verbose notification of $body start
1976 if {[IsVerbose start]} {
1977 puts [outputChannel] "---- $name start"
1978 flush [outputChannel]
1981 set command [list [namespace origin RunTest] $name $body]
1982 if {[info exists output] || [info exists errorOutput]} {
1983 set testResult [uplevel 1 [list [namespace origin Eval] $command 0]]
1985 set testResult [uplevel 1 [list [namespace origin Eval] $command 1]]
1987 lassign $testResult actualAnswer returnCode
1988 if {$returnCode == 1} {
1989 set errorInfo(body) $::errorInfo
1990 set errorCode(body) $::errorCode
1994 # check if the return code matched the expected return code
1996 if {!$setupFailure && ($returnCode ni $returnCodes)} {
2000 # If expected output/error strings exist, we have to compare
2001 # them. If the comparison fails, then so did the test.
2004 if {[info exists output] && !$codeFailure} {
2005 if {[set outputCompare [catch {
2006 CompareStrings $outData $output $match
2007 } outputMatch]] == 0} {
2008 set outputFailure [expr {!$outputMatch}]
2016 if {[info exists errorOutput] && !$codeFailure} {
2017 if {[set errorCompare [catch {
2018 CompareStrings $errData $errorOutput $match
2019 } errorMatch]] == 0} {
2020 set errorFailure [expr {!$errorMatch}]
2026 # check if the answer matched the expected answer
2027 # Only check if we ran the body of the test (no setup failure)
2028 if {$setupFailure || $codeFailure} {
2030 } elseif {[set scriptCompare [catch {
2031 CompareStrings $actualAnswer $result $match
2032 } scriptMatch]] == 0} {
2033 set scriptFailure [expr {!$scriptMatch}]
2038 # Always run the cleanup script
2039 set code [catch {uplevel 1 $cleanup} cleanupMsg]
2041 set errorInfo(cleanup) $::errorInfo
2042 set errorCode(cleanup) $::errorCode
2044 set cleanupFailure [expr {$code != 0}]
2048 # check for a core file first - if one was created by the test,
2049 # then the test failed
2050 if {[preserveCore]} {
2051 if {[file exists [file join [workingDirectory] core]]} {
2052 # There's only a test failure if there is a core file
2053 # and (1) there previously wasn't one or (2) the new
2054 # one is different from the old one.
2055 if {[info exists coreModTime]} {
2056 if {$coreModTime != [file mtime \
2057 [file join [workingDirectory] core]]} {
2064 if {([preserveCore] > 1) && ($coreFailure)} {
2065 append coreMsg "\nMoving file to:\
2066 [file join [temporaryDirectory] core-$name]"
2067 catch {file rename -force -- \
2068 [file join [workingDirectory] core] \
2069 [file join [temporaryDirectory] core-$name]
2072 append coreMsg "\nError:\
2073 Problem renaming core file: $msg"
2079 # if we didn't experience any failures, then we passed
2081 if {!($setupFailure || $cleanupFailure || $coreFailure
2082 || $outputFailure || $errorFailure || $codeFailure
2083 || $scriptFailure)} {
2084 if {$testLevel == 1} {
2085 incr numTests(Passed)
2086 if {[IsVerbose pass]} {
2087 puts [outputChannel] "++++ $name PASSED"
2094 # We know the test failed, tally it...
2095 if {$testLevel == 1} {
2096 incr numTests(Failed)
2099 # ... then report according to the type of failure
2100 variable currentFailure true
2101 if {![IsVerbose body]} {
2104 puts [outputChannel] "\n"
2105 if {[IsVerbose line]} {
2106 if {![catch {set testFrame [info frame -1]}] &&
2107 [dict get $testFrame type] eq "source"} {
2108 set testFile [dict get $testFrame file]
2109 set testLine [dict get $testFrame line]
2111 set testFile [file normalize [uplevel 1 {info script}]]
2112 if {[file readable $testFile]} {
2113 set testFd [open $testFile r]
2114 set testLine [expr {[lsearch -regexp \
2115 [split [read $testFd] "\n"] \
2116 "^\[ \t\]*test [string map {. \\.} $name] "] + 1}]
2120 if {[info exists testLine]} {
2121 puts [outputChannel] "$testFile:$testLine: error: test failed:\
2122 $name [string trim $description]"
2125 puts [outputChannel] "==== $name\
2126 [string trim $description] FAILED"
2127 if {[string length $body]} {
2128 puts [outputChannel] "==== Contents of test case:"
2129 puts [outputChannel] $body
2131 if {$setupFailure} {
2132 puts [outputChannel] "---- Test setup\
2134 if {[info exists errorInfo(setup)]} {
2135 puts [outputChannel] "---- errorInfo(setup): $errorInfo(setup)"
2136 puts [outputChannel] "---- errorCode(setup): $errorCode(setup)"
2139 if {$scriptFailure} {
2140 if {$scriptCompare} {
2141 puts [outputChannel] "---- Error testing result: $scriptMatch"
2143 puts [outputChannel] "---- Result was:\n$actualAnswer"
2144 puts [outputChannel] "---- Result should have been\
2145 ($match matching):\n$result"
2149 switch -- $returnCode {
2150 0 { set msg "Test completed normally" }
2151 1 { set msg "Test generated error" }
2152 2 { set msg "Test generated return exception" }
2153 3 { set msg "Test generated break exception" }
2154 4 { set msg "Test generated continue exception" }
2155 default { set msg "Test generated exception" }
2157 puts [outputChannel] "---- $msg; Return code was: $returnCode"
2158 puts [outputChannel] "---- Return code should have been\
2159 one of: $returnCodes"
2160 if {[IsVerbose error]} {
2161 if {[info exists errorInfo(body)] && (1 ni $returnCodes)} {
2162 puts [outputChannel] "---- errorInfo: $errorInfo(body)"
2163 puts [outputChannel] "---- errorCode: $errorCode(body)"
2167 if {$outputFailure} {
2168 if {$outputCompare} {
2169 puts [outputChannel] "---- Error testing output: $outputMatch"
2171 puts [outputChannel] "---- Output was:\n$outData"
2172 puts [outputChannel] "---- Output should have been\
2173 ($match matching):\n$output"
2176 if {$errorFailure} {
2177 if {$errorCompare} {
2178 puts [outputChannel] "---- Error testing errorOutput: $errorMatch"
2180 puts [outputChannel] "---- Error output was:\n$errData"
2181 puts [outputChannel] "---- Error output should have\
2182 been ($match matching):\n$errorOutput"
2185 if {$cleanupFailure} {
2186 puts [outputChannel] "---- Test cleanup failed:\n$cleanupMsg"
2187 if {[info exists errorInfo(cleanup)]} {
2188 puts [outputChannel] "---- errorInfo(cleanup): $errorInfo(cleanup)"
2189 puts [outputChannel] "---- errorCode(cleanup): $errorCode(cleanup)"
2193 puts [outputChannel] "---- Core file produced while running\
2196 puts [outputChannel] "==== $name FAILED\n"
2204 # Given a test name and it constraints, returns a boolean indicating
2205 # whether the current configuration says the test should be skipped.
2207 # Side Effects: Maintains tally of total tests seen and tests skipped.
2209 proc tcltest::Skipped {name constraints} {
2212 variable testConstraints
2214 if {$testLevel == 1} {
2215 incr numTests(Total)
2217 # skip the test if it's name matches an element of skip
2218 foreach pattern [skip] {
2219 if {[string match $pattern $name]} {
2220 if {$testLevel == 1} {
2221 incr numTests(Skipped)
2222 DebugDo 1 {AddToSkippedBecause userSpecifiedSkip}
2227 # skip the test if it's name doesn't match any element of match
2229 foreach pattern [match] {
2230 if {[string match $pattern $name]} {
2236 if {$testLevel == 1} {
2237 incr numTests(Skipped)
2238 DebugDo 1 {AddToSkippedBecause userSpecifiedNonMatch}
2242 if {$constraints eq {}} {
2243 # If we're limited to the listed constraints and there aren't
2244 # any listed, then we shouldn't run the test.
2245 if {[limitConstraints]} {
2246 AddToSkippedBecause userSpecifiedLimitConstraint
2247 if {$testLevel == 1} {
2248 incr numTests(Skipped)
2253 # "constraints" argument exists;
2254 # make sure that the constraints are satisfied.
2257 if {[string match {*[$\[]*} $constraints] != 0} {
2258 # full expression, e.g. {$foo > [info tclversion]}
2259 catch {set doTest [uplevel #0 [list expr $constraints]]}
2260 } elseif {[regexp {[^.:_a-zA-Z0-9 \n\r\t]+} $constraints] != 0} {
2261 # something like {a || b} should be turned into
2262 # $testConstraints(a) || $testConstraints(b).
2263 regsub -all {[.\w]+} $constraints {$testConstraints(&)} c
2264 catch {set doTest [eval [list expr $c]]}
2265 } elseif {![catch {llength $constraints}]} {
2266 # just simple constraints such as {unixOnly fonts}.
2268 foreach constraint $constraints {
2269 if {(![info exists testConstraints($constraint)]) \
2270 || (!$testConstraints($constraint))} {
2273 # store the constraint that kept the test from
2275 set constraints $constraint
2282 if {[IsVerbose skip]} {
2283 puts [outputChannel] "++++ $name SKIPPED: $constraints"
2286 if {$testLevel == 1} {
2287 incr numTests(Skipped)
2288 AddToSkippedBecause $constraints
2298 # This is where the body of a test is evaluated. The combination of
2299 # [RunTest] and [Eval] allows the output and error output of the test
2300 # body to be captured for comparison against the expected values.
2302 proc tcltest::RunTest {name script} {
2303 DebugPuts 3 "Running $name {$script}"
2305 # If there is no "memory" command (because memory debugging isn't
2306 # enabled), then don't attempt to use the command.
2308 if {[llength [info commands memory]] == 1} {
2312 set code [catch {uplevel 1 $script} actualAnswer]
2314 return [list $actualAnswer $code]
2317 #####################################################################
2319 # tcltest::cleanupTestsHook --
2321 # This hook allows a harness that builds upon tcltest to specify
2322 # additional things that should be done at cleanup.
2325 if {[llength [info commands tcltest::cleanupTestsHook]] == 0} {
2326 proc tcltest::cleanupTestsHook {} {}
2329 # tcltest::cleanupTests --
2331 # Remove files and dirs created using the makeFile and makeDirectory
2332 # commands since the last time this proc was invoked.
2334 # Print the names of the files created without the makeFile command
2335 # since the tests were invoked.
2337 # Print the number tests (total, passed, failed, and skipped) since the
2338 # tests were invoked.
2340 # Restore original environment (as reported by special variable env).
2343 # calledFromAllFile - if 0, behave as if we are running a single
2344 # test file within an entire suite of tests. if we aren't running
2345 # a single test file, then don't report status. check for new
2346 # files created during the test run and report on them. if 1,
2347 # report collated status from all the test file runs.
2356 proc tcltest::cleanupTests {{calledFromAllFile 0}} {
2358 variable filesExisted
2359 variable createdNewFiles
2360 variable testSingleFile
2362 variable numTestFiles
2364 variable skippedBecause
2365 variable currentFailure
2366 variable originalEnv
2367 variable originalTclPlatform
2368 variable coreModTime
2371 set testFileName [file tail [info script]]
2373 # Hook to handle reporting to a parent interpreter
2374 if {[llength [info commands [namespace current]::ReportToMaster]]} {
2375 ReportToMaster $numTests(Total) $numTests(Passed) $numTests(Skipped) \
2376 $numTests(Failed) [array get skippedBecause] \
2377 [array get createdNewFiles]
2378 set testSingleFile false
2381 # Call the cleanup hook
2384 # Remove files and directories created by the makeFile and
2385 # makeDirectory procedures. Record the names of files in
2386 # workingDirectory that were not pre-existing, and associate them
2387 # with the test file that created them.
2389 if {!$calledFromAllFile} {
2390 foreach file $filesMade {
2391 if {[file exists $file]} {
2392 DebugDo 1 {Warn "cleanupTests deleting $file..."}
2393 catch {file delete -force -- $file}
2397 foreach file [glob -nocomplain \
2398 -directory [temporaryDirectory] *] {
2399 lappend currentFiles [file tail $file]
2402 foreach file $currentFiles {
2403 if {$file ni $filesExisted} {
2404 lappend newFiles $file
2407 set filesExisted $currentFiles
2408 if {[llength $newFiles] > 0} {
2409 set createdNewFiles($testFileName) $newFiles
2413 if {$calledFromAllFile || $testSingleFile} {
2417 puts -nonewline [outputChannel] "$testFileName:"
2418 foreach index [list "Total" "Passed" "Skipped" "Failed"] {
2419 puts -nonewline [outputChannel] \
2420 "\t$index\t$numTests($index)"
2422 puts [outputChannel] ""
2424 # print number test files sourced
2425 # print names of files that ran tests which failed
2427 if {$calledFromAllFile} {
2428 puts [outputChannel] \
2429 "Sourced $numTestFiles Test Files."
2431 if {[llength $failFiles] > 0} {
2432 puts [outputChannel] \
2433 "Files with failing tests: $failFiles"
2438 # if any tests were skipped, print the constraints that kept
2439 # them from running.
2441 set constraintList [array names skippedBecause]
2442 if {[llength $constraintList] > 0} {
2443 puts [outputChannel] \
2444 "Number of tests skipped for each constraint:"
2445 foreach constraint [lsort $constraintList] {
2446 puts [outputChannel] \
2447 "\t$skippedBecause($constraint)\t$constraint"
2448 unset skippedBecause($constraint)
2452 # report the names of test files in createdNewFiles, and reset
2453 # the array to be empty.
2455 set testFilesThatTurded [lsort [array names createdNewFiles]]
2456 if {[llength $testFilesThatTurded] > 0} {
2457 puts [outputChannel] "Warning: files left behind:"
2458 foreach testFile $testFilesThatTurded {
2459 puts [outputChannel] \
2460 "\t$testFile:\t$createdNewFiles($testFile)"
2461 unset createdNewFiles($testFile)
2465 # reset filesMade, filesExisted, and numTests
2468 foreach index [list "Total" "Passed" "Skipped" "Failed"] {
2469 set numTests($index) 0
2472 # exit only if running Tk in non-interactive mode
2473 # This should be changed to determine if an event
2474 # loop is running, which is the real issue.
2475 # Actually, this doesn't belong here at all. A package
2476 # really has no business [exit]-ing an application.
2477 if {![catch {package present Tk}] && ![testConstraint interactive]} {
2482 # if we're deferring stat-reporting until all files are sourced,
2483 # then add current file to failFile list if any tests in this
2486 if {$currentFailure && ($testFileName ni $failFiles)} {
2487 lappend failFiles $testFileName
2489 set currentFailure false
2491 # restore the environment to the state it was in before this package
2497 foreach index [array names ::env] {
2498 if {![info exists originalEnv($index)]} {
2499 lappend newEnv $index
2503 foreach index [array names originalEnv] {
2504 if {![info exists ::env($index)]} {
2505 lappend removedEnv $index
2506 set ::env($index) $originalEnv($index)
2507 } elseif {$::env($index) ne $originalEnv($index)} {
2508 lappend changedEnv $index
2509 set ::env($index) $originalEnv($index)
2512 if {[llength $newEnv] > 0} {
2513 puts [outputChannel] \
2514 "env array elements created:\t$newEnv"
2516 if {[llength $changedEnv] > 0} {
2517 puts [outputChannel] \
2518 "env array elements changed:\t$changedEnv"
2520 if {[llength $removedEnv] > 0} {
2521 puts [outputChannel] \
2522 "env array elements removed:\t$removedEnv"
2525 set changedTclPlatform {}
2526 foreach index [array names originalTclPlatform] {
2527 if {$::tcl_platform($index) \
2528 != $originalTclPlatform($index)} {
2529 lappend changedTclPlatform $index
2530 set ::tcl_platform($index) $originalTclPlatform($index)
2533 if {[llength $changedTclPlatform] > 0} {
2534 puts [outputChannel] "tcl_platform array elements\
2535 changed:\t$changedTclPlatform"
2538 if {[file exists [file join [workingDirectory] core]]} {
2539 if {[preserveCore] > 1} {
2540 puts "rename core file (> 1)"
2541 puts [outputChannel] "produced core file! \
2543 [file join [temporaryDirectory] core-$testFileName]"
2544 catch {file rename -force -- \
2545 [file join [workingDirectory] core] \
2546 [file join [temporaryDirectory] core-$testFileName]
2549 PrintError "Problem renaming file: $msg"
2552 # Print a message if there is a core file and (1) there
2553 # previously wasn't one or (2) the new one is different
2556 if {[info exists coreModTime]} {
2557 if {$coreModTime != [file mtime \
2558 [file join [workingDirectory] core]]} {
2559 puts [outputChannel] "A core file was created!"
2562 puts [outputChannel] "A core file was created!"
2567 flush [outputChannel]
2568 flush [errorChannel]
2572 #####################################################################
2574 # Procs that determine which tests/test files to run
2576 # tcltest::GetMatchingFiles
2578 # Looks at the patterns given to match and skip files and uses
2579 # them to put together a list of the tests that will be run.
2582 # directory to search
2585 # The constructed list is returned to the user. This will
2586 # primarily be used in 'all.tcl' files. It is used in
2592 # a lower case version is needed for compatibility with tcltest 1.0
2593 proc tcltest::getMatchingFiles args {GetMatchingFiles {*}$args}
2595 proc tcltest::GetMatchingFiles { args } {
2596 if {[llength $args]} {
2599 # Finding tests only in [testsDirectory] is normal operation.
2600 # This procedure is written to accept multiple directory arguments
2601 # only to satisfy version 1 compatibility.
2602 set dirList [list [testsDirectory]]
2605 set matchingFiles [list]
2606 foreach directory $dirList {
2608 # List files in $directory that match patterns to run.
2609 set matchFileList [list]
2610 foreach match [matchFiles] {
2611 set matchFileList [concat $matchFileList \
2612 [glob -directory $directory -types {b c f p s} \
2613 -nocomplain -- $match]]
2616 # List files in $directory that match patterns to skip.
2617 set skipFileList [list]
2618 foreach skip [skipFiles] {
2619 set skipFileList [concat $skipFileList \
2620 [glob -directory $directory -types {b c f p s} \
2621 -nocomplain -- $skip]]
2624 # Add to result list all files in match list and not in skip list
2625 foreach file $matchFileList {
2626 if {$file ni $skipFileList} {
2627 lappend matchingFiles $file
2632 if {[llength $matchingFiles] == 0} {
2633 PrintError "No test files remain after applying your match and\
2636 return $matchingFiles
2639 # tcltest::GetMatchingDirectories --
2641 # Looks at the patterns given to match and skip directories and
2642 # uses them to put together a list of the test directories that we
2643 # should attempt to run. (Only subdirectories containing an
2644 # "all.tcl" file are put into the list.)
2647 # root directory from which to search
2650 # The constructed list is returned to the user. This is used in
2651 # the primary all.tcl file.
2656 proc tcltest::GetMatchingDirectories {rootdir} {
2658 # Determine the skip list first, to avoid [glob]-ing over subdirectories
2659 # we're going to throw away anyway. Be sure we skip the $rootdir if it
2660 # comes up to avoid infinite loops.
2661 set skipDirs [list $rootdir]
2662 foreach pattern [skipDirectories] {
2663 set skipDirs [concat $skipDirs [glob -directory $rootdir -types d \
2664 -nocomplain -- $pattern]]
2667 # Now step through the matching directories, prune out the skipped ones
2669 set matchDirs [list]
2670 foreach pattern [matchDirectories] {
2671 foreach path [glob -directory $rootdir -types d -nocomplain -- \
2673 if {$path ni $skipDirs} {
2674 set matchDirs [concat $matchDirs [GetMatchingDirectories $path]]
2675 if {[file exists [file join $path all.tcl]]} {
2676 lappend matchDirs $path
2682 if {[llength $matchDirs] == 0} {
2683 DebugPuts 1 "No test directories remain after applying match\
2689 # tcltest::runAllTests --
2691 # prints output and sources test files according to the match and
2692 # skip patterns provided. after sourcing test files, it goes on
2693 # to source all.tcl files in matching test subdirectories.
2696 # shell being tested
2704 proc tcltest::runAllTests { {shell ""} } {
2705 variable testSingleFile
2706 variable numTestFiles
2709 variable DefaultValue
2712 if {[llength [info level 0]] == 1} {
2713 set shell [interpreter]
2716 set testSingleFile false
2718 puts [outputChannel] "Tests running in interp: $shell"
2719 puts [outputChannel] "Tests located in: [testsDirectory]"
2720 puts [outputChannel] "Tests running in: [workingDirectory]"
2721 puts [outputChannel] "Temporary files stored in\
2722 [temporaryDirectory]"
2724 # [file system] first available in Tcl 8.4
2725 if {![catch {file system [testsDirectory]} result]
2726 && ([lindex $result 0] ne "native")} {
2727 # If we aren't running in the native filesystem, then we must
2728 # run the tests in a single process (via 'source'), because
2729 # trying to run then via a pipe will fail since the files don't
2734 if {[singleProcess]} {
2735 puts [outputChannel] \
2736 "Test files sourced into current interpreter"
2738 puts [outputChannel] \
2739 "Test files run in separate interpreters"
2741 if {[llength [skip]] > 0} {
2742 puts [outputChannel] "Skipping tests that match: [skip]"
2744 puts [outputChannel] "Running tests that match: [match]"
2746 if {[llength [skipFiles]] > 0} {
2747 puts [outputChannel] \
2748 "Skipping test files that match: [skipFiles]"
2750 if {[llength [matchFiles]] > 0} {
2751 puts [outputChannel] \
2752 "Only running test files that match: [matchFiles]"
2755 set timeCmd {clock format [clock seconds]}
2756 puts [outputChannel] "Tests began at [eval $timeCmd]"
2758 # Run each of the specified tests
2759 foreach file [lsort [GetMatchingFiles]] {
2760 set tail [file tail $file]
2761 puts [outputChannel] $tail
2762 flush [outputChannel]
2764 if {[singleProcess]} {
2766 uplevel 1 [list ::source $file]
2768 # Pass along our configuration to the child processes.
2769 # EXCEPT for the -outfile, because the parent process
2770 # needs to read and process output of children.
2771 set childargv [list]
2772 foreach opt [Configure] {
2773 if {$opt eq "-outfile"} {continue}
2774 set value [Configure $opt]
2775 # Don't bother passing default configuration options
2776 if {$value eq $DefaultValue($opt)} {
2779 lappend childargv $opt $value
2781 set cmd [linsert $childargv 0 | $shell $file]
2784 set pipeFd [open $cmd "r"]
2785 while {[gets $pipeFd line] >= 0} {
2789 {Passed\t([0-9]+)\t}
2790 {Skipped\t([0-9]+)\t}
2792 } ""] $line null testFile \
2793 Total Passed Skipped Failed]} {
2794 foreach index {Total Passed Skipped Failed} {
2795 incr numTests($index) [set $index]
2798 lappend failFiles $testFile
2800 } elseif {[regexp [join {
2801 {^Number of tests skipped }
2802 {for each constraint:}
2804 } ""] $line match skipped constraint]} {
2805 if {[string match \t* $match]} {
2806 AddToSkippedBecause $constraint $skipped
2809 puts [outputChannel] $line
2814 puts [outputChannel] "Test file error: $msg"
2815 # append the name of the test to a list to be reported
2817 lappend testFileFailures $file
2823 puts [outputChannel] "\nTests ended at [eval $timeCmd]"
2825 if {[info exists testFileFailures]} {
2826 puts [outputChannel] "\nTest files exiting with errors: \n"
2827 foreach file $testFileFailures {
2828 puts [outputChannel] " [file tail $file]\n"
2832 # Checking for subdirectories in which to run tests
2833 foreach directory [GetMatchingDirectories [testsDirectory]] {
2834 set dir [file tail $directory]
2835 puts [outputChannel] [string repeat ~ 44]
2836 puts [outputChannel] "$dir test began at [eval $timeCmd]\n"
2838 uplevel 1 [list ::source [file join $directory all.tcl]]
2840 set endTime [eval $timeCmd]
2841 puts [outputChannel] "\n$dir test ended at $endTime"
2842 puts [outputChannel] ""
2843 puts [outputChannel] [string repeat ~ 44]
2848 #####################################################################
2850 # Test utility procs - not used in tcltest, but may be useful for
2853 # tcltest::loadTestedCommands --
2855 # Uses the specified script to load the commands to test. Allowed to
2856 # be empty, as the tested commands could have been compiled into the
2868 proc tcltest::loadTestedCommands {} {
2869 return [uplevel 1 [loadScript]]
2872 # tcltest::saveState --
2874 # Save information regarding what procs and variables exist.
2880 # Modifies the variable saveState
2885 proc tcltest::saveState {} {
2887 uplevel 1 [list ::set [namespace which -variable saveState]] \
2888 {[::list [::info procs] [::info vars]]}
2889 DebugPuts 2 "[lindex [info level 0] 0]: $saveState"
2893 # tcltest::restoreState --
2895 # Remove procs and variables that didn't exist before the call to
2902 # Removes procs and variables from your environment if they don't
2903 # exist in the saveState variable.
2908 proc tcltest::restoreState {} {
2910 foreach p [uplevel 1 {::info procs}] {
2911 if {($p ni [lindex $saveState 0]) && ("[namespace current]::$p" ne
2912 [uplevel 1 [list ::namespace origin $p]])} {
2914 DebugPuts 2 "[lindex [info level 0] 0]: Removing proc $p"
2915 uplevel 1 [list ::catch [list ::rename $p {}]]
2918 foreach p [uplevel 1 {::info vars}] {
2919 if {$p ni [lindex $saveState 1]} {
2920 DebugPuts 2 "[lindex [info level 0] 0]:\
2921 Removing variable $p"
2922 uplevel 1 [list ::catch [list ::unset $p]]
2928 # tcltest::normalizeMsg --
2930 # Removes "extra" newlines from a string.
2933 # msg String to be modified
2936 # string with extra newlines removed
2941 proc tcltest::normalizeMsg {msg} {
2942 regsub "\n$" [string tolower $msg] "" msg
2943 set msg [string map [list "\n\n" "\n"] $msg]
2944 return [string map [list "\n\}" "\}"] $msg]
2947 # tcltest::makeFile --
2949 # Create a new file with the name <name>, and write <contents> to it.
2951 # If this file hasn't been created via makeFile since the last time
2952 # cleanupTests was called, add it to the $filesMade list, so it will be
2953 # removed by the next call to cleanupTests.
2956 # contents content of the new file
2957 # name name of the new file
2958 # directory directory name for new file
2961 # absolute path to the file created
2966 proc tcltest::makeFile {contents name {directory ""}} {
2970 if {[llength [info level 0]] == 3} {
2971 set directory [temporaryDirectory]
2974 set fullName [file join $directory $name]
2976 DebugPuts 3 "[lindex [info level 0] 0]:\
2977 putting ``$contents'' into $fullName"
2979 set fd [open $fullName w]
2980 chan configure $fd -translation lf
2981 if {[string index $contents end] eq "\n"} {
2982 puts -nonewline $fd $contents
2988 if {$fullName ni $filesMade} {
2989 lappend filesMade $fullName
2994 # tcltest::removeFile --
2996 # Removes the named file from the filesystem
2999 # name file to be removed
3000 # directory directory from which to remove file
3003 # return value from [file delete]
3008 proc tcltest::removeFile {name {directory ""}} {
3011 if {[llength [info level 0]] == 2} {
3012 set directory [temporaryDirectory]
3014 set fullName [file join $directory $name]
3015 DebugPuts 3 "[lindex [info level 0] 0]: removing $fullName"
3016 set idx [lsearch -exact $filesMade $fullName]
3017 set filesMade [lreplace $filesMade $idx $idx]
3020 Warn "removeFile removing \"$fullName\":\n not created by makeFile"
3023 if {![file isfile $fullName]} {
3025 Warn "removeFile removing \"$fullName\":\n not a file"
3028 return [file delete -- $fullName]
3031 # tcltest::makeDirectory --
3033 # Create a new dir with the name <name>.
3035 # If this dir hasn't been created via makeDirectory since the last time
3036 # cleanupTests was called, add it to the $directoriesMade list, so it
3037 # will be removed by the next call to cleanupTests.
3040 # name name of the new directory
3041 # directory directory in which to create new dir
3044 # absolute path to the directory created
3049 proc tcltest::makeDirectory {name {directory ""}} {
3052 if {[llength [info level 0]] == 2} {
3053 set directory [temporaryDirectory]
3055 set fullName [file join $directory $name]
3056 DebugPuts 3 "[lindex [info level 0] 0]: creating $fullName"
3057 file mkdir $fullName
3058 if {$fullName ni $filesMade} {
3059 lappend filesMade $fullName
3064 # tcltest::removeDirectory --
3066 # Removes a named directory from the file system.
3069 # name Name of the directory to remove
3070 # directory Directory from which to remove
3073 # return value from [file delete]
3078 proc tcltest::removeDirectory {name {directory ""}} {
3081 if {[llength [info level 0]] == 2} {
3082 set directory [temporaryDirectory]
3084 set fullName [file join $directory $name]
3085 DebugPuts 3 "[lindex [info level 0] 0]: deleting $fullName"
3086 set idx [lsearch -exact $filesMade $fullName]
3087 set filesMade [lreplace $filesMade $idx $idx]
3090 Warn "removeDirectory removing \"$fullName\":\n not created\
3094 if {![file isdirectory $fullName]} {
3096 Warn "removeDirectory removing \"$fullName\":\n not a directory"
3099 return [file delete -force -- $fullName]
3102 # tcltest::viewFile --
3104 # reads the content of a file and returns it
3107 # name of the file to read
3108 # directory in which file is located
3111 # content of the named file
3116 proc tcltest::viewFile {name {directory ""}} {
3118 if {[llength [info level 0]] == 2} {
3119 set directory [temporaryDirectory]
3121 set fullName [file join $directory $name]
3122 set f [open $fullName]
3123 set data [read -nonewline $f]
3128 # tcltest::bytestring --
3130 # Construct a string that consists of the requested sequence of bytes,
3131 # as opposed to a string of properly formed UTF-8 characters.
3132 # This allows the tester to
3133 # 1. Create denormalized or improperly formed strings to pass to C
3134 # procedures that are supposed to accept strings with embedded NULL
3136 # 2. Confirm that a string result has a certain pattern of bytes, for
3137 # instance to confirm that "\xe0\0" in a Tcl script is stored
3138 # internally in UTF-8 as the sequence of bytes "\xc3\xa0\xc0\x80".
3140 # Generally, it's a bad idea to examine the bytes in a Tcl string or to
3141 # construct improperly formed strings in this manner, because it involves
3142 # exposing that Tcl uses UTF-8 internally.
3145 # string being converted
3148 # result fom encoding
3153 proc tcltest::bytestring {string} {
3154 return [encoding convertfrom identity $string]
3157 # tcltest::OpenFiles --
3159 # used in io tests, uses testchannel
3170 proc tcltest::OpenFiles {} {
3171 if {[catch {testchannel open} result]} {
3177 # tcltest::LeakFiles --
3179 # used in io tests, uses testchannel
3190 proc tcltest::LeakFiles {old} {
3191 if {[catch {testchannel open} new]} {
3204 # Internationalization / ISO support procs -- dl
3207 # tcltest::SetIso8859_1_Locale --
3209 # used in cmdIL.test, uses testlocale
3220 proc tcltest::SetIso8859_1_Locale {} {
3221 variable previousLocale
3223 if {[info commands testlocale] != ""} {
3224 set previousLocale [testlocale ctype]
3225 testlocale ctype $isoLocale
3230 # tcltest::RestoreLocale --
3232 # used in cmdIL.test, uses testlocale
3243 proc tcltest::RestoreLocale {} {
3244 variable previousLocale
3245 if {[info commands testlocale] != ""} {
3246 testlocale ctype $previousLocale
3251 # tcltest::threadReap --
3253 # Kill all threads except for the main thread.
3254 # Do nothing if testthread is not defined.
3260 # Returns the number of existing threads.
3266 proc tcltest::threadReap {} {
3267 if {[info commands testthread] ne {}} {
3269 # testthread built into tcltest
3271 testthread errorproc ThreadNullError
3272 while {[llength [testthread names]] > 1} {
3273 foreach tid [testthread names] {
3274 if {$tid != [mainThread]} {
3276 testthread send -async $tid {testthread exit}
3280 ## Enter a bit a sleep to give the threads enough breathing
3281 ## room to kill themselves off, otherwise the end up with a
3282 ## massive queue of repeated events
3285 testthread errorproc ThreadError
3286 return [llength [testthread names]]
3287 } elseif {[info commands thread::id] ne {}} {
3291 thread::errorproc ThreadNullError
3292 while {[llength [thread::names]] > 1} {
3293 foreach tid [thread::names] {
3294 if {$tid != [mainThread]} {
3295 catch {thread::send -async $tid {thread::exit}}
3298 ## Enter a bit a sleep to give the threads enough breathing
3299 ## room to kill themselves off, otherwise the end up with a
3300 ## massive queue of repeated events
3303 thread::errorproc ThreadError
3304 return [llength [thread::names]]
3311 # Initialize the constraints and set up command line arguments
3312 namespace eval tcltest {
3313 # Define initializers for all the built-in contraint definitions
3314 DefineConstraintInitializers
3316 # Set up the constraints in the testConstraints array to be lazily
3317 # initialized by a registered initializer, or by "false" if no
3318 # initializer is registered.
3319 trace add variable testConstraints read [namespace code SafeFetch]
3321 # Only initialize constraints at package load time if an
3322 # [initConstraintsHook] has been pre-defined. This is only
3323 # for compatibility support. The modern way to add a custom
3324 # test constraint is to just call the [testConstraint] command
3325 # straight away, without all this "hook" nonsense.
3326 if {[namespace current] eq
3327 [namespace qualifiers [namespace which initConstraintsHook]]} {
3330 proc initConstraintsHook {} {}
3333 # Define the standard match commands
3334 customMatch exact [list string equal]
3335 customMatch glob [list string match]
3336 customMatch regexp [list regexp --]
3338 # If the TCLTEST_OPTIONS environment variable exists, configure
3339 # tcltest according to the option values it specifies. This has
3340 # the effect of resetting tcltest's default configuration.
3341 proc ConfigureFromEnvironment {} {
3342 upvar #0 env(TCLTEST_OPTIONS) options
3343 if {[catch {llength $options} msg]} {
3344 Warn "invalid TCLTEST_OPTIONS \"$options\":\n invalid\
3348 if {[llength $options] % 2} {
3349 Warn "invalid TCLTEST_OPTIONS: \"$options\":\n should be\
3350 -option value ?-option value ...?"
3353 if {[catch {Configure {*}$options} msg]} {
3354 Warn "invalid TCLTEST_OPTIONS: \"$options\":\n $msg"
3358 if {[info exists ::env(TCLTEST_OPTIONS)]} {
3359 ConfigureFromEnvironment
3362 proc LoadTimeCmdLineArgParsingRequired {} {
3364 if {[info exists ::argv] && ("-help" in $::argv)} {
3365 # The command line asks for -help, so give it (and exit)
3366 # right now. ([configure] does not process -help)
3369 foreach hook { PrintUsageInfoHook processCmdLineArgsHook
3370 processCmdLineArgsAddFlagsHook } {
3371 if {[namespace current] eq
3372 [namespace qualifiers [namespace which $hook]]} {
3381 # Only initialize configurable options from the command line arguments
3382 # at package load time if necessary for backward compatibility. This
3383 # lets the tcltest user call [configure] for themselves if they wish.
3384 # Traces are established for auto-configuration from the command line
3385 # if any configurable options are accessed before the user calls
3387 if {[LoadTimeCmdLineArgParsingRequired]} {
3390 EstablishAutoConfigureTraces
3393 package provide [namespace tail [namespace current]] $Version