genode/tool/run/run

889 lines
20 KiB
Plaintext
Raw Normal View History

2015-01-08 21:08:48 +00:00
#!/usr/bin/expect
#
# \brief Framework for running automated tests
# \author Norman Feske
# \date 2010-03-16
#
# Usage: run --name <run_name> --include <run_script> ...
#
# The '--name' argument is used for as name for the boot-image and
# temporary directories. The files includes via the '--include'
# argument provide platform-specific additions/refinements to the
# test framework as well as the actual test steps.
#
##
# Remove leading and trailing whitespace from string
#
proc strip_whitespace {string} {
regsub -all {^\s+} $string "" string
regsub -all {\s+$} $string "" string
return $string
}
##
# Check if the specified spec requirement is satisfied
#
proc assert_spec {spec} {
global specs
if {[lsearch $specs $spec] == -1} {
puts stderr "Test requires '$spec'"
exit 0
}
}
##
# Build genode targets specified as space-separated strings
#
# If the build process fails, this procedure will exit the program with
# the error code -4.
#
proc build {targets} {
if {[get_cmd_switch --skip-build]} return
regsub -all {\s\s+} $targets " " targets
puts "building targets: $targets"
set timeout 10000
set pid [eval "spawn make $targets"]
expect { eof { } }
if {[lindex [wait $pid] end] != 0} {
puts "Error: Genode build failed"
exit -4
}
puts "genode build completed"
}
##
# Create a fresh boot directory
#
proc create_boot_directory { } {
exec rm -rf [run_dir]
exec mkdir -p [run_dir]
exec mkdir -p [run_dir]/genode
}
##
# Append string to variable only if 'condition' is satisfied
#
proc append_if {condition var args} {
2015-01-08 21:08:48 +00:00
upvar $var up_var
if {$condition} { append up_var [join $args ""] }
2015-01-08 21:08:48 +00:00
}
##
# Append element to list only if 'condition' is satisfied
#
proc lappend_if {condition var string} {
upvar $var up_var
if {$condition} { lappend up_var $string }
}
##
# Check syntax of specified XML file using xmllint
#
proc check_xml_syntax {xml_file} {
if {![have_installed xmllint]} {
puts "Warning: Cannot validate config syntax (please install xmllint)"
return;
}
if {[catch {exec xmllint --noout $xml_file} result]} {
puts stderr $result
puts stderr "Error: Invalid XML syntax in $xml_file"
exit 1
}
}
##
# Validate file to match Genode's XML schema using xmllint
#
proc check_xml_with_schema {xml_file} {
if {![have_installed xmllint]} {
puts "Warning: Cannot validate config syntax (please install xmllint)"
return;
}
if {[catch {exec xmllint --noout -schema [genode_dir]/tool/run/genode.xsd $xml_file} result]} {
if {$result != "$xml_file validates"} {
puts stderr "$result"
puts stderr "Error: Invalid XML syntax in $xml_file"
exit 1
}
}
}
##
# Install content of specified variable as init config file
2015-01-08 21:08:48 +00:00
#
proc install_config { args } {
2015-01-08 21:08:48 +00:00
set fh [open "[run_dir]/genode/config" "WRONLY CREAT TRUNC"]
puts $fh [join $args {}]
2015-01-08 21:08:48 +00:00
close $fh
check_xml_with_schema [run_dir]/genode/config
2015-01-08 21:08:48 +00:00
}
##
# Integrate specified binaries into boot image
#
# \param binaries space-separated list of file names located within the
# '<build-dir>/bin/' directory
#
proc build_boot_image {binaries} {
check_for_missing_depot_archives
2015-01-08 21:08:48 +00:00
run_boot_dir $binaries
}
# set expect match-buffer size
match_max -d 40000
2015-01-08 21:08:48 +00:00
##
# Execute Genode
#
# \param wait_for_re regular expression that matches the test completion
# \param timeout_value timeout in seconds
# \param spawn_id spawn_id of a already running and spawned process
# spawn_id may be a list of spawned processes if needed
# \global output contains the core output (modified)
#
# If the function is called without any argument, Genode is executed in
# interactive mode.
#
# If the test execution times out, this procedure will exit the program with
# the error code -2.
#
proc run_genode_until {{wait_for_re forever} {timeout_value 0} {running_spawn_id -1}} {
#
# If a running_spawn_id is specified, wait for the expected output
#
if {$running_spawn_id != -1} {
wait_for_output $wait_for_re $timeout_value $running_spawn_id
return;
}
set retry 3
while { $retry != 0 } {
if {[expr [run_power_on] == false]} {
puts "Power on step failed, retry."
sleep 3
incr retry -1;
continue
}
2015-01-08 21:08:48 +00:00
if {![run_load]} {
puts "Load step failed, retry."
# kill the spawned load process if there is one
if {[load_spawn_id] != -1} {
set pid [exp_pid -i [load_spawn_id]]
exec kill -9 $pid
}
incr retry -1;
continue;
}
if {![run_log $wait_for_re $timeout_value]} {
puts "Log step failed, retry."
incr retry -1;
continue;
}
return;
}
puts stderr "Boot process failed 3 times in series. I give up!";
exit -1;
}
##
# Remove color information from output
#
proc filter_out_color_escape_sequences { } {
global output
regsub -all {\e\[.*?m} $output "" output
}
##
# Remove superfluous empty lines and unify line endings from output
#
proc trim_lines { } {
global output
regsub -all {[\r\n]+} $output "\n" output
}
##
# Filter output based on the specified pattern
#
# Only those lines that match the pattern are preserved.
#
proc grep_output {pattern} {
global output
filter_out_color_escape_sequences
trim_lines
set output_list [split $output "\n"]
set filtered ""
foreach line $output_list {
if {[regexp $pattern $line]} {
append filtered "$line\n"
}
}
set output $filtered
}
##
# Unify known variations that appear in the test output
#
# \global output test output (modified)
#
proc unify_output {pattern replacement} {
global output
regsub -all $pattern $output $replacement output
}
##
# Compare output against expected output line by line
#
# \param good expected test output
# \global output test output
#
# This procedure will exit the program with the error code -1 if the
# comparison fails.
#
proc compare_output_to { good } {
global output
set output_list [split [strip_whitespace $output] "\n"]
set good_list [split [strip_whitespace $good] "\n"]
set i 0
set mismatch_cnt 0
foreach good_line $good_list {
set output_line [strip_whitespace [lindex $output_list $i]]
set good_line [strip_whitespace $good_line]
if {$output_line != $good_line} {
puts ""
puts stderr "Line $i of output is unexpected"
puts stderr " expected: '$good_line'"
puts stderr " got: '$output_line'"
incr mismatch_cnt
}
incr i
}
#
# if $good is empty the foreach-loop isn't entered
# so we've to check for it separately
#
if {![llength $good_list] && [llength $output_list]} {
foreach output_line $output_list {
set output_line [strip_whitespace $output_line]
puts ""
puts stderr "Line $i of output is unexpected"
puts stderr " got: '$output_line'"
incr mismatch_cnt
incr i
}
}
if {$mismatch_cnt > 0} {
puts "Error: Test failed, $mismatch_cnt unexpected lines of output"
exit -1
}
}
##
# Return true if command-line switch was specified
#
proc get_cmd_switch { arg_name } {
global argv
return [expr [lsearch $argv $arg_name] >= 0]
}
##
# Return command-line argument value
#
# If a argument name is specified multiple times, a
# list of argument values is returned.
#
proc get_cmd_arg { arg_name default_value } {
global argv
# find argument name in argv list
set arg_idx_list [lsearch -all $argv $arg_name]
if {[llength $arg_idx_list] == 0} { return $default_value }
set result {}
foreach arg_idx $arg_idx_list {
set next_idx [expr $arg_idx + 1]
# stop if argv ends with the argument name
if {$next_idx >= [llength $argv]} continue
# return list element following the argument name
lappend result [lindex $argv $next_idx]
}
# if argument occurred only once, return its value
if {[llength $result] == 1} { return [lindex $result 0] }
# if argument occurred multiple times, contain list of arguments
return $result
}
##
# Return command-line argument value
#
# If a argument name is specified multiple times, return only the
# first match.
#
proc get_cmd_arg_first { arg_name default_value } {
global argv
set arg_idx [lsearch $argv $arg_name]
if {$arg_idx == -1} { return $default_value }
return [lindex $argv [expr $arg_idx + 1]]
}
#
# Read command-line arguments
#
set run_name [get_cmd_arg --name "noname"]
set genode_dir [get_cmd_arg --genode-dir ""]
set cross_dev_prefix [get_cmd_arg --cross-dev-prefix ""]
set specs [get_cmd_arg --specs ""]
set repositories [get_cmd_arg --repositories ""]
# accessor functions for command-line arguments
proc run_name { } { global run_name; return $run_name }
proc run_dir { } { global run_name; return var/run/$run_name }
proc genode_dir { } { global genode_dir; return $genode_dir }
proc cross_dev_prefix { } { global cross_dev_prefix; return $cross_dev_prefix }
##
# Return true if spec value is set for the build
#
proc have_spec {spec} { global specs; return [expr [lsearch $specs $spec] != -1] }
##
# Return true if specified program is installed
#
proc have_installed {program} {
if {[catch { exec which $program }]} { return false; }
return true
}
##
# Return true if specified program is installed on the host platform
#
proc requires_installation_of {program} {
if {![have_installed $program]} {
puts "Run script aborted because $program is not installed"; exit
}
}
##
# Return first repository containing the given path
#
proc repository_contains {path} {
global repositories;
foreach i $repositories {
if {[file exists $i/$path]} { return $i }
}
}
##
## Utilities for performing steps that are the same on several platforms
##
##
# Read kernel location from build-directory configuration
#
# If config file does not exist or if there is no 'KERNEL' declaration in the
# config file, the function returns 'default_location'. If the config file
# points to a non-existing kernel image, the function aborts with the exit
# value -6.
#
proc kernel_location_from_config_file { config_file default_location } {
global _kernel
if {![info exists _kernel]} {
if {[file exists $config_file]} {
set _kernel [exec sed -n "/^KERNEL/s/^.*=\\s*//p" $config_file]
# check if the regular expression matched
if {$_kernel != ""} {
if {[file exists $_kernel]} {
return $_kernel
} else {
puts stderr "Error: kernel specified in '$config_file' does not exist"
exit -6
}
}
}
# try to fall back to version hosted with the Genode build directory
set _kernel $default_location
}
return $_kernel
}
##
# Return name of kernel-specific binary for a given generic name
#
# The boot_dir plugin may provide functions named 'binary_name_<binary>'
# where '<binary>' stands for a generic name like "timer" or "nic". The
# function returns the name of the binary to integrate into the boot
# directory under the name '<binary>'.
#
# If no such function exists, it returns the argument as is. This is the
# case for regular binaries that appear in the boot directory under their
# original name.
#
proc kernel_specific_binary { binary {silent ""} } {
regsub -all {\.} $binary "_" function_suffix
set function_name "binary_name_$function_suffix"
if {[info procs $function_name] == $function_name} {
set binary_name [$function_name]
if {$silent != "silent"} {
puts "using '$binary_name' as '$binary'"
}
return [$function_name]
}
return $binary
}
proc copy_genode_binaries_to_run_dir { binaries } {
2015-01-08 21:08:48 +00:00
foreach binary $binaries {
file copy -force bin/[kernel_specific_binary $binary] [run_dir]/genode/$binary
2015-01-08 21:08:48 +00:00
}
}
##
# Wait for a specific output of a already running spawned process
#
proc wait_for_output { wait_for_re timeout_value running_spawn_id } {
global output
if {$wait_for_re == "forever"} {
set timeout -1
interact {
\003 {
send_user "Expect: 'interact' received 'strg+c' and was cancelled\n";
exit
}
-i $running_spawn_id
}
} else {
set timeout $timeout_value
}
2015-10-26 14:02:14 +00:00
set platform_msg [run_boot_string]
if {$platform_msg eq ""} {
set platform_msg "undefined platform command startup string sequence"
}
2015-01-08 21:08:48 +00:00
expect {
2015-10-26 14:02:14 +00:00
-i $running_spawn_id $platform_msg { puts stderr "Error: platform rebooted unexpectedly"; exit -4 }
2015-01-08 21:08:48 +00:00
-i $running_spawn_id -re $wait_for_re { }
eof { puts stderr "Error: Spawned process died unexpectedly"; exit -3 }
timeout { puts stderr "Error: Test execution timed out"; exit -2 }
}
append output $expect_out(buffer)
2015-01-08 21:08:48 +00:00
}
##
## Fall-back implementations of all run module procedures
##
##
# Dummy boot_string procedure
#
proc run_boot_string { } { return ""; }
2015-01-08 21:08:48 +00:00
##
# Fall-back boot_dir module
#
# If this function is called someone forgot to include an appropriate boot_dir
# module. So, we exit with an error.
#
proc run_boot_dir { binaries } {
puts stderr "Error: boot_dir module missing, e.g., '--include boot_dir/hw'"
exit 1
}
2015-01-08 21:08:48 +00:00
##
# Dummy image build module
#
proc run_image { {image "" } } { return true; }
##
# Dummy load module
#
proc run_load { } { return true; }
##
# Dummy output module
#
# XXX explain why exit 0 is appropiate
#
proc run_log { wait_for_re timeout_value } { exit 0 }
##
# Dummy power_on module
#
proc run_power_on { } { return true; }
##
# Dummy power_off module
#
proc run_power_off { } { return true; }
##
# Default core linker options
#
proc core_ld_opts { } {
set ret { -Wl,-T }
lappend ret "-Wl,[genode_dir]/repos/base/src/ld/genode.ld"
return $ret
}
##
# Default core link address
#
proc core_link_address { } { return "0x01000000" }
2015-01-08 21:08:48 +00:00
##
# Check if a specific file is included
#
proc have_include { name } {
global include_list
foreach element $include_list {
if {[string equal $element $name]} {
return true
}
}
return false
}
##
# Override the exit procedure
#
# We have to override the exit procedure to make sure that a loaded
# run_power_off procedure is in any case execute when stopping the
# execution of the run tool.
#
rename exit real_exit
proc exit {{status 0}} {
run_power_off
real_exit $status
}
##
# Determine terminal program
#
proc terminal { } {
global env
if {[info exists env(COLORTERM)]} {
return $env(COLORTERM)
}
return $env(TERM)
}
##
# Determine GDB executable installed at the host
#
proc gdb { } {
if {[have_installed "[cross_dev_prefix]gdb"]} {
return "[cross_dev_prefix]gdb" }
if {[have_installed gdb]} {
return "gdb" }
requires_installation_of gdb
}
##
# Check if a shell command is installed
#
# \param command name of the command to search
#
# \return absolute path of command if found, or exists if not
#
proc check_installed {command} {
if { [catch {set path [exec which $command]}] == 0} {
return $path
}
set dir { /sbin /usr/sbin /usr/local/bin }
foreach location $dir {
append location / $command
if { [file exists $location] == 1} {
return $location
}
}
puts stderr "Error: '$command' command could be not found. Please make sure to install the"
puts stderr " packet containing '$command', or make it available in your PATH variable.\n"
exit 1
}
##
# Generate assembly code aggregating boot-module data from specified files.
#
proc generate_boot_modules_asm {path modules} {
# architecture dependent definitions
if {[have_spec "64bit"]} { set address_type ".quad"
} else { set address_type ".long" }
# header
set asm_src {}
append asm_src ".set MIN_PAGE_SIZE_LOG2, 12\n"
append asm_src ".set DATA_ACCESS_ALIGNM_LOG2, 3\n"
append asm_src "\n"
append asm_src ".section .data\n"
append asm_src "\n"
append asm_src ".p2align DATA_ACCESS_ALIGNM_LOG2\n"
append asm_src ".global _boot_modules_headers_begin\n"
append asm_src "_boot_modules_headers_begin:\n"
append asm_src "\n"
# module list
set i 0
foreach module $modules {
incr i
append asm_src "${address_type} _boot_module_${i}_name\n"
append asm_src "${address_type} _boot_module_${i}_begin\n"
append asm_src "${address_type} _boot_module_${i}_end -"
append asm_src " _boot_module_${i}_begin\n"
append asm_src "\n"
}
append asm_src ".global _boot_modules_headers_end\n"
append asm_src "_boot_modules_headers_end:\n"
append asm_src "\n"
# module names
set i 0
foreach module $modules {
incr i
append asm_src ".p2align DATA_ACCESS_ALIGNM_LOG2\n"
append asm_src "_boot_module_${i}_name:\n"
append asm_src ".string \"${module}\"\n"
append asm_src ".byte 0\n"
append asm_src "\n"
}
# header end
append asm_src ".section .data.boot_modules_binaries\n"
append asm_src "\n"
append asm_src ".global _boot_modules_binaries_begin\n"
append asm_src "_boot_modules_binaries_begin:\n"
append asm_src "\n"
# module data
set i 0
foreach module $modules {
incr i
append asm_src ".p2align MIN_PAGE_SIZE_LOG2\n"
append asm_src "_boot_module_${i}_begin:\n"
append asm_src ".incbin \"${path}/${module}\"\n"
append asm_src "_boot_module_${i}_end:\n"
append asm_src "\n"
}
append asm_src ".p2align MIN_PAGE_SIZE_LOG2\n"
append asm_src ".global _boot_modules_binaries_end\n"
append asm_src "_boot_modules_binaries_end:\n"
return $asm_src
}
##
# Link core image containing given modules
#
proc build_core {lib modules target link_address} {
# generate assembly code aggregating the modules data
set asm_src [generate_boot_modules_asm [run_dir]/genode $modules]
# architecture dependent definitions
set arch {}
if {[have_spec "x86_64"]} { set arch { -m64 -mcmodel=large } }
if {[have_spec "x86_32"]} { set arch -m32 }
# determine the libgcc
set libgcc [exec [cross_dev_prefix]gcc -print-libgcc-file-name {*}$arch]
# compile the boot modules into one object file
exec [cross_dev_prefix]gcc {*}$arch -c -x assembler -o [run_dir].boot_modules.o - << $asm_src
# link final image
exec [cross_dev_prefix]g++ {*}$arch -nostdlib {*}[core_ld_opts] \
-Wl,-z -Wl,max-page-size=0x1000 \
-Wl,-Ttext=$link_address -Wl,-gc-sections \
-Wl,-nostdlib -Wl,--whole-archive -Wl,--start-group \
$lib [run_dir].boot_modules.o -Wl,--no-whole-archive \
-Wl,--end-group $libgcc -o $target
}
##
# Return kernel-specific files to be excluded from the core image
#
proc kernel_files { } { return { } }
##
# Generate bootable core image containing all boot-modules
#
proc build_core_image { modules } {
set core_obj [kernel_specific_binary core.o]
# replace 'core' with actual core-object name in 'modules' list
if {[lsearch $modules "core"] != -1} {
set idx [lsearch $modules "core"]
set modules [lreplace $modules $idx $idx]
lappend modules $core_obj
}
copy_genode_binaries_to_run_dir $modules
Disambiguate kernel-specific file names This patch removes possible ambiguities with respect to the naming of kernel-dependent binaries and libraries. It also removes the use of kernel-specific global side effects from the build system. The reach of kernel-specific peculiarities has thereby become limited to the actual users of the respective 'syscall-<kernel>' libraries. Kernel-specific build artifacts are no longer generated at magic places within the build directory (like okl4's includes, or the L4 build directories of L4/Fiasco and Fiasco.OC, or the build directories of various kernels). Instead, such artifacts have been largely moved to the libcache. E.g., the former '<build-dir>/l4/' build directory for the L4 build system resides at '<build-dir>/var/libcache/syscall-foc/build/'. This way, the location is unique to the kernel. Note that various tools are still generated somewhat arbitrarily under '<build-dir>/tool/' as there is no proper formalism for building host tools yet. As the result of this work, it has become possible to use a joint Genode build directory that is usable with all kernels of a given hardware platform. E.g., on x86_32, one can now seamlessly switch between linux, nova, sel4, okl4, fiasco, foc, and pistachio without rebuilding any components except for core, the kernel, the dynamic linker, and the timer driver. At the current stage, such a build directory must still be created manually. A change of the 'create_builddir' tool will follow to make this feature easily available. This patch also simplifies various 'run/boot_dir' plugins by removing the option for an externally hosted kernel. This option remained unused for many years now. Issue #2190
2016-12-10 00:30:38 +00:00
# create core binary without modules for debugging
build_core debug/$core_obj {} [run_dir].core [core_link_address]
# determine modules to be incorporated into the core image
set modules [glob -nocomplain -tails -directory [run_dir]/genode/ *]
set excluded_modules [kernel_files]
lappend excluded_modules $core_obj
foreach excluded $excluded_modules {
set modules [lsearch -inline -not -all $modules $excluded] }
# check syntax of all boot modules named *.config
foreach file [glob -nocomplain [run_dir]/genode/*.config] {
check_xml_syntax $file }
# create core binary containing the boot modules
build_core [run_dir]/genode/$core_obj $modules [run_dir]/image.elf [core_link_address]
exec [cross_dev_prefix]strip [run_dir]/image.elf
# Save config part of the image.elf for easy inspection
exec cp -f [run_dir]/genode/config [run_dir].config
}
source [genode_dir]/tool/run/depot.inc
2015-01-08 21:08:48 +00:00
##
## Execution of run scripts
##
set include_list { }
##
# Read and execute files specified as '--include' arguments
#
# Out of convenience run modules may be included by using a relative path.
#
foreach include_name [get_cmd_arg --include ""] {
# first check if the include name is absolute
if {[string first "/" $include_name] == 0} {
puts "including $include_name"
lappend include_list $include_name
source $include_name
continue
}
# if it is relative, check run modules
set run_path [genode_dir]/tool/run
set dir { etc }
lappend dir $run_path
set found 0
foreach location $dir {
set include_file $location/$include_name
if {[file exists $include_file] == 1} {
puts "including $include_file"
lappend include_list $include_name
source $include_file
set found 1
break
} else {
}
}
if {!$found} {
puts stderr "Aborting, could not load '$include_file'"
exit -1;
}
}
puts "\nRun script execution successful."
exit 0