# runtest.exp -- Test framework driver # Copyright (C) 1992-2019, 2020 Free Software Foundation, Inc. # # This file is part of DejaGnu. # # DejaGnu is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # DejaGnu is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DejaGnu; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. # This file was written by Rob Savoye . set frame_version 1.6.3-git if {![info exists argv0]} { send_error "Must use a version of Expect greater than 5.0\n" exit 1 } # trap some signals so we know whats happening. These definitions are only # temporary until we read in the library stuff # trap { send_user "\ninterrupted by user\n"; exit 130 } SIGINT trap { send_user "\nquit\n"; exit 131 } SIGQUIT trap { send_user "\nterminated\n"; exit 143 } SIGTERM # # Initialize a few global variables used by all tests. # `reset_vars' resets several of these, we define them here to document their # existence. In fact, it would be nice if all globals used by some interface # of dejagnu proper were documented here. # # Keep these all lowercase. Interface variables used by the various # testsuites (eg: the gcc testsuite) should be in all capitals # (eg: TORTURE_OPTIONS). # set mail_logs 0 ;# flag for mailing of summary and diff logs set psum_file "latest" ;# file name of previous summary to diff against set exit_status 0 ;# exit code returned by this program set xfail_flag 0 ;# indicates that a failure is expected set xfail_prms 0 ;# GNATS prms id number for this expected failure set kfail_flag 0 ;# indicates that it is a known failure set kfail_prms 0 ;# bug id for the description of the known failure set sum_file "" ;# name of the file that contains the summary log set base_dir "" ;# the current working directory set xml_file "" ;# handle on the XML file if requested set xml 0 ;# flag for requesting xml set logname "" ;# the users login name set prms_id 0 ;# GNATS prms id number set bug_id 0 ;# optional bug id number set dir "" ;# temp variable for directory names set srcdir "." ;# source directory containing the test suite set ignoretests "" ;# list of tests to not execute set objdir "." ;# directory where test case binaries live set reboot 0 set multipass "" ;# list of passes and var settings set errno ""; ;# set exit_error 1 ;# Toggle for whether to set the exit status ;# on Tcl bugs in test case drivers. # # These describe the host and target environments. # set build_triplet "" ;# type of architecture to run tests on set build_os "" ;# type of os the tests are running on set build_vendor "" ;# vendor name of the OS or workstation the test are running on set build_cpu "" ;# type of the cpu tests are running on set host_triplet "" ;# type of architecture to run tests on, sometimes remotely set host_os "" ;# type of os the tests are running on set host_vendor "" ;# vendor name of the OS or workstation the test are running on set host_cpu "" ;# type of the cpu tests are running on set target_triplet "" ;# type of architecture to run tests on, final remote set target_os "" ;# type of os the tests are running on set target_vendor "" ;# vendor name of the OS or workstation the test are running on set target_cpu "" ;# type of the cpu tests are running on set target_alias "" ;# standard abbreviation of target set compiler_flags "" ;# the flags used by the compiler # # These set configuration file names and are local to this file. # set local_init_file site.exp ;# testsuite-local init file name set global_init_file site.exp ;# global init file name # # These are used to locate parts of the testsuite. # set testsuitedir "testsuite" ;# top-level testsuite source directory set testbuilddir "testsuite" ;# top-level testsuite object directory # # These are used for internal command-line flags. # namespace eval ::dejagnu::opt { variable keep_going 0 ;# continue after a fatal error in testcase? } # Various ccache versions provide incorrect debug info such as ignoring # different current directory, breaking GDB testsuite. set env(CCACHE_DISABLE) 1 unset -nocomplain env(CCACHE_NODISABLE) # # some convenience abbreviations # set hex "0x\[0-9A-Fa-f\]+" set decimal "\[0-9\]+" # # set the base dir (current working directory) # set base_dir [pwd] # # These are set here instead of the init module so they can be overridden # by command line options. # set all_flag 0 set binpath "" set debug 0 set options "" set outdir "." set reboot 1 set tracelevel 0 set verbose 0 set log_dialog 0 # # verbose [-n] [-log] [--] message [level] # # Print MESSAGE if the verbose level is >= LEVEL. # The default value of LEVEL is 1. # "-n" says to not print a trailing newline. # "-log" says to add the text to the log file even if it won't be printed. # Note that the apparent behaviour of `send_user' dictates that if the message # is printed it is also added to the log file. # Use "--" if MESSAGE begins with "-". # # This is defined here rather than in framework.exp so we can use it # while still loading in the support files. # proc verbose { args } { global verbose set newline 1 set logfile 0 set i 0 if { [string index [lindex $args 0] 0] eq "-" } { for { set i 0 } { $i < [llength $args] } { incr i } { if { [lindex $args $i] eq "--" } { incr i break } elseif { [lindex $args $i] eq "-n" } { set newline 0 } elseif { [lindex $args $i] eq "-log" } { set logfile 1 } elseif { [lindex $args $i] eq "-x" } { set xml 1 } elseif { [string index [lindex $args $i] 0] eq "-" } { clone_output "ERROR: verbose: illegal argument: [lindex $args $i]" return } else { break } } if { [llength $args] == $i } { clone_output "ERROR: verbose: nothing to print" return } } set level 1 if { [llength $args] > $i + 1 } { set level [lindex $args [expr { $i + 1 }]] } set message [lindex $args $i] if { $verbose >= $level } { # We assume send_user also sends the text to the log file (which # appears to be the case though the docs aren't clear on this). if { $newline } { send_user -- "$message\n" } else { send_user -- $message } } elseif { $logfile } { if { $newline } { send_log -- "$message\n" } else { send_log -- $message } } } # # Transform a tool name to get the installed name. # target_triplet is the canonical target name. target_alias is the # target name used when configure was run. # proc transform { name } { global target_triplet global target_alias global host_triplet global board if { $target_triplet eq $host_triplet } { return $name } if { $target_triplet eq "native" } { return $name } if {[board_info host exists no_transform_name]} { return $name } if { $target_triplet eq "" } { return $name } else { if {[info exists board]} { if {[board_info $board exists target_install]} { set target_install [board_info $board target_install] } } if {[target_info exists target_install]} { set target_install [target_info target_install] } if {[info exists target_alias]} { set tmp $target_alias-$name } elseif {[info exists target_install]} { if { [lsearch -exact $target_install $target_alias] >= 0 } { set tmp $target_alias-$name } else { set tmp "[lindex $target_install 0]-$name" } } verbose "Transforming $name to $tmp" return $tmp } } # # findfile arg0 [arg1] [arg2] # # Find a file and see if it exists. If you only care about the false # condition, then you'll need to pass a null "" for arg1. # arg0 is the filename to look for. If the only arg, # then that's what gets returned. If this is the # only arg, then if it exists, arg0 gets returned. # if it doesn't exist, return only the prog name. # arg1 is optional, and it's what gets returned if # the file exists. # arg2 is optional, and it's what gets returned if # the file doesn't exist. # proc findfile { args } { # look for the file verbose "Seeing if [lindex $args 0] exists." 2 if {[file exists [lindex $args 0]]} { if { [llength $args] > 1 } { verbose "Found file, returning [lindex $args 1]" return [lindex $args 1] } else { verbose "Found file, returning [lindex $args 0]" return [lindex $args 0] } } else { if { [llength $args] > 2 } { verbose "Didn't find file [lindex $args 0], returning [lindex $args 2]" return [lindex $args 2] } else { verbose "Didn't find file, returning [file tail [lindex $args 0]]" return [transform [file tail [lindex $args 0]]] } } } # # load_file [-1] [--] file1 [ file2 ... ] # # Utility to source a file. All are sourced in order unless the flag "-1" # is given in which case we stop after finding the first one. # The result is 1 if a file was found, 0 if not. # If a tcl error occurs while sourcing a file, we print an error message # and exit. # proc load_file { args } { set i 0 set only_one 0 if { [lindex $args $i] eq "-1" } { set only_one 1 incr i } if { [lindex $args $i] eq "--" } { incr i } set found 0 foreach file [lrange $args $i end] { verbose "Looking for $file" 2 # In Tcl, "file exists" fails if the filename looks like # ~/FILE and the environment variable HOME does not exist. if {! [catch {file exists $file} result] && $result} { set found 1 verbose "Found $file" if { [catch "uplevel #0 source $file"] == 1 } { send_error "ERROR: tcl error sourcing $file.\n" global errorInfo if {[info exists errorInfo]} { send_error "$errorInfo\n" } exit 1 } if { $only_one } { break } } } return $found } # # search_and_load_file -- search DIRLIST looking for FILELIST. # TYPE is used when displaying error and progress messages. # proc search_and_load_file { type filelist dirlist } { set found 0 foreach dir $dirlist { foreach initfile $filelist { set filename [file join $dir $initfile] verbose "Looking for $type $filename" 2 if {[file exists $filename]} { set found 1 set error "" if { $type ne "library file" } { send_user "Using $filename as $type.\n" } else { verbose "Loading $filename" } if {[catch "uplevel #0 source $filename" error] == 1} { global errorInfo send_error "ERROR: tcl error sourcing $type $filename.\n$error\n" if {[info exists errorInfo]} { send_error "$errorInfo\n" } exit 1 } break } } if { $found } { break } } return $found } # # Give a usage statement. # proc usage { } { global tool send_user "USAGE: runtest \[options...\]\n" send_user "\t--all, -a\t\tPrint all test output to screen\n" send_user "\t--build \[triplet\]\tThe canonical triplet of the build machine\n" send_user "\t--debug\t\t\tSet expect debugging ON\n" send_user "\t--directory name\tRun only the tests in directory 'name'\n" send_user "\t--global_init \[name\]\tThe file to load for global configuration\n" send_user "\t--help\t\t\tPrint help text\n" send_user "\t--host \[triplet\]\tThe canonical triplet of the host machine\n" send_user "\t--host_board \[name\]\tThe host board to use\n" send_user "\t--ignore \[name(s)\]\tThe names of specific tests to ignore\n" send_user "\t--keep_going\t\tContinue testing even if a script aborts\n" send_user "\t--local_init \[name\]\tThe file to load for local configuration\n" send_user "\t--log_dialog\t\t\Emit Expect output on stdout\n" send_user "\t--mail \[name(s)\]\tWhom to mail the results to\n" send_user "\t--objdir \[name\]\t\tThe test suite binary directory\n" send_user "\t--outdir \[name\]\t\tThe directory to put logs in\n" send_user "\t--reboot\t\tReboot the target (if supported)\n" send_user "\t--srcdir \[name\]\t\tThe test suite source code directory\n" send_user "\t--status\t\tSet the exit status to fail on Tcl errors\n" send_user "\t--strace \[number\]\tTurn on Expect tracing\n" send_user "\t--target \[triplet\]\tThe canonical triplet of the target board\n" send_user "\t--target_board \[name(s)\] The list of target boards to run tests on\n" send_user "\t--tool \[name(s)\]\tRun tests on these tools\n" send_user "\t--tool_exec \[name\]\tThe path to the tool executable to test\n" send_user "\t--tool_opts \[options\]\tA list of additional options to pass to the tool\n" send_user "\t--verbose, -v\t\tProduce verbose output\n" send_user "\t--version, -V\t\tPrint all relevant version numbers\n" send_user "\t--xml, -x\t\tWrite out an XML results file\n" send_user "\t--D\[0-1\]\t\tTcl debugger\n" send_user "\tscript.exp\[=arg(s)\]\tRun these tests only\n" if { [info exists tool] } { if { [info procs ${tool}_option_help] ne "" } { ${tool}_option_help } } } # # Parse the arguments the first time looking for these. We will ultimately # parse them twice. Things are complicated because: # - we want to parse --verbose early on # - we don't want config files to override command line arguments # (eg: $base_dir/$local_init_file vs --host/--target) # - we need some command line arguments before we can process some config files # (eg: --objdir before $objdir/$local_init_file, --host/--target before $DEJAGNU) # The use of `arg_host_triplet' and `arg_target_triplet' lets us avoid parsing # the arguments three times. # namespace eval ::dejagnu::command_line { variable cmd_var_list [list] proc save_cmd_var {name} { variable cmd_var_list upvar 1 $name target_var lappend cmd_var_list $name $target_var } proc restore_cmd_vars {} { variable cmd_var_list foreach {name value} $cmd_var_list { uplevel 1 set $name $value } verbose "Variables set by command line arguments restored." 4 } proc dump_cmd_vars {} { variable cmd_var_list verbose "Variables set by command line arguments:" 4 foreach {name value} $cmd_var_list { verbose " $name -> $value" 4 } } } set arg_host_triplet "" set arg_target_triplet "" set arg_build_triplet "" set argc [ llength $argv ] for { set i 0 } { $i < $argc } { incr i } { set option [lindex $argv $i] # make all options have two hyphens switch -glob -- $option { "--*" { } "-*" { set option "-$option" } } # split out the argument for options that take them switch -glob -- $option { "--*=*" { regexp {^[^=]*=(.*)$} $option nil optarg } "--bu*" - "--g*" - "--ho*" - "--ig*" - "--loc*" - "--m*" - "--n*" - "--ob*" - "--ou*" - "--sr*" - "--str*" - "--ta*" - "--di*" - "--to*" { incr i set optarg [lindex $argv $i] } } switch -glob -- $option { "--V*" - "--vers*" { # (--version) version numbers send_user "DejaGnu version\t$frame_version\n" send_user "Expect version\t[exp_version]\n" send_user "Tcl version\t[ info tclversion ]\n" exit 0 } "--bu*" { # (--build) the build host configuration set arg_build_triplet $optarg ::dejagnu::command_line::save_cmd_var arg_build_triplet continue } "--g*" { # (--global_init) the global init file name set global_init_file $optarg ::dejagnu::command_line::save_cmd_var global_init_file continue } "--host_bo*" { set host_board $optarg ::dejagnu::command_line::save_cmd_var host_board continue } "--ho*" { # (--host) the host configuration set arg_host_triplet $optarg ::dejagnu::command_line::save_cmd_var arg_host_triplet continue } "--loc*" { # (--local_init) the local init file name set local_init_file $optarg ::dejagnu::command_line::save_cmd_var local_init_file continue } "--ob*" { # (--objdir) where the test case object code lives set objdir $optarg ::dejagnu::command_line::save_cmd_var objdir continue } "--sr*" { # (--srcdir) where the testsuite source code lives set srcdir $optarg ::dejagnu::command_line::save_cmd_var srcdir continue } "--target_bo*" { set target_list $optarg ::dejagnu::command_line::save_cmd_var target_list continue } "--ta*" { # (--target) the target configuration set arg_target_triplet $optarg ::dejagnu::command_line::save_cmd_var arg_target_triplet continue } "--tool_opt*" { set TOOL_OPTIONS $optarg ::dejagnu::command_line::save_cmd_var TOOL_OPTIONS continue } "--tool_exec*" { set TOOL_EXECUTABLE $optarg ::dejagnu::command_line::save_cmd_var TOOL_EXECUTABLE continue } "--to*" { # (--tool) specify tool name set tool $optarg set comm_line_tool $optarg ::dejagnu::command_line::save_cmd_var tool ::dejagnu::command_line::save_cmd_var comm_line_tool continue } "--di*" { set cmdline_dir_to_run $optarg ::dejagnu::command_line::save_cmd_var cmdline_dir_to_run continue } "--v" - "--verb*" { # (--verbose) verbose output incr verbose continue } "[A-Z0-9_-.]*=*" { # process makefile style args like CC=gcc, etc... if {[regexp "^(\[A-Z0-9_-\]+)=(.*)$" $option junk var val]} { set $var $val verbose "$var is now $val" append makevars "set $var $val;" ;# FIXME: Used anywhere? unset junk var val } else { send_error "Illegal variable specification:\n" send_error "$option\n" } continue } } } verbose "Verbose level is $verbose" verbose [concat "Initial working directory is" [pwd]] ::dejagnu::command_line::dump_cmd_vars # # get the users login name # if { $logname eq "" } { if {[info exists env(USER)]} { set logname $env(USER) } else { if {[info exists env(LOGNAME)]} { set logname $env(LOGNAME) } else { # try getting it with whoami catch "set logname [exec whoami]" tmp if {[string match "*couldn't find*to execute*" $tmp]} { # try getting it with who am i unset tmp catch "set logname [exec who am i]" tmp if {[string match "*Command not found*" $tmp]} { send_user "ERROR: couldn't get the users login name\n" set logname "Unknown" } else { set logname [lindex [split $logname " !"] 1] } } } } } verbose "Login name is $logname" # # lookfor_file -- try to find a file by searching up multiple directory levels # proc lookfor_file { dir name } { foreach x [list . .. ../.. ../../.. ../../../..] { verbose $dir/$x/$name 2 if {[file exists [file join $dir $name]]} { return [file join $dir $name] } set dir [remote_file build dirname $dir] } return "" } # # load_lib -- load a library by sourcing it # # If there a multiple files with the same name, stop after the first one found. # The order is first look in the install dir, then in a parallel dir in the # source tree (up one or two levels), then in the current dir. # proc load_lib { file } { global verbose execpath tool global libdir libdirs srcdir testsuitedir base_dir global loaded_libs if {[info exists loaded_libs($file)]} { return } set loaded_libs($file) "" set search_dirs [list ../lib $libdir $libdir/lib] lappend search_dirs [file dirname [file dirname $srcdir]]/dejagnu/lib lappend search_dirs $testsuitedir/lib lappend search_dirs $execpath/lib "." lappend search_dirs [file dirname [file dirname [file dirname $srcdir]]]/dejagnu/lib if {[info exists libdirs]} { lappend search_dirs $libdirs } if { [search_and_load_file "library file" $file $search_dirs ] == 0 } { send_error "ERROR: Couldn't find library file $file.\n" exit 1 } } # # Begin sourcing the config files. # All are sourced in order. # # Search order: # (local) $base_dir/$local_init_file -> $objdir/$local_init_file -> # (global) installed($global_init_file) -> $DEJAGNU -> $HOME/.dejagnurc # # For the normal case, we expect $base_dir/$local_init_file to set # host_triplet and target_triplet. # load_file [file join $base_dir $local_init_file] # Ensure that command line parameters override testsuite init files. ::dejagnu::command_line::restore_cmd_vars # # If objdir didn't get set in $base_dir/$local_init_file, set it to # $base_dir. Make sure we source $objdir/$local_init_file in case # $base_dir/$local_init_file doesn't exist and objdir was given on the # command line. # if { $objdir eq "." || $objdir eq $srcdir } { set objdir $base_dir } else { load_file [file join $objdir $local_init_file] } # Ensure that command line parameters override testsuite init files. ::dejagnu::command_line::restore_cmd_vars # # Find the testsuite. # # The DejaGnu manual has always stated that a testsuite must be in a # testsuite/ subdirectory. verbose "Finding testsuite ..." 3 verbose "\$base_dir -> $base_dir" 3 verbose "\$srcdir -> $srcdir" 3 verbose "\$objdir -> $objdir" 3 verbose [concat "file tail \$srcdir -> " [file tail $srcdir]] 3 verbose [concat "file join \$srcdir testsuite -> " \ [file join $srcdir testsuite]] 3 verbose [concat "file isdirectory [file join \$srcdir testsuite] -> " \ [file isdirectory [file join $srcdir testsuite]]] 3 verbose [concat "file tail \$base_dir -> " [file tail $base_dir]] 3 if { [file tail $srcdir] eq "testsuite" } { # Subdirectory case -- $srcdir includes testsuite/ set testsuitedir $srcdir set testbuilddir $objdir } elseif { [file tail $srcdir] ne "testsuite" && [file isdirectory [file join $srcdir testsuite]] } { # Top-level case -- testsuite in $srcdir/testsuite/ set testsuitedir [file join $srcdir testsuite] set testbuilddir [file join $objdir testsuite] } elseif { $srcdir eq "." && [file tail $base_dir] eq "testsuite" } { # Development scaffold case -- testsuite in ".", but "." is "testsuite" set testsuitedir $base_dir set testbuilddir $base_dir } else { if { $testsuitedir eq "testsuite" && $srcdir eq "." && $objdir eq "." } { # Broken legacy case -- testsuite not actually in testsuite/ # Produce a warning, but continue. send_error "WARNING: testsuite is not in a testsuite/ directory.\n" set testsuitedir $srcdir set testbuilddir $objdir } else { # Custom case -- all variables are assumed to have been set correctly } } verbose "Finding testsuite ... done" 3 # Well, this just demonstrates the real problem... if {![info exists tool_root_dir]} { set tool_root_dir [file dirname $objdir] if {[file exists [file join $tool_root_dir testsuite]]} { set tool_root_dir [file dirname $tool_root_dir] } } verbose "Using test sources in $srcdir" verbose "Using test binaries in $objdir" verbose "Testsuite root is $testsuitedir" verbose "Tool root directory is $tool_root_dir" set execpath [file dirname $argv0] # The runtest.exp file is installed directly in libdir. # Conveniently, the source tree layout is the same as the installed libdir. set libdir [file dirname $argv0] if {[info exists env(DEJAGNULIBS)]} { set libdir $env(DEJAGNULIBS) } # list of extra search directories used by load_lib to look for libs set libdirs {} verbose "Using $libdir to find libraries" # # If the host or target was given on the command line, override the above # config files. We allow $DEJAGNU to massage them though in case it would # ever want to do such a thing. # if { $arg_host_triplet ne "" } { set host_triplet $arg_host_triplet } if { $arg_build_triplet ne "" } { set build_triplet $arg_build_triplet } # If we only specify --host, then that must be the build machine too, # and we're stuck using the old functionality of a simple cross test. if {[expr { $build_triplet eq "" && $host_triplet ne "" } ]} { set build_triplet $host_triplet } # If we only specify --build, then we'll use that as the host too. if {[expr { $build_triplet ne "" && $host_triplet eq "" } ]} { set host_triplet $build_triplet } unset arg_host_triplet arg_build_triplet # # If the build machine type hasn't been specified by now, use config.guess. # if {[expr {$build_triplet eq "" && $host_triplet eq ""}]} { # find config.guess foreach dir [list $libdir $libdir/libexec $libdir/.. $execpath $srcdir $srcdir/.. $srcdir/../..] { verbose "Looking for $dir/config.guess" 2 if {[file exists [file join $dir config.guess]]} { set config_guess [file join $dir config.guess] verbose "Found [file join $dir config.guess]" break } } # get the canonical triplet if {![info exists config_guess]} { send_error "ERROR: Couldn't find config.guess program.\n" exit 1 } catch "exec $config_guess" build_triplet switch -- $build_triplet { "No uname command or uname output not recognized" - "Unable to guess system type" { verbose "WARNING: Uname output not recognized" set build_triplet unknown } } verbose "Assuming build host is $build_triplet" if { $host_triplet eq "" } { set host_triplet $build_triplet } } # # Figure out the target. If the target hasn't been specified, then we have to # assume we are native. # if { $arg_target_triplet ne "" } { set target_triplet $arg_target_triplet } elseif { $target_triplet eq "" } { set target_triplet $build_triplet verbose "Assuming native target is $target_triplet" 2 } unset arg_target_triplet # # Default target_alias to target_triplet. # if {![info exists target_alias]} { set target_alias $target_triplet } proc get_local_hostname { } { if {[catch "info hostname" hb]} { set hb "" } else { regsub "\\..*$" $hb "" hb } verbose "hostname=$hb" 3 return $hb } # # We put these here so that they can be overridden later by site.exp or # friends. # # Set up the target as machine NAME. We also load base-config.exp as a # default configuration. The config files are sourced with the global # variable $board set to the name of the current target being defined. # proc setup_target_hook { whole_name name } { global board global host_board if {[info exists host_board]} { set hb $host_board } else { set hb [get_local_hostname] } set board $whole_name global board_type set board_type "target" load_config base-config.exp if {![load_board_description $name $whole_name $hb]} { if { $name ne "unix" } { perror "couldn't load description file for $name" exit 1 } else { load_generic_config "unix" } } if {[board_info $board exists generic_name]} { load_tool_target_config [board_info $board generic_name] } unset board unset board_type push_target $whole_name if { [info procs ${whole_name}_init] ne "" } { ${whole_name}_init $whole_name } if { ![isnative] && ![isremote target] } { global env build_triplet target_triplet if { (![info exists env(DEJAGNU)]) && ($build_triplet ne $target_triplet) } { warning "Assuming target board is the local machine (which is probably wrong).\nYou may need to set your DEJAGNU environment variable." } } } # # Clean things up afterwards. # proc cleanup_target_hook { name } { global tool # Clean up the target board. if { [info procs ${name}_exit] ne "" } { ${name}_exit } # We also call the tool exit routine here. if {[info exists tool]} { if { [info procs ${tool}_exit] ne "" } { ${tool}_exit } } remote_close target pop_target } proc setup_host_hook { name } { global board global board_info global board_type set board $name set board_type "host" load_board_description $name unset board unset board_type push_host $name if { [info procs ${name}_init] ne "" } { ${name}_init $name } } proc setup_build_hook { name } { global board global board_info global board_type set board $name set board_type "build" load_board_description $name unset board unset board_type push_build $name if { [info procs ${name}_init] ne "" } { ${name}_init $name } } # # Find and load the global config file if it exists. # The global config file is used to set the connect mode and other # parameters specific to each particular target. # These files assume the host and target have been set. # if { [load_file -- [file join $libdir $global_init_file]] == 0 } { # If $DEJAGNU isn't set either then there isn't any global config file. # Warn the user as there really should be one. if { ! [info exists env(DEJAGNU)] } { send_error "WARNING: Couldn't find the global config file.\n" } } if {[info exists env(DEJAGNU)]} { if { [load_file -- $env(DEJAGNU)] == 0 } { # It may seem odd to only issue a warning if there isn't a global # config file, but issue an error if $DEJAGNU is erroneously defined. # Since $DEJAGNU is set there is *supposed* to be a global config file, # so the current behaviour seems reasonable. send_error "ERROR: global config file $env(DEJAGNU) not found.\n" exit 1 } if {![info exists boards_dir]} { set boards_dir "[file dirname $env(DEJAGNU)]/boards" } } # Load user .dejagnurc file last as the ultimate override. load_file ~/.dejagnurc if {![info exists boards_dir]} { set boards_dir "" } # # parse out the config parts of the triplet name # # build values if { $build_cpu eq "" } { regsub -- "-.*-.*" $build_triplet "" build_cpu } if { $build_vendor eq "" } { regsub -- "^\[a-z0-9\]*-" $build_triplet "" build_vendor regsub -- "-.*" $build_vendor "" build_vendor } if { $build_os eq "" } { regsub -- ".*-.*-" $build_triplet "" build_os } # host values if { $host_cpu eq "" } { regsub -- "-.*-.*" $host_triplet "" host_cpu } if { $host_vendor eq "" } { regsub -- "^\[a-z0-9\]*-" $host_triplet "" host_vendor regsub -- "-.*" $host_vendor "" host_vendor } if { $host_os eq "" } { regsub -- ".*-.*-" $host_triplet "" host_os } # target values if { $target_cpu eq "" } { regsub -- "-.*-.*" $target_triplet "" target_cpu } if { $target_vendor eq "" } { regsub -- "^\[a-z0-9\]*-" $target_triplet "" target_vendor regsub -- "-.*" $target_vendor "" target_vendor } if { $target_os eq "" } { regsub -- ".*-.*-" $target_triplet "" target_os } # # Load the primary tool initialization file. # proc load_tool_init { file } { global srcdir testsuitedir global loaded_libs if {[info exists loaded_libs(tool/$file)]} { return } set loaded_libs(tool/$file) "" lappend searchpath [file join $testsuitedir lib tool] lappend searchpath [file join $testsuitedir lib] # for legacy testsuites that might have files in lib/ instead of # testsuite/lib/ in the package source tree; deprecated lappend searchpath [file join $srcdir lib] if { ![search_and_load_file "tool init file" [list $file] $searchpath] } { warning "Couldn't find tool init file" } } # # load the testing framework libraries # load_lib utils.exp load_lib framework.exp load_lib debugger.exp load_lib remote.exp load_lib target.exp load_lib targetdb.exp load_lib libgloss.exp # Initialize the test counters and reset them to 0. init_testcounts reset_vars # # Parse the command line arguments. # # Load the tool initialization file. Allow the --tool option to override # what's set in the site.exp file. if {[info exists comm_line_tool]} { set tool $comm_line_tool } if {[info exists tool]} { load_tool_init ${tool}.exp } set argc [ llength $argv ] for { set i 0 } { $i < $argc } { incr i } { set option [ lindex $argv $i ] # make all options have two hyphens switch -glob -- $option { "--*" { } "-*" { set option "-$option" } } # split out the argument for options that take them switch -glob -- $option { "--*=*" { regexp {^[^=]*=(.*)$} $option nil optarg } "--bu*" - "--g*" - "--ho*" - "--ig*" - "--loc*" - "--m*" - "--n*" - "--ob*" - "--ou*" - "--sr*" - "--str*" - "--ta*" - "--di*" - "--to*" { incr i set optarg [lindex $argv $i] } } switch -glob -- $option { "--v*" { # (--verbose) verbose output # Already parsed. continue } "--g*" { # (--global_init) the global init file name # Already parsed (and no longer useful). The file has been loaded. continue } "--loc*" { # (--local_init) the local init file name # Already parsed (and no longer useful). The file has been loaded. continue } "--bu*" { # (--build) the build host configuration # Already parsed (and don't set again). Let $DEJAGNU rename it. continue } "--ho*" { # (--host) the host configuration # Already parsed (and don't set again). Let $DEJAGNU rename it. continue } "--target_bo*" { # Set it again, father knows best. set target_list $optarg continue } "--ta*" { # (--target) the target configuration # Already parsed (and don't set again). Let $DEJAGNU rename it. continue } "--a*" { # (--all) print all test output to screen set all_flag 1 verbose "Print all test output to screen" continue } "--di*" { # Already parsed (and don't set again). Let $DEJAGNU rename it. continue } "--de*" { # (--debug) expect internal debugging if {[file exists ./dbg.log]} { catch [file delete -force -- dbg.log] } if { $verbose > 2 } { exp_internal -f dbg.log 1 } else { exp_internal -f dbg.log 0 } verbose "Expect Debugging is ON" continue } "--D[01]" { # (-Debug) turn on Tcl debugger # The runtest shell script handles this option, but it # still appears in the options in the Tcl code. verbose "Tcl debugger is ON" continue } "--k*" { # (--keep_going) reduce fatal errors set ::dejagnu::opt::keep_going 1 } "--m*" { # (--mail) mail the output set mailing_list $optarg set mail_logs 1 verbose "Mail results to $mailing_list" continue } "--r*" { # (--reboot) reboot the target set reboot 1 verbose "Will reboot the target (if supported)" continue } "--ob*" { # (--objdir) where the test case object code lives # Already parsed, but parse again to make sure command line # options override any config file. set objdir $optarg verbose "Using test binaries in $objdir" continue } "--ou*" { # (--outdir) where to put the output files set outdir $optarg verbose "Test output put in $outdir" continue } "--log_dialog*" { incr log_dialog continue } "*.exp" { # specify test names to run set all_runtests($option) "" verbose "Running only tests $option" continue } "*.exp=*" { # specify test names to run set tmp [split $option "="] set all_runtests([lindex $tmp 0]) [lindex $tmp 1] verbose "Running only tests $option" unset tmp continue } "--ig*" { # (--ignore) specify test names to exclude set ignoretests $optarg verbose "Ignoring test $ignoretests" continue } "--sr*" { # (--srcdir) where the testsuite source code lives # Already parsed, but parse again to make sure command line # options override any config file. set srcdir $optarg continue } "--str*" { # (--strace) expect trace level set tracelevel $optarg strace $tracelevel verbose "Source Trace level is now $tracelevel" continue } "--sta*" { # (--status) exit status flag # preserved for compatability, do nothing continue } "--tool_opt*" { continue } "--tool_exec*" { set TOOL_EXECUTABLE $optarg continue } "--to*" { # (--tool) specify tool name set tool $optarg verbose "Testing $tool" continue } "--x*" { set xml 1 verbose "XML logging turned on" continue } "--he*" { # (--help) help text usage exit 0 } "[A-Z0-9_-.]*=*" { # skip makefile style args like CC=gcc, etc... (processed in first pass) continue } default { if {[info exists tool]} { if { [info procs ${tool}_option_proc] ne "" } { if {[${tool}_option_proc $option]} { continue } } } send_error "\nIllegal Argument \"$option\"\n" send_error "try \"runtest --help\" for option list\n" exit 1 } } } # # check for a few crucial variables # if {![info exists tool]} { send_error "WARNING: No tool specified\n" set tool "" } # # initialize a few Tcl variables to something other than their default # if { $verbose > 2 || $log_dialog } { log_user 1 } else { log_user 0 } set timeout 10 # # open log files # open_logs # print the config info clone_output "Test run by $logname on [timestamp -format %c]" if {[is3way]} { clone_output "Target is $target_triplet" clone_output "Host is $host_triplet" clone_output "Build is $build_triplet" } else { if {[isnative]} { clone_output "Native configuration is $target_triplet" } else { clone_output "Target is $target_triplet" clone_output "Host is $host_triplet" } } clone_output "\n\t\t=== $tool tests ===\n" # # Look for the generic board configuration file. It searches in several # places: $libdir/config, $libdir/../config, and $boards_dir. # proc load_generic_config { name } { global libdir global board global board_info global boards_dir global board_type if {[info exists board]} { if {![info exists board_info($board,generic_name)]} { set board_info($board,generic_name) $name } } if {[info exists board_type]} { set type "for $board_type" } else { set type "" } set dirlist [concat $libdir/config [file dirname $libdir]/config $boards_dir] set result [search_and_load_file "generic interface file $type" $name.exp $dirlist] return $result } # # Load the tool-specific target description. # proc load_config { args } { global testsuitedir set found 0 return [search_and_load_file "tool-and-target-specific interface file" $args [list $testsuitedir/config $testsuitedir/../config $testsuitedir/../../config $testsuitedir/../../../config]] } # # Find the files that set up the configuration for the target. There # are assumed to be two of them; one defines a basic set of # functionality for the target that can be used by all tool # testsuites, and the other defines any necessary tool-specific # functionality. These files are loaded via load_config. # # These used to all be named $target_abbrev-$tool.exp, but as the # $tool variable goes away, it's now just $target_abbrev.exp. First # we look for a file named with both the abbrev and the tool names. # Then we look for one named with just the abbrev name. Finally, we # look for a file called default, which is the default actions, as # some tools could be purely host based. Unknown is mostly for error # trapping. # proc load_tool_target_config { name } { global target_os libdir testsuitedir set found [load_config $name.exp $target_os.exp "default.exp" "unknown.exp"] if { $found == 0 } { send_error "WARNING: Couldn't find tool config file for $name, using default.\n" # If we can't load the tool init file, this must be a simple natively hosted # test suite, so we use the default procs for Unix. if { [search_and_load_file "library file" default.exp [list $libdir $libdir/config [file dirname [file dirname $testsuitedir]]/dejagnu/config $testsuitedir/config . [file dirname [file dirname [file dirname $testsuitedir]]]/dejagnu/config]] == 0 } { send_error "ERROR: Couldn't find default tool init file.\n" exit 1 } } } # # Find the file that describes the machine specified by board_name. # proc load_board_description { board_name args } { global libdir global board global board_info global boards_dir global board_type set dejagnu "" if { [llength $args] > 0 } { set whole_name [lindex $args 0] } else { set whole_name $board_name } set board_info($whole_name,name) $whole_name if {![info exists board]} { set board $whole_name set board_set 1 } else { set board_set 0 } set dirlist {} if { [llength $args] > 1 } { set suffix [lindex $args 1] if { $suffix ne "" } { foreach x $boards_dir { lappend dirlist $x/$suffix } lappend dirlist $libdir/baseboards/$suffix } } set dirlist [concat $dirlist $boards_dir] lappend dirlist $libdir/baseboards verbose "dirlist is $dirlist" if {[info exists board_type]} { set type "for $board_type" } else { set type "" } if {![info exists board_info($whole_name,isremote)]} { set board_info($whole_name,isremote) 1 if {[info exists board_type]} { if { $board_type eq "build" } { set board_info($whole_name,isremote) 0 } } if { $board_name eq [get_local_hostname] } { set board_info($whole_name,isremote) 0 } } search_and_load_file "standard board description file $type" standard.exp $dirlist set found [search_and_load_file "board description file $type" $board_name.exp $dirlist] if { $board_set != 0 } { unset board } return $found } # # Find the base-level file that describes the machine specified by args. We # only look in one directory, $libdir/baseboards. # proc load_base_board_description { board_name } { global libdir global board global board_info global board_type set board_set 0 set board_info($board_name,name) $board_name if {![info exists board]} { set board $board_name set board_set 1 } if {[info exists board_type]} { set type "for $board_type" } else { set type "" } if {![info exists board_info($board_name,isremote)]} { set board_info($board_name,isremote) 1 if {[info exists board_type]} { if { $board_type eq "build" } { set board_info($board_name,isremote) 0 } } } if { $board_name eq [get_local_hostname] } { set board_info($board_name,isremote) 0 } set found [search_and_load_file "board description file $type" $board_name.exp [list $libdir/baseboards]] if { $board_set != 0 } { unset board } return $found } # # Source the testcase in TEST_FILE_NAME. # proc runtest { test_file_name } { global prms_id global bug_id global test_result global errcnt global errorInfo global tool global testdir clone_output "Running $test_file_name ..." set prms_id 0 set bug_id 0 set test_result "" # set testdir so testsuite file -test has a starting point set testdir [file dirname $test_file_name] if {[file exists $test_file_name]} { set timestart [timestamp] if {[info exists tool]} { if { [info procs ${tool}_init] ne "" } { ${tool}_init $test_file_name } } if { [catch "uplevel #0 source $test_file_name"] == 1 } { # If we have a Tcl error, propagate the exit status so # that 'make' (if it invokes runtest) notices the error. global exit_status exit_error # exit error is set by the --status command line option if { $exit_status == 0 } { set exit_status 2 } # We can't call `perror' here, it resets `errorInfo' # before we want to look at it. Also remember that perror # increments `errcnt'. If we do call perror we'd have to # reset errcnt afterwards. clone_output "ERROR: tcl error sourcing $test_file_name." if {[info exists errorInfo]} { clone_output "ERROR: $errorInfo" unset errorInfo } } if {[info exists tool]} { if { [info procs ${tool}_finish] ne "" } { ${tool}_finish } } set timeend [timestamp] set timediff [expr {$timeend - $timestart}] verbose -log "testcase $test_file_name completed in $timediff seconds" 4 } else { # This should never happen, but maybe if the file got removed # between the `find' above and here. perror "$test_file_name does not exist." 0 } } # Trap some signals so we know what's happening. These replace the previous # ones because we've now loaded the library stuff. # if {![exp_debug]} { foreach sig {{SIGINT {interrupted by user} 130} \ {SIGQUIT {interrupted by user} 131} \ {SIGTERM {terminated} 143}} { set signal [lindex $sig 0] set str [lindex $sig 1] set code [lindex $sig 2] trap "send_error \"got a \[trap -name\] signal, $str \\n\"; set exit_status $code; log_and_exit;" $signal verbose "setting trap for $signal to $str" 1 } unset signal str sig } # # Given a list of targets, process any iterative lists. # proc process_target_variants { target_list } { set result {} foreach x $target_list { if {[regexp "\\(" $x]} { regsub {^.*\(([^()]*)\)$} $x {\1} variant_list regsub {\([^(]*$} $x "" x set list [process_target_variants $x] set result {} foreach x $list { set result [concat $result [iterate_target_variants $x [split $variant_list ","]]] } } elseif {[regexp "\{" $x]} { regsub "^.*\{(\[^\{\}\]*)\}$" $x {\1} variant_list regsub "\{\[^\{\]*$" $x "" x set list [process_target_variants $x] foreach x $list { foreach i [split $variant_list ","] { set name $x if { $i ne "" } { append name "/" $i } lappend result $name } } } else { lappend result $x } } return $result } proc iterate_target_variants { target variants } { return [iterate_target_variants_two $target $target $variants] } # # Given a list of variants, produce the list of all possible combinations. # proc iterate_target_variants_two { orig_target target variants } { if { [llength $variants] == 0 } { return [list $target] } else { if { [llength $variants] > 1 } { set result [iterate_target_variants_two $orig_target $target [lrange $variants 1 end]] } else { if { $target ne $orig_target } { set result [list $target] } else { set result {} } } if { [lindex $variants 0] ne "" } { append target "/" [lindex $variants 0] return [concat $result [iterate_target_variants_two $orig_target $target [lrange $variants 1 end]]] } else { return [concat $result $target] } } } setup_build_hook [get_local_hostname] if {[info exists host_board]} { setup_host_hook $host_board } else { set hb [get_local_hostname] if { $hb ne "" } { setup_host_hook $hb } } # # main test execution loop # if {[info exists errorInfo]} { unset errorInfo } # make sure we have only single path delimiters regsub -all {([^/])//*} $srcdir {\1/} srcdir regsub -all {([^/])//*} $objdir {\1/} objdir regsub -all {([^/])//*} $testsuitedir {\1/} testsuitedir regsub -all {([^/])//*} $testbuilddir {\1/} testbuilddir if {![info exists target_list]} { # Make sure there is at least one target machine. It's probably a Unix box, # but that's just a guess. set target_list { "unix" } } else { verbose "target list is $target_list" } # # Iterate through the list of targets. # global current_target set target_list [process_target_variants $target_list] set target_count [llength $target_list] clone_output "Schedule of variations:" foreach current_target $target_list { clone_output " $current_target" } clone_output "" foreach current_target $target_list { verbose "target is $current_target" set current_target_name $current_target set tlist [split $current_target /] set current_target [lindex $tlist 0] set board_variant_list [lrange $tlist 1 end] # Set the counts for this target to 0. reset_vars clone_output "Running target $current_target_name" setup_target_hook $current_target_name $current_target # If multiple passes requested, set them up. Otherwise prepare just one. # The format of `MULTIPASS' is a list of elements containing # "{ name var1=value1 ... }" where `name' is a generic name for the pass and # currently has no other meaning. global env if { [info exists MULTIPASS] } { set multipass $MULTIPASS } if { $multipass eq "" } { set multipass { "" } } # If PASS is specified, we want to run only the tests specified. # Its value should be a number or a list of numbers that specify # the passes that we want to run. if {[info exists PASS]} { set pass $PASS } else { set pass "" } if {$pass ne ""} { set passes [list] foreach p $pass { foreach multipass_elem $multipass { set multipass_name [lindex $multipass_elem 0] if {$p == $multipass_name} { lappend passes $multipass_elem break } } } set multipass $passes } foreach pass $multipass { # multipass_name is set for `record_test' to use (see framework.exp). if { [lindex $pass 0] ne "" } { set multipass_name [lindex $pass 0] clone_output "Running pass `$multipass_name' ..." } else { set multipass_name "" } set restore "" foreach varval [lrange $pass 1 end] { set tmp [string first "=" $varval] set var [string range $varval 0 [expr {$tmp - 1}]] # Save previous value. if {[info exists $var]} { lappend restore "$var [list [eval concat \$$var]]" } else { lappend restore $var } # Handle "CFLAGS=$CFLAGS foo". eval set $var \[string range \"$varval\" [expr {$tmp + 1}] end\] verbose "$var is now [eval concat \$$var]" unset tmp var } # look for the top level testsuites. if $tool doesn't # exist and there are no subdirectories in $testsuitedir, then # we print a warning and default to srcdir. set test_top_dirs [lsort [getdirs -all $testsuitedir $tool*]] if { $test_top_dirs eq "" } { send_error "WARNING: could not find testsuite; trying $srcdir.\n" set test_top_dirs [list $srcdir] } else { # JYG: # DejaGNU's notion of test tree and test files is very # general: # given $testsuitedir and $tool, any subdirectory (at any # level deep) with the "$tool" prefix starts a test tree # given a test tree, any *.exp file underneath (at any # level deep) is a test file. # # For test tree layouts with $tool prefix on # both a parent and a child directory, we need to eliminate # the child directory entry from test_top_dirs list. # e.g. gdb.hp/gdb.base-hp/ would result in two entries # in the list: gdb.hp, gdb.hp/gdb.base-hp. # If the latter not eliminated, test files under # gdb.hp/gdb.base-hp would be run twice (since test files # are gathered from all sub-directories underneath a # directory). # # Since $tool may be g++, etc. which could confuse # regexp, we cannot do the simpler test: # ... # if [regexp "$testsuitedir/.*$tool.*/.*$tool.*" $dir] # ... # instead, we rely on the fact that test_top_dirs is # a sorted list of entries, and any entry that contains # the previous valid test top dir entry in its own pathname # must be excluded. set temp_top_dirs [list] set prev_dir "" foreach dir $test_top_dirs { if { $prev_dir eq "" || [string first $prev_dir/ $dir] == -1 } { # the first top dir entry, or an entry that # does not share the previous entry's entire # pathname, record it as a valid top dir entry. # lappend temp_top_dirs $dir set prev_dir $dir } } set test_top_dirs $temp_top_dirs } verbose "Top level testsuite dirs are $test_top_dirs" 2 set testlist "" if {[array exists all_runtests]} { foreach x [array names all_runtests] { verbose "trying to glob $testsuitedir/$x" 2 set s [glob -nocomplain $testsuitedir/$x] if { $s ne "" } { set testlist [concat $testlist $s] } } } # # If we have a list of tests, run all of them. # if { $testlist ne "" } { foreach test_name $testlist { if { $ignoretests ne "" } { if { 0 <= [lsearch $ignoretests [file tail $test_name]]} { continue } } # set subdir to the tail of the dirname after $srcdir, # for the driver files that want it. XXX this is silly. # drivers should get a single var, not $srcdir/$subdir set subdir [relative_filename $srcdir \ [file dirname $test_name]] # XXX not the right thing to do. set runtests [list [file tail $test_name] ""] runtest $test_name } } else { # # Go digging for tests. # foreach dir $test_top_dirs { if { $dir ne $testsuitedir } { # Ignore this directory if is a directory to be # ignored. if {[info exists ignoredirs] && $ignoredirs ne ""} { set found 0 foreach directory $ignoredirs { if {[string match *$directory* $dir]} { set found 1 break } } if { $found } { continue } } # Run the test if dir_to_run was specified as a # value (for example in MULTIPASS) and the test # directory matches that directory. if {[info exists dir_to_run] && $dir_to_run ne ""} { # JYG: dir_to_run might be a space delimited list # of directories. Look for match on each item. set found 0 foreach directory $dir_to_run { if {[string match *$directory* $dir]} { set found 1 break } } if {!$found} { continue } } # Run the test if cmdline_dir_to_run was specified # by the user using --directory and the test # directory matches that directory if {[info exists cmdline_dir_to_run] \ && $cmdline_dir_to_run ne ""} { # JYG: cmdline_dir_to_run might be a space delimited # list of directories. Look for match on each item. set found 0 foreach directory $cmdline_dir_to_run { # Look for a directory that ends with the # provided --directory name. if {[string match $directory $dir] || [string match "*/$directory" $dir]} { set found 1 break } } if {!$found} { continue } } foreach test_name [lsort [find $dir *.exp]] { if { $test_name eq "" } { continue } # Ignore this one if asked to. if { $ignoretests ne "" } { if { 0 <= [lsearch $ignoretests [file tail $test_name]]} { continue } } # Get the path after the $srcdir so we know # the subdir we're in. set subdir [relative_filename $srcdir \ [file dirname $test_name]] # Check to see if the range of tests is limited, # set `runtests' to a list of two elements: the script name # and any arguments ("" if none). if {[array exists all_runtests]} { verbose "searching for $test_name in [array names all_runtests]" 2 if { 0 > [lsearch [array names all_runtests] [file tail $test_name]]} { if { 0 > [lsearch [array names all_runtests] $test_name] } { continue } } set runtests [list [file tail $test_name] $all_runtests([file tail $test_name])] } else { set runtests [list [file tail $test_name] ""] } runtest $test_name } } } } # Restore the variables set by this pass. foreach varval $restore { if { [llength $varval] > 1 } { verbose "Restoring [lindex $varval 0] to [lindex $varval 1]" 4 set [lindex $varval 0] [lindex $varval 1] } else { verbose "Restoring [lindex $varval 0] to `unset'" 4 unset -- [lindex $varval 0] } } } cleanup_target_hook $current_target if { $target_count > 1 } { log_summary } } log_and_exit