summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAbdellatif El Khlifi <abdellatif.elkhlifi@arm.com>2020-08-07 13:06:40 +0100
committerRui Miguel Silva <rui.silva@linaro.org>2020-08-17 10:31:37 +0100
commit98bfa31c7c10fb2ddb6bd7922cd27c5ad7389f6c (patch)
tree017f6fac4522c1f06a763ad0f93cd7ea78cdcd28
parentba72bdd5d8dbf327732cdfb2fea753076416a177 (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-xiot/run_model.sh80
-rw-r--r--iot/scripts/platforms/a5ds/a5ds_fvp.py131
-rw-r--r--iot/scripts/platforms/a5ds/a5ds_testrunner.py56
-rw-r--r--iot/scripts/platforms/corstone700/corstone700_fvp.py184
-rw-r--r--iot/scripts/platforms/corstone700/corstone700_testrunner.py114
-rw-r--r--iot/scripts/test/fvp_wrapper.py555
-rw-r--r--iot/scripts/test/testrunner.py198
-rw-r--r--iot/scripts/test/testselector.py166
-rw-r--r--iot/scripts/test/utils.py21
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)