diff options
author | Abdellatif El Khlifi <abdellatif.elkhlifi@arm.com> | 2020-08-07 13:06:40 +0100 |
---|---|---|
committer | Rui Miguel Silva <rui.silva@linaro.org> | 2020-08-17 10:31:37 +0100 |
commit | 98bfa31c7c10fb2ddb6bd7922cd27c5ad7389f6c (patch) | |
tree | 017f6fac4522c1f06a763ad0f93cd7ea78cdcd28 | |
parent | ba72bdd5d8dbf327732cdfb2fea753076416a177 (diff) |
platform/corstone700: platform/CA5DS: adding model scripts
This commit adds support for corstone700 and CA5DS FVP scripts.
Change-Id: Ib1a3f61ecf685572428f70df49631b05861ae4b1
Signed-off-by: Abdellatif El Khlifi <abdellatif.elkhlifi@arm.com>
-rwxr-xr-x | iot/run_model.sh | 80 | ||||
-rw-r--r-- | iot/scripts/platforms/a5ds/a5ds_fvp.py | 131 | ||||
-rw-r--r-- | iot/scripts/platforms/a5ds/a5ds_testrunner.py | 56 | ||||
-rw-r--r-- | iot/scripts/platforms/corstone700/corstone700_fvp.py | 184 | ||||
-rw-r--r-- | iot/scripts/platforms/corstone700/corstone700_testrunner.py | 114 | ||||
-rw-r--r-- | iot/scripts/test/fvp_wrapper.py | 555 | ||||
-rw-r--r-- | iot/scripts/test/testrunner.py | 198 | ||||
-rw-r--r-- | iot/scripts/test/testselector.py | 166 | ||||
-rw-r--r-- | iot/scripts/test/utils.py | 21 |
9 files changed, 1505 insertions, 0 deletions
diff --git a/iot/run_model.sh b/iot/run_model.sh new file mode 100755 index 0000000..1187a6c --- /dev/null +++ b/iot/run_model.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# This proprietary software may be used only as authorised by a licensing +# agreement from Arm Limited +# (C) COPYRIGHT 2019-2020 Arm Limited +# The entire notice above must be reproduced on all authorised copies and +# copies may only be made to the extent permitted by a licensing agreement from +# ARM Limited. + +# Set your ARMLMD_LICENSE_FILE License path for FVP licenses before running this script + +# Get full absolute path to the directory of this file +pushd $(dirname "$0") +BASEDIR=$(pwd) +popd + +help() { + echo "usage: run_model.sh \${FVP executable path} [ -u ]" + echo " -u: Run unit test selector using pyIRIS" + echo " No additional argument: load and execute model" + exit 1 +} + +# Ensure that an FVP path has been provided +if [ -z "$1" ] +then + help +fi + +cs700="Corstone-700" +a5ds="CA5DS" +YOCTO_DISTRO="poky-tiny" +YOCTO_IMAGE="arm-reference-image" + +if [[ $1 =~ $cs700 ]]; then + + if [ -z "$MACHINE" ]; then + MACHINE="corstone700-fvp" + fi + + echo "Corstone700: using $MACHINE machine , $YOCTO_DISTRO DISTRO" + + OUTDIR=${BASEDIR}/../../build-${YOCTO_DISTRO}/tmp-$(echo ${YOCTO_DISTRO} | sed 's/-/_/g')/deploy/images/${MACHINE} + DIRNAME=corstone700 + +else + OUTDIR=${BASEDIR}/../../build-${YOCTO_DISTRO}/tmp-${echo ${YOCTO_DISTRO} | sed 's/-/_/g'}/deploy/images/a5ds + DIRNAME=a5ds +fi + +if [ -z "$2" -o "$2" == "-S" ] +then + if [[ $1 =~ $cs700 ]]; then + echo "================== Launching Corstone700 Model ===============================" + $1 \ + -C se.trustedBootROMloader.fname="${OUTDIR}/se_romfw.bin" \ + -C board.flashloader0.fname="${OUTDIR}/${YOCTO_IMAGE}-${MACHINE}.wic.nopt" \ + -C extsys_harness0.extsys_flashloader.fname="${OUTDIR}/es_flashfw.bin" \ + -C board.xnvm_size=64 \ + -C board.hostbridge.interfaceName="tap0" \ + -C board.smsc_91c111.enabled=1 \ + $2 + elif [[ $1 =~ $a5ds ]]; then + echo "================== Launching CA5-DS Model ===============================" + $1 \ + -C board.flashloader0.fname="${OUTDIR}/bl1.bin" \ + --data css.cluster.cpu0="${OUTDIR}/iota-tiny-image-a5ds.wic@0x80000000" \ + $2 + else + help + fi +elif [ "$2" == "-u" ] +then + # The FVP model executable is used. + python ${BASEDIR}/scripts/test/testselector.py \ + --${DIRNAME} "--image_dir ${OUTDIR} --fvp ${1}" +else + help +fi + + diff --git a/iot/scripts/platforms/a5ds/a5ds_fvp.py b/iot/scripts/platforms/a5ds/a5ds_fvp.py new file mode 100644 index 0000000..78430c4 --- /dev/null +++ b/iot/scripts/platforms/a5ds/a5ds_fvp.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python2.7 +# Python 2.7 is <required> for fm.debug + +__copyright__ = """ +Copyright (c) 2019, Arm Limited and Contributors. All rights reserved. + +SPDX-License-Identifier: BSD-3-Clause +""" + +import sys +import os +a5ds_dir = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join((a5ds_dir),'..','..', 'test')) +from fvp_wrapper import FVPWrapper, TelnetWatcher + +""" a5ds_fvp.py +This file contains the a5ds FVP subclass of the generic FVP wrapper class. +Upon instantiation, telnet watchers are created for each UART exposed by the +FVP, and - using the provided test specification - watchers are set up with their +respective stop and verification conditions. +""" + + +"""a5dsDefaultConfig +default a5ds configuration parameters. +Note that these are fully platform dependant, and are only specified in the +following map to provide a clear overview of what the configuration constants +of this script are. +""" +a5dsDefaultConfig = { + # =============== FVP Parameters =============== + # Stop condition + "stop_cnd" : "/OSCI/SystemC: Simulation stopped by user", + # data + "host_cpu0" : "css.cluster.cpu0", + # address1 + "address1" : "0x80000000", + # Flash loaders + "board_ROMloader" : "board.flashloader0.fname", + # UART logs + "host_uart0" : "css.uart_0.out_file", + + # Telnet parameters + "telnet_host" : 'localhost', + "host_telnet_port0" : 5000, + + # =============== Test parameters ============== + "linux_login_prompt" : "a5ds login:", + "linux_user" : "root", + "linux_shstring" : "root@a5ds:~# " +} + + +""" a5dsDefaultTestspec +Test-specification parameters for a5ds. +Note that this is fully platform-dependant, and only used during the +initialization of a5dsFVP. +""" +a5dsDefaultTestspec = { + "name" : None, # Test name + "commands" : [], # Commands to execute on Host + "board_flash" : None, # Board flash image + "host_stop_str" : None, # Stop condition string for Host + "host_ver_strs" : [], # Verification strings for Host +} + +class A5dsFVP(FVPWrapper): + def __init__(self, testspec, fvp_path, image_dir, usermode, fvp_timeout, stdin): + FVPWrapper.__init__( + self, + fvp_path=fvp_path, + fvp_name="A5ds", + usermode=usermode, + work_dir=a5ds_dir, + fvp_timeout=fvp_timeout, + testname=testspec['name'], + stdin=stdin + ) + + self.config = a5dsDefaultConfig + self.testspec = self.parseTestspec(testspec) + self.image_dir = image_dir + + # Define watchers for each terminal + # Host terminal 0 watcher + host0_watcher = TelnetWatcher( + name="host0", + termfile=os.path.join(self.work_dir, self.log_dir, self.testspec['name'] + "_host0.txt"), + stop_str=self.testspec['host_stop_str'], + fvp_uart=self.config['host_uart0'], + port=self.config['host_telnet_port0'], + sys_stop_str=self.config['stop_cnd'], + verification_strs=self.testspec['host_ver_strs'] + ) + # We define an initial command sequence for host0 which will login + # and await until a user can enter commands + host0_watcher.addCommand('r', self.config['linux_login_prompt']) + host0_watcher.addCommand('w', self.config['linux_user']) + host0_watcher.addCommand('r', self.config['linux_shstring']) + # Once the host is logged in, we add the user-provided test commands + for commandtype, command in self.testspec['commands']: + host0_watcher.addCommand(commandtype, command) + self.watchers.append(host0_watcher) + + + def getModelParameters(self): + # Assign images to FVP flashloaders + fvp_params = {} + fvp_params[self.config['board_ROMloader']] = os.path.join(self.image_dir, "bl1.bin") + print(fvp_params) + return fvp_params + + def getModelData(self): + # Assign images to FVP flashloaders + fvp_data = {} + fvp_data[self.config['host_cpu0']] = os.path.join(self.image_dir, "iota-tiny-image-a5ds.wic" + "@" + self.config['address1']) + print(fvp_data) + return fvp_data + + + def parseTestspec(self, testspec): + """ Function for parsing a test-specification in line with the arguments + made available by a5dsDefaultTestspec + """ + if 'name' not in testspec or 'commands' not in testspec: + sys.exit(1) + + # Merge user-specified arguments with default test specification + defaultTestspec = a5dsDefaultTestspec + defaultTestspec.update(testspec) + return defaultTestspec diff --git a/iot/scripts/platforms/a5ds/a5ds_testrunner.py b/iot/scripts/platforms/a5ds/a5ds_testrunner.py new file mode 100644 index 0000000..d728ebc --- /dev/null +++ b/iot/scripts/platforms/a5ds/a5ds_testrunner.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python2.7 + +__copyright__ = """ +Copyright (c) 2019, Arm Limited and Contributors. All rights reserved. + +SPDX-License-Identifier: BSD-3-Clause +""" + +import sys +import os +sys.path.append(os.path.join((os.path.dirname(os.path.realpath(__file__))),'..','..','test')) +from testrunner import TestRunner +from a5ds_fvp import A5dsFVP + +class A5dsTestRunner(TestRunner): + def __init__(self): + TestRunner.__init__( + self, A5dsFVP + ) + + def setSpecializationArguments(self): + # a5ds_fvp requires an image_dir argument for its constructor + # to be able to locate various binaries. Add this as a command-line + # argument + self.parser.add_argument("--image_dir", type=str, + help="Directory containing the a5ds images") + + def parseSpecializationArguments(self, args): + def tryParseStringArg(arg, argstring): + if arg is None: + print("Argument {0} was not specified, but required. exiting...".format(argstring)) + sys.exit(1) + return arg + + # Set the arguments required for constructing an a5dsFVP object + # NOTE: the 'key' in FVPWrapperArgs is identical to the named argument + # 'image_dir' in the a5dsFVP constructor. This is important, given that + # the FVP Subclass is instantiated by the named arguments present in + # FVPWrapperArgs via kwargs expansion. + self.FVPWrapperArgs['image_dir'] = tryParseStringArg(args.image_dir, "--image_dir") + # The remainder of the arguments for a5dsFVP construction will be + # provided by the TestRunner base class + + def registerTestSpecifications(self): + self.registerTest({ + 'name' : "boot_test", + 'description' : "Test A5 Boot", + 'commands' : [ + ('w', "uname -srmn"), + ], + 'host_ver_strs' : ["Linux a5ds 5.2.0 armv7l"], + 'host_stop_str' : "Linux a5ds 5.2.0 armv7l" + }) + +if __name__ == "__main__": + A5dsTestRunner() diff --git a/iot/scripts/platforms/corstone700/corstone700_fvp.py b/iot/scripts/platforms/corstone700/corstone700_fvp.py new file mode 100644 index 0000000..0aac1d1 --- /dev/null +++ b/iot/scripts/platforms/corstone700/corstone700_fvp.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python2.7 +# Python 2.7 is <required> for fm.debug + +__copyright__ = """ +Copyright (c) 2019, Arm Limited and Contributors. All rights reserved. + +SPDX-License-Identifier: BSD-3-Clause +""" + +import sys +import os +corstone700_dir = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join((corstone700_dir),'..','..', 'test')) +from fvp_wrapper import FVPWrapper, TelnetWatcher + +""" corstone700_fvp.py +This file contains the corstone700 FVP subclass of the generic FVP wrapper class. +Upon instantiation, telnet watchers are created for each UART exposed by the +FVP, and - using the provided test specification - watchers are set up with their +respective stop and verification conditions. +""" + + +"""corstone700DefaultConfig +default corstone700 configuration parameters. +Note that these are fully platform dependant, and are only specified in the +following map to provide a clear overview of what the configuration constants +of this script are. +""" +corstone700DefaultConfig = { + # =============== FVP Parameters =============== + # Stop condition + "stop_cnd" : "/OSCI/SystemC: Simulation stopped by user", + + # ROM & Flash loaders + "se_bootloader" : "se.trustedBootROMloader.fname", + "board_flashloader" : "board.flashloader0.fname", + "es_flashloader" : "extsys_harness{0}.extsys_flashloader.fname", + + # UART logs + "host_uart0" : "host.uart0.out_file", + "host_uart1" : "host.uart1.out_file", + "se_uart" : "se.uart0.out_file", + "es_uart" : "extsys0.uart{0}.out_file", + + # Telnet parameters + "telnet_host" : 'localhost', + "host_telnet_port0" : 5000, + "host_telnet_port1" : 5001, + "se_telnet_port0" : 5002, + "es_telnet_ports" : [5003,-1,-1,-1], + + # =============== Test parameters ============== + "linux_login_prompt" : "corstone700-fvp login:", + "linux_user" : "root", + "linux_shstring" : "root@corstone700-fvp:~# " +} + + +""" corstone700DefaultTestspec +Test-specification parameters for corstone700. +Note that this is fully platform-dependant, and only used during the +initialization of corstone700FVP. +""" +corstone700DefaultTestspec = { + "name" : None, # Test name + "commands" : [], # Commands to execute on Host + "se_bootrom" : None, # se boot rom + "board_flash" : None, # Board flash image + "es_images" : None, # External system images [] + "host_stop_str" : None, # Stop condition string for Host + "se_stop_str" : None, # Stop condition string for SE + "es_stop_strs" : [None], # Stop condition string for ES + "host_ver_strs" : [], # Verification strings for Host + "se_ver_strs" : [], # Verification strings for SE + "es_ver_strs" : [[]] # Verification strings for ES +} + +class Corstone700FVP(FVPWrapper): + def __init__(self, testspec, fvp_path, image_dir, usermode, fvp_timeout, stdin): + FVPWrapper.__init__( + self, + fvp_path=fvp_path, + fvp_name="Corstone-700", + usermode=usermode, + work_dir=corstone700_dir, + fvp_timeout=fvp_timeout, + testname=testspec['name'], + stdin=stdin + ) + + self.config = corstone700DefaultConfig + self.testspec = self.parseTestspec(testspec) + self.image_dir = image_dir + + # Define watchers for each terminal + # Host terminal 0 watcher + host0_watcher = TelnetWatcher( + name="host0", + termfile=os.path.join(self.work_dir, self.log_dir, self.testspec['name'] + "_host0.txt"), + stop_str=self.testspec['host_stop_str'], + fvp_uart=self.config['host_uart0'], + port=self.config['host_telnet_port0'], + sys_stop_str=self.config['stop_cnd'], + verification_strs=self.testspec['host_ver_strs'] + ) + # We define an initial command sequence for host0 which will login + # and await until a user can enter commands + host0_watcher.addCommand('r', self.config['linux_login_prompt']) + host0_watcher.addCommand('w', self.config['linux_user']) + host0_watcher.addCommand('r', self.config['linux_shstring']) + # Once the host is logged in, we add the user-provided test commands + for commandtype, command in self.testspec['commands']: + host0_watcher.addCommand(commandtype, command) + self.watchers.append(host0_watcher) + + # Host terminal 1 watcher + self.watchers.append( + TelnetWatcher( + name="host1", + termfile=os.path.join(self.work_dir, self.log_dir, self.testspec['name'] + "_host1.txt"), + stop_str=None, + fvp_uart=self.config['host_uart1'], + port=self.config['host_telnet_port1'], + sys_stop_str=self.config['stop_cnd'], + ) + ) + + + # se watcher + self.watchers.append( + TelnetWatcher( + name="se", + termfile=os.path.join(self.work_dir, self.log_dir, self.testspec['name'] + "_se.txt"), + fvp_uart=self.config['se_uart'], + stop_str=self.testspec['se_stop_str'], + port=self.config['se_telnet_port0'], + sys_stop_str=self.config['stop_cnd'], + verification_strs=self.testspec['se_ver_strs'] + ) + ) + + # External system watchers. For now, only ES 0 is created + self.es_cnt = 1 + for i in range(0, self.es_cnt): + self.watchers.append(TelnetWatcher( + name="es{0}".format(str(i)), + termfile=os.path.join(self.work_dir, self.log_dir, self.testspec['name'] + + "_es{0}.txt".format(str(i))), + fvp_uart=self.config['es_uart'].format(str(i)), + stop_str=self.testspec['es_stop_strs'][i], + port=self.config['es_telnet_ports'][i], + sys_stop_str=self.config['stop_cnd'], + verification_strs=self.testspec['es_ver_strs'][i] + ) + ) + + def getModelParameters(self): + # Assign images to FVP flashloaders + fvp_params = {} + fvp_params[self.config['se_bootloader']] = os.path.join(self.image_dir, "se_romfw.bin") + fvp_params[self.config['board_flashloader']] = os.path.join(self.image_dir, "arm-reference-image-corstone700-fvp.wic.nopt") + + # For now, only external system 0 image is expected + fvp_params[self.config['es_flashloader'].format(str(0))] = os.path.join(self.image_dir, "es_flashfw.bin") + + return fvp_params + + def getModelData(self): + # Assign images to FVP data + fvp_data = {} + return fvp_data + + def parseTestspec(self, testspec): + """ Function for parsing a test-specification in line with the arguments + made available by corstone700DefaultTestspec + """ + if 'name' not in testspec or 'commands' not in testspec: + sys.exit(1) + + # Merge user-specified arguments with default test specification + defaultTestspec = corstone700DefaultTestspec + defaultTestspec.update(testspec) + return defaultTestspec diff --git a/iot/scripts/platforms/corstone700/corstone700_testrunner.py b/iot/scripts/platforms/corstone700/corstone700_testrunner.py new file mode 100644 index 0000000..33f5267 --- /dev/null +++ b/iot/scripts/platforms/corstone700/corstone700_testrunner.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python2.7 + +__copyright__ = """ +Copyright (c) 2019, Arm Limited and Contributors. All rights reserved. + +SPDX-License-Identifier: BSD-3-Clause +""" + +import sys +import os +sys.path.append(os.path.join((os.path.dirname(os.path.realpath(__file__))),'..','..','test')) +from testrunner import TestRunner +from corstone700_fvp import Corstone700FVP + +class Corstone700TestRunner(TestRunner): + def __init__(self): + TestRunner.__init__( + self, Corstone700FVP + ) + + def setSpecializationArguments(self): + # corstone700_fvp (Corstone700FVP) requires an image_dir argument for its constructor + # to be able to locate various binaries. Add this as a command-line + # argument + self.parser.add_argument("--image_dir", type=str, + help="Directory containing the corstone700 images") + + def parseSpecializationArguments(self, args): + def tryParseStringArg(arg, argstring): + if arg is None: + print("Argument {0} was not specified, but required. exiting...".format(argstring)) + sys.exit(1) + return arg + + # Set the arguments required for constructing an corstone700FVP object + # NOTE: the 'key' in FVPWrapperArgs is identical to the named argument + # 'image_dir' in the corstone700FVP constructor. This is important, given that + # the FVP Subclass is instantiated by the named arguments present in + # FVPWrapperArgs via kwargs expansion. + self.FVPWrapperArgs['image_dir'] = tryParseStringArg(args.image_dir, "--image_dir") + # The remainder of the arguments for corstone700FVP construction will be + # provided by the TestRunner base class + + def registerTestSpecifications(self): + # Test external system boot + self.registerTest({ + 'name' : "es_boot", + 'description' : "Test external system boot", + 'commands' : [ + ('w', "cd /usr/bin/"), + ('r', "/usr/bin#"), + ('w', "./test-app 1"), + ], + 'es_stop_strs' : ["Running RTX RTOS"], + 'es_ver_strs' : [ + [# ES 0: + "External System Cortex-M3 Processor", + "Running RTX RTOS" + ] + ] + }) + + # Test external system MHUs + self.registerTest({ + 'name' : "es_mhu_test", + 'description' : "Test ES <=> (host | SE) MHU devices", + 'commands' : [ + ('w', "cd /usr/bin/"), + ('r', "/usr/bin#"), + ('w', "./test-app 2"), + ], + 'es_stop_strs' : ["Received 'abcdf10' From SE MHU1"], + 'se_ver_strs' : ["MHUv2: Message from 'MHU0_ES0': 0xabcdf01", + "MHUv2: Message from 'MHU1_ES0': 0xabcdf01"], + 'host_ver_strs' : ["Received abcdf00 from es0mhu0", + "Received abcdf00 from es0mhu1"], + 'es_ver_strs' : [["Received 'abcdef1' From Host MHU0", + "Received 'abcdef2' From Host MHU0", + "Received 'abcdf10' From SE MHU0", + "Received 'abcdef1' From Host MHU1", + "Received 'abcdef2' From Host MHU1", + "Received 'abcdf10' From SE MHU1"]] + }) + + # Test se MHU + self.registerTest({ + 'name' : "se_mhu_test", + 'description' : "Test BP <=> Host MHU device", + 'commands' : [ + ('w', "cd /usr/bin/"), + ('r', "/usr/bin#"), + ('w', "./test-app 3"), + ], + 'host_stop_str' : "Received abcdf00 from boot processor", + 'se_ver_strs' : ["MHUv2: Message from 'MHU_NS': 0xabcdef1"], + 'host_ver_strs' : ["Received abcdf00 from boot processor"], + }) + + # Test REFCLK and interrupt router + self.registerTest({ + 'name' : "se_timer_test", + 'description' : "Test REFCLK timer, Interrupt Router and Collator", + 'commands' : [ + ('w', "cd /usr/bin/"), + ('r', "/usr/bin#"), + ('w', "./test-app 4"), + ], + 'se_stop_str' : "Timer callback executed", + 'se_ver_strs' : ["Timer started", "Timer callback executed"], + 'host_ver_strs' : ["Sent timer test command to boot processor"], + }) + +if __name__ == "__main__": + Corstone700TestRunner() diff --git a/iot/scripts/test/fvp_wrapper.py b/iot/scripts/test/fvp_wrapper.py new file mode 100644 index 0000000..d65eb4b --- /dev/null +++ b/iot/scripts/test/fvp_wrapper.py @@ -0,0 +1,555 @@ +#!/usr/bin/env python2.7 +# Python 2.7 is <required> for fm.debug +from __future__ import print_function + +__copyright__ = """ +Copyright (c) 2019-2020, Arm Limited and Contributors. All rights reserved. + +SPDX-License-Identifier: BSD-3-Clause +""" + + +""" fvp_wrapper.py: +Arm Fast model wrapper. +Provides functionality for executing a fast model together with 'watchers' +to perform unit testing on an fvp. +The FVPWrapper object must be inherited by a target-specific subclass. +This subclass should do any model-specific initialization, as well as +define the watchers (TelnetWatcher) for the FVP. +""" + +import os +import signal +import sys +from iris.debug import * +import json +import distutils.spawn +import argparse +import telnetlib +from time import sleep +from threading import Thread +import multiprocessing +import Queue +import time +from subprocess import Popen, PIPE, check_output + +from utils import printHeader0, printHeader1 + +# Default network details for a model running locally a pyIRIS server +g_model_hostname = "localhost" +g_model_port = 7100 + +g_wait_fvp_ready = 30 #maximum waiting time for the model to be operational (in seconds) +g_wait_fvp_finish = 30 #waiting for the model to terminate and release the TXT log files is expressed in seconds + +g_fvp_cmd = ["" , '-I'] + +#verbose FVP command +#g_fvp_cmd = ["" , '-I' , '-ii' , '-p'] + +def show_exception_details(e,e_fvp_path,e_fvp_params): + + print("\nError: Exception occured") + + if len(e.message) != 0: + print("Exception Message: " + e.message) + + print("FVP Info:") + print("Path: {0}".format(e_fvp_path)) + print("Parameters:") + print(json.dumps(e_fvp_params, indent=4)) + + +# +# wait_iris_server: +# +# this function supports two modes: +# +# Mode 1: waiting for FVP IRIS server to be ready to receive connections. +# This mode is selected by setting wait_reason to 0 +# +# Mode 2: waiting for FVP IRIS server to terminate. This mode is selected by setting wait_reason to a non null value +# +# The function checks every second if the IRIS server port is open until max_wait_time delay expires +# +def wait_iris_server(fvp_process, iris_port, max_wait_time,wait_reason=0): + + netstat_cmd = ["sh", "-c", 'netstat -tpnl 2>/dev/null | egrep -i ":{0}.+{1}" | wc -l'.format(iris_port, fvp_process)] + + i = 0 + + while 1: + + i += 1 + + ret = check_output(netstat_cmd) + + if int(ret) != 0: #found + if wait_reason == 0: #waiting for connection + return True + else: + if wait_reason != 0: #waiting for termination + return True + + if i == max_wait_time: + return False + + sleep(1) + +class TelnetWatcher: + """ Class for hooking into an ARM telnet session exposed by an FVP. """ + def __init__(self, + name, + termfile, + stop_str, + sys_stop_str, + verification_strs = [], + fvp_uart = None, + port = None, + host='localhost', + ): + # Watcher configuration + self.name = name + "_watcher" # Watcher name + + ''' self.termfile: + - A text file containing UART logs output in the terminal + - Created and updated by the FVP + - Not used for the test strings verifications + ''' + self.termfile = str(termfile) + + self.stop_str = stop_str # Test-specific stop string + self.sys_stop_str = sys_stop_str # Generic FVP stop string + self.fvp_uart = fvp_uart # FVP UART parameter associated with the watcher + + # String which much be present in the UART log after execution + self.verification_strs = verification_strs + + # Watcher Telnet configuration + self.host = host + self.port = port + self.commandqueue = [] + + # Clear file if it is present + self.clearFile() + self.termProcess = None + + ''' self.termfilePipe: + - a file desciptor of a text file with txt_pipe extension + - contains all the logs displayed in xterm + - not used for the test strings verifications + ''' + self.termfilePipe = None + + self.success = True + + def startTerminalPipe(self, fvpname): + if not distutils.spawn.find_executable("xterm"): + print("ERROR: xterm not found in path, display telnet contents") + return + # Create an intermediate pipe file, which the watcher will print to when + # a full line has been read. + termfilePipeName = self.termfile + "_pipe" + + # Remove file if it exists + try: + os.remove(termfilePipeName) + except OSError: + pass + self.termfilePipe = open(termfilePipeName, "a+") + self.termProcess = Popen(['xterm', '-T', self.name, '-e', 'tail', '-f', termfilePipeName]) + sleep(0.1) # Let xterm start before writing to the monitored file + + headerTitle = ("ARM {0} FVP Wrapper".format(fvpname)).center(80) + termfileHeader = """================================================================================ +{0} +This xterm instance will mirror the contents written to the log file: + {1} +The terminal is read-only, and intended to be used solely for manual monitoring +of a test execution. +================================================================================ +""".format(headerTitle, self.termfile) + self.termfilePipe.write(termfileHeader) + self.termfilePipe.flush() + + def stopTerminalPipe(self): + if self.termProcess is not None: + self.termProcess.kill() + self.termfilePipe.close() + os.remove(self.termfilePipe.name) + + def verify(self): + """ Verifies whether all verification strings are found in the + watcher log file + """ + success = True + + if len(self.verification_strs) != 0: + + with open(self.termfile, "r") as logFile: + logFile.flush() + log = logFile.read() + for string in self.verification_strs: + print("{0}: verifying '{1}'... ".format(self.name, string), end='') + if string not in log: + print("\n{0}: FAIL; '{1}' not found in log".format(self.name, string)) + success = False + else: + print("Found!") + + return success + + def addCommand(self, cmdtype, string): + if cmdtype not in ['r', 'w']: + print("Unknown command type '{0}'".format(cmdtype)) + print("Commands must be specified as either a read or write command") + sys.exit(1) + self.commandqueue.insert(0, (cmdtype, string)) + + def getParameters(self): + if self.fvp_uart is not None: + return {self.fvp_uart : self.termfile} + return {} + + def clearFile(self): + if os.path.isfile(self.termfile): + # Clear the file + with open(self.termfile, "w") as f: + f.write("") + else: + # Create the file + f = open(self.termfile,"w+") + f.close() + + +class FVPWrapper(object): + """ Controlling Class that wraps around an ARM Fastmodel and controls + execution. + This class should be subclassed to configure for a specific FVP model. """ + def __init__(self, + fvp_path, + fvp_name, + work_dir, + testname, + fvp_timeout, + stdin = None, + usermode = False + ): + + # Configuration + self.fvp_path = str(fvp_path) + self.work_dir = os.path.abspath(work_dir) + self.log_dir = os.path.join(self.work_dir, "logs") + self.fvp_timeout = fvp_timeout + self.fvp_name = fvp_name + self.watchers = [] # Inheriting class must initialize watchers + self.testspec = {} # Inheriting class mus define a test spec + + # FVP Wrapper may be spawned in a child process of some other python + # script. In this case, to allow for input() within this script, + # the std input of the parent process must be used (which in turn + # provides access to the parent terminal stdinput) + if stdin is not None: + sys.stdin = os.fdopen(stdin) + + # Check whether log directory exists + if not os.path.exists(self.log_dir): + os.makedirs(self.log_dir) + + # Asserted only after a complete test run,including end string matching + self.test_complete = False + self.test_report = None + + self.monitor_q = multiprocessing.Queue() + self.stop_all = False + self.threads = [] + + # If userMode is true, the test will display xterm instances for all + # terminals of the FVP, which will mirror the contents of the terminal + # log files within the xterm instance. + # Furthermore, a user will be required to input a character into the + # python terminal for the test execution to finish, allowing for + # manual inspection of the terminals + self.userMode = usermode + + # FVP parameters dictionary. + # FVP Parameters are derived partly from information from self.watchers, + # and partly from model-specific configuration as specified by a subclass + self.fvp_params = {} + self.fvp_data = {} + + def getModelParameters(self): + """ The platform specific subclass should implement this function for parsing + platform-specific model parameters into the fvp_params map. This could be + parameters specifying ie. images for various flashloaders, initialization + constants for the FVP etc. + NOTE: The naming of these arguments must be identical to what is presented + when an FVP is executed with the --list-params flag + """ + raise Exception("Model-specific class must implement getModelParameters") + + def getModelData(self): + raise Exception("Model-specific class must implement getModeldata") + + def load_fvp(self): + try: + # Get model specific parameters specified by inheriting class + self.fvp_params.update(self.getModelParameters()) + self.fvp_data.update(self.getModelData()) + # Get watcher specific model parameters from each watcher + for watcher in self.watchers: + self.fvp_params.update(watcher.getParameters()) + + g_fvp_cmd[0] = self.fvp_path + + for param,param_val in self.fvp_params.items() : + g_fvp_cmd.append("-C") + g_fvp_cmd.append(param+"="+param_val) + + Popen(g_fvp_cmd) #running the FVP with pyIRIS server enabled + + fvp_ready = wait_iris_server(fvp_process=self.fvp_name.lower().replace("-",""), + iris_port=g_model_port,max_wait_time=g_wait_fvp_ready,wait_reason=0) + + if fvp_ready == False: + raise Exception("FVP not ready to connect") + + # Connect to the model through pyIRIS + + # Using pyIRIS network model to connect to the FVP + + self.fvp = NetworkModel(g_model_hostname, g_model_port) + + cpu = self.fvp.get_cpus()[0] + + for key,value in self.fvp_data.items(): + fop = open(value[:-11], "rb") + image = bytearray(fop.read()) + location = value[len(value)-10:len(value)] + cpu.write_memory(int(location,0), image) + + except Exception as e: + + show_exception_details(e,self.fvp_path,self.fvp_params) + sys.exit(1) + + # Model is now loaded and telnet sessions have been started + + def run_watcher(self, watcher): + """ Run parallel threaded proccesses that monitors a telnet session + of the FVP and stops it when the a user specified string is found. + It returns the pid of the proccess for housekeeping """ + + def watcher_loop(queue, watcher): + + # Start telnet session + tn = telnetlib.Telnet(host=watcher.host, port=watcher.port) + + # Poll the telnet session, reading until a line is received + getNextCmd = True + while(True): + # Attempt to pop a command off of the command stack + while getNextCmd: + try: + command = watcher.commandqueue.pop() + except IndexError: + command = None + # Execute all write commands which are in sequence + if command and command[0] == 'w': + tn.write((command[1] + '\n').encode('utf-8')) + getNextCmd = True + else: + getNextCmd = False + + # Read until a full line has been received or the watcher is + # signalled to stop + line = "" + while(# Stop if a newline character is seen + '\n' not in line and + # Stop if the global stop signal has been asserted + not self.stop_all and + # Stop if the string of a 'read' command is found + (command[1] not in line if command and command[0] == 'r' else True)): + line += tn.read_very_eager().decode('utf-8') + + if watcher.termProcess is not None: + # Pipe terminal contents to user-visible terminal if available + watcher.termfilePipe.write(line) + watcher.termfilePipe.flush() + + if self.stop_all: + return + + # Process a 'read' command + if command and command[0] == 'r' and command[1] in line: + getNextCmd = True + + if(watcher.stop_str is not None and watcher.stop_str in line): + queue.put("{0}: Found end string \"{1}\"".format(watcher.name, watcher.stop_str)) + queue.put("{0}: Stopping all other threads...".format(watcher.name)) + self.test_complete = True + self.stop() + return + + # Check for the system stop string (ie. FVP stopped by itself) + if watcher.sys_stop_str in line: + self.success = False + queue.put("Simulation Ended: \"{0}\"".format(line)) + self.stop() + return + + # Yield + sleep(0) + + # Run the watcher as a separate thread + watcher_thread = Thread(target=watcher_loop, + args=(self.monitor_q, watcher)) + watcher_thread.setName(watcher.name) + watcher_thread.start() + + if self.userMode: + watcher.startTerminalPipe(self.fvp_name) + + return watcher_thread + + def monitor_consume(self): + """ Read the ouptut of the monitor thread and print the queue entries + one entry at the time (One line per call) """ + try: + line = self.monitor_q.get_nowait() + except Queue.Empty: + return + else: + print(line.rstrip()) + self.monitor_consume() + + def has_stopped(self): + """Return status of stop flag. True indicated stopped state """ + return self.stop_all + + def start(self): + # Load the FVP, exposing the Telnet sessions + self.load_fvp() + + # Start telnet watchers + for watcher in self.watchers: + if watcher.port != None: + self.threads.append(self.run_watcher(watcher)) + + # With all watchers hooked into their telnet sessions, the test may + # commence. + # To log the FVP output, the fvp is executed in a separate process, + # assigning stdout for the process to the FVP log file + + self.fvp.run(blocking=False) + + # Start test timer + self.startTime = time.time() + + def stop(self): + """ Send stop signal to all threads """ + self.stop_all = True + + def test(self): + """ Compare each watcher log file with its corresponding verification + strings. + """ + for watcher in self.watchers: + self.success &= watcher.verify() + + + def blocking_wait(self): + """ Block execution flow and wait for one of the watchers to complete """ + try: + while True: + for thread in self.threads: + self.monitor_consume() + if not thread.isAlive(): + print(("Thread '{0}' finished," + + "sending stop signal to all threads...").format(thread.getName())) + self.stop() + break + if self.has_stopped(): + break + # Check for timeout + if (time.time() - self.startTime) > self.fvp_timeout: + print("ERROR: Timeout reached! ({0} seconds)".format(self.fvp_timeout)) + self.stop() + break + + + except KeyboardInterrupt: + print("User initiated interrupt") + self.stop() + + # Join all threads + print("Awaiting all threads to finish...") + for thread in self.threads: + print("Joining with thread: " + thread.getName()) + thread.join() + print("All threads finished") + + def verifyInitialization(self): + """ Various sanity checks to verify that an inheriting class has + initialized the FVP correctly + """ + if len(self.watchers) == 0: + raise Exception("No watchers were set, aborting...") + + def executeTest(self): + try: + self.success = True + self.verifyInitialization() + + printHeader0("FVP Test: {0}".format(self.testspec['name'])) + + # Start the wrapper + print() + printHeader1("FVP Execution") + self.start() + + # Wait for the wrapper to complete + self.blocking_wait() + + print("Test execution finished, shutting down model...") + + #terminates the model and allows the FVP to release the TXT log files + self.fvp.release(True) + + fvp_terminated = wait_iris_server(fvp_process=self.fvp_name.lower().replace("-", ""), + iris_port=g_model_port, max_wait_time=g_wait_fvp_finish, wait_reason=1) + + if fvp_terminated == False: + raise Exception("FVP failed to shutdown") + + print("FVP shutdown successfully") + + if self.success: + print() + printHeader1("FVP Test Verification: {0}".format(self.testspec['name'])) + # Test the output of the system only after a full execution + self.test() + + if self.userMode: + # Await user input, allowing the terminals to be inspected before + # finishing the test. + raw_input("Press enter to continue...") + # Stop terminal processes and clean up pipe files + for watcher in self.watchers: + watcher.stopTerminalPipe() + + print("\n") + if self.success: + printHeader1("FVP Test successfull: {0}".format(self.testspec['name'])) + print('='*80 + "\n\n") + return 0 + else: + printHeader1("FVP Test failed: {0}".format(self.testspec['name'])) + print('='*80 + "\n\n") + + return 1 + + except Exception as e: + + show_exception_details(e, self.fvp_path, self.fvp_params) + sys.exit(1) diff --git a/iot/scripts/test/testrunner.py b/iot/scripts/test/testrunner.py new file mode 100644 index 0000000..032d522 --- /dev/null +++ b/iot/scripts/test/testrunner.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python2.7 +# Python 2.7 is <required> for fm.debug + +__copyright__ = """ +Copyright (c) 2019-2020, Arm Limited and Contributors. All rights reserved. + +SPDX-License-Identifier: BSD-3-Clause +""" + +from multiprocessing import Process +import os +import argparse +import functools +import sys +import json + +""" class TestRunner +Base class for running tests on an FVP wrapper derived class. +It is expected that a user will implement a platform-specific testrunner class +for each platform-specific fvp wrapper which is created. +The testrunner class helps manage command line arguments for driving an +fvp wrapper as well as managing the registration and execution of test cases +for a given platform. + +Default arguments required by TestRunner, shared by all FVP Wrappers: +- --usermode +- --timeout +- --fvp +- --list +- --runTest +- --runAll + +requirements on a test-specification from a TestRunner's point of view: +test must contain: +- "name" field +- "description" field +""" + +class TestRunner: + def __init__(self, FVPType): + #FVPType is a platform-specific object which inherits from FVPWrapper + + self.tests = {} + self.FVPType = FVPType + + self.parser = argparse.ArgumentParser() + self.usermode = False + + # Map of keyworded arguments for executing the specialized FVP wrapper + self.FVPWrapperArgs = {} + + # Set TestRunner generic arguments + self.parser.add_argument_group('General options') + self.parser.add_argument("--usermode", dest='usermode', action='store_true', + help="If in usermode, terminals will be visible to the user." + " Furthermore, tests will not exit until input has been" + + " provided by the user (default: %(default)s)") + + self.parser.add_argument("--timeout", dest='timeout', type=int, + help="FVP Execution timeout in seconds (default: %(default)s)", default=60) + + self.parser.add_argument("--fvp", type=str, + help="Absolute path to the FVP .so file") + + # Add options for the execution mode of the testrunner. + # These are mutually exclusive + self.parser.add_argument("--list", dest='list', action='store_true', + help="""List registered tests. + May be passed as the only argument to the script. In this case, the + platform specific implementation is queried for its registered tests, + which are then printed as a JSON-formatted string.""", + default=False, required=False) + + self.parser.add_argument("--runTest", dest='runTest', type=str, + help="Run specific test. Execute --list to view available tests", + required = False, default=None) + + self.parser.add_argument("--runAll", dest='runAll', default=False, + help="Run all registered tests", required = False, action='store_true') + + # Set Specialization-specific arguments + self.parser.add_argument_group('FVP specific arguments') + self.setSpecializationArguments() + + # Generic and specialized arguments are now registered, parse arguments + args = self.parser.parse_args() + self.parseArguments(args) + + # Parse test specification + self.registerTestSpecifications() + + # Check for '--list' execution mode + # This is done before parsing specialization arguments, allowing for + # viewing the --list of available tests for a given platform without + # requiring all of the platform-specific arguments to be specified + if self.list: + print(self.getTests()) + sys.exit(0) + + # Parse specialization arguments + self.parseSpecializationArguments(args) + + # Do execution mode + if self.runSingle is not None: + self.runTest(self.runSingle) + elif self.runAll: + self.runAllTests() + + def runTest(self, testname): + """ fm.debug may throw a segmentation fault if a model is launched multiple + times within the same process. This issue also presents itself if the + model is run as a separate thread but within the same process. + To ensure proper clean-up between test executions, execute the model + in a separate process. + """ + def runModel(stdin, **kwargs): + sys.exit(self.FVPType( + stdin=stdin, + **kwargs).executeTest()) + + try: + testspec = self.tests[testname] + except KeyError: + print("Trying to execute unknown test '{0}', aborting...".format(testname)) + sys.exit(1) + + # Generate argument dictionary for FVP executor + # Note that these are >named< arguments, and expects the naming + # to be consistent across FVP constructor argument names. + kwargs = dict({"testspec": testspec}, **self.FVPWrapperArgs) + + # stdin of this process is passed to spawned processed, enabling stdin + # in child processes, see: + # https://stackoverflow.com/questions/7489967/python-using-stdin-in-child-process/15766145#15766145 + stdin = sys.stdin.fileno() + + # Start FVP execution in separate process and await test finished + p = Process(target=runModel, args=[stdin], kwargs=kwargs) + p.start() + p.join() + + # Stop test execution if test failed + if p.exitcode != 0: + sys.exit(p.exitcode) + + def runAllTests(self): + for testname, _ in self.tests.iteritems(): + self.runTest(testname) + + def registerTestSpecifications(self): + print("Subclass did not implement test registration") + raise BaseException() + + def parseSpecializationArguments(self, args): + print("Subclass did not implement argument parsing") + raise BaseException() + + def setSpecializationArguments(self): + print("Subclass did not implement argument parsing") + raise BaseException() + + def parseArguments(self, args): + # Parse generic arguments (arguments for all FVP wrappers) + self.FVPWrapperArgs['usermode'] = args.usermode + self.FVPWrapperArgs['fvp_timeout'] = args.timeout + self.FVPWrapperArgs['fvp_path'] = args.fvp + + def booleanize(arg): + return True if arg is not None else False + + # Parse arguments specifying the execution mode of the Test runner + self.list = args.list + self.runAll = args.runAll + self.runSingle = args.runTest + + # Test runner execution mode is mutually exclusive + if not reduce((lambda x,y: x ^ y), [args.list, args.runAll, booleanize(args.runTest)]) : + if not (args.list and args.runAll and booleanize(args.runTest)): + # None of the mutually exclusive arguments were provided + self.parser.print_help() + else: + print('--list, --runTest and --runAll are mutually exclusive') + sys.exit(1) + + def registerTest(self, test): + if not 'name' in test or not 'description' in test: + print("Invalid test specification, test must contain" + + "'name' and 'description' entries") + sys.exit(1) + self.tests[test['name']] = test + + def getTests(self): + """ Returns a JSON formatted string of tests which have been registered with the + runner, their names and descriptions""" + testDescriptions = {} + for _, test in self.tests.iteritems(): + testDescriptions[test['name']] = test['description'] + return json.dumps(testDescriptions, indent=4) diff --git a/iot/scripts/test/testselector.py b/iot/scripts/test/testselector.py new file mode 100644 index 0000000..85137c9 --- /dev/null +++ b/iot/scripts/test/testselector.py @@ -0,0 +1,166 @@ +#!usr/bin/env python + +__copyright__ = """ +Copyright (c) 2019, Arm Limited and Contributors. All rights reserved. + +SPDX-License-Identifier: BSD-3-Clause +""" + +""" Testselector +This script will query all platforms within the 'platforms/' folder for their +available tests ${platform}_testrunner.py script and present options to run these +through a command-line menu. + +The testselector may be run with additional arguments for each platform. +These arguments should be the required arguments for the FVP wrappers of a given +platform. Ie. an argument an path for the FVP should be specified. + +As an example, the following are needed to execute the Corstone700 testrunner: +python am_ci/scripts/testselector.py --corstone700 "--image_dir ${OUTDIR} --fvp ${BASEDIR}/FVP_Corstone-700.so" + +These arguments will then be added to the execution of the Corstone700 testrunner script. +""" + +import sys +import os +import json +import re +import subprocess +import argparse + +from consolemenu import * +from consolemenu.items import * + +pwd = os.path.dirname(os.path.realpath(__file__)) + +# Index the available platforms in the 'platform/' folder +# The following variable will be a mapping between the available platforms +# and additional argument strings to pass to the given platform's testrunner +# script. +platforms = {} +platforms_dir = os.path.join(pwd, '..', 'platforms') +for platform in [dI for dI in os.listdir(platforms_dir) if os.path.isdir(os.path.join(platforms_dir ,dI))]: + platforms[platform] = None + +# For each platform, additional arguments for executing its respective +# testrunner may be provided. these should be provided as a single string +# for each platform +parser = argparse.ArgumentParser(description="ARM FVP Unit Test Selector") +for platform in platforms: + parser.add_argument("--{0}".format(platform), + help="Additional arguments for the testrunner of the {0} platform.".format(platform) + + " Shall be formatted as a single string (Eg. \"--foo \\\"bar\\\" -a\")", + required=False, type=str, default = None) +args = parser.parse_args() +for platform, argstring in (vars(args)).iteritems(): + platforms[platform] = argstring + +# ============================================================================== + +# The test menu will prompt the user to generate a test run configuration +# This run configuration functor is stored to the following variable, +# which will be executed when the menu exits +queuedTest = None + +testsForPlatforms = {} +for platform in platforms: + try: + testrunnerpath = os.path.join(platforms_dir, platform,platform + "_testrunner.py") + if not os.path.isfile(testrunnerpath): + print("Testrunner file for platform '{0}' was not found".format(platform)) + print("Expected file: {0}".format(testrunnerpath)) + sys.exit(1) + + jsonString = os.popen( + "python {0} --list".format(testrunnerpath)).read() + testmap = json.loads(jsonString) + testsForPlatforms[platform] = testmap + except: + print("Could not parse json string from testrunner") + sys.exit(1) + +# Create main menu object +menu = ConsoleMenu(title="ARM FVP Unit Test Runner", + subtitle="Select Platform") + +# Function for creating menu entries which allows the user to specify +# whether to run a specific test in user-mode or not. +def createRuntestMenu(testname = None): + runTest = False if testname is None else True + + subtitle="""Run {0} in usermode? + | In usermode, the test will display xterm instances for all + | terminals of the FVP, which will mirror the contents of the + | terminal log files within the xterm instance. Terminals are + | read-only.""".format(testname if testname is not None else "") + + runtest_submenu = ConsoleMenu(title="ARM FVP Unit Test Runner", subtitle=subtitle) + # Selecting whether to run in usermode is a leaf-node in the menu navigation + # tree. These are created as 'FunctionItem's which, upon selection, will + # execute a registered function. + runWithUsermode_item = FunctionItem("yes", + function=testRunnerGenerator(usermode=True, testname=testname, + runTest=runTest, runAll=not runTest),should_exit=True) + runWithoutUsermode_item = FunctionItem("no", + function=testRunnerGenerator(usermode=False, testname=testname, + runTest=runTest, runAll=not runTest),should_exit=True) + runtest_submenu.append_item(runWithUsermode_item) + runtest_submenu.append_item(runWithoutUsermode_item) + + return runtest_submenu + +# Add submenu for each platform and its tests +for platform, tests in testsForPlatforms.iteritems(): + # Function which returns a lambda that will, upon execution, instantiate + # a given test speficiation, as per how the menu was navigated to reach the + # function + def testRunnerGenerator(usermode, testname = None, runTest = None, runAll = False): + testrunnerpath = os.path.join(platforms_dir, platform,platform + "_testrunner.py") + if runAll: + testarg = "--runAll" + else: + testarg = "--runTest {0}".format(testname) + + platform_args = platforms[platform] + platform_args = "" if platform_args is None else platform_args + + # setQueuedTest will return a lambda which upon execution assigns + # a platform-specific execution of a given test for a given platform + # to the queuedTest variable. The subsequent execution of queuedTest + # will then execute the subprocess.Popen command. + def setQueuedTest(): + global queuedTest + # Upon execution, the global 'queuedTest' variable will be set + # according to the menu traversal that led to this option + queuedTest = lambda : subprocess.Popen( + "python {0} {1} {2} {3}".format(testrunnerpath, + testarg, + platform_args, + "" if usermode is False else "--usermode"), shell=True).wait() + + return setQueuedTest + + # Add submenu for platform + arg_info = "Additional arguments: \n" + json.dumps(platforms[platform], indent=4) + platform_submenu = ConsoleMenu(title="ARM FVP Unit Test Runner", subtitle= + "{0} Unit Tests".format(platform), prologue_text=arg_info) + platform_submenu_item = SubmenuItem(platform, platform_submenu, menu=menu, should_exit=True) + menu.append_item(platform_submenu_item) + + # Add a "run all" menu option + runtest_item = SubmenuItem("Run all tests", createRuntestMenu(), menu=menu, should_exit=True) + platform_submenu.append_item(runtest_item) + + # Add options for execution of each test + for k,v in tests.iteritems(): + runtest_item = SubmenuItem("{0}: {1}".format(k,v), createRuntestMenu(k), menu=menu, should_exit=True) + platform_submenu.append_item(runtest_item) + +# The menu structure has now been created, show the menu and await a command +# which will terminate the menu +menu.show() +menu.join() + +if queuedTest is not None: + # Execute the test specification which was generated during menu traversal + queuedTest() diff --git a/iot/scripts/test/utils.py b/iot/scripts/test/utils.py new file mode 100644 index 0000000..4ecf069 --- /dev/null +++ b/iot/scripts/test/utils.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python2 + +__copyright__ = """ +Copyright (c) 2019, Arm Limited and Contributors. All rights reserved. + +SPDX-License-Identifier: BSD-3-Clause +""" + +import math +HEADER_WIDTH = 80 + +def printHeader1(string): + eqcnt = (float(HEADER_WIDTH) - 2 - len(string)) / 2 + eql = int(eqcnt if eqcnt.is_integer() else math.floor(eqcnt)) + eqr = int(eqcnt if eqcnt.is_integer() else math.ceil(eqcnt)) + print("{0} {1} {2}".format('='*eql, string, '='*eqr)) + +def printHeader0(string): + print('='*HEADER_WIDTH) + printHeader1(string) + print('='*HEADER_WIDTH) |