# # Integration of Depot Autopilot into a Run-tool-based test infrastructure # # For a detailed documentation of the user interface and the features of this # Run script please see _repos/gems/src/app/depot_autopilot/README_. # ############################################################## ## Local copies of run tool procedures with small adaptions ## ## ## ## FIXME: Adapt original and remove local copies ## ############################################################## proc autopilot_wait_for_output { wait_for_re timeout_value running_spawn_id } { global output global run_genode_failed 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 } set platform_msg [run_boot_string] if {$platform_msg eq ""} { set platform_msg "undefined platform command startup string sequence" } expect { -i $running_spawn_id $platform_msg { puts stderr "Error: platform rebooted unexpectedly"; exit -4 } -i $running_spawn_id -re $wait_for_re { } # sel4 -i $running_spawn_id -re {Error: ~Cnode - not implemented.*?\n} { puts stderr "Error: Core presumably went out of resources"; set run_genode_failed 1 return } # pistachio kernel fault -i $running_spawn_id -re {--- "KD# Exception caught" ---.*?\n} { puts stderr "Error: Kernel fault"; set run_genode_failed 1 return } # sel4 unknown fault caught by core -i $running_spawn_id -re {Error: unexpected exception during fault.*?stopped.*?\n} { puts stderr "Error: Unknown fault"; set run_genode_failed 1 return } # can happen, for instance, on socat TCP-timeout eof { puts stderr "Error: Spawned process died unexpectedly"; set run_genode_failed 1 return } timeout { puts stderr "Error: Test execution timed out"; set run_genode_failed 1 return } } append output $expect_out(buffer) } proc autopilot_create_tar_from_depot_binaries { archive_path args } { # filter out api and src archives from requested depot content set content {} foreach subdir [_collect_from_depot $args] { if {[regexp [_depot_archive_versioned_path_pattern] $subdir dummy user type]} { if {$type == "src"} continue; if {$type == "api"} continue; } lappend content $subdir } check_for_missing_depot_archives eval "exec tar cf $archive_path -T /dev/null -C [depot_dir] [lsort -unique $content]" } proc autopilot_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} { autopilot_wait_for_output $wait_for_re $timeout_value $running_spawn_id return; } set retry 3 while { $retry != 0 } { if {![run_power_cycle]} { puts "Power cycle step failed, retry." sleep 3 incr retry -1; continue } if {![run_load]} { puts "Load step failed, retry." # kill the spawned load process if there is one if {[load_spawn_id] != -1} { kill_spawned [load_spawn_id] } 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; } ######################## ## Utility procedures ## ######################## # # Whether the run script is used interactively # proc interactive {} { return [expr ![get_cmd_switch --autopilot]] } # # Get value of an environment variable # proc get_env_var { var_name default_value } { if {[info exists ::env($var_name)]} { return $::env($var_name) } return $default_value } # # Check if archives are available without doing anything with them # proc check_archives_available { args } { # filter out api and src archives from requested depot content set content {} foreach subdir [_collect_from_depot $args] { if {[regexp [_depot_archive_versioned_path_pattern] $subdir dummy user type]} { if {$type == "src"} continue; if {$type == "api"} continue; } lappend content $subdir } check_for_missing_depot_archives } # # Return routes for boot modules that shall overlay the test-depot content # proc single_test_module_routes { } { global test_modules set result "" foreach module $test_modules { append result { } } return $result } # # Return autopilot start-nodes for the test packages that shall be run # proc test_pkgs_start_nodes { } { global test_pkgs set result "" foreach test_pkg $test_pkgs { if {[skip_test $test_pkg]} { append result { } } else { append result { } } } return $result } # # Prepare to call run_genode_until (again, with a changed setup) # proc prepare_to_run_genode { } { global output global qemu_args global previous_results global previous_time_ms global previous_succeeded global previous_failed global previous_skipped global test_pkgs global test_srcs global test_builds global test_modules global test_repeat global last_test_pkg global run_genode_failed global serial_id global timeout global initial_qemu_args # reset the QEMU arguments to the value when the run script was entered set qemu_args $initial_qemu_args # compose list of the package archives to add to the depot archive set depot_tar_pkg_archives "" foreach test_pkg $test_pkgs { if {![skip_test $test_pkg]} { append depot_tar_pkg_archives " [depot_user]/pkg/$test_pkg " } } # compose list of the source archives to add to the depot archive set depot_tar_src_archives "" foreach test_src $test_srcs { if {![skip_test $test_src]} { append depot_tar_src_archives " [depot_user]/src/$test_src " } } # compose list of the archives to import to the boot directory set import_archives "" append import_archives { } [depot_user] {/src/} [base_src] { } [depot_user] {/src/report_rom } [depot_user] {/src/fs_rom } [depot_user] {/src/vfs } [depot_user] {/src/loader } [depot_user] {/src/init } [depot_user] {/src/depot_query } if {![skip_test test-lx_block]} { append import_archives [depot_user] {/raw/test-lx_block } } # check existance of all required archives at once set all_archives [concat $depot_tar_pkg_archives \ $depot_tar_src_archives \ $import_archives] if {[interactive]} { check_archives_available {*}$all_archives } else { check_archives_available {*}$import_archives } # create the depot archive in the boot directory create_boot_directory autopilot_create_tar_from_depot_binaries [run_dir]/genode/depot.tar \ {*}$depot_tar_pkg_archives set depot_tar_wo_src_size [file size [run_dir]/genode/depot.tar] append_src_and_api_depot_packages_to_tar [run_dir]/genode/depot.tar \ {*}$depot_tar_src_archives set depot_tar_size [file size [run_dir]/genode/depot.tar] puts "Depot archive has a size of $depot_tar_size bytes \ ($depot_tar_wo_src_size bytes without sources)" # # Install the root-init config # append config { } [single_test_module_routes] { } [string map {\" .} $previous_results] { } [test_pkgs_start_nodes] { } [single_test_module_routes] { } install_config $config # # Create the rest of the boot modules # set build_components { app/depot_autopilot } append build_components $test_builds build $build_components import_from_depot {*}$import_archives # # Build boot image from boot modules # set boot_modules [build_artifacts] append boot_modules $test_modules build_boot_image $boot_modules set last_test_pkg "" set run_genode_failed 0 set serial_id -1 set timeout 40 append qemu_args " -nographic " } # # Initialize variables that accumulate test results for possible reboots # proc init_previous_results {} { global previous_results_on_exit global previous_results global previous_time_ms global previous_succeeded global previous_failed global previous_skipped set previous_results_on_exit 1 set previous_results "" set previous_time_ms 0 set previous_succeeded 0 set previous_failed 0 set previous_skipped 0 } # # Whether a test shall be skipped or not # proc skip_test { test } { global skip_test if {![info exists skip_test($test)]} { return 0 } return $skip_test($test) } # # Whether all given archives and the archives they depend on are available # proc archives_available { archives } { global _missing_depot_archives set buf $_missing_depot_archives set _missing_depot_archives "" _collect_from_depot $archives set result [expr [llength $_missing_depot_archives] == 0] set _missing_depot_archives $buf return $result } # # Initialize variables that can be configured via the user interface # proc init_test_setting {} { global test_pkgs global test_srcs global test_builds global test_modules global test_repeat global default_test_pkgs global default_test_srcs # # Initialize the lists of test package-archives, source-archives, # build-targets, and boot-modules to be considered by the run script # set test_pkgs [get_env_var TEST_PKGS "default"] set test_srcs [get_env_var TEST_SRCS "default"] set test_builds [get_env_var TEST_BUILDS ""] set test_modules [get_env_var TEST_MODULES ""] set test_repeat [get_env_var TEST_REPEAT "false"] set nr_of_tests_to_run 0 # initialize list of test package archives if {$test_pkgs == "default"} { set test_pkgs $default_test_pkgs } foreach test_pkg $test_pkgs { if {[skip_test $test_pkg]} { continue } incr nr_of_tests_to_run if {[interactive]} { continue } if {[archives_available [depot_user]/pkg/$test_pkg]} { continue } set test_pkgs [lsearch -all -inline -not -exact \ $test_pkgs $test_pkg] puts stderr "Warning: remove test package of \"$test_pkg\" because of\ missing archives" } # initialize list of test source archives if {$test_srcs == "default"} { set test_srcs $default_test_srcs } foreach test_src $test_srcs { if {[skip_test $test_src]} { continue } if {[interactive]} { continue } if {[archives_available [depot_user]/src/$test_src]} { continue } set test_srcs [lsearch -all -inline -not -exact \ $test_srcs $test_src] puts stderr "Warning: remove test source of \"$test_src\" because of\ missing archives" } puts "Number of tests to run: $nr_of_tests_to_run" } # # Overwrite exit procedure to ensure to always print a results overview # rename exit run_tool_exit proc exit {{status 0}} { global previous_results_on_exit global previous_results global previous_time_ms global previous_succeeded global previous_failed global previous_skipped if {![info exists previous_results_on_exit] || \ ![info exists previous_results] || \ ![info exists previous_time_ms] || \ ![info exists previous_succeeded] || \ ![info exists previous_failed] || \ ![info exists previous_skipped]} \ { init_previous_results } if {$previous_results_on_exit != 0} { set previous_time_sec [expr $previous_time_ms / 1000] set previous_time_sec_frac [expr $previous_time_ms - $previous_time_sec * 1000] set previous_results_list [split $previous_results "\n"] puts "" puts "Failed to let Depot Autopilot finish!" puts "Result overview simulated by run script:" puts "" puts "\[init -> depot_autopilot] --- Finished after $previous_time_sec.$previous_time_sec_frac sec ---" puts "\[init -> depot_autopilot] " foreach previous_result $previous_results_list { puts "\[init -> depot_autopilot] $previous_result" } puts "\[init -> depot_autopilot] " puts "\[init -> depot_autopilot] succeeded: $previous_succeeded failed: $previous_failed skipped: $previous_skipped" puts "\[init -> depot_autopilot] " } run_tool_exit $status } ################## ## Main routine ## ################## # # Check platform support # if {[expr ![have_spec x86] && \ ![have_spec arm_v6] && \ ![have_spec arm_v7a] && \ ![have_spec arm_v8a] && \ ![have_spec riscv]]} \ { puts "\n Run script is not supported on this platform. \n"; exit 0 } # # Default list of test package-archives # # Was obtained by issuing: # # ! cd /repos # ! find . -type d -wholename *recipes/pkg/test-* -printf '%f\n' | sort # set default_test_pkgs { test-spark test-spark_exception test-spark_secondary_stack test-black_hole test-clipboard test-depot_query_index test-ds_ownership test-dynamic_config test-dynamic_config_loader test-entrypoint test-expat test-fault_detection test-file_vault_config_report test-file_vault_config_report_no_entropy test-fs_packet test-fs_report test-fs_rom_update test-fs_rom_update_fs test-fs_rom_update_ram test-fs_tool test-init test-init_loop test-ldso test-libc test-libc_connect_lwip test-libc_connect_lxip test-libc_connect_vfs_server_lwip test-libc_connect_vfs_server_lxip test-libc_counter test-libc_execve test-libc_fifo_pipe test-libc_fork test-libc_getenv test-libc_pipe test-libc_vfs test-libc_vfs_audit test-libc_vfs_block test-libc_vfs_counter test-libc_vfs_fs test-libc_vfs_fs_chained test-libc_vfs_ram test-log test-lx_block test-mmio test-new_delete test-nic_loopback test-part_block_gpt test-part_block_mbr test-pipe_read_ready test-pthread test-ram_fs_chunk test-read_only_rom test-reconstructible test-registry test-report_rom test-resource_request test-resource_yield test-rm_fault test-rm_fault_no_nox test-rm_nested test-rm_stress test-rom_filter test-sandbox test-sanitizer test-sequence test-signal test-slab test-stack_smash test-stdcxx test-synced_interface test-tcp_bulk_lwip test-tcp_bulk_lxip test-terminal_crosslink test-timer test-tls test-token test-trace test-trace_buffer test-trace_logger test-utf8 test-vfs_block test-vfs_stress_fs test-vfs_stress_ram test-weak_ptr test-xml_generator test-xml_node gcov } # # Default list of test source-archives # set default_test_srcs { test-xml_generator } # # Whether the platform supports non-executable dataspaces # proc non_executable_supported { } { if {[have_spec hw] && [have_spec x86_64]} { return true } if {[have_spec hw] && [have_spec arm]} { return true } if {[have_spec nova] && [have_spec x86_64]} { return true } if {[have_spec foc] && [have_spec x86_64]} { return true } if {[have_spec foc] && [have_spec arm]} { return true } if {[have_spec sel4] && [have_spec arm]} { return true } return false } proc skip_test_if { condition test } { global skip_test if {$condition} { set skip_test($test) true } } # # Whether to skip a test - if undefined for a test, the test is not skipped # # # pbxa9 and zynq_qemu don't support jitterentropy # skip_test_if [expr ([have_board pbxa9] || [have_board zynq_qemu])] test-file_vault_config_report skip_test_if [expr !([have_board pbxa9] || [have_board zynq_qemu])] test-file_vault_config_report_no_entropy # rpi has a quite limited amount of RAM skip_test_if [have_board rpi] test-file_vault_config_report skip_test_if [have_board rpi] test-file_vault_config_report_no_entropy set skip_test(test-fault_detection) [expr [have_spec pistachio] || [have_spec fiasco]] set skip_test(test-fs_packet) [expr ![interactive] && [have_include "power_on/qemu"]] set skip_test(test-libc) [expr [have_spec sel4] || [have_board rpi] || [have_board imx53_qsb_tz]] set skip_test(test-lx_block) [expr ![have_board linux]] set skip_test(test-rm_fault) [expr [have_board linux] || ![non_executable_supported]] set skip_test(test-rm_fault_no_nox) [expr [have_board linux] || ![skip_test test-rm_fault]] set skip_test(test-rm_nested) [expr [have_board linux] || [have_spec pistachio]] set skip_test(test-slab) [expr ![interactive] && [have_include "power_on/qemu"]] set skip_test(test-spark_exception) [expr [have_spec arm]] set skip_test(test-tcp_bulk_lwip) [expr ![have_spec x86] && ![have_include "power_on/qemu"]] set skip_test(test-tcp_bulk_lxip) [expr ![have_spec x86] && ![have_include "power_on/qemu"]] if {[have_spec foc]} { set skip_test(test-entrypoint) [have_board pbxa9] set skip_test(test-libc) [have_board pbxa9] set skip_test(test-tcp_bulk_lxip) [have_board pbxa9] # foc on pbxa9 caps RAM at 256 MiB - skip tests with excessive RAM demand skip_test_if [have_board pbxa9] test-file_vault_config_report skip_test_if [have_board pbxa9] test-file_vault_config_report_no_entropy } if {[have_spec riscv]} { set skip_test(gcov) true set skip_test(test-libc_connect_lxip) true set skip_test(test-libc_connect_vfs_server_lxip) true set skip_test(test-rm_fault_no_nox) true set skip_test(test-spark) true set skip_test(test-spark_exception) true set skip_test(test-spark_secondary_stack) true set skip_test(test-tcp_bulk_lxip) true set skip_test(test-trace_logger) true set skip_test(test-xml_generator) true set skip_test(test-file_vault_config_report) true } # # FIXME # # When doing the libc_getenv test on autopilot+foc+x86 and one of the # subsequent tests crashes the system so it gets rebooted by the run script, # the system doesn't come up again. It gets stuck after core initialization. # set skip_test(test-libc_getenv) [expr ![interactive] && [have_spec foc] && [have_spec x86]] # remember initial qemu args in case we have to re-boot later set initial_qemu_args "" if {[info exists qemu_args]} { set initial_qemu_args $qemu_args } # initialize global variables init_test_setting init_previous_results # # Some platforms have a problem with executing all tests in a single boot. # Thus, we have to re-boot them periodically. # set max_nr_of_tests_per_boot 0 if {[have_spec sel4]} { set max_nr_of_tests_per_boot 20 } # generic preparation for each system boot prepare_to_run_genode while {1} { # check whether the system is already running if {$serial_id == -1} { # boot the system and wait for the depot autopilot to come up autopilot_run_genode_until {depot_autopilot\] --- .*?\n} $timeout set serial_id [output_spawn_id] set nr_of_tests_this_boot 0 # if the system didn't even boot, exit (prints previous results) if {$run_genode_failed} { exit -1 } } else { # wait for the next step of the depot autopilot set init_time_ms [clock clicks -millisec] autopilot_run_genode_until {depot_autopilot\] --- .*?\n} $timeout $serial_id set previous_time_ms [expr $previous_time_ms + [expr ([clock clicks -millisec] - $init_time_ms)] ] set serial_id [output_spawn_id] # remove last test from list and check if we have to reboot the system set test_pkgs [lsearch -all -inline -not -exact $test_pkgs $last_test_pkg] if {$run_genode_failed} { # shut-down running system kill_spawned $serial_id run_power_off # remember result of last test if {$previous_results != ""} { append previous_results \012 } append previous_results { } [format {%-31s %-6s %7s} $last_test_pkg "failed " "$timeout.000"] { reboot} incr previous_failed # prepare system re-boot prepare_to_run_genode continue } } # if the autopilot finished all tests, evaluate its return value if {[regexp {depot_autopilot\] --- Finished} $output]} { set output "" run_genode_until {child "depot_autopilot" exited with exit value.*?\n} 10 $serial_id set previous_results_on_exit 0 grep_output {^\[init\] } compare_output_to {[init] child "depot_autopilot" exited with exit value 0} exit 0 } # if the autopilot started a new test, set a new timeout if {[regexp {depot_autopilot\] --- Run} $output]} { # if we ran and finished another test beforehand, parse its result if {$last_test_pkg != ""} { # remember result of last test in case the system must be restartet set last_test_result "" regexp {depot_autopilot\] ( [^\033]+)} $output ignored last_test_result regsub -all {<} $last_test_result {\<} last_test_result set failed_off [string first " failed" $last_test_result] set skipped_off [string first " skipped" $last_test_result] set ok_off [string first " ok" $last_test_result] if {$failed_off > 0 && ($skipped_off < 0 || $failed_off < $skipped_off) && ($ok_off < 0 || $failed_off < $ok_off)} { incr previous_failed } elseif {$skipped_off > 0 && ($ok_off < 0 || $skipped_off < $ok_off)} { incr previous_skipped } elseif {$ok_off > 0} { incr previous_succeeded } else { puts "Error: malformed test result" puts $last_test_result exit -1 } if {$previous_results != ""} { append previous_results \012 } append previous_results $last_test_result incr nr_of_tests_this_boot } # if the Autopilot is currently repeating, reset repeat-influenced variables if {[llength $test_pkgs] == 0} { init_test_setting init_previous_results } # determine timeout for the next timeout set min_timeout 30 regexp {depot_autopilot\] --- Run "(.*?)" \(max ([0-9]*?) } $output] ignore last_test_pkg min_timeout set timeout [expr $min_timeout + 30] # re-boot if the maximum number of tests per boot is set and reached if { [expr $max_nr_of_tests_per_boot && \ $nr_of_tests_this_boot >= $max_nr_of_tests_per_boot] } \ { # shut-down running system puts "Re-booting to meet maximum number of tests per boot for this platform" kill_spawned $serial_id run_power_off # prepare system re-boot prepare_to_run_genode continue } set output "" } }