ltrace.exp revision 3aac545b7947db4225a37408bc1c765d5e32ff7c
1# This file is part of ltrace. 2# Copyright (C) 2012, 2013 Petr Machata, Red Hat Inc. 3# Copyright (C) 2006 Yao Qi, IBM Corporation 4# 5# This program is free software; you can redistribute it and/or 6# modify it under the terms of the GNU General Public License as 7# published by the Free Software Foundation; either version 2 of the 8# License, or (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, but 11# WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13# General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program; if not, write to the Free Software 17# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 18# 02110-1301 USA 19 20# Generic ltrace test subroutines that should work for any target. If these 21# need to be modified for any target, it can be done with a variable 22# or by passing arguments. 23 24source $objdir/env.exp 25 26if [info exists TOOL_EXECUTABLE] { 27 set LTRACE $TOOL_EXECUTABLE 28} else { 29 set LTRACE $objdir/../ltrace 30} 31 32if {[info exists VALGRIND] && ![string equal $VALGRIND {}]} { 33 verbose "Running under valgrind command: `$VALGRIND'" 34 set LTRACE "$VALGRIND $LTRACE" 35} 36 37set LTRACE_OPTIONS {} 38set LTRACE_ARGS {} 39set LTRACE_TEMP_FILES {} 40 41# ltrace_compile SOURCE DEST TYPE OPTIONS 42# 43# Compile PUT(program under test) by native compiler. ltrace_compile runs 44# the right compiler, and TCL captures the output, and I evaluate the output. 45# 46# SOURCE is the name of program under test, with full directory. 47# DEST is the name of output of compilation, with full directory. 48# TYPE is an enum-like variable to affect the format or result of compiler 49# output. Values: 50# executable if output is an executable. 51# object if output is an object. 52# OPTIONS is option to compiler in this compilation. 53proc ltrace_compile {source dest type options} { 54 global LTRACE_TESTCASE_OPTIONS; 55 56 if {![string equal "object" $type]} { 57 # Add platform-specific options if a shared library was specified using 58 # "shlib=librarypath" in OPTIONS. 59 set new_options "" 60 set shlib_found 0 61 62 foreach opt $options { 63 if [regexp {^shlib=(.*)} $opt dummy_var shlib_name] { 64 if [test_compiler_info "xlc*"] { 65 # IBM xlc compiler doesn't accept shared library named other 66 # than .so: use "-Wl," to bypass this 67 lappend source "-Wl,$shlib_name" 68 } else { 69 lappend source $shlib_name 70 } 71 72 if {$shlib_found == 0} { 73 set shlib_found 1 74 75 if { ([test_compiler_info "gcc-*"]&& ([istarget "powerpc*-*-aix*"]|| [istarget "rs6000*-*-aix*"] ))} { 76 lappend options "additional_flags=-L${objdir}/${subdir}" 77 } elseif { [istarget "mips-sgi-irix*"] } { 78 lappend options "additional_flags=-rpath ${objdir}/${subdir}" 79 } 80 } 81 82 } else { 83 lappend new_options $opt 84 } 85 } 86 87 #end of for loop 88 set options $new_options 89 } 90 91 # dump some information for debug purpose. 92 verbose "options are $options" 93 verbose "source is $source $dest $type $options" 94 95 # Wipe the DEST file, so that we don't end up running an obsolete 96 # version of the binary. 97 exec rm -f $dest 98 99 set result [target_compile $source $dest $type $options]; 100 verbose "result is $result" 101 regsub "\[\r\n\]*$" "$result" "" result; 102 regsub "^\[\r\n\]*" "$result" "" result; 103 if { $result != "" && [lsearch $options quiet] == -1} { 104 clone_output "compile failed for ltrace test, $result" 105 } 106 return $result; 107} 108 109proc get_compiler_info {binfile args} { 110 # For compiler.c and compiler.cc 111 global srcdir 112 113 # I am going to play with the log to keep noise out. 114 global outdir 115 global tool 116 117 # These come from compiler.c or compiler.cc 118 global compiler_info 119 120 # Legacy global data symbols. 121 #global gcc_compiled 122 123 # Choose which file to preprocess. 124 set ifile "${srcdir}/lib/compiler.c" 125 if { [llength $args] > 0 && [lindex $args 0] == "c++" } { 126 set ifile "${srcdir}/lib/compiler.cc" 127 } 128 129 # Run $ifile through the right preprocessor. 130 # Toggle ltrace.log to keep the compiler output out of the log. 131 #log_file 132 set cppout [ ltrace_compile "${ifile}" "" preprocess [list "$args" quiet] ] 133 #log_file -a "$outdir/$tool.log" 134 135 # Eval the output. 136 set unknown 0 137 foreach cppline [ split "$cppout" "\n" ] { 138 if { [ regexp "^#" "$cppline" ] } { 139 # line marker 140 } elseif { [ regexp "^\[\n\r\t \]*$" "$cppline" ] } { 141 # blank line 142 } elseif { [ regexp "^\[\n\r\t \]*set\[\n\r\t \]" "$cppline" ] } { 143 # eval this line 144 verbose "get_compiler_info: $cppline" 2 145 eval "$cppline" 146 } else { 147 # unknown line 148 verbose "get_compiler_info: $cppline" 149 set unknown 1 150 } 151 } 152 153 # Reset to unknown compiler if any diagnostics happened. 154 if { $unknown } { 155 set compiler_info "unknown" 156 } 157 return 0 158} 159 160proc test_compiler_info { {compiler ""} } { 161 global compiler_info 162 163 if [string match "" $compiler] { 164 if [info exists compiler_info] { 165 verbose "compiler_info=$compiler_info" 166 # if no arg, return the compiler_info string 167 return $compiler_info 168 } else { 169 perror "No compiler info found." 170 } 171 } 172 173 return [string match $compiler $compiler_info] 174} 175 176proc ltrace_compile_shlib {sources dest options} { 177 set obj_options $options 178 verbose "+++++++ [test_compiler_info]" 179 switch -glob [test_compiler_info] { 180 "xlc-*" { 181 lappend obj_options "additional_flags=-qpic" 182 } 183 "gcc-*" { 184 if { !([istarget "powerpc*-*-aix*"] 185 || [istarget "rs6000*-*-aix*"]) } { 186 lappend obj_options "additional_flags=-fpic" 187 } 188 } 189 "xlc++-*" { 190 lappend obj_options "additional_flags=-qpic" 191 } 192 193 default { 194 fail "Bad compiler!" 195 } 196 } 197 198 if {![LtraceCompileObjects $sources $obj_options objects]} { 199 return -1 200 } 201 202 set link_options $options 203 if { [test_compiler_info "xlc-*"] || [test_compiler_info "xlc++-*"]} { 204 lappend link_options "additional_flags=-qmkshrobj" 205 } else { 206 lappend link_options "additional_flags=-shared" 207 } 208 if {[ltrace_compile "${objects}" "${dest}" executable $link_options] != ""} { 209 return -1 210 } 211 212 return 213} 214 215# WipeFiles -- 216# 217# Delete each file in the list. 218# 219# Arguments: 220# files List of files to delete. 221# 222# Results: 223# Each of the files is deleted. Files are deleted in reverse 224# order, so that directories are emptied and can be deleted 225# without using -force. Returns nothing. 226 227proc WipeFiles {files} { 228 verbose "WipeFiles: $files\n" 229 foreach f [lreverse $files] { 230 file delete $f 231 } 232} 233 234# LtraceTmpDir -- 235# 236# Guess what directory to use for temporary files. 237# This was adapted from http://wiki.tcl.tk/772 238# 239# Results: 240# A temporary directory to use. The current directory if no 241# other seems to be available. 242 243proc LtraceTmpDir {} { 244 set tmpdir [pwd] 245 246 if {[file exists "/tmp"]} { 247 set tmpdir "/tmp" 248 } 249 250 catch {set tmpdir $::env(TMP)} 251 catch {set tmpdir $::env(TEMP)} 252 catch {set tmpdir $::env(TMPDIR)} 253 254 return $tmpdir 255} 256 257set LTRACE_TEMP_DIR [LtraceTmpDir] 258 259# LtraceTempFile -- 260# 261# Create a temporary file according to a pattern, and return its 262# name. This behaves similar to mktemp. We don't use mktemp 263# directly, because on older systems, mktemp requires that the 264# array of X's be at the very end of the string, while ltrace 265# temporary files need to have suffixes. 266# 267# Arguments: 268# pat Pattern to use. See mktemp for description of its format. 269# 270# Results: 271# Creates the temporary file and returns its name. The name is 272# also appended to LTRACE_TEMP_FILES. 273 274proc LtraceTempFile {pat} { 275 global LTRACE_TEMP_FILES 276 global LTRACE_TEMP_DIR 277 278 set letters "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 279 set numLetters [string length $letters] 280 281 if {![regexp -indices {(X{3,})} $pat m]} { 282 send_error -- "Pattern $pat contains insufficient number of X's." 283 return {} 284 } 285 286 set start [lindex $m 0] 287 set end [lindex $m 1] 288 set len [expr {$end - $start + 1}] 289 290 for {set j 0} {$j < 10} {incr j} { 291 292 # First, generate a random name. 293 294 set randstr {} 295 for {set i 0} {$i < $len} {incr i} { 296 set r [expr {int(rand() * $numLetters)}] 297 append randstr [string index $letters $r] 298 } 299 set prefix [string range $pat 0 [expr {$start - 1}]] 300 set suffix [string range $pat [expr {$end + 1}] end] 301 set name [file join $LTRACE_TEMP_DIR "$prefix$randstr$suffix"] 302 303 # Now check that it's free. This is of course racy, but this 304 # is a test suite, not anything used in actual production. 305 306 if {[file exists $name]} { 307 continue 308 } 309 310 # We don't bother attempting to open the file. Downstream 311 # code can do it itself. 312 313 lappend LTRACE_TEMP_FILES $name 314 return $name 315 } 316 317 send_error -- "Couldn't create a temporary file for pattern $pat." 318 return 319} 320 321# ltraceNamedSource -- 322# 323# Create a file named FILENAME, and prime it with TEXT. If 324# REMEMBERTEMP, add the file into LTRACE_TEMP_FILES, so that 325# ltraceDone (or rather WipeFiles) erases it later. 326# 327# Arguments: 328# filename Name of the file to create. 329# 330# text Contents of the new file. 331# 332# rememberTemp Whether to add filename to LTRACE_TEMP_FILES. 333# 334# Results: 335# Returns $filename, which now refers to a file with contents 336# given by TEXT. 337 338proc ltraceNamedSource {filename text {rememberTemp 1}} { 339 global LTRACE_TEMP_FILES 340 341 set chan [open $filename w] 342 puts $chan $text 343 close $chan 344 345 if $rememberTemp { 346 lappend LTRACE_TEMP_FILES $filename 347 } 348 349 return $filename 350} 351 352# ltraceSource -- 353# 354# Create a temporary file with a given suffix and prime it with 355# contents given in text. 356# 357# Arguments: 358# suffix Suffix of the temp file to be created. 359# 360# text Contents of the new file. 361# 362# Results: 363# Returns file name of created file. 364 365proc ltraceSource {suffix text} { 366 return [ltraceNamedSource \ 367 [LtraceTempFile "lt-XXXXXXXXXX.$suffix"] $text 0] 368} 369 370# ltraceDir -- 371# 372# Create a temporary directory. 373# 374# Arguments: 375# 376# Results: 377# Returns name of created directory. 378 379proc ltraceDir {} { 380 set ret [LtraceTempFile "lt-XXXXXXXXXX.dir"] 381 file mkdir $ret 382 return $ret 383} 384 385# LtraceCompileObjects -- 386# 387# Compile each source file into an object file. ltrace_compile 388# is called to perform actual compilation. 389# 390# Arguments: 391# sources List of source files. 392# 393# options Options for ltrace_compile. 394# 395# retName Variable where the resulting list of object names is 396# to be placed. 397# Results: 398# Returns true or false depending on whether there were any 399# errors. If it returns true, then variable referenced by 400# retName contains list of object files, produced by compiling 401# files in sources list. 402 403proc LtraceCompileObjects {sources options retName} { 404 global LTRACE_TEMP_FILES 405 upvar $retName ret 406 set ret {} 407 408 foreach source $sources { 409 set sourcebase [file tail $source] 410 set dest $source.o 411 lappend LTRACE_TEMP_FILES $dest 412 verbose "LtraceCompileObjects: $source -> $dest" 413 if {[ltrace_compile $source $dest object $options] != ""} { 414 return false 415 } 416 lappend ret $dest 417 } 418 419 return true 420} 421 422# ltraceCompile -- 423# 424# This attempts to compile a binary from sources given in ARGS. 425# 426# Arguments: 427# dest A binary to be produced. If this is called lib*.so, then 428# the resulting binary will be a library, if *.pie, it 429# will be a PIE, otherwise it will be an executable. In 430# theory this could also be *.o for "object" and *.i for 431# "preprocess" for cases with one source file, but that 432# is not supported at the moment. The binary will be 433# placed in $objdir/$subdir. 434# 435# args List of options and source files. 436# 437# Options are arguments that start with a dash. Options 438# (sans the dash) are passed to ltrace_compile. 439# 440# Source files named lib*.so are libraries. Those are 441# passed to ltrace_compile as options shlib=X. Source 442# files named *.o are objects. The remaining source 443# files are first compiled (by LtraceCompileObjects) and 444# then together with other objects passed to 445# ltrace_compile to produce resulting binary. 446# 447# Any argument that is empty string prompts the function 448# to fail. This is done so that errors caused by 449# ltraceSource (or similar) distribute naturally 450# upwards. 451# 452# Results: 453# This compiles given source files into a binary. Full file name 454# of that binary is returned. Empty string is returned in case 455# of a failure. 456 457proc ltraceCompile {dest args} { 458 global objdir 459 global subdir 460 461 get_compiler_info {} c 462 get_compiler_info {} c++ 463 464 if {[string match "lib*.so" $dest]} { 465 set type "library" 466 set extraObjOptions "additional_flags=-fpic" 467 set extraOptions "additional_flags=-shared" 468 } elseif {[string match "*.pie" $dest]} { 469 set type "executable" 470 set extraObjOptions "additional_flags=-fpic" 471 set extraOptions "additional_flags=-pie" 472 } else { 473 set type "executable" 474 set extraObjOptions {} 475 set extraOptions {} 476 } 477 478 set options {} 479 set sources {} 480 set objects {} 481 foreach a $args { 482 if {[string match "-l*" $a]} { 483 lappend options "shlib=$a" 484 } elseif {[string match "-?*" $a]} { 485 lappend options [string range $a 1 end] 486 } elseif {[string match "*.so" $a]} { 487 lappend options "shlib=$a" 488 } elseif {[string match "*.o" $a]} { 489 lappend objects $a 490 } else { 491 lappend sources $a 492 } 493 } 494 495 if {[string equal $dest {}]} { 496 set dest [LtraceTempFile "exe-XXXXXXXXXX"] 497 } elseif {[string equal $dest ".pie"]} { 498 set dest [LtraceTempFile "pie-XXXXXXXXXX"] 499 } else { 500 set dest $objdir/$subdir/$dest 501 } 502 503 verbose "ltraceCompile: dest $dest" 504 verbose " : options $options" 505 verbose " : sources $sources" 506 verbose " : objects $objects" 507 508 if {![LtraceCompileObjects $sources \ 509 [concat $options $extraObjOptions] newObjects]} { 510 return {} 511 } 512 set objects [concat $objects $newObjects] 513 514 verbose "ltraceCompile: objects $objects" 515 516 if {[ltrace_compile $objects $dest $type \ 517 [concat $options $extraOptions]] != ""} { 518 return {} 519 } 520 521 return $dest 522} 523 524# ltraceRun -- 525# 526# Invoke command identified by LTRACE global variable with given 527# ARGS. A logfile redirection is automatically ordered by 528# passing -o and a temporary file name. 529# 530# Arguments: 531# args Arguments to ltrace binary. 532# 533# Results: 534# Returns name of logfile. The "exec" command that it uses 535# under the hood fails loudly if the process exits with a 536# non-zero exit status, or uses stderr in any way. 537 538proc ltraceRun {args} { 539 global LTRACE 540 global objdir 541 global subdir 542 543 set LdPath [ld_library_path $objdir/$subdir] 544 set logfile [ltraceSource ltrace {}] 545 546 # Run ltrace. expect will show an error if this doesn't exit with 547 # zero exit status (i.e. ltrace fails, valgrind finds errors, 548 # etc.). 549 550 set command "exec env LD_LIBRARY_PATH=$LdPath $LTRACE -o $logfile $args" 551 verbose $command 552 if {[catch {eval $command}] } { 553 fail "test case execution failed" 554 send_error -- $command 555 send_error -- $::errorInfo 556 } 557 558 return $logfile 559} 560 561# ltraceDone -- 562# 563# Wipes or dumps all temporary files after a test suite has 564# finished. 565# 566# Results: 567# Doesn't return anything. Wipes all files gathered in 568# LTRACE_TEMP_FILES. If SAVE_TEMPS is defined and true, the 569# temporary files are not wiped, but their names are dumped 570# instead. Contents of LTRACE_TEMP_FILES are deleted in any 571# case. 572 573proc ltraceDone {} { 574 global SAVE_TEMPS 575 global LTRACE_TEMP_FILES 576 577 if {[info exists SAVE_TEMPS] && $SAVE_TEMPS} { 578 foreach tmp $LTRACE_TEMP_FILES { 579 send_user "$tmp\n" 580 } 581 } else { 582 WipeFiles $LTRACE_TEMP_FILES 583 } 584 585 set LTRACE_TEMP_FILES {} 586 return 587} 588 589# Grep -- 590# 591# Return number of lines in a given file, matching a given 592# regular expression. 593# 594# Arguments: 595# logfile File to search through. 596# 597# re Regular expression to match. 598# 599# Results: 600# Returns number of matching lines. 601 602proc Grep {logfile re} { 603 set count 0 604 set fp [open $logfile] 605 while {[gets $fp line] >= 0} { 606 if [regexp -- $re $line] { 607 incr count 608 } 609 } 610 close $fp 611 return $count 612} 613 614# ltraceMatch1 -- 615# 616# Look for a pattern in a given logfile, comparing number of 617# occurences of the pattern with expectation. 618# 619# Arguments: 620# logfile The name of file where to look for patterns. 621# 622# pattern Regular expression pattern to look for. 623# 624# op Operator to compare number of occurences. 625# 626# expect Second operand to op, the first being number of 627# occurences of pattern. 628# 629# Results: 630# Doesn't return anything, but calls fail or pass depending on 631# whether the patterns matches expectation. 632 633proc ltraceMatch1 {logfile pattern {op ==} {expect 1}} { 634 set count [Grep $logfile $pattern] 635 set msgMain "$pattern appears in $logfile $count times" 636 set msgExpect ", expected $op $expect" 637 638 if {[eval expr $count $op $expect]} { 639 pass $msgMain 640 } else { 641 fail $msgMain$msgExpect 642 } 643 return 644} 645 646# ltraceMatch -- 647# 648# Look for series of patterns in a given logfile, comparing 649# number of occurences of each pattern with expectations. 650# 651# Arguments: 652# logfile The name of file where to look for patterns. 653# 654# patterns List of patterns to look for. ltraceMatch1 is called 655# on each of these in turn. 656# 657# Results: 658# 659# Doesn't return anything, but calls fail or pass depending on 660# whether each of the patterns holds. 661 662proc ltraceMatch {logfile patterns} { 663 foreach pat $patterns { 664 eval ltraceMatch1 [linsert $pat 0 $logfile] 665 } 666 return 667} 668 669# ltraceLibTest -- 670# 671# Generate a binary, a library (liblib.so) and a config file. 672# Run the binary using ltraceRun, passing it -F to load the 673# config file. 674# 675# Arguments: 676# conf Contents of ltrace config file. 677# 678# cdecl Contents of header file. 679# 680# libcode Contents of library implementation file. 681# 682# maincode Contents of function "main". 683# 684# params Additional parameters to pass to ltraceRun. 685# 686# Results: 687# 688# Returns whatever ltraceRun returns. 689 690proc ltraceLibTest {conf cdecl libcode maincode {params ""}} { 691 set conffile [ltraceSource conf $conf] 692 set lib [ltraceCompile liblib.so [ltraceSource c [concat $cdecl $libcode]]] 693 set bin [ltraceCompile {} $lib \ 694 [ltraceSource c \ 695 [concat $cdecl "int main(void) {" $maincode "}"]]] 696 697 return [eval [concat "ltraceRun -F $conffile " $params "-- $bin"]] 698} 699 700# 701# ltrace_options OPTIONS_LIST 702# Pass ltrace commandline options. 703# 704proc ltrace_options { args } { 705 706 global LTRACE_OPTIONS 707 set LTRACE_OPTIONS $args 708} 709 710# 711# ltrace_args ARGS_LIST 712# Pass ltrace'd program its own commandline options. 713# 714proc ltrace_args { args } { 715 716 global LTRACE_ARGS 717 set LTRACE_ARGS $args 718} 719 720# 721# handle run-time library paths 722# 723proc ld_library_path { args } { 724 725 set ALL_LIBRARY_PATHS { } 726 if [info exists LD_LIBRARY_PATH] { 727 lappend ALL_LIBRARY_PATHS $LD_LIBRARY_PATH 728 } 729 global libelf_LD_LIBRARY_PATH 730 if {[string length $libelf_LD_LIBRARY_PATH] > 0} { 731 lappend ALL_LIBRARY_PATHS $libelf_LD_LIBRARY_PATH 732 } 733 global libunwind_LD_LIBRARY_PATH 734 if {[string length $libunwind_LD_LIBRARY_PATH] > 0} { 735 lappend ALL_LIBRARY_PATHS $libunwind_LD_LIBRARY_PATH 736 } 737 lappend ALL_LIBRARY_PATHS $args 738 join $ALL_LIBRARY_PATHS ":" 739} 740 741# 742# ltrace_runtest LD_LIBRARY_PATH BIN FILE 743# Trace the execution of BIN and return result. 744# 745# BIN is program-under-test. 746# LD_LIBRARY_PATH is the env for program-under-test to run. 747# FILE is to save the output from ltrace with default name $BIN.ltrace. 748# Retrun output from ltrace. 749# 750proc ltrace_runtest { args } { 751 752 global LTRACE 753 global LTRACE_OPTIONS 754 global LTRACE_ARGS 755 756 verbose "LTRACE = $LTRACE" 757 758 set LD_LIBRARY_PATH_ [ld_library_path [lindex $args 0]] 759 set BIN [lindex $args 1] 760 761 # specify the output file, the default one is $BIN.ltrace 762 if [llength $args]==3 then { 763 set file [lindex $args 2] 764 } else { 765 set file $BIN.ltrace 766 } 767 768 # Remove the file first. If ltrace fails to overwrite it, we 769 # would be comparing output to an obsolete run. 770 exec rm -f $file 771 772 # append this option to LTRACE_OPTIONS. 773 lappend LTRACE_OPTIONS "-o" 774 lappend LTRACE_OPTIONS "$file" 775 verbose "LTRACE_OPTIONS = $LTRACE_OPTIONS" 776 set command "exec sh -c {export LD_LIBRARY_PATH=$LD_LIBRARY_PATH_; \ 777 $LTRACE $LTRACE_OPTIONS $BIN $LTRACE_ARGS;exit}" 778 #ltrace the PUT. 779 if {[catch $command output]} { 780 fail "test case execution failed" 781 send_error -- $command 782 send_error -- $::errorInfo 783 } 784 785 # return output from ltrace. 786 return $output 787} 788 789# 790# ltrace_verify_output FILE_TO_SEARCH PATTERN MAX_LINE 791# Verify the ltrace output by comparing the number of PATTERN in 792# FILE_TO_SEARCH with INSTANCE_NO. Do not specify INSTANCE_NO if 793# instance number is ignored in this test. 794# Reutrn: 795# 0 = number of PATTERN in FILE_TO_SEARCH inqual to INSTANCE_NO. 796# 1 = number of PATTERN in FILE_TO_SEARCH qual to INSTANCE_NO. 797# 798proc ltrace_verify_output { file_to_search pattern {instance_no 0} {grep_command "grep"}} { 799 800 # compute the number of PATTERN in FILE_TO_SEARCH by grep and wc. 801 catch "exec sh -c {$grep_command \"$pattern\" $file_to_search | wc -l ;exit}" output 802 verbose "output = $output" 803 804 if [ regexp "syntax error" $output ] then { 805 fail "Invalid regular expression $pattern" 806 } elseif { $instance_no == 0 } then { 807 if { $output == 0 } then { 808 fail "Fail to find $pattern in $file_to_search" 809 } else { 810 pass "$pattern in $file_to_search" 811 } 812 } elseif { $output >= $instance_no } then { 813 pass "$pattern in $file_to_search for $output times" 814 } else { 815 fail "$pattern in $file_to_search for $output times, should be $instance_no" 816 } 817} 818