3 # This file contains support code for the Tcl/Tk test suite.It is
4 # It is normally sourced by the individual files in the test suite
5 # before they run their tests. This improved approach to testing
6 # was designed and initially implemented by Mary Ann May-Pumphrey
9 # Copyright (c) 1990-1994 The Regents of the University of California.
10 # Copyright (c) 1994-1996 Sun Microsystems, Inc.
11 # Copyright (c) 1998-1999 by Scriptics Corporation.
12 # All rights reserved.
16 # Initialize wish shell
18 if {[info exists tk_version]} {
23 # Ensure that we have a minimal auto_path so we don't pick up extra junk.
25 set auto_path [list [info library]]
28 # create the "tcltest" namespace for all testing variables and procedures
30 namespace eval tcltest {
31 set procList [list test cleanupTests dotests saveState restoreState \
32 normalizeMsg makeFile removeFile makeDirectory removeDirectory \
33 viewFile bytestring set_iso8859_1_locale restore_locale \
35 if {[info exists tk_version]} {
36 lappend procList setupbg dobg bgReady cleanupbg fixfocus
38 foreach proc $procList {
39 namespace export $proc
42 # ::tcltest::verbose defaults to "b"
46 # match defaults to the empty list
50 # skip defaults to the empty list
54 # Tests should not rely on the current working directory.
55 # Files that are part of the test suite should be accessed relative to
56 # ::tcltest::testsDir.
59 set tDir [file join $originalDir [file dirname [info script]]]
61 variable testsDir [pwd]
64 # Count the number of files tested (0 if all.tcl wasn't called).
65 # The all.tcl file will set testSingleFile to false, so stats will
66 # not be printed until all.tcl calls the cleanupTests proc.
67 # The currentFailure var stores the boolean value of whether the
68 # current test file has had any failures. The failFiles list
69 # stores the names of test files that had failures.
71 variable numTestFiles 0
72 variable testSingleFile true
73 variable currentFailure false
76 # Tests should remove all files they create. The test suite will
77 # check the current working dir for files created by the tests.
78 # ::tcltest::filesMade keeps track of such files created using the
79 # ::tcltest::makeFile and ::tcltest::makeDirectory procedures.
80 # ::tcltest::filesExisted stores the names of pre-existing files.
83 variable filesExisted {}
85 # ::tcltest::numTests will store test files as indices and the list
86 # of files (that should not have been) left behind by the test files.
88 array set ::tcltest::createdNewFiles {}
90 # initialize ::tcltest::numTests array to keep track fo the number of
91 # tests that pass, fial, and are skipped.
93 array set numTests [list Total 0 Passed 0 Skipped 0 Failed 0]
95 # initialize ::tcltest::skippedBecause array to keep track of
96 # constraints that kept tests from running
98 array set ::tcltest::skippedBecause {}
100 # tests that use thread need to know which is the main thread
102 variable ::tcltest::mainThread 1
103 if {[info commands testthread] != {}} {
104 set ::tcltest::mainThread [testthread names]
108 # If there is no "memory" command (because memory debugging isn't
109 # enabled), generate a dummy command that does nothing.
111 if {[info commands memory] == ""} {
115 # ::tcltest::initConfig --
117 # Check configuration information that will determine which tests
118 # to run. To do this, create an array ::tcltest::testConfig. Each
119 # element has a 0 or 1 value. If the element is "true" then tests
120 # with that constraint will be run, otherwise tests with that constraint
121 # will be skipped. See the README file for the list of built-in
122 # constraints defined in this procedure.
128 # The ::tcltest::testConfig array is reset to have an index for
129 # each built-in test constraint.
131 proc ::tcltest::initConfig {} {
133 global tcl_platform tcl_interactive tk_version
135 catch {unset ::tcltest::testConfig}
137 # The following trace procedure makes it so that we can safely refer to
138 # non-existent members of the ::tcltest::testConfig array without causing an
139 # error. Instead, reading a non-existent member will return 0. This is
140 # necessary because tests are allowed to use constraint "X" without ensuring
141 # that ::tcltest::testConfig("X") is defined.
143 trace variable ::tcltest::testConfig r ::tcltest::safeFetch
145 proc ::tcltest::safeFetch {n1 n2 op} {
146 if {($n2 != {}) && ([info exists ::tcltest::testConfig($n2)] == 0)} {
147 set ::tcltest::testConfig($n2) 0
151 set ::tcltest::testConfig(unixOnly) \
152 [expr {$tcl_platform(platform) == "unix"}]
153 set ::tcltest::testConfig(macOnly) \
154 [expr {$tcl_platform(platform) == "macintosh"}]
155 set ::tcltest::testConfig(pcOnly) \
156 [expr {$tcl_platform(platform) == "windows"}]
158 set ::tcltest::testConfig(unix) $::tcltest::testConfig(unixOnly)
159 set ::tcltest::testConfig(mac) $::tcltest::testConfig(macOnly)
160 set ::tcltest::testConfig(pc) $::tcltest::testConfig(pcOnly)
162 set ::tcltest::testConfig(unixOrPc) \
163 [expr {$::tcltest::testConfig(unix) || $::tcltest::testConfig(pc)}]
164 set ::tcltest::testConfig(macOrPc) \
165 [expr {$::tcltest::testConfig(mac) || $::tcltest::testConfig(pc)}]
166 set ::tcltest::testConfig(macOrUnix) \
167 [expr {$::tcltest::testConfig(mac) || $::tcltest::testConfig(unix)}]
169 set ::tcltest::testConfig(nt) [expr {$tcl_platform(os) == "Windows NT"}]
170 set ::tcltest::testConfig(95) [expr {$tcl_platform(os) == "Windows 95"}]
172 # The following config switches are used to mark tests that should work,
173 # but have been temporarily disabled on certain platforms because they don't
174 # and we haven't gotten around to fixing the underlying problem.
176 set ::tcltest::testConfig(tempNotPc) [expr {!$::tcltest::testConfig(pc)}]
177 set ::tcltest::testConfig(tempNotMac) [expr {!$::tcltest::testConfig(mac)}]
178 set ::tcltest::testConfig(tempNotUnix) [expr {!$::tcltest::testConfig(unix)}]
180 # The following config switches are used to mark tests that crash on
181 # certain platforms, so that they can be reactivated again when the
182 # underlying problem is fixed.
184 set ::tcltest::testConfig(pcCrash) [expr {!$::tcltest::testConfig(pc)}]
185 set ::tcltest::testConfig(macCrash) [expr {!$::tcltest::testConfig(mac)}]
186 set ::tcltest::testConfig(unixCrash) [expr {!$::tcltest::testConfig(unix)}]
188 # Set the "fonts" constraint for wish apps
190 if {[info exists tk_version]} {
191 set ::tcltest::testConfig(fonts) 1
193 entry .e -width 0 -font {Helvetica -12} -bd 1
194 .e insert end "a.bcd"
195 if {([winfo reqwidth .e] != 37) || ([winfo reqheight .e] != 20)} {
196 set ::tcltest::testConfig(fonts) 0
200 text .t -width 80 -height 20 -font {Times -14} -bd 1
202 .t insert end "This is\na dot."
204 set x [list [.t bbox 1.3] [.t bbox 2.5]]
206 if {[string match {{22 3 6 15} {31 18 [34] 15}} $x] == 0} {
207 set ::tcltest::testConfig(fonts) 0
213 set ::tcltest::testConfig(emptyTest) 0
215 # By default, tests that expost known bugs are skipped.
217 set ::tcltest::testConfig(knownBug) 0
219 # By default, non-portable tests are skipped.
221 set ::tcltest::testConfig(nonPortable) 0
223 # Some tests require user interaction.
225 set ::tcltest::testConfig(userInteraction) 0
227 # Some tests must be skipped if the interpreter is not in interactive mode
229 set ::tcltest::testConfig(interactive) $tcl_interactive
231 # Some tests must be skipped if you are running as root on Unix.
232 # Other tests can only be run if you are running as root on Unix.
234 set ::tcltest::testConfig(root) 0
235 set ::tcltest::testConfig(notRoot) 1
237 if {$tcl_platform(platform) == "unix"} {
238 catch {set user [exec whoami]}
240 catch {regexp {^[^(]*\(([^)]*)\)} [exec id] dummy user}
242 if {($user == "root") || ($user == "")} {
243 set ::tcltest::testConfig(root) 1
244 set ::tcltest::testConfig(notRoot) 0
248 # Set nonBlockFiles constraint: 1 means this platform supports
249 # setting files into nonblocking mode.
251 if {[catch {set f [open defs r]}]} {
252 set ::tcltest::testConfig(nonBlockFiles) 1
254 if {[catch {fconfigure $f -blocking off}] == 0} {
255 set ::tcltest::testConfig(nonBlockFiles) 1
257 set ::tcltest::testConfig(nonBlockFiles) 0
262 # Set asyncPipeClose constraint: 1 means this platform supports
263 # async flush and async close on a pipe.
265 # Test for SCO Unix - cannot run async flushing tests because a
266 # potential problem with select is apparently interfering.
269 if {$tcl_platform(platform) == "unix"} {
270 if {[catch {exec uname -X | fgrep {Release = 3.2v}}] == 0} {
271 set ::tcltest::testConfig(asyncPipeClose) 0
273 set ::tcltest::testConfig(asyncPipeClose) 1
276 set ::tcltest::testConfig(asyncPipeClose) 1
279 # Test to see if we have a broken version of sprintf with respect
280 # to the "e" format of floating-point numbers.
282 set ::tcltest::testConfig(eformat) 1
283 if {[string compare "[format %g 5e-5]" "5e-05"] != 0} {
284 set ::tcltest::testConfig(eformat) 0
287 # Test to see if execed commands such as cat, echo, rm and so forth are
288 # present on this machine.
290 set ::tcltest::testConfig(unixExecs) 1
291 if {$tcl_platform(platform) == "macintosh"} {
292 set ::tcltest::testConfig(unixExecs) 0
294 if {($::tcltest::testConfig(unixExecs) == 1) && \
295 ($tcl_platform(platform) == "windows")} {
296 if {[catch {exec cat defs}] == 1} {
297 set ::tcltest::testConfig(unixExecs) 0
299 if {($::tcltest::testConfig(unixExecs) == 1) && \
300 ([catch {exec echo hello}] == 1)} {
301 set ::tcltest::testConfig(unixExecs) 0
303 if {($::tcltest::testConfig(unixExecs) == 1) && \
304 ([catch {exec sh -c echo hello}] == 1)} {
305 set ::tcltest::testConfig(unixExecs) 0
307 if {($::tcltest::testConfig(unixExecs) == 1) && \
308 ([catch {exec wc defs}] == 1)} {
309 set ::tcltest::testConfig(unixExecs) 0
311 if {$::tcltest::testConfig(unixExecs) == 1} {
312 exec echo hello > removeMe
313 if {[catch {exec rm removeMe}] == 1} {
314 set ::tcltest::testConfig(unixExecs) 0
317 if {($::tcltest::testConfig(unixExecs) == 1) && \
318 ([catch {exec sleep 1}] == 1)} {
319 set ::tcltest::testConfig(unixExecs) 0
321 if {($::tcltest::testConfig(unixExecs) == 1) && \
322 ([catch {exec fgrep unixExecs defs}] == 1)} {
323 set ::tcltest::testConfig(unixExecs) 0
325 if {($::tcltest::testConfig(unixExecs) == 1) && \
326 ([catch {exec ps}] == 1)} {
327 set ::tcltest::testConfig(unixExecs) 0
329 if {($::tcltest::testConfig(unixExecs) == 1) && \
330 ([catch {exec echo abc > removeMe}] == 0) && \
331 ([catch {exec chmod 644 removeMe}] == 1) && \
332 ([catch {exec rm removeMe}] == 0)} {
333 set ::tcltest::testConfig(unixExecs) 0
335 catch {exec rm -f removeMe}
337 if {($::tcltest::testConfig(unixExecs) == 1) && \
338 ([catch {exec mkdir removeMe}] == 1)} {
339 set ::tcltest::testConfig(unixExecs) 0
341 catch {exec rm -r removeMe}
346 ::tcltest::initConfig
349 # ::tcltest::processCmdLineArgs --
351 # Use command line args to set the verbose, skip, and
352 # match variables. This procedure must be run after
353 # constraints are initialized, because some constraints can be
360 # ::tcltest::verbose is set to <value>
362 proc ::tcltest::processCmdLineArgs {} {
365 # The "argv" var doesn't exist in some cases, so use {}
366 # The "argv" var doesn't exist in some cases.
368 if {(![info exists argv]) || ([llength $argv] < 2)} {
374 if {[catch {array set flag $flagArray}]} {
375 puts stderr "Error: odd number of command line args specified:"
380 # Allow for 1-char abbreviations, where applicable (e.g., -match == -m).
381 # Note that -verbose cannot be abbreviated to -v in wish because it
382 # conflicts with the wish option -visual.
384 foreach arg {-verbose -match -skip -constraints} {
385 set abbrev [string range $arg 0 1]
386 if {([info exists flag($abbrev)]) && \
387 ([lsearch -exact $flagArray $arg] < \
388 [lsearch -exact $flagArray $abbrev])} {
389 set flag($arg) $flag($abbrev)
393 # Set ::tcltest::workingDir to [pwd].
394 # Save the names of files that already exist in ::tcltest::workingDir.
396 set ::tcltest::workingDir [pwd]
397 foreach file [glob -nocomplain [file join $::tcltest::workingDir *]] {
398 lappend ::tcltest::filesExisted [file tail $file]
401 # Set ::tcltest::verbose to the arg of the -verbose flag, if given
403 if {[info exists flag(-verbose)]} {
404 set ::tcltest::verbose $flag(-verbose)
407 # Set ::tcltest::match to the arg of the -match flag, if given
409 if {[info exists flag(-match)]} {
410 set ::tcltest::match $flag(-match)
413 # Set ::tcltest::skip to the arg of the -skip flag, if given
415 if {[info exists flag(-skip)]} {
416 set ::tcltest::skip $flag(-skip)
419 # Use the -constraints flag, if given, to turn on constraints that are
420 # turned off by default: userInteractive knownBug nonPortable. This
421 # code fragment must be run after constraints are initialized.
423 if {[info exists flag(-constraints)]} {
424 foreach elt $flag(-constraints) {
425 set ::tcltest::testConfig($elt) 1
430 ::tcltest::processCmdLineArgs
433 # ::tcltest::cleanupTests --
435 # Remove files and dirs created using the makeFile and makeDirectory
436 # commands since the last time this proc was invoked.
438 # Print the names of the files created without the makeFile command
439 # since the tests were invoked.
441 # Print the number tests (total, passed, failed, and skipped) since the
442 # tests were invoked.
445 proc ::tcltest::cleanupTests {{calledFromAllFile 0}} {
446 set tail [file tail [info script]]
448 # Remove files and directories created by the :tcltest::makeFile and
449 # ::tcltest::makeDirectory procedures.
450 # Record the names of files in ::tcltest::workingDir that were not
451 # pre-existing, and associate them with the test file that created them.
453 if {!$calledFromAllFile} {
455 foreach file $::tcltest::filesMade {
456 if {[file exists $file]} {
457 catch {file delete -force $file}
461 foreach file [glob -nocomplain [file join $::tcltest::workingDir *]] {
462 lappend currentFiles [file tail $file]
465 foreach file $currentFiles {
466 if {[lsearch -exact $::tcltest::filesExisted $file] == -1} {
467 lappend newFiles $file
470 set ::tcltest::filesExisted $currentFiles
471 if {[llength $newFiles] > 0} {
472 set ::tcltest::createdNewFiles($tail) $newFiles
476 if {$calledFromAllFile || $::tcltest::testSingleFile} {
480 puts -nonewline stdout "$tail:"
481 foreach index [list "Total" "Passed" "Skipped" "Failed"] {
482 puts -nonewline stdout "\t$index\t$::tcltest::numTests($index)"
486 # print number test files sourced
487 # print names of files that ran tests which failed
489 if {$calledFromAllFile} {
490 puts stdout "Sourced $::tcltest::numTestFiles Test Files."
491 set ::tcltest::numTestFiles 0
492 if {[llength $::tcltest::failFiles] > 0} {
493 puts stdout "Files with failing tests: $::tcltest::failFiles"
494 set ::tcltest::failFiles {}
498 # if any tests were skipped, print the constraints that kept them
501 set constraintList [array names ::tcltest::skippedBecause]
502 if {[llength $constraintList] > 0} {
503 puts stdout "Number of tests skipped for each constraint:"
504 foreach constraint [lsort $constraintList] {
506 "\t$::tcltest::skippedBecause($constraint)\t$constraint"
507 unset ::tcltest::skippedBecause($constraint)
511 # report the names of test files in ::tcltest::createdNewFiles, and
512 # reset the array to be empty.
514 set testFilesThatTurded [lsort [array names ::tcltest::createdNewFiles]]
515 if {[llength $testFilesThatTurded] > 0} {
516 puts stdout "Warning: test files left files behind:"
517 foreach testFile $testFilesThatTurded {
518 puts "\t$testFile:\t$::tcltest::createdNewFiles($testFile)"
519 unset ::tcltest::createdNewFiles($testFile)
523 # reset filesMade, filesExisted, and numTests
525 set ::tcltest::filesMade {}
526 foreach index [list "Total" "Passed" "Skipped" "Failed"] {
527 set ::tcltest::numTests($index) 0
530 # exit only if running Tk in non-interactive mode
532 global tk_version tcl_interactive
533 if {[info exists tk_version] && !$tcl_interactive} {
538 # if we're deferring stat-reporting until all files are sourced,
539 # then add current file to failFile list if any tests in this file
542 incr ::tcltest::numTestFiles
543 if {($::tcltest::currentFailure) && \
544 ([lsearch -exact $::tcltest::failFiles $tail] == -1)} {
545 lappend ::tcltest::failFiles $tail
547 set ::tcltest::currentFailure false
554 # This procedure runs a test and prints an error message if the test fails.
555 # If ::tcltest::verbose has been set, it also prints a message even if the
556 # test succeeds. The test will be skipped if it doesn't match the
557 # ::tcltest::match variable, if it matches an element in
558 # ::tcltest::skip, or if one of the elements of "constraints" turns
559 # out not to be true.
562 # name - Name of test, in the form foo-1.2.
563 # description - Short textual description of the test, to
564 # help humans understand what it does.
565 # constraints - A list of one or more keywords, each of
566 # which must be the name of an element in
567 # the array "::tcltest::testConfig". If any of these
568 # elements is zero, the test is skipped.
569 # This argument may be omitted.
570 # script - Script to run to carry out the test. It must
571 # return a result that can be checked for
573 # expectedAnswer - Expected result from script.
575 proc ::tcltest::test {name description script expectedAnswer args} {
576 incr ::tcltest::numTests(Total)
578 # skip the test if it's name matches an element of skip
580 foreach pattern $::tcltest::skip {
581 if {[string match $pattern $name]} {
582 incr ::tcltest::numTests(Skipped)
586 # skip the test if it's name doesn't match any element of match
588 if {[llength $::tcltest::match] > 0} {
590 foreach pattern $::tcltest::match {
591 if {[string match $pattern $name]} {
597 incr ::tcltest::numTests(Skipped)
601 set i [llength $args]
606 # "constraints" argument exists; shuffle arguments down, then
607 # make sure that the constraints are satisfied.
609 set constraints $script
610 set script $expectedAnswer
611 set expectedAnswer [lindex $args 0]
613 if {[string match {*[$\[]*} $constraints] != 0} {
615 # full expression, e.g. {$foo > [info tclversion]}
617 catch {set doTest [uplevel #0 expr $constraints]}
619 } elseif {[regexp {[^.a-zA-Z0-9 ]+} $constraints] != 0} {
621 # something like {a || b} should be turned into
622 # $::tcltest::testConfig(a) || $::tcltest::testConfig(b).
624 regsub -all {[.a-zA-Z0-9]+} $constraints \
625 {$::tcltest::testConfig(&)} c
626 catch {set doTest [eval expr $c]}
629 # just simple constraints such as {unixOnly fonts}.
632 foreach constraint $constraints {
633 if {![info exists ::tcltest::testConfig($constraint)]
634 || !$::tcltest::testConfig($constraint)} {
637 # store the constraint that kept the test from running
639 set constraints $constraint
645 incr ::tcltest::numTests(Skipped)
646 if {[string first s $::tcltest::verbose] != -1} {
647 puts stdout "++++ $name SKIPPED: $constraints"
650 # add the constraint to the list of constraints the kept tests
653 if {[info exists ::tcltest::skippedBecause($constraints)]} {
654 incr ::tcltest::skippedBecause($constraints)
656 set ::tcltest::skippedBecause($constraints) 1
661 error "wrong # args: must be \"test name description ?constraints? script expectedAnswer\""
664 set code [catch {uplevel $script} actualAnswer]
665 if {$code != 0 || [string compare $actualAnswer $expectedAnswer] != 0} {
666 incr ::tcltest::numTests(Failed)
667 set ::tcltest::currentFailure true
668 if {[string first b $::tcltest::verbose] == -1} {
671 puts stdout "\n==== $name $description FAILED"
673 puts stdout "==== Contents of test case:"
678 puts stdout "==== Test generated error:"
679 puts stdout $actualAnswer
680 } elseif {$code == 2} {
681 puts stdout "==== Test generated return exception; result was:"
682 puts stdout $actualAnswer
683 } elseif {$code == 3} {
684 puts stdout "==== Test generated break exception"
685 } elseif {$code == 4} {
686 puts stdout "==== Test generated continue exception"
688 puts stdout "==== Test generated exception $code; message was:"
689 puts stdout $actualAnswer
692 puts stdout "---- Result was:\n$actualAnswer"
694 puts stdout "---- Result should have been:\n$expectedAnswer"
695 puts stdout "==== $name FAILED\n"
697 incr ::tcltest::numTests(Passed)
698 if {[string first p $::tcltest::verbose] != -1} {
699 puts stdout "++++ $name PASSED"
704 # ::tcltest::dotests --
706 # takes two arguments--the name of the test file (such
707 # as "parse.test"), and a pattern selecting the tests you want to
708 # execute. It sets ::tcltest::matching to the second argument, calls
709 # "source" on the file specified in the first argument, and restores
710 # ::tcltest::matching to its pre-call value at the end.
713 # file name of tests file to source
714 # args pattern selecting the tests you want to execute
719 proc ::tcltest::dotests {file args} {
720 set savedTests $::tcltest::match
721 set ::tcltest::match $args
723 set ::tcltest::match $savedTests
726 proc ::tcltest::openfiles {} {
727 if {[catch {testchannel open} result]} {
733 proc ::tcltest::leakfiles {old} {
734 if {[catch {testchannel open} new]} {
739 if {[lsearch $old $p] < 0} {
746 set ::tcltest::saveState {}
748 proc ::tcltest::saveState {} {
749 uplevel #0 {set ::tcltest::saveState [list [info procs] [info vars]]}
752 proc ::tcltest::restoreState {} {
753 foreach p [info procs] {
754 if {[lsearch [lindex $::tcltest::saveState 0] $p] < 0} {
758 foreach p [uplevel #0 {info vars}] {
759 if {[lsearch [lindex $::tcltest::saveState 1] $p] < 0} {
760 uplevel #0 "unset $p"
765 proc ::tcltest::normalizeMsg {msg} {
766 regsub "\n$" [string tolower $msg] "" msg
767 regsub -all "\n\n" $msg "\n" msg
768 regsub -all "\n\}" $msg "\}" msg
774 # Create a new file with the name <name>, and write <contents> to it.
776 # If this file hasn't been created via makeFile since the last time
777 # cleanupTests was called, add it to the $filesMade list, so it will
778 # be removed by the next call to cleanupTests.
780 proc ::tcltest::makeFile {contents name} {
781 set fd [open $name w]
782 fconfigure $fd -translation lf
783 if {[string index $contents [expr {[string length $contents] - 1}]] == "\n"} {
784 puts -nonewline $fd $contents
790 set fullName [file join [pwd] $name]
791 if {[lsearch -exact $::tcltest::filesMade $fullName] == -1} {
792 lappend ::tcltest::filesMade $fullName
796 proc ::tcltest::removeFile {name} {
802 # Create a new dir with the name <name>.
804 # If this dir hasn't been created via makeDirectory since the last time
805 # cleanupTests was called, add it to the $directoriesMade list, so it will
806 # be removed by the next call to cleanupTests.
808 proc ::tcltest::makeDirectory {name} {
811 set fullName [file join [pwd] $name]
812 if {[lsearch -exact $::tcltest::filesMade $fullName] == -1} {
813 lappend ::tcltest::filesMade $fullName
817 proc ::tcltest::removeDirectory {name} {
818 file delete -force $name
821 proc ::tcltest::viewFile {name} {
823 if {($tcl_platform(platform) == "macintosh") || \
824 ($::tcltest::testConfig(unixExecs) == 0)} {
826 set data [read -nonewline $f]
835 # Construct a string that consists of the requested sequence of bytes,
836 # as opposed to a string of properly formed UTF-8 characters.
837 # This allows the tester to
838 # 1. Create denormalized or improperly formed strings to pass to C procedures
839 # that are supposed to accept strings with embedded NULL bytes.
840 # 2. Confirm that a string result has a certain pattern of bytes, for instance
841 # to confirm that "\xe0\0" in a Tcl script is stored internally in
842 # UTF-8 as the sequence of bytes "\xc3\xa0\xc0\x80".
844 # Generally, it's a bad idea to examine the bytes in a Tcl string or to
845 # construct improperly formed strings in this manner, because it involves
846 # exposing that Tcl uses UTF-8 internally.
848 proc ::tcltest::bytestring {string} {
849 encoding convertfrom identity $string
852 # Locate tcltest executable
854 if {![info exists tk_version]} {
855 set tcltest [info nameofexecutable]
857 if {$tcltest == "{}"} {
862 set ::tcltest::testConfig(stdio) 0
864 catch {file delete -force tmp}
871 set f [open "|[list $tcltest tmp]" r]
874 set ::tcltest::testConfig(stdio) 1
876 catch {file delete -force tmp}
878 # Deliberately call the socket with the wrong number of arguments. The error
879 # message you get will indicate whether sockets are available on this system.
882 set ::tcltest::testConfig(socket) \
883 [expr {$msg != "sockets are not available on this system"}]
886 # Internationalization / ISO support procs -- dl
889 if {[info commands testlocale]==""} {
891 # No testlocale command, no tests...
892 # (it could be that we are a sub interp and we could just load
893 # the Tcltest package but that would interfere with tests
894 # that tests packages/loading in slaves...)
896 set ::tcltest::testConfig(hasIsoLocale) 0
898 proc ::tcltest::set_iso8859_1_locale {} {
899 set ::tcltest::previousLocale [testlocale ctype]
900 testlocale ctype $::tcltest::isoLocale
903 proc ::tcltest::restore_locale {} {
904 testlocale ctype $::tcltest::previousLocale
907 if {![info exists ::tcltest::isoLocale]} {
908 set ::tcltest::isoLocale fr
909 switch $tcl_platform(platform) {
912 # Try some 'known' values for some platforms:
914 switch -exact -- $tcl_platform(os) {
916 set ::tcltest::isoLocale fr_FR.ISO_8859-1
919 set ::tcltest::isoLocale fr_FR.iso88591
923 set ::tcltest::isoLocale fr
927 # Works on SunOS 4 and Solaris, and maybe others...
928 # define it to something else on your system
929 #if you want to test those.
931 set ::tcltest::isoLocale iso_8859_1
936 set ::tcltest::isoLocale French
941 set ::tcltest::testConfig(hasIsoLocale) \
942 [string length [::tcltest::set_iso8859_1_locale]]
943 ::tcltest::restore_locale
947 # procedures that are Tk specific
950 if {[info exists tk_version]} {
952 # If the main window isn't already mapped (e.g. because the tests are
953 # being run automatically) , specify a precise size for it so that the
954 # user won't have to position it manually.
956 if {![winfo ismapped .]} {
961 # The following code can be used to perform tests involving a second
962 # process running in the background.
964 # Locate the tktest executable
966 set ::tcltest::tktest [info nameofexecutable]
967 if {$::tcltest::tktest == "{}"} {
968 set ::tcltest::tktest {}
970 "Unable to find tktest executable, skipping multiple process tests."
973 # Create background process
975 proc ::tcltest::setupbg args {
976 if {$::tcltest::tktest == ""} {
977 error "you're not running tktest so setupbg should not have been called"
979 if {[info exists ::tcltest::fd] && ($::tcltest::fd != "")} {
983 # The following code segment cannot be run on Windows in Tk8.1b2
984 # This bug is logged as a pipe bug (bugID 1495).
987 if {$tcl_platform(platform) != "windows"} {
988 set ::tcltest::fd [open "|[list $::tcltest::tktest -geometry +0+0 -name tktest] $args" r+]
989 puts $::tcltest::fd "puts foo; flush stdout"
991 if {[gets $::tcltest::fd data] < 0} {
992 error "unexpected EOF from \"$::tcltest::tktest\""
994 if {[string compare $data foo]} {
995 error "unexpected output from background process \"$data\""
997 fileevent $::tcltest::fd readable bgReady
1001 # Send a command to the background process, catching errors and
1002 # flushing I/O channels
1004 proc ::tcltest::dobg {command} {
1005 puts $::tcltest::fd "catch [list $command] msg; update; puts \$msg; puts **DONE**; flush stdout"
1006 flush $::tcltest::fd
1007 set ::tcltest::bgDone 0
1008 set ::tcltest::bgData {}
1009 tkwait variable ::tcltest::bgDone
1010 set ::tcltest::bgData
1013 # Data arrived from background process. Check for special marker
1014 # indicating end of data for this command, and make data available
1015 # to dobg procedure.
1017 proc ::tcltest::bgReady {} {
1018 set x [gets $::tcltest::fd]
1019 if {[eof $::tcltest::fd]} {
1020 fileevent $::tcltest::fd readable {}
1021 set ::tcltest::bgDone 1
1022 } elseif {$x == "**DONE**"} {
1023 set ::tcltest::bgDone 1
1025 append ::tcltest::bgData $x
1029 # Exit the background process, and close the pipes
1031 proc ::tcltest::cleanupbg {} {
1033 puts $::tcltest::fd "exit"
1034 close $::tcltest::fd
1036 set ::tcltest::fd ""
1039 # Clean up focus after using generate event, which
1040 # can leave the window manager with the wrong impression
1041 # about who thinks they have the focus. (BW)
1043 proc ::tcltest::fixfocus {} {
1044 catch {destroy .focus}
1046 wm geometry .focus +0+0
1048 .focus.e insert 0 "fixfocus"
1051 focus -force .focus.e
1058 # Kill all threads except for the main thread.
1059 # Do nothing if testthread is not defined.
1065 # Returns the number of existing threads.
1067 if {[info commands testthread] != {}} {
1068 proc ::tcltest::threadReap {} {
1069 testthread errorproc ThreadNullError
1070 while {[llength [testthread names]] > 1} {
1071 foreach tid [testthread names] {
1072 if {$tid != $::tcltest::mainThread} {
1073 catch {testthread send -async $tid {testthread exit}}
1078 testthread errorproc ThreadError
1079 return [llength [testthread names]]
1082 proc ::tcltest::threadReap {} {
1087 # Need to catch the import because it fails if defs.tcl is sourced
1090 catch {namespace import ::tcltest::*}