X-Git-Url: https://git.kernelconcepts.de/?p=karo-tx-redboot.git;a=blobdiff_plain;f=tools%2Fsrc%2Finfra%2Fhosttest.exp;fp=tools%2Fsrc%2Finfra%2Fhosttest.exp;h=19d45938df1a41fa459c7fcc1f2fe2081db76cb9;hp=0000000000000000000000000000000000000000;hb=2b5bec7716c03d42cfb16d8c98c9cea573bf6722;hpb=47412fc4bd1aefc0d5498bcb3860a9d727196f16 diff --git a/tools/src/infra/hosttest.exp b/tools/src/infra/hosttest.exp new file mode 100644 index 00000000..19d45938 --- /dev/null +++ b/tools/src/infra/hosttest.exp @@ -0,0 +1,735 @@ +#=============================================================================== +# +# hosttest.exp +# +# Support for host-side testing +# +#=============================================================================== +######COPYRIGHTBEGIN#### +# +# ---------------------------------------------------------------------------- +# Copyright (C) 1998, 1999, 2000, 2001 Red Hat, Inc. +# +# This file is part of the eCos host tools. +# +# This program 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 2 of the License, or (at your option) +# any later version. +# +# This program 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 +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# ---------------------------------------------------------------------------- +# +######COPYRIGHTEND#### +#=============================================================================== +######DESCRIPTIONBEGIN#### +# +# Author(s): bartv +# Contributors: bartv +# Date: 1998-11-25 +# Note: Arguably this should be a loadable package +# +#####DESCRIPTIONEND#### +#=============================================================================== +# + +# ---------------------------------------------------------------------------- +# This script gets loaded by host-side DejaGnu test harnesses to provide +# various utilities for testing eCos host applications. It lives in the +# host-side infrastructure directory and gets installed in +# $(PREFIX)/share/dejagnu. +# +# The script can assume that a number of globals from the site.exp +# file have been read in. These include: +# tool - name of the tool (i.e. the package) +# srcdir - location of the source directory +# objdir - location of the build tree +# host_alias - config triplet +# host_triplet - ditto +# +# The generated Makefile has some additional information that is useful. +# CC - name of the C compiler that is used +# CXX - name of the C++ compiler +# prefix - where everything gets installed +# OBJEXT - either o or obj +# EXEEXT - either nothing or .exe +# VERSION - the version number +# CFLAGS - flags to use when compiling C code +# CXXFLAGS - flags to use when compiling C++ code +# +# Some additional variables should be present in any generated +# makefiles in the eCos tree. +# INCLUDES - header file search path +# LIBS - libraries, search paths, ... +# +# hosttest_initialize +# Perform any initialization steps that are needed. Currently this +# means reading in the Makefile from the top-level of the build tree +# and figuring out the values of CC, CXX, and anything else that is +# useful. Any errors should be reported via perror and then the +# script should exit. +# +# There is an optional argument, a list of additional variables which +# should be present in the makefile and whose values are desired. + +proc hosttest_initialize { { pkg_vars {} } } { + + # First check that this script is actually running inside DejaGnu + if { [info exists ::objdir] == 0 } { + puts "Variable ::objdir is not defined, is this script really running inside DejaGnu?" + exit 1 + } + + # The information is stored in an array hosttest_data. Make sure this + # array exists. + array set ::hosttest_data {} + + # Now clear out any entries in the array + foreach entry [array names ::hosttest_data] { + unset ::hosttest_data($entry) + } + + # Now read in the build tree's Makefile (and not the testsuite's Makefile) + set filename [file join $::objdir .. Makefile] + if { [file exists $filename] == 0 } { + perror "Initialization error: the build tree's Makefile $filename does not exist." + exit 1 + } + set status [ catch { + set fd [open $filename r] + set contents [read $fd] + close $fd + } message] + if { $status != 0 } { + perror "Error reading $filename.\n$message" + exit 1 + } + + # The data is available. Search it for each of the variables of + # interest. Some variables are optional and are given default + # values. + set ::hosttest_data(CFLAGS) "" + set ::hosttest_data(CXXFLAGS) "" + + set lines [split $contents "\n"] + + foreach var [concat { CC CXX prefix OBJEXT EXEEXT VERSION CFLAGS CXXFLAGS INCLUDES LIBS } $pkg_vars] { + + set pattern "^$var\[ \t\]*:?=\[ \t\]* (.*)\$" + set dummy "" + set match "" + + foreach line $lines { + if { [regexp -- $pattern $line dummy match] == 1 } { + set ::hosttest_data($var) $match + break + } + } + if { [info exists ::hosttest_data($var)] == 0 } { + perror "Variable $var is not defined in $filename" + exit 1 + } + } + + # If compiling with VC++ remove any cygwin-isms from the prefix + if { [string match "cl*" $::hosttest_data(CC)] } { + set status [catch "exec cygpath -w $::hosttest_data(prefix)" message] + if { $status == 0 } { + regsub -all -- {\\} $message {/} ::hosttest_data(prefix) + } else { + perror "Converting cygwin pathname $::hosttest_data(prefix)\n$message" + exit 1 + } + } +} + + +# ---------------------------------------------------------------------------- +# hosttest_extract_version +# Assuming there has been a call to initialize, the required information +# should be available in the hosttest_data array. The initialize +# function should have aborted if the data is not available. + +proc hosttest_extract_version { } { + + if { [info exists ::hosttest_data(VERSION)] == 0 } { + error "No version information - host testing has not been properly initialized." + } + + if { [info exists ::objdir] == 0 } { + error "Variable ::objdir is not defined, is this script really running inside DejaGnu?" + } + return $::hosttest_data(VERSION) +} + +# ---------------------------------------------------------------------------- +# hosttest_compile +# compile and link one or more source files. The arguments are: +# 1) the name of the test case +# 2) a list of one or more source files that need to be compiled. +# Both .c and .cxx files are supported, and the appropriate +# compiler will be used. If this list is empty then the +# code will look for a .c or a .cxx file which matches the +# name of the test executable. Source files are assumed to +# be relative to $::srcdir/$::subdir +# 3) a list (possibly empty) of directories that should be in the +# include path. The build tree's directory is automatically in +# the path, as is $(PREFIX)/include. Note that the build tree +# is actually one level above objdir, on the assumption that +# objdir is the testsuite subdirectory of the real objdir. +# 4) ditto for library search paths. +# 5) and a list of additional libraries that should be linked. +# +# Currently it is not possible to pass compiler flags since those +# might need translating between gcc and VC++. This may have to be +# resolved. +# +# Currently linking is not done via libtool. This may have to change. +# +# The various object files and the executable are placed in a directory +# testcase in the build tree, to avoid the risk of name clashes. This +# directory must not exist yet. There is a separate routine hosttest_clean +# which simply expunges the entire testcase directory. +# +# The output of a succesful compile or built is reported using +# verbose at level 2. Unsuccesful compiles or builts are reported using +# level 1. + +proc hosttest_compile { name sources includes libdirs libs } { + + # Make sure that the testcase directory does not yet exist, then + # create it. This guarantees a clean system and reasonable access + # permissions. Each testcase invocation should involve a call to + # the clean function. + set dirname [file join $::objdir "testcase"] + if { [file exists $dirname] != 0 } { + # An empty directory is ok. + if { [llength [glob -nocomplain -- [file join $dirname "*"]]] != 0 } { + error "hosttest_compile: $dirname already exists" + } + } + + set status [catch { file mkdir $dirname } message] + if { $status != 0 } { + error "hosttest_compile: unable to create directory $dirname" + } + + # The only argument that must be valid is the test name. + if { $name == "" } { + error "hosttest_compile: invalid test case name" + } + + # If the list of sources is empty then look for a suitable + # file in the appropriate directory. + if { [llength $sources] == 0 } { + set filename [file join $::srcdir $::subdir "${name}.c"] + if { [file exists $filename] && [file isfile $filename] } { + lappend sources [file tail $filename] + } else { + set filename [file join $::srcdir $::subdir "${name}.cxx"] + if { [file exists $filename] && [file isfile $filename] } { + lappend sources [file tail $filename] + } else { + error "hosttest_compile: no sources listed and unable to find ${name}.c or ${name}.cxx" + } + } + } + + # For each source file, generate a compile command line and try to execute + # it. The command line takes the form: + # (CC|CXX) -c (CFLAGS|CXXFLAGS) (INCDIRS) -o xxx yyy + # + # It is also useful to produce a list of the object files that need to + # linked later on, and to work out which tool should be invoked for + # linking. + set object_files {} + set has_cxx_files 0 + + foreach source $sources { + set commandline "" + if { [file extension $source] == ".c" } { + append commandline "$::hosttest_data(CC) -c $::hosttest_data(CFLAGS) " + } elseif { [file extension $source] == ".cxx" } { + set has_cxx_files 1 + append commandline "$::hosttest_data(CXX) -c $::hosttest_data(CXXFLAGS) " + } else { + error "hosttest_compile: files of type [file extension $source] ($source) are not yet supported." + } + + # Include path: start with the source tree. Then the build tree. + # Then the makefile's INCLUDES variable. + # Then any additional directories specified explicitly by the + # testcase. Finish off with the prefix. Note that header files + # in the prefix directory may be out of date, depending on whether + # or not there has been an install recently. + append commandline "-I[file join [pwd] [file dirname $::srcdir]] " + append commandline "-I[file join [pwd] [file dirname $::objdir]] " + append commandline "$::hosttest_data(INCLUDES) " + foreach dir $includes { + append commandline "-I[file join [pwd] $dir] " + } + append commandline "-I[file join [pwd] $::hosttest_data(prefix) include] " + + # The output file must go into the testcase directory and have the right suffix + set objfile "[file root [file tail $source]].$::hosttest_data(OBJEXT)" + lappend object_files $objfile + if { [string match "cl*" $::hosttest_data(CC)] } { + append commandline "-Fo[file join $::objdir testcase $objfile] " + } else { + append commandline "-o [file join $::objdir testcase $objfile] " + } + + # Finally provide the source file. + append commandline "[file join $::srcdir $::subdir $source]" + verbose -log -- $commandline + + # Time to invoke the compiler. + set status [catch { set result [eval exec -keepnewline -- $commandline] } message] + if { $status == 0 } { + # The compile succeeded and the output is in result. Report the + # output. + verbose -log -- $result + } else { + # The compile failed and the output is in message. + verbose -log -- $message + error "hosttest_compile: failed to compile $source" + } + } + + # At this stage all the source files have been compiled, a list of + # object files has been constructed, and it is known whether or + # not any of the sources were c++. Time to construct a new command + # line. + set commandline "" + if { $has_cxx_files == 0 } { + append commandline "$::hosttest_data(CC) $::hosttest_data(CFLAGS) " + } else { + append commandline "$::hosttest_data(CXX) $::hosttest_data(CXXFLAGS) " + } + set exename [file join $::objdir "testcase" "$name$::hosttest_data(EXEEXT)"] + + # List all of the object files + foreach obj $object_files { + append commandline "[file join $::objdir "testcase" $obj] " + } + + # Now take care of libraries and search paths. This requires different + # code for gcc and VC++. + + if { [string match "cl*" $::hosttest_data(CC)] } { + append commandline "-Fe$exename " + + foreach lib $libs { + append commandline "${lib}.lib " + } + append commandline "$::hosttest_data(LIBS) " + append commandline "-libpath=[file join [pwd] [file dirname $::objdir]] " + foreach dir $libdirs { + append commandline "-libpath=[file join [pwd] $dir] " + } + append commandline "-libpath=[file join [pwd] $::hosttest_data(prefix) lib] " + } else { + append commandline "-o $exename " + append commandline "-L[file join [pwd] [file dirname $::objdir]] " + foreach dir $libdirs { + append commandline "-L[file join [pwd] $dir] " + } + append commandline "-L[file join [pwd] $::hosttest_data(prefix) lib] " + foreach lib $libs { + append commandline "-l$lib " + } + append commandline "$::hosttest_data(LIBS) " + } + + # We have a command line, go for it. + verbose -log -- $commandline + set status [catch { set result [eval exec -keepnewline -- $commandline] } message] + if { $status == 0 } { + # The link has succeeded, we have an executable. + verbose -log -- $result + } else { + # The link failed and the output is in message. + # Report things are per compilation failures + verbose -log -- $message + error "hosttest_compile: failed to link $exename" + } + + # There should be a test executable. +} + +# ---------------------------------------------------------------------------- +# hosttest_clean +# Clean up a testcase directory. + +proc hosttest_clean { } { + + set dirname [file join $::objdir "testcase"] + if { [file exists $dirname] == 0 } { + + # Something must have gone seriously wrong during the build phase, + # there is nothing there. + return + } + + if { [file isdirectory $dirname] == 0 } { + error "hosttest_clean: $dirname should be a directory" + } + + foreach entry [glob -nocomplain -- [file join $dirname "*"]] { + set filename [file join $dirname $entry] + if { [file isfile $filename] == 0 } { + error "hosttest_clean: $filename is not a file" + } + set status [catch { file delete -force -- $filename } message] + if { $status != 0 } { + error "hosttest_clean: unable to delete $filename, $message" + } + } + set status [catch { file delete -force -- $dirname } message] + if { $status != 0 } { + error "hosttest_clean: unable to delete directory $dirname, $message" + } +} + +# ---------------------------------------------------------------------------- +# Run a test executable, returning the status code and the output. +# The results are returned in variables. It is assumed that some test cases +# will fail, so raising an exception is appropriate only if something +# has gone wrong at the test harness level. The argument list +# should be the name of the test case (from which the executable file name +# can be derived) and a list of arguments. + +proc hosttest_run { result_arg output_arg test args } { + + upvar $result_arg result + upvar $output_arg output + + # Figure out the filename corresponding to the test and make + # sure it exists. + set filename [file join $::objdir "testcase" $test] + append filename $::hosttest_data(EXEEXT) + if { ([file exists $filename] == 0) || ([file isfile $filename] == 0) } { + error "hosttest_run: testcase file $filename does not exist" + } + + # There is no need to worry about interacting with the program, + # just exec it. It is a good idea to do this in the testcase directory, + # so that any core dumps get cleaned up as well. + set current_dir [pwd] + set status [ catch { cd [file join $::objdir "testcase"] } message ] + if { $status != 0 } { + error "unable to change directory to [file join $::objdir testcase]\n$message" + } + + verbose -log -- $filename $args + set status [ catch { set result [eval exec -keepnewline -- $filename $args] } output] + if { $status == 0 } { + # The command has succeeded. The exit code is 0 and the output + # was returned by the exec. + set output $result + set result 0 + } else { + # The command has failed. The exit code is 1 and the output is + # already in the right place. + verbose -log -- $output + set result 1 + } + set status [catch { cd $current_dir } message] + if { $status != 0 } { + error "unable to change directory back to $current_dir" + } +} + +# ---------------------------------------------------------------------------- +# Given some test output, look through it for pass and fail messages. +# These should result in appropriate DejaGnu pass and fail calls. +# In addition, if the program exited with a non-zero status code but +# did not report any failures then a special failure is reported. + +proc hosttest_handle_output { name result output } { + + set passes 0 + set fails 0 + + foreach line [split $output "\n"] { + + # The output should be of one of the following forms: + # PASS: + # FAIL: Line: xx File: xx + # Whatever + # + # PASS and FAIL messages will be reported via DejaGnu pass and fail + # calls. Everything else gets passed to verbose, so the user gets + # to choose how much information gets reported. + + set dummy "" + set match1 "" + set match2 "" + + if { [regexp -- {^PASS:<(.*)>.*$} $line dummy match1] == 1 } { + pass $match1 + incr passes + } elseif { [regexp -- {^FAIL:<(.*)>(.*)$} $line dummy match1 match2] == 1 } { + fail "$match1 $match2" + incr fails + } else { + verbose $line + } + } + + if { ($result != 0) && ($fails == 0) } { + fail "program terminated with non-zero exit code but did not report any failures" + } elseif { ($passes == 0) && ($fails == 0) } { + unresolved "test case $name did not report any passes or failures" + } +} + +# ---------------------------------------------------------------------------- +# hosttest_run_test_with_filter +# This routines combines the compile, run and clean operations, +# plus it invokes a supplied callback to filter the output. The +# callback is passed three arguments: the test name, the exit code, +# and all of the program output. + +proc hosttest_run_test_with_filter { name filter sources incdirs libdirs libs args } { + + set result 0 + set output "" + + set status [ catch { hosttest_compile $name $sources $incdirs $libdirs $libs } message] + if { $status != 0 } { + fail "unable to compile test case $name, $message" + hosttest_clean + return + } + set status [ catch { hosttest_run result output $name $args } message] + if { $status != 0 } { + fail "unable to run test case $name, $message" + hosttest_clean + return + } + set status [ catch { $filter $name $result $output } message] + if { $status != 0 } { + fail "unable to parse output from test case $name" + hosttest_clean + return + } + + hosttest_clean +} + +# ---------------------------------------------------------------------------- +# hosttest_run_simple_test +# This routine combines the compile, run, output, and clean operations. +# The arguments are the same as for compilation, plus an additional +# list for run-time parameters to the test case. + +proc hosttest_run_simple_test { name sources incdirs libdirs libs args } { + + + set result 0 + set output "" + + set status [ catch { hosttest_compile $name $sources $incdirs $libdirs $libs } message] + if { $status != 0 } { + fail "unable to compile test case $name, $message" + hosttest_clean + return + } + set status [ catch { hosttest_run result output $name $args } message] + if { $status != 0 } { + fail "unable to run test case $name, $message" + hosttest_clean + return + } + set status [ catch { hosttest_handle_output $name $result $output } message] + if { $status != 0 } { + fail "unable to parse output from test case $name" + hosttest_clean + return + } + + hosttest_clean +} + +# ---------------------------------------------------------------------------- +# Filename translation. A particular file has been created and must now +# be accessed from Tcl. +# +# Under Unix everything just works. +# +# Under Windows, well there is cygwin and there is the Windows world. +# A file may have come from either. cygtclsh80 and hence DejaGnu is not +# fully integrated with cygwin, for example it does not know about +# cygwin mount points. There are also complications because of +# volume-relative filenames. +# +# The code here tries a number of different ways of finding a file which +# matches the name. It is possible that the result is not actually what +# was intended, but hopefully this case will never arise. + +proc hosttest_translate_existing_filename { name } { + + if { $::tcl_platform(platform) == "unix" } { + # The file should exist. It is worth checking just in case. + if { [file exists $name] == 0 } { + return "" + } else { + return $name + } + } + + if { $::tcl_platform(platform) != "windows" } { + perror "The testing framework does not know about platform $::tcl_platform(platform)" + return "" + } + + # Always get rid of any backslashes, they just cause trouble + regsub -all -- {\\} $name {/} name + + # If the name is already valid, great. + if { [file exists $name] } { + return $name + } + + # OK, try to use cygwin's cygpath utility to convert it. + set status [catch "exec cygpath -w $name" message] + if { $status == 0 } { + set cygwin_name "" + regsub -all -- {\\} $message {/} cygwin_name + if { [file exists $cygwin_name] } { + return $cygwin_name + } + } + + # Is the name volumerelative? If so work out the current volume + # from the current directory and prepend this. + if { [file pathtype $name] == "volumerelative" } { + append fullname [string range [pwd] 0 1] $name + if { [file exists $fullname] } { + return $fullname + } + } + + # There are other possibilities, e.g. d:xxx indicating a file + # relative to the current directory on drive d:. For now such + # Lovecraftian abominations are ignored. + return "" +} + +# ---------------------------------------------------------------------------- +# Support for assertion dumps. The infrastructure allows other subsystems +# to add their own callbacks which get invoked during a panic and which +# can write additional output to the dump file. For example it would be +# possible to output full details of the current configuration. These +# routines make it easier to write test cases for such callbacks. +# +# hosttest_assert_check(result output) +# Make sure that the test case really triggered an assertion. +# +# hosttest_assert_read_dump(output) +# Identify the temporary file used for the dump, read it in, delete +# it (no point in leaving such temporaries lying around when running +# testcases) and return the contents of the file. +# +# hosttest_assert_extract_callback(dump title) +# Given a dump output as returned by read_dump, look for a section +# generated by a callback with the given title. Return the contents +# of the callback. + +proc hosttest_assert_check { result output } { + + if { $result == 0 } { + return 0 + } + + foreach line [split $output "\n"] { + if { [string match "Assertion failure*" $line] } { + return 1 + } + } + return 0 +} + +# This routine assumes that assert_check has already been called. +proc hosttest_assert_read_dump { output } { + + foreach line [split $output "\n"] { + set dummy "" + set match "" + + if { [regexp -nocase -- {^writing additional output to (.*)$} $line dummy match] } { + + # The filename is in match, but it may not be directly accessible. + set filename [hosttest_translate_existing_filename $match] + if { $filename == "" } { + return "" + } + set status [ catch { + set fd [open $filename r] + set data [read $fd] + close $fd + file delete $filename + } message] + if { $status != 0 } { + unresolved "Unable to process assertion dump file $filename" + unresolved "File $filename may have to be deleted manually" + return "" + } + return $data + } + } + return "" +} + +# Look for the appropriate markers. Also clean up blank lines +# at the start and end. +proc hosttest_assert_extract_callback { dump title } { + + set lines [split $dump "\n"] + set result "" + + while { [llength $lines] > 0 } { + set line [lindex $lines 0] + set lines [lreplace $lines 0 0] + + if { [regexp -nocase -- "^\# \{\{\{.*${title}.*\$" $line] } { + + # Skip any blank lines at the start + while { [llength $lines] > 0 } { + set line [lindex $lines 0] + if { [regexp -- {^ *$} $line] == 0} { + break + } + set lines [lreplace $lines 0 0] + } + + # Now add any lines until the close marker. + # Nested folds are not supported yet. + while { [llength $lines] > 0 } { + set line [lindex $lines 0] + set lines [lreplace $lines 0 0] + if { [regexp -nocase -- {^\# \}\}\}.*$} $line] } { + break + } + append result $line "\n" + } + + return $result + } + } + + return "" +} + + + +