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, etc. used
6 # by Tcl tests. See the tcltest man page for more details.
8 # This design was based on the Tcl testing approach designed and
9 # initially implemented by Mary Ann May-Pumphrey of Sun Microsystems.
11 # Copyright (c) 1994-1997 Sun Microsystems, Inc.
12 # Copyright (c) 1998-1999 by Scriptics Corporation.
13 # All rights reserved.
17 package provide tcltest 1.0
19 # create the "tcltest" namespace for all testing variables and procedures
21 namespace eval tcltest {
23 # Export the public tcltest procs
24 set procList [list test cleanupTests saveState restoreState \
25 normalizeMsg makeFile removeFile makeDirectory removeDirectory \
26 viewFile bytestring safeFetch threadReap getMatchingFiles \
27 loadTestedCommands normalizePath]
28 foreach proc $procList {
29 namespace export $proc
32 # ::tcltest::verbose defaults to "b"
33 if {![info exists verbose]} {
37 # Match and skip patterns default to the empty list, except for
38 # matchFiles, which defaults to all .test files in the testsDirectory
40 if {![info exists match]} {
43 if {![info exists skip]} {
46 if {![info exists matchFiles]} {
47 variable matchFiles {*.test}
49 if {![info exists skipFiles]} {
53 # By default, don't save core files
54 if {![info exists preserveCore]} {
55 variable preserveCore 0
58 # output goes to stdout by default
59 if {![info exists outputChannel]} {
60 variable outputChannel stdout
63 # errors go to stderr by default
64 if {![info exists errorChannel]} {
65 variable errorChannel stderr
68 # debug output doesn't get printed by default; debug level 1 spits
69 # up only the tests that were skipped because they didn't match or were
70 # specifically skipped. A debug level of 2 would spit up the tcltest
71 # variables and flags provided; a debug level of 3 causes some additional
72 # output regarding operations of the test harness. The tcltest package
73 # currently implements only up to debug level 3.
74 if {![info exists debug]} {
78 # Save any arguments that we might want to pass through to other programs.
79 # This is used by the -args flag.
80 if {![info exists parameters]} {
81 variable parameters {}
84 # Count the number of files tested (0 if all.tcl wasn't called).
85 # The all.tcl file will set testSingleFile to false, so stats will
86 # not be printed until all.tcl calls the cleanupTests proc.
87 # The currentFailure var stores the boolean value of whether the
88 # current test file has had any failures. The failFiles list
89 # stores the names of test files that had failures.
91 if {![info exists numTestFiles]} {
92 variable numTestFiles 0
94 if {![info exists testSingleFile]} {
95 variable testSingleFile true
97 if {![info exists currentFailure]} {
98 variable currentFailure false
100 if {![info exists failFiles]} {
101 variable failFiles {}
104 # Tests should remove all files they create. The test suite will
105 # check the current working dir for files created by the tests.
106 # ::tcltest::filesMade keeps track of such files created using the
107 # ::tcltest::makeFile and ::tcltest::makeDirectory procedures.
108 # ::tcltest::filesExisted stores the names of pre-existing files.
110 if {![info exists filesMade]} {
111 variable filesMade {}
113 if {![info exists filesExisted]} {
114 variable filesExisted {}
117 # ::tcltest::numTests will store test files as indices and the list
118 # of files (that should not have been) left behind by the test files.
120 if {![info exists createdNewFiles]} {
121 variable createdNewFiles
122 array set ::tcltest::createdNewFiles {}
125 # initialize ::tcltest::numTests array to keep track fo the number of
126 # tests that pass, fail, and are skipped.
128 if {![info exists numTests]} {
130 array set ::tcltest::numTests \
131 [list Total 0 Passed 0 Skipped 0 Failed 0]
134 # initialize ::tcltest::skippedBecause array to keep track of
135 # constraints that kept tests from running; a constraint name of
136 # "userSpecifiedSkip" means that the test appeared on the list of tests
137 # that matched the -skip value given to the flag; "userSpecifiedNonMatch"
138 # means that the test didn't match the argument given to the -match flag;
139 # both of these constraints are counted only if ::tcltest::debug is set to
142 if {![info exists skippedBecause]} {
143 variable skippedBecause
144 array set ::tcltest::skippedBecause {}
147 # initialize the ::tcltest::testConstraints array to keep track of valid
148 # predefined constraints (see the explanation for the
149 # ::tcltest::initConstraints proc for more details).
151 if {![info exists testConstraints]} {
152 variable testConstraints
153 array set ::tcltest::testConstraints {}
156 # Don't run only the constrained tests by default
158 if {![info exists limitConstraints]} {
159 variable limitConstraints false
162 # A test application has to know how to load the tested commands into
165 if {![info exists loadScript]} {
166 variable loadScript {}
169 # tests that use threads need to know which is the main thread
171 if {![info exists mainThread]} {
172 variable mainThread 1
173 if {[info commands thread::id] != {}} {
174 set mainThread [thread::id]
175 } elseif {[info commands testthread] != {}} {
176 set mainThread [testthread id]
180 # save the original environment so that it can be restored later
182 if {![info exists originalEnv]} {
184 array set ::tcltest::originalEnv [array get ::env]
187 # Set ::tcltest::workingDirectory to [pwd]. The default output directory
188 # for Tcl tests is the working directory.
190 if {![info exists workingDirectory]} {
191 variable workingDirectory [pwd]
193 if {![info exists temporaryDirectory]} {
194 variable temporaryDirectory $workingDirectory
197 # Tests should not rely on the current working directory.
198 # Files that are part of the test suite should be accessed relative to
199 # ::tcltest::testsDirectory.
201 if {![info exists testsDirectory]} {
203 catch {cd [file join [file dirname [info script]] .. .. tests]}
204 variable testsDirectory [pwd]
209 # the variables and procs that existed when ::tcltest::saveState was
210 # called are stored in a variable of the same name
211 if {![info exists saveState]} {
212 variable saveState {}
215 # Internationalization support
216 if {![info exists isoLocale]} {
217 variable isoLocale fr
218 switch $tcl_platform(platform) {
221 # Try some 'known' values for some platforms:
223 switch -exact -- $tcl_platform(os) {
225 set ::tcltest::isoLocale fr_FR.ISO_8859-1
228 set ::tcltest::isoLocale fr_FR.iso88591
232 set ::tcltest::isoLocale fr
236 # Works on SunOS 4 and Solaris, and maybe others...
237 # define it to something else on your system
238 #if you want to test those.
240 set ::tcltest::isoLocale iso_8859_1
245 set ::tcltest::isoLocale French
250 # Set the location of the execuatble
251 if {![info exists tcltest]} {
252 variable tcltest [info nameofexecutable]
255 # save the platform information so it can be restored later
256 if {![info exists originalTclPlatform]} {
257 variable originalTclPlatform [array get tcl_platform]
260 # If a core file exists, save its modification time.
261 if {![info exists coreModificationTime]} {
262 if {[file exists [file join $::tcltest::workingDirectory core]]} {
263 variable coreModificationTime [file mtime [file join \
264 $::tcltest::workingDirectory core]]
268 # Tcl version numbers
269 if {![info exists version]} {
272 if {![info exists patchLevel]} {
273 variable patchLevel 8.3.0
277 # ::tcltest::Debug* --
279 # Internal helper procedures to write out debug information
280 # dependent on the chosen level. A test shell may overide
281 # them, f.e. to redirect the output into a different
282 # channel, or even into a GUI.
284 # ::tcltest::DebugPuts --
286 # Prints the specified string if the current debug level is
287 # higher than the provided level argument.
290 # level The lowest debug level triggering the output
291 # string The string to print out.
294 # Prints the string. Nothing else is allowed.
297 proc ::tcltest::DebugPuts {level string} {
299 if {$debug >= $level} {
304 # ::tcltest::DebugPArray --
306 # Prints the contents of the specified array if the current
307 # debug level is higher than the provided level argument
310 # level The lowest debug level triggering the output
311 # arrayvar The name of the array to print out.
314 # Prints the contents of the array. Nothing else is allowed.
317 proc ::tcltest::DebugPArray {level arrayvar} {
320 if {$debug >= $level} {
321 catch {upvar $arrayvar $arrayvar}
326 # ::tcltest::DebugDo --
328 # Executes the script if the current debug level is greater than
329 # the provided level argument
332 # level The lowest debug level triggering the execution.
333 # script The tcl script executed upon a debug level high enough.
336 # Arbitrary side effects, dependent on the executed script.
339 proc ::tcltest::DebugDo {level script} {
342 if {$debug >= $level} {
347 # ::tcltest::AddToSkippedBecause --
349 # Increments the variable used to track how many tests were skipped
350 # because of a particular constraint.
353 # constraint The name of the constraint to be modified
356 # Modifies ::tcltest::skippedBecause; sets the variable to 1 if didn't
357 # previously exist - otherwise, it just increments it.
359 proc ::tcltest::AddToSkippedBecause { constraint } {
360 # add the constraint to the list of constraints that kept tests
363 if {[info exists ::tcltest::skippedBecause($constraint)]} {
364 incr ::tcltest::skippedBecause($constraint)
366 set ::tcltest::skippedBecause($constraint) 1
371 # ::tcltest::PrintError --
373 # Prints errors to ::tcltest::errorChannel and then flushes that
374 # channel, making sure that all messages are < 80 characters per line.
377 # errorMsg String containing the error to be printed
380 proc ::tcltest::PrintError {errorMsg} {
381 set InitialMessage "Error: "
382 set InitialMsgLen [string length $InitialMessage]
383 puts -nonewline $::tcltest::errorChannel $InitialMessage
385 # Keep track of where the end of the string is.
386 set endingIndex [string length $errorMsg]
388 if {$endingIndex < 80} {
389 puts $::tcltest::errorChannel $errorMsg
391 # Print up to 80 characters on the first line, including the
393 set beginningIndex [string last " " [string range $errorMsg 0 \
394 [expr {80 - $InitialMsgLen}]]]
395 puts $::tcltest::errorChannel [string range $errorMsg 0 $beginningIndex]
397 while {$beginningIndex != "end"} {
398 puts -nonewline $::tcltest::errorChannel \
399 [string repeat " " $InitialMsgLen]
400 if {[expr {$endingIndex - $beginningIndex}] < 72} {
401 puts $::tcltest::errorChannel [string trim \
402 [string range $errorMsg $beginningIndex end]]
403 set beginningIndex end
405 set newEndingIndex [expr [string last " " [string range \
406 $errorMsg $beginningIndex \
407 [expr {$beginningIndex + 72}]]] + $beginningIndex]
408 if {($newEndingIndex <= 0) \
409 || ($newEndingIndex <= $beginningIndex)} {
410 set newEndingIndex end
412 puts $::tcltest::errorChannel [string trim \
413 [string range $errorMsg \
414 $beginningIndex $newEndingIndex]]
415 set beginningIndex $newEndingIndex
419 flush $::tcltest::errorChannel
423 if {[namespace inscope ::tcltest info procs initConstraintsHook] == {}} {
424 proc ::tcltest::initConstraintsHook {} {}
427 # ::tcltest::initConstraints --
429 # Check Constraintsuration information that will determine which tests
430 # to run. To do this, create an array ::tcltest::testConstraints. Each
431 # element has a 0 or 1 value. If the element is "true" then tests
432 # with that constraint will be run, otherwise tests with that constraint
433 # will be skipped. See the tcltest man page for the list of built-in
434 # constraints defined in this procedure.
440 # The ::tcltest::testConstraints array is reset to have an index for
441 # each built-in test constraint.
443 proc ::tcltest::initConstraints {} {
444 global tcl_platform tcl_interactive tk_version
446 # The following trace procedure makes it so that we can safely refer to
447 # non-existent members of the ::tcltest::testConstraints array without
448 # causing an error. Instead, reading a non-existent member will return 0.
449 # This is necessary because tests are allowed to use constraint "X" without
450 # ensuring that ::tcltest::testConstraints("X") is defined.
452 trace variable ::tcltest::testConstraints r ::tcltest::safeFetch
454 proc ::tcltest::safeFetch {n1 n2 op} {
455 if {($n2 != {}) && ([info exists ::tcltest::testConstraints($n2)] == 0)} {
456 set ::tcltest::testConstraints($n2) 0
460 ::tcltest::initConstraintsHook
462 set ::tcltest::testConstraints(unixOnly) \
463 [string equal $tcl_platform(platform) "unix"]
464 set ::tcltest::testConstraints(macOnly) \
465 [string equal $tcl_platform(platform) "macintosh"]
466 set ::tcltest::testConstraints(pcOnly) \
467 [string equal $tcl_platform(platform) "windows"]
469 set ::tcltest::testConstraints(unix) $::tcltest::testConstraints(unixOnly)
470 set ::tcltest::testConstraints(mac) $::tcltest::testConstraints(macOnly)
471 set ::tcltest::testConstraints(pc) $::tcltest::testConstraints(pcOnly)
473 set ::tcltest::testConstraints(unixOrPc) \
474 [expr {$::tcltest::testConstraints(unix) \
475 || $::tcltest::testConstraints(pc)}]
476 set ::tcltest::testConstraints(macOrPc) \
477 [expr {$::tcltest::testConstraints(mac) \
478 || $::tcltest::testConstraints(pc)}]
479 set ::tcltest::testConstraints(macOrUnix) \
480 [expr {$::tcltest::testConstraints(mac) \
481 || $::tcltest::testConstraints(unix)}]
483 set ::tcltest::testConstraints(nt) [string equal $tcl_platform(os) \
485 set ::tcltest::testConstraints(95) [string equal $tcl_platform(os) \
487 set ::tcltest::testConstraints(98) [string equal $tcl_platform(os) \
490 # The following Constraints switches are used to mark tests that should
491 # work, but have been temporarily disabled on certain platforms because
492 # they don't and we haven't gotten around to fixing the underlying
495 set ::tcltest::testConstraints(tempNotPc) \
496 [expr {!$::tcltest::testConstraints(pc)}]
497 set ::tcltest::testConstraints(tempNotMac) \
498 [expr {!$::tcltest::testConstraints(mac)}]
499 set ::tcltest::testConstraints(tempNotUnix) \
500 [expr {!$::tcltest::testConstraints(unix)}]
502 # The following Constraints switches are used to mark tests that crash on
503 # certain platforms, so that they can be reactivated again when the
504 # underlying problem is fixed.
506 set ::tcltest::testConstraints(pcCrash) \
507 [expr {!$::tcltest::testConstraints(pc)}]
508 set ::tcltest::testConstraints(macCrash) \
509 [expr {!$::tcltest::testConstraints(mac)}]
510 set ::tcltest::testConstraints(unixCrash) \
511 [expr {!$::tcltest::testConstraints(unix)}]
515 set ::tcltest::testConstraints(emptyTest) 0
517 # By default, tests that expose known bugs are skipped.
519 set ::tcltest::testConstraints(knownBug) 0
521 # By default, non-portable tests are skipped.
523 set ::tcltest::testConstraints(nonPortable) 0
525 # Some tests require user interaction.
527 set ::tcltest::testConstraints(userInteraction) 0
529 # Some tests must be skipped if the interpreter is not in interactive mode
531 if {[info exists tcl_interactive]} {
532 set ::tcltest::testConstraints(interactive) $::tcl_interactive
534 set ::tcltest::testConstraints(interactive) 0
537 # Some tests can only be run if the installation came from a CD image
538 # instead of a web image
539 # Some tests must be skipped if you are running as root on Unix.
540 # Other tests can only be run if you are running as root on Unix.
542 set ::tcltest::testConstraints(root) 0
543 set ::tcltest::testConstraints(notRoot) 1
545 if {[string equal $tcl_platform(platform) "unix"]} {
546 catch {set user [exec whoami]}
547 if {[string equal $user ""]} {
548 catch {regexp {^[^(]*\(([^)]*)\)} [exec id] dummy user}
550 if {([string equal $user "root"]) || ([string equal $user ""])} {
551 set ::tcltest::testConstraints(root) 1
552 set ::tcltest::testConstraints(notRoot) 0
556 # Set nonBlockFiles constraint: 1 means this platform supports
557 # setting files into nonblocking mode.
559 if {[catch {set f [open defs r]}]} {
560 set ::tcltest::testConstraints(nonBlockFiles) 1
562 if {[catch {fconfigure $f -blocking off}] == 0} {
563 set ::tcltest::testConstraints(nonBlockFiles) 1
565 set ::tcltest::testConstraints(nonBlockFiles) 0
570 # Set asyncPipeClose constraint: 1 means this platform supports
571 # async flush and async close on a pipe.
573 # Test for SCO Unix - cannot run async flushing tests because a
574 # potential problem with select is apparently interfering.
577 if {[string equal $tcl_platform(platform) "unix"]} {
578 if {[catch {exec uname -X | fgrep {Release = 3.2v}}] == 0} {
579 set ::tcltest::testConstraints(asyncPipeClose) 0
581 set ::tcltest::testConstraints(asyncPipeClose) 1
584 set ::tcltest::testConstraints(asyncPipeClose) 1
587 # Test to see if we have a broken version of sprintf with respect
588 # to the "e" format of floating-point numbers.
590 set ::tcltest::testConstraints(eformat) 1
591 if {![string equal "[format %g 5e-5]" "5e-05"]} {
592 set ::tcltest::testConstraints(eformat) 0
595 # Test to see if execed commands such as cat, echo, rm and so forth are
596 # present on this machine.
598 set ::tcltest::testConstraints(unixExecs) 1
599 if {[string equal $tcl_platform(platform) "macintosh"]} {
600 set ::tcltest::testConstraints(unixExecs) 0
602 if {($::tcltest::testConstraints(unixExecs) == 1) && \
603 ([string equal $tcl_platform(platform) "windows"])} {
604 if {[catch {exec cat defs}] == 1} {
605 set ::tcltest::testConstraints(unixExecs) 0
607 if {($::tcltest::testConstraints(unixExecs) == 1) && \
608 ([catch {exec echo hello}] == 1)} {
609 set ::tcltest::testConstraints(unixExecs) 0
611 if {($::tcltest::testConstraints(unixExecs) == 1) && \
612 ([catch {exec sh -c echo hello}] == 1)} {
613 set ::tcltest::testConstraints(unixExecs) 0
615 if {($::tcltest::testConstraints(unixExecs) == 1) && \
616 ([catch {exec wc defs}] == 1)} {
617 set ::tcltest::testConstraints(unixExecs) 0
619 if {$::tcltest::testConstraints(unixExecs) == 1} {
620 exec echo hello > removeMe
621 if {[catch {exec rm removeMe}] == 1} {
622 set ::tcltest::testConstraints(unixExecs) 0
625 if {($::tcltest::testConstraints(unixExecs) == 1) && \
626 ([catch {exec sleep 1}] == 1)} {
627 set ::tcltest::testConstraints(unixExecs) 0
629 if {($::tcltest::testConstraints(unixExecs) == 1) && \
630 ([catch {exec fgrep unixExecs defs}] == 1)} {
631 set ::tcltest::testConstraints(unixExecs) 0
633 if {($::tcltest::testConstraints(unixExecs) == 1) && \
634 ([catch {exec ps}] == 1)} {
635 set ::tcltest::testConstraints(unixExecs) 0
637 if {($::tcltest::testConstraints(unixExecs) == 1) && \
638 ([catch {exec echo abc > removeMe}] == 0) && \
639 ([catch {exec chmod 644 removeMe}] == 1) && \
640 ([catch {exec rm removeMe}] == 0)} {
641 set ::tcltest::testConstraints(unixExecs) 0
643 catch {exec rm -f removeMe}
645 if {($::tcltest::testConstraints(unixExecs) == 1) && \
646 ([catch {exec mkdir removeMe}] == 1)} {
647 set ::tcltest::testConstraints(unixExecs) 0
649 catch {exec rm -r removeMe}
653 # Locate tcltest executable
655 if {![info exists tk_version]} {
656 set tcltest [info nameofexecutable]
658 if {$tcltest == "{}"} {
663 set ::tcltest::testConstraints(stdio) 0
665 catch {file delete -force tmp}
672 set f [open "|[list $tcltest tmp]" r]
675 set ::tcltest::testConstraints(stdio) 1
677 catch {file delete -force tmp}
679 # Deliberately call socket with the wrong number of arguments. The error
680 # message you get will indicate whether sockets are available on this
684 set ::tcltest::testConstraints(socket) \
685 [expr {$msg != "sockets are not available on this system"}]
687 # Check for internationalization
689 if {[info commands testlocale] == ""} {
690 # No testlocale command, no tests...
691 set ::tcltest::testConstraints(hasIsoLocale) 0
693 set ::tcltest::testConstraints(hasIsoLocale) \
694 [string length [::tcltest::set_iso8859_1_locale]]
695 ::tcltest::restore_locale
699 # ::tcltest::PrintUsageInfoHook
701 # Hook used for customization of display of usage information.
704 if {[namespace inscope ::tcltest info procs PrintUsageInfoHook] == {}} {
705 proc ::tcltest::PrintUsageInfoHook {} {}
708 # ::tcltest::PrintUsageInfo
710 # Prints out the usage information for package tcltest. This can be
711 # customized with the redefinition of ::tcltest::PrintUsageInfoHook.
717 proc ::tcltest::PrintUsageInfo {} {
718 puts [format "Usage: [file tail [info nameofexecutable]] \
719 script ?-help? ?flag value? ... \n\
720 Available flags (and valid input values) are: \n\
721 -help \t Display this usage information. \n\
722 -verbose level \t Takes any combination of the values \n\
723 \t 'p', 's' and 'b'. Test suite will \n\
724 \t display all passed tests if 'p' is \n\
725 \t specified, all skipped tests if 's' \n\
726 \t is specified, and the bodies of \n\
727 \t failed tests if 'b' is specified. \n\
728 \t The default value is 'b'. \n\
729 -constraints list\t Do not skip the listed constraints\n\
730 -limitconstraints bool\t Only run tests with the constraints\n\
731 \t listed in -constraints.\n\
732 -match pattern \t Run all tests within the specified \n\
733 \t files that match the glob pattern \n\
735 -skip pattern \t Skip all tests within the set of \n\
736 \t specified tests (via -match) and \n\
737 \t files that match the glob pattern \n\
739 -file pattern \t Run tests in all test files that \n\
740 \t match the glob pattern given. \n\
741 -notfile pattern\t Skip all test files that match the \n\
742 \t glob pattern given. \n\
743 -preservecore level \t If 2, save any core files produced \n\
744 \t during testing in the directory \n\
745 \t specified by -tmpdir. If 1, notify the\n\
746 \t user if core files are created. The default \n\
747 \t is $::tcltest::preserveCore. \n\
748 -tmpdir directory\t Save temporary files in the specified\n\
749 \t directory. The default value is \n\
750 \t $::tcltest::temporaryDirectory. \n\
751 -testdir directories\t Search tests in the specified\n\
752 \t directories. The default value is \n\
753 \t $::tcltest::testsDirectory. \n\
754 -outfile file \t Send output from test runs to the \n\
755 \t specified file. The default is \n\
757 -errfile file \t Send errors from test runs to the \n\
758 \t specified file. The default is \n\
760 -loadfile file \t Read the script to load the tested \n\
761 \t commands from the specified file. \n\
762 -load script \t Specifies the script to load the tested \n\
764 -debug level \t Internal debug flag."]
765 ::tcltest::PrintUsageInfoHook
769 # ::tcltest::CheckDirectory --
771 # This procedure checks whether the specified path is a readable
772 # and/or writable directory. If one of the conditions is not
773 # satisfied an error is printed and the application aborted. The
774 # procedure assumes that the caller already checked the existence
778 # rw Information what attributes to check. Allowed values:
779 # r, w, rw, wr. If 'r' is part of the value the directory
780 # must be readable. 'w' associates to 'writable'.
781 # dir The directory to check.
782 # errMsg The string to prepend to the actual error message before
789 proc ::tcltest::CheckDirectory {rw dir errMsg} {
790 # Allowed values for 'rw': r, w, rw, wr
792 if {![file isdir $dir]} {
793 ::tcltest::PrintError "$errMsg \"$dir\" is not a directory"
795 } elseif {([string first w $rw] >= 0) && ![file writable $dir]} {
796 ::tcltest::PrintError "$errMsg \"$dir\" is not writeable"
798 } elseif {([string first r $rw] >= 0) && ![file readable $dir]} {
799 ::tcltest::PrintError "$errMsg \"$dir\" is not readable"
804 # ::tcltest::normalizePath --
806 # This procedure resolves any symlinks in the path thus creating a
807 # path without internal redirection. It assumes that the incoming
811 # pathVar contains the name of the variable containing the path to modify.
814 # The path is modified in place.
817 proc ::tcltest::normalizePath {pathVar} {
826 # ::tcltest::MakeAbsolutePath --
828 # This procedure checks whether the incoming path is absolute or not.
829 # Makes it absolute if it was not.
832 # pathVar contains the name of the variable containing the path to modify.
833 # prefix is optional, contains the path to use to make the other an
834 # absolute one. The current working directory is used if it was
838 # The path is modified in place.
841 proc ::tcltest::MakeAbsolutePath {pathVar {prefix {}}} {
844 if {![string equal [file pathtype $path] "absolute"]} {
849 set path [file join $prefix $path]
853 # ::tcltest::processCmdLineArgsFlagsHook --
855 # This hook is used to add to the list of command line arguments that are
856 # processed by ::tcltest::processCmdLineArgs.
859 if {[namespace inscope ::tcltest info procs processCmdLineArgsAddFlagsHook] == {}} {
860 proc ::tcltest::processCmdLineArgsAddFlagsHook {} {}
863 # ::tcltest::processCmdLineArgsHook --
865 # This hook is used to actually process the flags added by
866 # ::tcltest::processCmdLineArgsAddFlagsHook.
869 # flags The flags that have been pulled out of argv
872 if {[namespace inscope ::tcltest info procs processCmdLineArgsHook] == {}} {
873 proc ::tcltest::processCmdLineArgsHook {flag} {}
876 # ::tcltest::processCmdLineArgs --
878 # Use command line args to set the verbose, skip, and
879 # match, outputChannel, errorChannel, debug, and temporaryDirectory
882 # This procedure must be run after constraints are initialized, because
883 # some constraints can be overridden.
889 # Sets the above-named variables in the tcltest namespace.
891 proc ::tcltest::processCmdLineArgs {} {
894 # The "argv" var doesn't exist in some cases, so use {}.
896 if {(![info exists argv]) || ([llength $argv] < 1)} {
902 # Allow for 1-char abbreviations, where applicable (e.g., -match == -m).
903 # Note that -verbose cannot be abbreviated to -v in wish because it
904 # conflicts with the wish option -visual.
906 # Process -help first
907 if {([lsearch -exact $flagArray {-help}] != -1) || \
908 ([lsearch -exact $flagArray {-h}] != -1)} {
909 ::tcltest::PrintUsageInfo
913 if {[catch {array set flag $flagArray}]} {
914 ::tcltest::PrintError "odd number of arguments specified on command line: \
916 ::tcltest::PrintUsageInfo
920 # -help is not listed since it has already been processed
921 lappend defaultFlags -verbose -match -skip -constraints \
922 -outfile -errfile -debug -tmpdir -file -notfile \
923 -preservecore -limitconstraints -args -testdir \
925 set defaultFlags [concat $defaultFlags \
926 [ ::tcltest::processCmdLineArgsAddFlagsHook ]]
928 foreach arg $defaultFlags {
929 set abbrev [string range $arg 0 1]
930 if {([info exists flag($abbrev)]) && \
931 ([lsearch -exact $flagArray $arg] < [lsearch -exact \
932 $flagArray $abbrev])} {
933 set flag($arg) $flag($abbrev)
937 # Set ::tcltest::parameters to the arg of the -args flag, if given
938 if {[info exists flag(-args)]} {
939 set ::tcltest::parameters $flag(-args)
942 # Set ::tcltest::verbose to the arg of the -verbose flag, if given
944 if {[info exists flag(-verbose)]} {
945 set ::tcltest::verbose $flag(-verbose)
948 # Set ::tcltest::match to the arg of the -match flag, if given.
950 if {[info exists flag(-match)]} {
951 set ::tcltest::match $flag(-match)
954 # Set ::tcltest::skip to the arg of the -skip flag, if given
956 if {[info exists flag(-skip)]} {
957 set ::tcltest::skip $flag(-skip)
960 # Handle the -file and -notfile flags
961 if {[info exists flag(-file)]} {
962 set ::tcltest::matchFiles $flag(-file)
964 if {[info exists flag(-notfile)]} {
965 set ::tcltest::skipFiles $flag(-notfile)
968 # Use the -constraints flag, if given, to turn on constraints that are
969 # turned off by default: userInteractive knownBug nonPortable. This
970 # code fragment must be run after constraints are initialized.
972 if {[info exists flag(-constraints)]} {
973 foreach elt $flag(-constraints) {
974 set ::tcltest::testConstraints($elt) 1
978 # Use the -limitconstraints flag, if given, to tell the harness to limit
979 # tests run to those that were specified using the -constraints flag. If
980 # the -constraints flag was not specified, print out an error and exit.
981 if {[info exists flag(-limitconstraints)]} {
982 if {![info exists flag(-constraints)]} {
983 puts "You can only use the -limitconstraints flag with \
987 set ::tcltest::limitConstraints $flag(-limitconstraints)
988 foreach elt [array names ::tcltest::testConstraints] {
989 if {[lsearch -exact $flag(-constraints) $elt] == -1} {
990 set ::tcltest::testConstraints($elt) 0
995 # Set the ::tcltest::temporaryDirectory to the arg of -tmpdir, if
998 # If the path is relative, make it absolute. If the file exists but
999 # is not a dir, then return an error.
1001 # If ::tcltest::temporaryDirectory does not already exist, create it.
1002 # If you cannot create it, then return an error.
1005 if {[info exists flag(-tmpdir)]} {
1006 set ::tcltest::temporaryDirectory $flag(-tmpdir)
1008 MakeAbsolutePath ::tcltest::temporaryDirectory
1009 set tmpDirError "bad argument \"$flag(-tmpdir)\" to -tmpdir: "
1011 if {[file exists $::tcltest::temporaryDirectory]} {
1012 ::tcltest::CheckDirectory rw $::tcltest::temporaryDirectory $tmpDirError
1014 file mkdir $::tcltest::temporaryDirectory
1017 normalizePath ::tcltest::temporaryDirectory
1019 # Set the ::tcltest::testsDirectory to the arg of -testdir, if
1022 # If the path is relative, make it absolute. If the file exists but
1023 # is not a dir, then return an error.
1025 # If ::tcltest::temporaryDirectory does not already exist return an error.
1028 if {[info exists flag(-testdir)]} {
1029 set ::tcltest::testsDirectory $flag(-testdir)
1031 MakeAbsolutePath ::tcltest::testsDirectory
1032 set testDirError "bad argument \"$flag(-testdir)\" to -testdir: "
1034 if {[file exists $::tcltest::testsDirectory]} {
1035 ::tcltest::CheckDirectory r $::tcltest::testsDirectory $testDirError
1037 ::tcltest::PrintError "$testDirError \"$::tcltest::testsDirectory\" \
1042 normalizePath ::tcltest::testsDirectory
1044 # Save the names of files that already exist in
1045 # the output directory.
1046 foreach file [glob -nocomplain \
1047 [file join $::tcltest::temporaryDirectory *]] {
1048 lappend ::tcltest::filesExisted [file tail $file]
1051 # If an alternate error or output files are specified, change the
1054 if {[info exists flag(-outfile)]} {
1055 set tmp $flag(-outfile)
1056 MakeAbsolutePath tmp $::tcltest::temporaryDirectory
1057 set ::tcltest::outputChannel [open $tmp w]
1060 if {[info exists flag(-errfile)]} {
1061 set tmp $flag(-errfile)
1062 MakeAbsolutePath tmp $::tcltest::temporaryDirectory
1063 set ::tcltest::errorChannel [open $tmp w]
1066 # If a load script was specified, either directly or through
1067 # a file, remember it for later usage.
1069 if {[info exists flag(-load)] && \
1070 ([lsearch -exact $flagArray -load] > \
1071 [lsearch -exact $flagArray -loadfile])} {
1072 set ::tcltest::loadScript $flag(-load)
1075 if {[info exists flag(-loadfile)] && \
1076 ([lsearch -exact $flagArray -loadfile] > \
1077 [lsearch -exact $flagArray -load]) } {
1078 set tmp $flag(-loadfile)
1079 MakeAbsolutePath tmp $::tcltest::temporaryDirectory
1080 set tmp [open $tmp r]
1081 set ::tcltest::loadScript [read $tmp]
1085 # If the user specifies debug testing, print out extra information during
1087 if {[info exists flag(-debug)]} {
1088 set ::tcltest::debug $flag(-debug)
1091 # Handle -preservecore
1092 if {[info exists flag(-preservecore)]} {
1093 set ::tcltest::preserveCore $flag(-preservecore)
1097 ::tcltest::processCmdLineArgsHook [array get flag]
1099 # Spit out everything you know if we're at a debug level 2 or greater
1101 DebugPuts 2 "Flags passed into tcltest:"
1103 DebugPuts 2 "::tcltest::debug = $::tcltest::debug"
1104 DebugPuts 2 "::tcltest::testsDirectory = $::tcltest::testsDirectory"
1105 DebugPuts 2 "::tcltest::workingDirectory = $::tcltest::workingDirectory"
1106 DebugPuts 2 "::tcltest::temporaryDirectory = $::tcltest::temporaryDirectory"
1107 DebugPuts 2 "::tcltest::outputChannel = $::tcltest::outputChannel"
1108 DebugPuts 2 "::tcltest::errorChannel = $::tcltest::errorChannel"
1109 DebugPuts 2 "Original environment (::tcltest::originalEnv):"
1110 DebugPArray 2 ::tcltest::originalEnv
1111 DebugPuts 2 "Constraints:"
1112 DebugPArray 2 ::tcltest::testConstraints
1115 # ::tcltest::loadTestedCommands --
1117 # Uses the specified script to load the commands to test. Allowed to
1118 # be empty, as the tested commands could have been compiled into the
1127 proc ::tcltest::loadTestedCommands {} {
1128 if {$::tcltest::loadScript == {}} {
1132 uplevel #0 $::tcltest::loadScript
1135 # ::tcltest::cleanupTests --
1137 # Remove files and dirs created using the makeFile and makeDirectory
1138 # commands since the last time this proc was invoked.
1140 # Print the names of the files created without the makeFile command
1141 # since the tests were invoked.
1143 # Print the number tests (total, passed, failed, and skipped) since the
1144 # tests were invoked.
1146 # Restore original environment (as reported by special variable env).
1148 proc ::tcltest::cleanupTests {{calledFromAllFile 0}} {
1150 set testFileName [file tail [info script]]
1152 # Call the cleanup hook
1153 ::tcltest::cleanupTestsHook
1155 # Remove files and directories created by the :tcltest::makeFile and
1156 # ::tcltest::makeDirectory procedures.
1157 # Record the names of files in ::tcltest::workingDirectory that were not
1158 # pre-existing, and associate them with the test file that created them.
1160 if {!$calledFromAllFile} {
1161 foreach file $::tcltest::filesMade {
1162 if {[file exists $file]} {
1163 catch {file delete -force $file}
1167 foreach file [glob -nocomplain \
1168 [file join $::tcltest::temporaryDirectory *]] {
1169 lappend currentFiles [file tail $file]
1172 foreach file $currentFiles {
1173 if {[lsearch -exact $::tcltest::filesExisted $file] == -1} {
1174 lappend newFiles $file
1177 set ::tcltest::filesExisted $currentFiles
1178 if {[llength $newFiles] > 0} {
1179 set ::tcltest::createdNewFiles($testFileName) $newFiles
1183 if {$calledFromAllFile || $::tcltest::testSingleFile} {
1187 puts -nonewline $::tcltest::outputChannel "$testFileName:"
1188 foreach index [list "Total" "Passed" "Skipped" "Failed"] {
1189 puts -nonewline $::tcltest::outputChannel \
1190 "\t$index\t$::tcltest::numTests($index)"
1192 puts $::tcltest::outputChannel ""
1194 # print number test files sourced
1195 # print names of files that ran tests which failed
1197 if {$calledFromAllFile} {
1198 puts $::tcltest::outputChannel \
1199 "Sourced $::tcltest::numTestFiles Test Files."
1200 set ::tcltest::numTestFiles 0
1201 if {[llength $::tcltest::failFiles] > 0} {
1202 puts $::tcltest::outputChannel \
1203 "Files with failing tests: $::tcltest::failFiles"
1204 set ::tcltest::failFiles {}
1208 # if any tests were skipped, print the constraints that kept them
1211 set constraintList [array names ::tcltest::skippedBecause]
1212 if {[llength $constraintList] > 0} {
1213 puts $::tcltest::outputChannel \
1214 "Number of tests skipped for each constraint:"
1215 foreach constraint [lsort $constraintList] {
1216 puts $::tcltest::outputChannel \
1217 "\t$::tcltest::skippedBecause($constraint)\t$constraint"
1218 unset ::tcltest::skippedBecause($constraint)
1222 # report the names of test files in ::tcltest::createdNewFiles, and
1223 # reset the array to be empty.
1225 set testFilesThatTurded [lsort [array names ::tcltest::createdNewFiles]]
1226 if {[llength $testFilesThatTurded] > 0} {
1227 puts $::tcltest::outputChannel "Warning: files left behind:"
1228 foreach testFile $testFilesThatTurded {
1229 puts $::tcltest::outputChannel \
1230 "\t$testFile:\t$::tcltest::createdNewFiles($testFile)"
1231 unset ::tcltest::createdNewFiles($testFile)
1235 # reset filesMade, filesExisted, and numTests
1237 set ::tcltest::filesMade {}
1238 foreach index [list "Total" "Passed" "Skipped" "Failed"] {
1239 set ::tcltest::numTests($index) 0
1242 # exit only if running Tk in non-interactive mode
1244 global tk_version tcl_interactive
1245 if {[info exists tk_version] && ![info exists tcl_interactive]} {
1250 # if we're deferring stat-reporting until all files are sourced,
1251 # then add current file to failFile list if any tests in this file
1254 incr ::tcltest::numTestFiles
1255 if {($::tcltest::currentFailure) && \
1256 ([lsearch -exact $::tcltest::failFiles $testFileName] == -1)} {
1257 lappend ::tcltest::failFiles $testFileName
1259 set ::tcltest::currentFailure false
1261 # restore the environment to the state it was in before this package
1267 foreach index [array names ::env] {
1268 if {![info exists ::tcltest::originalEnv($index)]} {
1269 lappend newEnv $index
1272 if {$::env($index) != $::tcltest::originalEnv($index)} {
1273 lappend changedEnv $index
1274 set ::env($index) $::tcltest::originalEnv($index)
1278 foreach index [array names ::tcltest::originalEnv] {
1279 if {![info exists ::env($index)]} {
1280 lappend removedEnv $index
1281 set ::env($index) $::tcltest::originalEnv($index)
1284 if {[llength $newEnv] > 0} {
1285 puts $::tcltest::outputChannel \
1286 "env array elements created:\t$newEnv"
1288 if {[llength $changedEnv] > 0} {
1289 puts $::tcltest::outputChannel \
1290 "env array elements changed:\t$changedEnv"
1292 if {[llength $removedEnv] > 0} {
1293 puts $::tcltest::outputChannel \
1294 "env array elements removed:\t$removedEnv"
1297 set changedTclPlatform {}
1298 foreach index [array names ::tcltest::originalTclPlatform] {
1299 if {$::tcl_platform($index) != \
1300 $::tcltest::originalTclPlatform($index)} {
1301 lappend changedTclPlatform $index
1302 set ::tcl_platform($index) \
1303 $::tcltest::originalTclPlatform($index)
1306 if {[llength $changedTclPlatform] > 0} {
1307 puts $::tcltest::outputChannel \
1308 "tcl_platform array elements changed:\t$changedTclPlatform"
1311 if {[file exists [file join $::tcltest::workingDirectory core]]} {
1312 if {$::tcltest::preserveCore > 1} {
1313 puts $::tcltest::outputChannel "produced core file! \
1315 [file join $::tcltest::temporaryDirectory core-$name]"
1316 flush $::tcltest::outputChannel
1317 catch {file rename -force \
1318 [file join $::tcltest::workingDirectory core] \
1319 [file join $::tcltest::temporaryDirectory \
1321 if {[string length $msg] > 0} {
1322 ::tcltest::PrintError "Problem renaming file: $msg"
1325 # Print a message if there is a core file and (1) there
1326 # previously wasn't one or (2) the new one is different from
1329 if {[info exists ::tcltest::coreModificationTime]} {
1330 if {$::tcltest::coreModificationTime != [file mtime \
1331 [file join $::tcltest::workingDirectory core]]} {
1332 puts $::tcltest::outputChannel "A core file was created!"
1335 puts $::tcltest::outputChannel "A core file was created!"
1342 # ::tcltest::cleanupTestsHook --
1344 # This hook allows a harness that builds upon tcltest to specify
1345 # additional things that should be done at cleanup.
1348 if {[namespace inscope ::tcltest info procs cleanupTestsHook] == {}} {
1349 proc ::tcltest::cleanupTestsHook {} {}
1354 # This procedure runs a test and prints an error message if the test fails.
1355 # If ::tcltest::verbose has been set, it also prints a message even if the
1356 # test succeeds. The test will be skipped if it doesn't match the
1357 # ::tcltest::match variable, if it matches an element in
1358 # ::tcltest::skip, or if one of the elements of "constraints" turns
1359 # out not to be true.
1362 # name - Name of test, in the form foo-1.2.
1363 # description - Short textual description of the test, to
1364 # help humans understand what it does.
1365 # constraints - A list of one or more keywords, each of
1366 # which must be the name of an element in
1367 # the array "::tcltest::testConstraints". If any of these
1368 # elements is zero, the test is skipped.
1369 # This argument may be omitted.
1370 # script - Script to run to carry out the test. It must
1371 # return a result that can be checked for
1373 # expectedAnswer - Expected result from script.
1375 proc ::tcltest::test {name description script expectedAnswer args} {
1377 DebugPuts 3 "Running $name ($description)"
1379 incr ::tcltest::numTests(Total)
1381 # skip the test if it's name matches an element of skip
1383 foreach pattern $::tcltest::skip {
1384 if {[string match $pattern $name]} {
1385 incr ::tcltest::numTests(Skipped)
1386 DebugDo 1 {::tcltest::AddToSkippedBecause userSpecifiedSkip}
1391 # skip the test if it's name doesn't match any element of match
1393 if {[llength $::tcltest::match] > 0} {
1395 foreach pattern $::tcltest::match {
1396 if {[string match $pattern $name]} {
1402 incr ::tcltest::numTests(Skipped)
1403 DebugDo 1 {::tcltest::AddToSkippedBecause userSpecifiedNonMatch}
1408 set i [llength $args]
1411 # If we're limited to the listed constraints and there aren't any
1412 # listed, then we shouldn't run the test.
1413 if {$::tcltest::limitConstraints} {
1414 ::tcltest::AddToSkippedBecause userSpecifiedLimitConstraint
1415 incr ::tcltest::numTests(Skipped)
1418 } elseif {$i == 1} {
1420 # "constraints" argument exists; shuffle arguments down, then
1421 # make sure that the constraints are satisfied.
1423 set constraints $script
1424 set script $expectedAnswer
1425 set expectedAnswer [lindex $args 0]
1427 if {[string match {*[$\[]*} $constraints] != 0} {
1428 # full expression, e.g. {$foo > [info tclversion]}
1429 catch {set doTest [uplevel #0 expr $constraints]}
1430 } elseif {[regexp {[^.a-zA-Z0-9 ]+} $constraints] != 0} {
1431 # something like {a || b} should be turned into
1432 # $::tcltest::testConstraints(a) || $::tcltest::testConstraints(b).
1433 regsub -all {[.\w]+} $constraints \
1434 {$::tcltest::testConstraints(&)} c
1435 catch {set doTest [eval expr $c]}
1437 # just simple constraints such as {unixOnly fonts}.
1439 foreach constraint $constraints {
1440 if {(![info exists ::tcltest::testConstraints($constraint)]) \
1441 || (!$::tcltest::testConstraints($constraint))} {
1444 # store the constraint that kept the test from running
1445 set constraints $constraint
1451 if {[string first s $::tcltest::verbose] != -1} {
1452 puts $::tcltest::outputChannel "++++ $name SKIPPED: $constraints"
1455 incr ::tcltest::numTests(Skipped)
1456 ::tcltest::AddToSkippedBecause $constraints
1460 error "wrong # args: must be \"test name description ?constraints? script expectedAnswer\""
1463 # Save information about the core file. You need to restore the original
1464 # tcl_platform environment because some of the tests mess with tcl_platform.
1466 if {$::tcltest::preserveCore} {
1467 set currentTclPlatform [array get tcl_platform]
1468 array set tcl_platform $::tcltest::originalTclPlatform
1469 if {[file exists [file join $::tcltest::workingDirectory core]]} {
1470 set coreModTime [file mtime [file join \
1471 $::tcltest::workingDirectory core]]
1473 array set tcl_platform $currentTclPlatform
1476 # If there is no "memory" command (because memory debugging isn't
1477 # enabled), then don't attempt to use the command.
1479 if {[info commands memory] != {}} {
1483 set code [catch {uplevel $script} actualAnswer]
1484 if {([string equal $actualAnswer $expectedAnswer]) && ($code == 0)} {
1485 incr ::tcltest::numTests(Passed)
1486 if {[string first p $::tcltest::verbose] != -1} {
1487 puts $::tcltest::outputChannel "++++ $name PASSED"
1490 incr ::tcltest::numTests(Failed)
1491 set ::tcltest::currentFailure true
1492 if {[string first b $::tcltest::verbose] == -1} {
1495 puts $::tcltest::outputChannel "\n==== $name $description FAILED"
1496 if {$script != ""} {
1497 puts $::tcltest::outputChannel "==== Contents of test case:"
1498 puts $::tcltest::outputChannel $script
1502 puts $::tcltest::outputChannel "==== Test generated error:"
1503 puts $::tcltest::outputChannel $actualAnswer
1504 } elseif {$code == 2} {
1505 puts $::tcltest::outputChannel "==== Test generated return exception; result was:"
1506 puts $::tcltest::outputChannel $actualAnswer
1507 } elseif {$code == 3} {
1508 puts $::tcltest::outputChannel "==== Test generated break exception"
1509 } elseif {$code == 4} {
1510 puts $::tcltest::outputChannel "==== Test generated continue exception"
1512 puts $::tcltest::outputChannel "==== Test generated exception $code; message was:"
1513 puts $::tcltest::outputChannel $actualAnswer
1516 puts $::tcltest::outputChannel "---- Result was:\n$actualAnswer"
1518 puts $::tcltest::outputChannel "---- Result should have been:\n$expectedAnswer"
1519 puts $::tcltest::outputChannel "==== $name FAILED\n"
1521 if {$::tcltest::preserveCore} {
1522 set currentTclPlatform [array get tcl_platform]
1523 if {[file exists [file join $::tcltest::workingDirectory core]]} {
1524 if {$::tcltest::preserveCore > 1} {
1525 puts $::tcltest::outputChannel "==== $name produced core file! \
1527 [file join $::tcltest::temporaryDirectory core-$name]"
1528 catch {file rename -force \
1529 [file join $::tcltest::workingDirectory core] \
1530 [file join $::tcltest::temporaryDirectory \
1532 if {[string length $msg] > 0} {
1533 ::tcltest::PrintError "Problem renaming file: $msg"
1536 # Print a message if there is a core file and (1) there
1537 # previously wasn't one or (2) the new one is different from
1540 if {[info exists coreModTime]} {
1541 if {$coreModTime != [file mtime \
1542 [file join $::tcltest::workingDirectory core]]} {
1543 puts $::tcltest::outputChannel "==== $name produced core file!"
1546 puts $::tcltest::outputChannel "==== $name produced core file!"
1550 array set tcl_platform $currentTclPlatform
1554 # ::tcltest::getMatchingFiles
1556 # Looks at the patterns given to match and skip files
1557 # and uses them to put together a list of the tests that will be run.
1563 # The constructed list is returned to the user. This will primarily
1564 # be used in 'all.tcl' files.
1566 proc ::tcltest::getMatchingFiles {args} {
1567 set matchingFiles {}
1568 if {[llength $args]} {
1569 set searchDirectory $args
1571 set searchDirectory [list $::tcltest::testsDirectory]
1573 # Find the matching files in the list of directories and then remove the
1574 # ones that match the skip pattern
1575 foreach directory $searchDirectory {
1576 set matchFileList {}
1577 foreach match $::tcltest::matchFiles {
1578 set matchFileList [concat $matchFileList \
1579 [glob -nocomplain [file join $directory $match]]]
1581 if {[string compare {} $::tcltest::skipFiles]} {
1583 foreach skip $::tcltest::skipFiles {
1584 set skipFileList [concat $skipFileList \
1585 [glob -nocomplain [file join $directory $skip]]]
1587 foreach file $matchFileList {
1588 # Only include files that don't match the skip pattern and
1589 # aren't SCCS lock files.
1590 if {([lsearch -exact $skipFileList $file] == -1) && \
1591 (![string match l.*.test [file tail $file]])} {
1592 lappend matchingFiles $file
1596 set matchingFiles [concat $matchingFiles $matchFileList]
1599 if {[string equal $matchingFiles {}]} {
1600 ::tcltest::PrintError "No test files remain after applying \
1601 your match and skip patterns!"
1603 return $matchingFiles
1606 # The following two procs are used in the io tests.
1608 proc ::tcltest::openfiles {} {
1609 if {[catch {testchannel open} result]} {
1615 proc ::tcltest::leakfiles {old} {
1616 if {[catch {testchannel open} new]} {
1621 if {[lsearch $old $p] < 0} {
1628 # ::tcltest::saveState --
1630 # Save information regarding what procs and variables exist.
1636 # Modifies the variable ::tcltest::saveState
1638 proc ::tcltest::saveState {} {
1639 uplevel #0 {set ::tcltest::saveState [list [info procs] [info vars]]}
1640 DebugPuts 2 "::tcltest::saveState: $::tcltest::saveState"
1643 # ::tcltest::restoreState --
1645 # Remove procs and variables that didn't exist before the call to
1646 # ::tcltest::saveState.
1652 # Removes procs and variables from your environment if they don't exist
1653 # in the ::tcltest::saveState variable.
1655 proc ::tcltest::restoreState {} {
1656 foreach p [info procs] {
1657 if {([lsearch [lindex $::tcltest::saveState 0] $p] < 0) && \
1658 (![string equal ::tcltest::$p [namespace origin $p]])} {
1660 DebugPuts 3 "::tcltest::restoreState: Removing proc $p"
1664 foreach p [uplevel #0 {info vars}] {
1665 if {[lsearch [lindex $::tcltest::saveState 1] $p] < 0} {
1666 DebugPuts 3 "::tcltest::restoreState: Removing variable $p"
1667 uplevel #0 "catch {unset $p}"
1672 # ::tcltest::normalizeMsg --
1674 # Removes "extra" newlines from a string.
1677 # msg String to be modified
1680 proc ::tcltest::normalizeMsg {msg} {
1681 regsub "\n$" [string tolower $msg] "" msg
1682 regsub -all "\n\n" $msg "\n" msg
1683 regsub -all "\n\}" $msg "\}" msg
1689 # Create a new file with the name <name>, and write <contents> to it.
1691 # If this file hasn't been created via makeFile since the last time
1692 # cleanupTests was called, add it to the $filesMade list, so it will
1693 # be removed by the next call to cleanupTests.
1695 proc ::tcltest::makeFile {contents name} {
1698 DebugPuts 3 "::tcltest::makeFile: putting $contents into $name"
1700 set fullName [file join $::tcltest::temporaryDirectory $name]
1701 set fd [open $fullName w]
1703 fconfigure $fd -translation lf
1705 if {[string equal [string index $contents end] "\n"]} {
1706 puts -nonewline $fd $contents
1712 if {[lsearch -exact $::tcltest::filesMade $fullName] == -1} {
1713 lappend ::tcltest::filesMade $fullName
1718 # ::tcltest::removeFile --
1720 # Removes the named file from the filesystem
1723 # name file to be removed
1726 proc ::tcltest::removeFile {name} {
1727 DebugPuts 3 "::tcltest::removeFile: removing $name"
1728 file delete [file join $::tcltest::temporaryDirectory $name]
1733 # Create a new dir with the name <name>.
1735 # If this dir hasn't been created via makeDirectory since the last time
1736 # cleanupTests was called, add it to the $directoriesMade list, so it will
1737 # be removed by the next call to cleanupTests.
1739 proc ::tcltest::makeDirectory {name} {
1742 set fullName [file join [pwd] $name]
1743 if {[lsearch -exact $::tcltest::filesMade $fullName] == -1} {
1744 lappend ::tcltest::filesMade $fullName
1748 # ::tcltest::removeDirectory --
1750 # Removes a named directory from the file system.
1753 # name Name of the directory to remove
1756 proc ::tcltest::removeDirectory {name} {
1757 file delete -force $name
1760 proc ::tcltest::viewFile {name} {
1762 if {([string equal $tcl_platform(platform) "macintosh"]) || \
1763 ($::tcltest::testConstraints(unixExecs) == 0)} {
1764 set f [open [file join $::tcltest::temporaryDirectory $name]]
1765 set data [read -nonewline $f]
1769 exec cat [file join $::tcltest::temporaryDirectory $name]
1775 # Evaluate a given expression against each element of a list and return all
1776 # elements for which the expression evaluates to true. For the purposes of
1777 # this proc, use of the keyword "CURRENT_ELEMENT" will flag the proc to use the
1778 # value of the current element within the expression. This is equivalent to
1779 # the perl grep command where CURRENT_ELEMENT would be the name for the special
1782 # Examples of usage would be:
1783 # set subList [grep {CURRENT_ELEMENT == 1} $listOfNumbers]
1784 # set subList [grep {regexp {abc} CURRENT_ELEMENT} $listOfStrings]
1786 # Use of the CURRENT_ELEMENT keyword is optional. If it is left out, it is
1787 # assumed to be the final argument to the expression provided.
1790 # grep {regexp a} $someList
1792 proc ::tcltest::grep { expression searchList } {
1793 foreach element $searchList {
1794 if {[regsub -all CURRENT_ELEMENT $expression $element \
1795 newExpression] == 0} {
1796 set newExpression "$expression {$element}"
1798 if {[eval $newExpression] == 1} {
1799 lappend returnList $element
1802 if {[info exists returnList]} {
1809 # Construct a string that consists of the requested sequence of bytes,
1810 # as opposed to a string of properly formed UTF-8 characters.
1811 # This allows the tester to
1812 # 1. Create denormalized or improperly formed strings to pass to C procedures
1813 # that are supposed to accept strings with embedded NULL bytes.
1814 # 2. Confirm that a string result has a certain pattern of bytes, for instance
1815 # to confirm that "\xe0\0" in a Tcl script is stored internally in
1816 # UTF-8 as the sequence of bytes "\xc3\xa0\xc0\x80".
1818 # Generally, it's a bad idea to examine the bytes in a Tcl string or to
1819 # construct improperly formed strings in this manner, because it involves
1820 # exposing that Tcl uses UTF-8 internally.
1822 proc ::tcltest::bytestring {string} {
1823 encoding convertfrom identity $string
1827 # Internationalization / ISO support procs -- dl
1829 proc ::tcltest::set_iso8859_1_locale {} {
1830 if {[info commands testlocale] != ""} {
1831 set ::tcltest::previousLocale [testlocale ctype]
1832 testlocale ctype $::tcltest::isoLocale
1837 proc ::tcltest::restore_locale {} {
1838 if {[info commands testlocale] != ""} {
1839 testlocale ctype $::tcltest::previousLocale
1846 # Kill all threads except for the main thread.
1847 # Do nothing if testthread is not defined.
1853 # Returns the number of existing threads.
1854 proc ::tcltest::threadReap {} {
1855 if {[info commands testthread] != {}} {
1857 # testthread built into tcltest
1859 testthread errorproc ThreadNullError
1860 while {[llength [testthread names]] > 1} {
1861 foreach tid [testthread names] {
1862 if {$tid != $::tcltest::mainThread} {
1863 catch {testthread send -async $tid {testthread exit}}
1866 ## Enter a bit a sleep to give the threads enough breathing
1867 ## room to kill themselves off, otherwise the end up with a
1868 ## massive queue of repeated events
1871 testthread errorproc ThreadError
1872 return [llength [testthread names]]
1873 } elseif {[info commands thread::id] != {}} {
1877 thread::errorproc ThreadNullError
1878 while {[llength [thread::names]] > 1} {
1879 foreach tid [thread::names] {
1880 if {$tid != $::tcltest::mainThread} {
1881 catch {thread::send -async $tid {thread::exit}}
1884 ## Enter a bit a sleep to give the threads enough breathing
1885 ## room to kill themselves off, otherwise the end up with a
1886 ## massive queue of repeated events
1889 thread::errorproc ThreadError
1890 return [llength [thread::names]]
1896 # Initialize the constraints and set up command line arguments
1897 namespace eval tcltest {
1898 # Ensure that we have a minimal auto_path so we don't pick up extra junk.
1899 set ::auto_path [list [info library]]
1901 ::tcltest::initConstraints
1902 if {[namespace children ::tcltest] == {}} {
1903 ::tcltest::processCmdLineArgs