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