ltrace.exp revision 36a2b2565068bf4f8fa0a5edfb844a61afb50683
1# This file is part of ltrace. 2# Copyright (C) 2012 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 "-?*" $a]} { 483 lappend options [string range $a 1 end] 484 } elseif {[string match "*.so" $a]} { 485 lappend options "shlib=$a" 486 } elseif {[string match "*.o" $a]} { 487 lappend objects $a 488 } else { 489 lappend sources $a 490 } 491 } 492 493 if {[string equal $dest {}]} { 494 set dest [LtraceTempFile "exe-XXXXXXXXXX"] 495 } elseif {[string equal $dest ".pie"]} { 496 set dest [LtraceTempFile "pie-XXXXXXXXXX"] 497 } else { 498 set dest $objdir/$subdir/$dest 499 } 500 501 verbose "ltraceCompile: dest $dest" 502 verbose " : options $options" 503 verbose " : sources $sources" 504 verbose " : objects $objects" 505 506 if {![LtraceCompileObjects $sources \ 507 [concat $options $extraObjOptions] newObjects]} { 508 return {} 509 } 510 set objects [concat $objects $newObjects] 511 512 verbose "ltraceCompile: objects $objects" 513 514 if {[ltrace_compile $objects $dest $type \ 515 [concat $options $extraOptions]] != ""} { 516 return {} 517 } 518 519 return $dest 520} 521 522# ltraceRun -- 523# 524# Invoke command identified by LTRACE global variable with given 525# ARGS. A logfile redirection is automatically ordered by 526# passing -o and a temporary file name. 527# 528# Arguments: 529# args Arguments to ltrace binary. 530# 531# Results: 532# Returns name of logfile. The "exec" command that it uses 533# under the hood fails loudly if the process exits with a 534# non-zero exit status, or uses stderr in any way. 535 536proc ltraceRun {args} { 537 global LTRACE 538 global objdir 539 global subdir 540 541 set LdPath [ld_library_path $objdir/$subdir] 542 set logfile [ltraceSource ltrace {}] 543 544 # Run ltrace. expect will show an error if this doesn't exit with 545 # zero exit status (i.e. ltrace fails, valgrind finds errors, 546 # etc.). 547 548 set command "exec env LD_LIBRARY_PATH=$LdPath $LTRACE -o $logfile $args" 549 verbose $command 550 if {[catch {eval $command}] } { 551 fail "test case execution failed" 552 send_error -- $command 553 send_error -- $::errorInfo 554 } 555 556 return $logfile 557} 558 559# ltraceDone -- 560# 561# Wipes or dumps all temporary files after a test suite has 562# finished. 563# 564# Results: 565# Doesn't return anything. Wipes all files gathered in 566# LTRACE_TEMP_FILES. If SAVE_TEMPS is defined and true, the 567# temporary files are not wiped, but their names are dumped 568# instead. Contents of LTRACE_TEMP_FILES are deleted in any 569# case. 570 571proc ltraceDone {} { 572 global SAVE_TEMPS 573 global LTRACE_TEMP_FILES 574 575 if {[info exists SAVE_TEMPS] && $SAVE_TEMPS} { 576 foreach tmp $LTRACE_TEMP_FILES { 577 send_user "$tmp\n" 578 } 579 } else { 580 WipeFiles $LTRACE_TEMP_FILES 581 } 582 583 set LTRACE_TEMP_FILES {} 584 return 585} 586 587# Grep -- 588# 589# Return number of lines in a given file, matching a given 590# regular expression. 591# 592# Arguments: 593# logfile File to search through. 594# 595# re Regular expression to match. 596# 597# Results: 598# Returns number of matching lines. 599 600proc Grep {logfile re} { 601 set count 0 602 set fp [open $logfile] 603 while {[gets $fp line] >= 0} { 604 if [regexp -- $re $line] { 605 incr count 606 } 607 } 608 close $fp 609 return $count 610} 611 612# ltraceMatch1 -- 613# 614# Look for a pattern in a given logfile, comparing number of 615# occurences of the pattern with expectation. 616# 617# Arguments: 618# logfile The name of file where to look for patterns. 619# 620# pattern Regular expression pattern to look for. 621# 622# op Operator to compare number of occurences. 623# 624# expect Second operand to op, the first being number of 625# occurences of pattern. 626# 627# Results: 628# Doesn't return anything, but calls fail or pass depending on 629# whether the patterns matches expectation. 630 631proc ltraceMatch1 {logfile pattern {op ==} {expect 1}} { 632 set count [Grep $logfile $pattern] 633 set msgMain "$pattern appears in $logfile $count times" 634 set msgExpect ", expected $op $expect" 635 636 if {[eval expr $count $op $expect]} { 637 pass $msgMain 638 } else { 639 fail $msgMain$msgExpect 640 } 641 return 642} 643 644# ltraceMatch -- 645# 646# Look for series of patterns in a given logfile, comparing 647# number of occurences of each pattern with expectations. 648# 649# Arguments: 650# logfile The name of file where to look for patterns. 651# 652# patterns List of patterns to look for. ltraceMatch1 is called 653# on each of these in turn. 654# 655# Results: 656# 657# Doesn't return anything, but calls fail or pass depending on 658# whether each of the patterns holds. 659 660proc ltraceMatch {logfile patterns} { 661 foreach pat $patterns { 662 eval ltraceMatch1 [linsert $pat 0 $logfile] 663 } 664 return 665} 666 667# ltraceLibTest -- 668# 669# Generate a binary, a library (liblib.so) and a config file. 670# Run the binary using ltraceRun, passing it -F to load the 671# config file. 672# 673# Arguments: 674# conf Contents of ltrace config file. 675# 676# cdecl Contents of header file. 677# 678# libcode Contents of library implementation file. 679# 680# maincode Contents of function "main". 681# 682# params Additional parameters to pass to ltraceRun. 683# 684# Results: 685# 686# Returns whatever ltraceRun returns. 687 688proc ltraceLibTest {conf cdecl libcode maincode {params ""}} { 689 set conffile [ltraceSource conf $conf] 690 set lib [ltraceCompile liblib.so [ltraceSource c [concat $cdecl $libcode]]] 691 set bin [ltraceCompile {} $lib \ 692 [ltraceSource c \ 693 [concat $cdecl "int main(void) {" $maincode "}"]]] 694 695 return [eval [concat "ltraceRun -F $conffile " $params "-- $bin"]] 696} 697 698# 699# ltrace_options OPTIONS_LIST 700# Pass ltrace commandline options. 701# 702proc ltrace_options { args } { 703 704 global LTRACE_OPTIONS 705 set LTRACE_OPTIONS $args 706} 707 708# 709# ltrace_args ARGS_LIST 710# Pass ltrace'd program its own commandline options. 711# 712proc ltrace_args { args } { 713 714 global LTRACE_ARGS 715 set LTRACE_ARGS $args 716} 717 718# 719# handle run-time library paths 720# 721proc ld_library_path { args } { 722 723 set ALL_LIBRARY_PATHS { } 724 if [info exists LD_LIBRARY_PATH] { 725 lappend ALL_LIBRARY_PATHS $LD_LIBRARY_PATH 726 } 727 global libelf_LD_LIBRARY_PATH 728 if {[string length $libelf_LD_LIBRARY_PATH] > 0} { 729 lappend ALL_LIBRARY_PATHS $libelf_LD_LIBRARY_PATH 730 } 731 global libunwind_LD_LIBRARY_PATH 732 if {[string length $libunwind_LD_LIBRARY_PATH] > 0} { 733 lappend ALL_LIBRARY_PATHS $libunwind_LD_LIBRARY_PATH 734 } 735 lappend ALL_LIBRARY_PATHS $args 736 join $ALL_LIBRARY_PATHS ":" 737} 738 739# 740# ltrace_runtest LD_LIBRARY_PATH BIN FILE 741# Trace the execution of BIN and return result. 742# 743# BIN is program-under-test. 744# LD_LIBRARY_PATH is the env for program-under-test to run. 745# FILE is to save the output from ltrace with default name $BIN.ltrace. 746# Retrun output from ltrace. 747# 748proc ltrace_runtest { args } { 749 750 global LTRACE 751 global LTRACE_OPTIONS 752 global LTRACE_ARGS 753 754 verbose "LTRACE = $LTRACE" 755 756 set LD_LIBRARY_PATH_ [ld_library_path [lindex $args 0]] 757 set BIN [lindex $args 1] 758 759 # specify the output file, the default one is $BIN.ltrace 760 if [llength $args]==3 then { 761 set file [lindex $args 2] 762 } else { 763 set file $BIN.ltrace 764 } 765 766 # Remove the file first. If ltrace fails to overwrite it, we 767 # would be comparing output to an obsolete run. 768 exec rm -f $file 769 770 # append this option to LTRACE_OPTIONS. 771 lappend LTRACE_OPTIONS "-o" 772 lappend LTRACE_OPTIONS "$file" 773 verbose "LTRACE_OPTIONS = $LTRACE_OPTIONS" 774 set command "exec sh -c {export LD_LIBRARY_PATH=$LD_LIBRARY_PATH_; \ 775 $LTRACE $LTRACE_OPTIONS $BIN $LTRACE_ARGS;exit}" 776 #ltrace the PUT. 777 if {[catch $command output]} { 778 fail "test case execution failed" 779 send_error -- $command 780 send_error -- $::errorInfo 781 } 782 783 # return output from ltrace. 784 return $output 785} 786 787# 788# ltrace_verify_output FILE_TO_SEARCH PATTERN MAX_LINE 789# Verify the ltrace output by comparing the number of PATTERN in 790# FILE_TO_SEARCH with INSTANCE_NO. Do not specify INSTANCE_NO if 791# instance number is ignored in this test. 792# Reutrn: 793# 0 = number of PATTERN in FILE_TO_SEARCH inqual to INSTANCE_NO. 794# 1 = number of PATTERN in FILE_TO_SEARCH qual to INSTANCE_NO. 795# 796proc ltrace_verify_output { file_to_search pattern {instance_no 0} {grep_command "grep"}} { 797 798 # compute the number of PATTERN in FILE_TO_SEARCH by grep and wc. 799 catch "exec sh -c {$grep_command \"$pattern\" $file_to_search | wc -l ;exit}" output 800 verbose "output = $output" 801 802 if [ regexp "syntax error" $output ] then { 803 fail "Invalid regular expression $pattern" 804 } elseif { $instance_no == 0 } then { 805 if { $output == 0 } then { 806 fail "Fail to find $pattern in $file_to_search" 807 } else { 808 pass "$pattern in $file_to_search" 809 } 810 } elseif { $output >= $instance_no } then { 811 pass "$pattern in $file_to_search for $output times" 812 } else { 813 fail "$pattern in $file_to_search for $output times, should be $instance_no" 814 } 815} 816