Open CI Scripts: Feature Update
* build_helper: Added --install argument to execute cmake install
* build_helper: Added the capability to parse axf files for
code/data/bss sizes and capture it to report
* build_helper: Added --relative-paths to calculate paths relative
to the root of the workspace
* build_helper_configs: Full restructure of config modules.
Extra build commands and expected artefacts can be defined per
platform basis
* Checkpatch: Added directive to ignore --ignore SPDX_LICENSE_TAG
and added the capability to run only on files changed in patch.
* CppCheck adjusted suppression directories for new external
libraries and code-base restructure
* Added fastmodel dispatcher. It will wrap around fastmodels
and test against a dynamically defined test_map. Fed with an
input of the build summary fastmodel dispatcher will detect
builds which have tests in the map and run them.
* Added Fastmodel configs for AN519 and AN521 platforms
* lava_helper. Added arguments for --override-jenkins-job/
--override-jenkins-url
* Adjusted JINJA2 template to include build number and
enable the overrides.
* Adjusted lava helper configs to support dual platform firmware
and added CoreIPC config
* Added report parser module to create/read/evaluate and
modify reports. Bash scripts for cppcheck checkpatch summaries
have been removed.
* Adjusted run_cppcheck/run_checkpatch for new project libraries,
new codebase structure and other tweaks.
* Restructured build manager, decoupling it from the tf-m
cmake requirements. Build manager can now dynamically build a
configuration from combination of parameters or can just execute
an array of build commands. Hardcoded tf-m assumptions have been
removed and moved into the configuration space.
* Build system can now produce MUSCA_A/ MUSCA_B1 binaries as well
as intel HEX files.
* Updated the utilities snippet collection in the tfm-ci-pylib.
Change-Id: Ifad7676e1cd47e3418e851b56dbb71963d85cd88
Signed-off-by: Minos Galanakis <minos.galanakis@linaro.org>
diff --git a/tfm_ci_pylib/fastmodel_wrapper/__init__.py b/tfm_ci_pylib/fastmodel_wrapper/__init__.py
new file mode 100644
index 0000000..d59ebcc
--- /dev/null
+++ b/tfm_ci_pylib/fastmodel_wrapper/__init__.py
@@ -0,0 +1,21 @@
+__copyright__ = """
+/*
+ * Copyright (c) 2018-2019, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+ """
+
+__all__ = ["config_templates",
+ "fastmodel_config_map",
+ "fastmodel_wrapper",
+ "fastmodel_wrapper_config"]
+
+from .fastmodel_wrapper_config import config_variant, fpv_wrapper_config
+from .fastmodel_wrapper import FastmodelWrapper
+from .fastmodel_config_map import FastmodelConfigMap
+
+from .config_templates import template_default_config, \
+ template_regression_config, template_coreipc_config, \
+ template_coreipctfmlevel2_config
diff --git a/tfm_ci_pylib/fastmodel_wrapper/config_templates.py b/tfm_ci_pylib/fastmodel_wrapper/config_templates.py
new file mode 100644
index 0000000..6c9c636
--- /dev/null
+++ b/tfm_ci_pylib/fastmodel_wrapper/config_templates.py
@@ -0,0 +1,229 @@
+#!/usr/bin/env python3
+
+""" config_templatess.py:
+
+ """
+
+from __future__ import print_function
+from copy import deepcopy
+from .fastmodel_wrapper_config import fpv_wrapper_config
+
+__copyright__ = """
+/*
+ * Copyright (c) 2018-2019, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+ """
+__author__ = "Minos Galanakis"
+__email__ = "minos.galanakis@linaro.org"
+__project__ = "Trusted Firmware-M Open CI"
+__status__ = "stable"
+__version__ = "1.1"
+
+
+# =================== Template Classes ===================
+class template_cfg(fpv_wrapper_config):
+ """ Creates a skeleton template configuration that allows creation of
+ configuration variants which set the parameters of:
+ buildpath, config, platform, compiler , as well as the missing test params,
+ test_rex, test_cases, test_end_string """
+
+ _name = fpv_wrapper_config._name + "_%(platform)s_%(compiler)s_" + \
+ "%(config)s_%(build_type)s_%(bootloader)s"
+ # variant dictionary allows indivudal and targeted parameter modification
+ _vdict = {
+ "build_path": "%(build_path)s",
+ "variant_name_tpl": "%(variant_name_tpl)s",
+ "app_bin_path": "%(app_bin_path)s",
+ "app_bin": "%(app_bin)s",
+ "data_bin_path": "%(data_bin_path)s",
+ "data_bin": "%(data_bin)s",
+ "data_bin_offset": "%(data_bin_offset)s",
+ "config": "%(config)s",
+ "platform": "%(platform)s",
+ "compiler": "%(compiler)s",
+ "build_type": "%(build_type)s",
+ "bootloader": "%(bootloader)s"
+ }
+
+ _cfg = deepcopy(fpv_wrapper_config._cfg)
+ _cfg["directory"] = "FVP_MPS2"
+ _cfg["terminal_log"] = "terminal_%(variant_name_tpl)s.log"
+ _cfg["bin"] = "FVP_MPS2_AEMv8M"
+ _cfg["error_on_failed"] = False
+ _cfg["application"] = (
+ "cpu0=%(build_path)s/%(variant_name_tpl)s/" +
+ "%(app_bin_path)s/%(app_bin)s")
+ _cfg["data"] = (
+ "cpu0=%(build_path)s/%(variant_name_tpl)s/%(data_bin_path)s/" +
+ "%(data_bin)s@%(data_bin_offset)s")
+ _cfg["simlimit"] = "600"
+ _cfg["parameters"] = [
+ "fvp_mps2.platform_type=2",
+ "cpu0.baseline=0",
+ "cpu0.INITVTOR_S=0x10000000",
+ "cpu0.semihosting-enable=0",
+ "fvp_mps2.DISABLE_GATING=0",
+ "fvp_mps2.telnetterminal0.start_telnet=0",
+ "fvp_mps2.telnetterminal1.start_telnet=0",
+ "fvp_mps2.telnetterminal2.start_telnet=0",
+ "fvp_mps2.telnetterminal0.quiet=1",
+ "fvp_mps2.telnetterminal1.quiet=1",
+ "fvp_mps2.telnetterminal2.quiet=1",
+ "fvp_mps2.UART0.out_file=$TERM_FILE",
+ "fvp_mps2.UART0.unbuffered_output=1",
+ "fvp_mps2.UART0.shutdown_on_eot=1",
+ "fvp_mps2.mps2_visualisation.disable-visualisation=1"]
+
+
+class template_default_config(template_cfg):
+ """ Will automatically populate the required information for tfm
+ Default configuration testing. User still needs to set the
+ buildpath, platform, compiler variants """
+
+ _cfg = deepcopy(template_cfg._cfg)
+
+ _vdict = deepcopy(template_cfg._vdict)
+
+ # Set defaults across all variants
+ _vdict["build_path"] = "build-ci-all"
+ _vdict["app_bin_path"] = "install/outputs/fvp"
+ _vdict["data_bin_path"] = "install/outputs/fvp"
+ _vdict["variant_name_tpl"] = "%(platform)s_%(compiler)s_%(config)s_" + \
+ "%(build_type)s_%(bootloader)s"
+
+ # Mofify the %(config)s parameter of the template
+ _vdict["config"] = "ConfigDefault"
+ _cfg["terminal_log"] = _cfg["terminal_log"] % _vdict
+
+ # System supports two types of matching with
+ # test_case_id and result match group and only test_case_id
+ _cfg["test_rex"] = (r'\x1b\[1;34m\[Sec Thread\] '
+ r'(?P<test_case_id>Secure image initializing!)\x1b\[0m'
+ )
+
+ # test_case_id capture group Should match test_cases entries
+ _cfg["test_cases"] = [
+ 'Secure image initializing!',
+ ]
+ # Testing will stop if string is reached
+ _cfg["test_end_string"] = "Secure image initializing"
+ _cfg["simlimit"] = "120"
+
+class template_regression_config(template_cfg):
+ """ Will automatically populate the required information for tfm
+ Regression configuration testing. User still needs to set the
+ buildpath, platform, compiler variants """
+
+ _cfg = deepcopy(template_cfg._cfg)
+ _vdict = deepcopy(template_cfg._vdict)
+
+ # Set defaults across all variants
+ _vdict["build_path"] = "build-ci-all"
+ _vdict["app_bin_path"] = "install/outputs/fvp"
+ _vdict["data_bin_path"] = "install/outputs/fvp"
+ _vdict["variant_name_tpl"] = "%(platform)s_%(compiler)s_%(config)s_" + \
+ "%(build_type)s_%(bootloader)s"
+
+ # Mofify the %(config)s parameter of the template
+ _vdict["config"] = "ConfigRegression"
+ _cfg["terminal_log"] = _cfg["terminal_log"] % _vdict
+
+ # Populate the test cases
+ _cfg["test_rex"] = (r"[\x1b]\[37mTest suite '(?P<test_case_id>[^\n]+)'"
+ r" has [\x1b]\[32m (?P<result>PASSED|FAILED)")
+ _cfg["test_cases"] = [
+ 'PSA protected storage S interface tests (TFM_SST_TEST_2XXX)',
+ 'PSA protected storage NS interface tests (TFM_SST_TEST_1XXX)',
+ 'SST reliability tests (TFM_SST_TEST_3XXX)',
+ 'Core non-secure positive tests (TFM_CORE_TEST_1XXX)',
+ 'AuditLog non-secure interface test (TFM_AUDIT_TEST_1XXX)',
+ 'Crypto non-secure interface test (TFM_CRYPTO_TEST_6XXX)',
+ 'Initial Attestation Service '
+ 'non-secure interface tests(TFM_ATTEST_TEST_2XXX)',
+ 'Invert non-secure interface tests (TFM_INVERT_TEST_1XXX)',
+ 'SST rollback protection tests (TFM_SST_TEST_4XXX)',
+ 'Audit Logging secure interface test (TFM_AUDIT_TEST_1XXX)',
+ 'Crypto secure interface tests (TFM_CRYPTO_TEST_5XXX)',
+ 'Initial Attestation Service secure '
+ 'interface tests(TFM_ATTEST_TEST_1XXX)',
+ 'Invert secure interface tests (TFM_INVERT_TEST_1XXX)',
+ ]
+ _cfg["test_end_string"] = "End of Non-secure test suites"
+
+ _cfg["simlimit"] = "1200"
+
+
+class template_coreipc_config(template_cfg):
+ """ Will automatically populate the required information for tfm
+ coreipc configuration testing. User still needs to set the
+ buildpath, platform, compiler variants """
+
+ _cfg = deepcopy(template_cfg._cfg)
+
+ _vdict = deepcopy(template_cfg._vdict)
+
+ # Set defaults across all variants
+ _vdict["build_path"] = "build-ci-all"
+
+ _vdict["app_bin_path"] = "install/outputs/fvp"
+ _vdict["data_bin_path"] = "install/outputs/fvp"
+
+ _vdict["variant_name_tpl"] = "%(platform)s_%(compiler)s_%(config)s_" + \
+ "%(build_type)s_%(bootloader)s"
+
+ # Mofify the %(config)s parameter of the template
+ _vdict["config"] = "ConfigCoreIPC"
+ _cfg["terminal_log"] = _cfg["terminal_log"] % _vdict
+
+ # System supports two types of matching with
+ # test_case_id and result match group and only test_case_id
+ _cfg["test_rex"] = (r'\x1b\[1;34m\[Sec Thread\] '
+ r'(?P<test_case_id>Secure image initializing!)\x1b\[0m'
+ )
+
+ # test_case_id capture group Should match test_cases entries
+ _cfg["test_cases"] = [
+ 'Secure image initializing!',
+ ]
+ # Testing will stop if string is reached
+ _cfg["test_end_string"] = "Secure image initializing"
+ _cfg["simlimit"] = "1200"
+
+class template_coreipctfmlevel2_config(template_cfg):
+ """ Will automatically populate the required information for tfm
+ coreipc tfmlevel2 configuration testing. User still needs to set the
+ buildpath, platform, compiler variants """
+
+ _cfg = deepcopy(template_cfg._cfg)
+
+ _vdict = deepcopy(template_cfg._vdict)
+
+ # Set defaults across all variants
+ _vdict["build_path"] = "build-ci-all"
+
+ _vdict["app_bin_path"] = "install/outputs/fvp"
+ _vdict["data_bin_path"] = "install/outputs/fvp"
+
+ _vdict["variant_name_tpl"] = "%(platform)s_%(compiler)s_%(config)s_" + \
+ "%(build_type)s_%(bootloader)s"
+
+ # Mofify the %(config)s parameter of the template
+ _vdict["config"] = "ConfigCoreIPCTfmLevel2"
+ _cfg["terminal_log"] = _cfg["terminal_log"] % _vdict
+
+ # System supports two types of matching with
+ # test_case_id and result match group and only test_case_id
+ _cfg["test_rex"] = (r'\x1b\[1;34m\[Sec Thread\] '
+ r'(?P<test_case_id>Secure image initializing!)\x1b\[0m'
+ )
+
+ # test_case_id capture group Should match test_cases entries
+ _cfg["test_cases"] = [
+ 'Secure image initializing!',
+ ]
+ # Testing will stop if string is reached
+ _cfg["test_end_string"] = "Secure image initializing"
+ _cfg["simlimit"] = "1200"
diff --git a/tfm_ci_pylib/fastmodel_wrapper/fastmodel_config_map.py b/tfm_ci_pylib/fastmodel_wrapper/fastmodel_config_map.py
new file mode 100644
index 0000000..1a58441
--- /dev/null
+++ b/tfm_ci_pylib/fastmodel_wrapper/fastmodel_config_map.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python3
+
+""" fastmodel_config_map.py:
+
+ Using Python clas inheritance model to generate modular and easily to scale
+ configuration models for the run_fpv module. Configuration data is also
+ combined with helper methods. If the file is run as a standalone file,
+ it can save json configuration files to disk if requested by --export
+ directive """
+
+from __future__ import print_function
+from copy import deepcopy
+from pprint import pprint
+
+__copyright__ = """
+/*
+ * Copyright (c) 2018-2019, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+ """
+__author__ = "Minos Galanakis"
+__email__ = "minos.galanakis@linaro.org"
+__project__ = "Trusted Firmware-M Open CI"
+__status__ = "stable"
+__version__ = "1.1"
+
+
+class FastmodelConfigMap(object):
+
+ def __init__(self, enviroment, platform):
+ pass
+
+ self._platforms = [platform]
+ self._cfg_map = self.global_import(enviroment)
+ self._invalid = []
+
+ def add_invalid(self, invalid_tuple):
+ self._invalid.append(invalid_tuple)
+
+ def get_invalid(self):
+ return deepcopy(self._invalid)
+
+ def global_import(self, enviroment, classname="TfmFastModelConfig"):
+ """ Import modules with specified classname from enviroment
+ provided by caller """
+
+ # Select the imported modules with a __name__ attribute
+ ol = {nme: cfg for nme, cfg in enviroment.items()
+ if hasattr(cfg, '__name__')}
+
+ # Select those who match the classname
+ fcfg = {nme: cfg_obj for nme, cfg_obj
+ in ol.items() if cfg_obj .__name__ == classname}
+
+ return {self._platforms[0]: fcfg}
+
+ def __add__(self, obj_b):
+ """ Override addition operator """
+
+ # Create a new object of left hand operant for return
+ ret_obj = deepcopy(self)
+
+ # Get references to new class members
+ map_a = ret_obj._cfg_map
+ platforms_a = ret_obj._platforms
+ map_b = obj_b.get_object_map()
+ for platform, config in map_b.items():
+
+ if platform in map_a.keys():
+ for cfg_name, cfg_object in config.items():
+ if cfg_name in map_a[platform].keys():
+ print("Matching entrty name %s" % (cfg_name))
+ print("Left operant entry: %s "
+ "will be replaced by: %s" %
+ (map_a[platform][cfg_name], cfg_object))
+ map_a[platform][cfg_name] = cfg_object
+ else:
+ map_a[platform] = deepcopy(config)
+ platforms_a.append(platform)
+
+ return ret_obj
+
+ def _cmerge(self):
+ """ Join all the platform configs """
+
+ ret = {}
+ for entry in self._cfg_map.values():
+ for name, cfg in entry.items():
+ ret[name] = cfg
+ return ret
+
+ def get_object_map(self):
+ """ Returns the config map as objects """
+
+ return deepcopy(self._cfg_map)
+
+ def get_config_map(self):
+ """ Return a copy of the config map with the config objects rendered
+ as dictionaries """
+
+ ret_dict = deepcopy(self._cfg_map)
+ for platform, config in self._cfg_map.items():
+ for name, cfg_object in config.items():
+ ret_dict[platform][name] = cfg_object.get_config()
+ return ret_dict
+
+ def list(self):
+ """ Print a quick list of the contained platforms and
+ configuration names """
+
+ return list(self._cmerge().keys())
+
+ def print_list(self):
+ """ Print a quick list of the contained platforms and
+ configuration names """
+
+ for platform, config in self._cfg_map.items():
+ print("=========== Platform: %s ===========" % platform)
+ for name, cfg_object in config.items():
+ print(name)
+
+ def print(self):
+ """ Print the contents of a human readable config map """
+
+ pprint(self.get_config_map())
+
+ def get_config_object(self, config_name, platform=None):
+ try:
+ cfg_dict = self._cfg_map[platform]
+ except Exception as e:
+ cfg_dict = self._cmerge()
+
+ return cfg_dict[config_name]
+
+ def get_config(self, config_name, platform=None):
+
+ return self.get_config_object(config_name, platform).get_config()
+
+ def patch_config(self, cfg_name, key, new_data, platform=None):
+ """ Modify a configuration entry, and re-render the class """
+
+ cfg_object = self.get_config_object(cfg_name, platform)
+
+ # Do not
+ if cfg_object.get_variant_metadata()[key] == new_data:
+ return
+ v_meta = cfg_object.get_variant_metadata()
+ v_meta[key] = new_data
+ cfg_object.set_variant_metadata(v_meta).rebuild()
+
+
+def fvp_config_object_change_path(cfg_object, new_path):
+ """ Change the common artifact storage path and update its
+ configuration """
diff --git a/tfm_ci_pylib/fastmodel_wrapper/fastmodel_wrapper.py b/tfm_ci_pylib/fastmodel_wrapper/fastmodel_wrapper.py
new file mode 100755
index 0000000..7566c2e
--- /dev/null
+++ b/tfm_ci_pylib/fastmodel_wrapper/fastmodel_wrapper.py
@@ -0,0 +1,553 @@
+#!/usr/bin/env python3
+
+""" fastmodel_wrapper.py:
+
+ Wraps around Fast models which will execute in headless model
+ producing serial output to a defined log file. It will spawn two Proccesses
+ and one thread to monitor the output of the simulation and end it when a
+ user defined condition is matched. It will perform a set of tests and will
+ change the script exit code based on the output of the test """
+
+from __future__ import print_function
+
+__copyright__ = """
+/*
+ * Copyright (c) 2018-2019, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+ """
+__author__ = "Minos Galanakis"
+__email__ = "minos.galanakis@linaro.org"
+__project__ = "Trusted Firmware-M Open CI"
+__status__ = "stable"
+__version__ = "1.1"
+
+import os
+import re
+import sys
+import argparse
+from time import sleep
+from pprint import pprint
+from copy import deepcopy
+from threading import Thread
+from queue import Queue, Empty
+from subprocess import Popen, PIPE, STDOUT
+
+try:
+ from tfm_ci_pylib.utils import find_missing_files, \
+ detect_python3, test, check_pid_status, save_json, save_dict_json, \
+ load_json
+except ImportError:
+ dir_path = os.path.dirname(os.path.realpath(__file__))
+ sys.path.append(os.path.join(dir_path, "../"))
+ from tfm_ci_pylib.utils import find_missing_files, \
+ detect_python3, test, check_pid_status, save_json, save_dict_json, \
+ load_json
+
+
+class FastmodelWrapper(object):
+ """ Controlling Class that wraps around an ARM Fastmodel and controls
+ execution, adding regex flow controls, and headless testing """
+
+ def __init__(self,
+ fvp_cfg=None,
+ work_dir="./",
+ fvp_dir=None,
+ fvp_binary=None,
+ fvp_app=None,
+ fvp_boot=None,
+ terminal_file=None,
+ fvp_time_out=None,
+ fvp_test_error=None):
+
+ # Required by other methods, always set working directory first
+ self.work_dir = os.path.abspath(work_dir)
+
+ # Load the configuration from object or file
+ self.config, self.name = self.load_config(fvp_cfg)
+
+ self.show_config()
+
+ # Print a header
+ ln = int((62 - len(self.name) + 1) / 2)
+ print("\n%s Running Test: %s %s\n" % ("#" * ln, self.name, "#" * ln))
+
+ # consume the configuration parameters not related to FPV
+ # Extract test cases
+ self.test_list = self.config.pop("test_cases")
+ self.test_end_string = self.config.pop("test_end_string")
+ self.test_rex = self.config.pop("test_rex")
+
+ # Command line arguments overrides
+ # When those arguments are provided they override config entries
+ f_dir = self.config.pop("directory")
+ if fvp_dir:
+ self.fvp_dir = os.path.abspath(fvp_dir)
+ else:
+ self.fvp_dir = os.path.abspath(f_dir)
+
+ ef = self.config.pop("error_on_failed")
+ if fvp_test_error:
+ self.fvp_test_error = fvp_test_error
+ else:
+ self.fvp_test_error = ef
+
+ tf = self.config.pop("terminal_log")
+ if terminal_file:
+ self.term_file = os.path.abspath(terminal_file)
+ else:
+ tf = os.path.join(self.work_dir, tf)
+ self.term_file = os.path.abspath(tf)
+
+ # Override config entries directly
+ if fvp_binary:
+ self.config["bin"] = fvp_binary
+
+ if fvp_boot:
+ if re.match(r'[\S]+.axf$', fvp_boot):
+ self.config["application"] = "cpu0=" +\
+ os.path.abspath(fvp_boot)
+ else:
+ print("Invalid bootloader %s. Expecting .axf file" % fvp_app)
+ sys.exit(1)
+
+ # Ensure that the firmware is copied at the appropriate memory region
+ # perfect mathc regx for future ref r'^(?:cpu=)[\S]+.bin@0x10080000$'
+ # TODO remove that when other platforms are added
+ if fvp_app:
+ if re.match(r'[\S]+.bin$', fvp_app):
+ self.config["data"] = "cpu0=" +\
+ os.path.abspath(fvp_app) +\
+ "@0x10080000"
+ else:
+ print("Invalid firmware %s. Expecting .bin file" % fvp_app)
+ sys.exit(1)
+
+ if fvp_time_out:
+ self.fvp_time_out = fvp_time_out
+ self.config["simlimit"] = fvp_time_out
+
+ self.monitor_q = Queue()
+ self.stop_all = False
+ self.pids = []
+ self.fvp_test_summary = False
+
+ # Asserted only after a complete test run,including end string matching
+ self.test_complete = False
+
+ self.test_report = None
+
+ # Change to working directory
+ os.chdir(self.work_dir)
+ print("Switching to working directory: %s" % self.work_dir)
+ # Clear the file it it has been created before
+ with open(self.term_file, "w") as F:
+ F.write("")
+
+ def show_config(self):
+ """ print the configuration to console """
+
+ print("\n%s config:\n" % self.name)
+ pprint(self.config)
+
+ def load_config(self, config):
+ """ Load the configuration from a json file or a memory map"""
+
+ try:
+ # If config is an dictionary object use it as is
+ if isinstance(config, dict):
+ ret_config = config
+ elif isinstance(config, str):
+ # if the file provided is not detected attempt to look for it
+ # in working directory
+ if not os.path.isfile(config):
+ # remove path from file
+ cfg_file_2 = os.path.split(config)[-1]
+ # look in the current working directory
+ cfg_file_2 = os.path.join(self.work_dir, cfg_file_2)
+ if not os.path.isfile(cfg_file_2):
+ m = "Could not find cfg in %s or %s " % (config,
+ cfg_file_2)
+ raise Exception(m)
+ # If fille exists in working directory
+ else:
+ config = cfg_file_2
+ # Attempt to load the configuration from File
+ ret_config = load_json(config)
+ else:
+ raise Exception("Need to provide a valid config name or file."
+ "Please use --config/--config-file parameter.")
+
+ except Exception as e:
+ print("Error! Could not load config. Quitting")
+ sys.exit(1)
+
+ # Generate Test name (Used in test report) from terminal file.
+ tname = ret_config["terminal_log"].replace("terminal_", "")\
+ .split(".")[0].lower()
+
+ return deepcopy(ret_config), tname
+
+ def save_config(self, config_file="fvp_tfm_config.json"):
+ """ Safe current configuration to a json file """
+
+ # Add stripped information to config
+ exp_cfg = deepcopy(self.config)
+
+ exp_cfg["terminal_log"] = self.term_file
+ exp_cfg["error_on_failed"] = self.fvp_test_error
+ exp_cfg["directory"] = self.fvp_dir
+ exp_cfg["test_cases"] = self.test_list
+ exp_cfg["test_end_string"] = self.test_end_string
+ exp_cfg["test_rex"] = self.test_rex
+
+ cfg_f = os.path.join(self.work_dir, config_file)
+ save_dict_json(cfg_f, exp_cfg, exp_cfg.get_sort_order())
+ print("Configuration %s exported." % cfg_f)
+
+ def compile_cmd(self):
+ """ Compile all the FPV realted information into a command that can
+ be executed manually """
+
+ cmd = ""
+ for name, value in self.config.items():
+ # Place executable to the beggining of the machine
+ if name == "bin":
+ cmd = value + cmd
+ elif name == "parameters":
+ cmd += " " + " ".join(["--parameter %s" % p for p in value])
+ # Allows setting a second binary file as data field
+ elif name == "application" and ".bin@0x0" in value:
+ cmd += " --data %s" % value
+ else:
+ cmd += " --%s %s" % (name, value)
+
+ # Add the path to the command
+ cmd = os.path.join(self.fvp_dir, cmd)
+
+ # Add the log file to the command (optional)
+ cmd = cmd.replace("$TERM_FILE", self.term_file)
+ return cmd
+
+ def show_cmd(self):
+ """ print the FPV command to console """
+
+ print(self.compile_cmd())
+
+ def run_fpv(self):
+ """ Run the Fast Model test in a different proccess and return
+ the pid for housekeeping puproses """
+
+ def fpv_stdout_parser(dstream, queue):
+ """ THREAD: Read STDOUT/STDERR and stop if proccess is done """
+
+ for line in iter(dstream.readline, b''):
+ if self.stop_all:
+ break
+ else:
+ # Python2 ignores byte literals, P3 requires parsing
+ if detect_python3():
+ line = line.decode("utf-8")
+ if "Info: /OSCI/SystemC: Simulation stopped by user" in line:
+ print("/OSCI/SystemC: Simulation stopped")
+ self.stop()
+ break
+
+ # Convert to list
+ cmd = self.compile_cmd().split(" ")
+
+ # Run it as subproccess
+ self.fvp_proc = Popen(cmd, stdout=PIPE, stderr=STDOUT, shell=False)
+ self._fvp_thread = Thread(target=fpv_stdout_parser,
+ args=(self.fvp_proc.stdout,
+ self.monitor_q))
+ self._fvp_thread.daemon = True
+ self._fvp_thread.start()
+ return self.fvp_proc.pid
+
+ def run_monitor(self):
+ """ Run a parallel threaded proccess that monitors the output of
+ the FPV and stops it when the a user specified string is found.
+ It returns the pid of the proccess for housekeeping """
+
+ def monitor_producer(dstream, queue):
+ """ THREAD: Read STDOUT and push data into a queue """
+
+ for line in iter(dstream.readline, b''):
+ if self.stop_all:
+ break
+ else:
+ # Python2 ignores byte literals, P3 requires parsing
+ if detect_python3():
+ line = line.decode("utf-8")
+
+ queue.put(line)
+
+ # If the text end string is found terminate
+ if self.test_end_string in str(line):
+
+ queue.put("Found End String \"%s\"" % self.test_end_string)
+ self.test_complete = True
+ self.stop()
+ break
+ # If the FPV stopps by iteself (i.e simlimit reached) terminate
+ if "SystemC: Simulation stopped by user" in str(line):
+
+ queue.put("Simulation Ended \"%s\"" % self.test_end_string)
+ self.stop()
+ break
+
+ dstream.close()
+ return
+
+ # Run the tail as a separate proccess
+ cmd = ["tail", "-f", self.term_file]
+ self.monitor_proc = Popen(cmd, stdout=PIPE, stderr=STDOUT, shell=False)
+
+ self._fvp_mon_thread = Thread(target=monitor_producer,
+ args=(self.monitor_proc.stdout,
+ self.monitor_q))
+ self._fvp_mon_thread.daemon = True
+ self._fvp_mon_thread.start()
+ return self.monitor_proc.pid
+
+ def monitor_consumer(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 Empty:
+ pass
+ else:
+ print(line.rstrip())
+
+ def has_stopped(self):
+ """Retrun status of stop flag. True indicated stopped state """
+
+ return self.stop_all
+
+ def start(self):
+ """ Start the FPV and the montor procccesses and keep
+ track of their pids"""
+
+ # Do not spawn fpv unless everything is in place if
+ bin_list = [os.path.join(self.fvp_dir, self.config["bin"]),
+ self.config["application"].replace("cpu0=", "")
+ .replace("@0x0", ""),
+ self.config["data"].replace("@0x10080000", "")
+ .replace("@0x00100000", "")
+ .replace("cpu0=", "")]
+
+ if find_missing_files(bin_list):
+ print("Could not find all binaries from %s" % ", ".join(bin_list))
+ print("Missing Files:", ", ".join(find_missing_files(bin_list)))
+ sys.exit(1)
+
+ self.pids.append(self.run_fpv())
+ self.pids.append(self.run_monitor())
+ print("Spawned Proccesses with PID %s" % repr(self.pids)[1:-1])
+ return self
+
+ def stop(self):
+ """ Stop all threads, proccesses and make sure there are no leaks """
+
+ self.stop_all = True
+
+ # Send the gratious shutdown signal
+ self.monitor_proc.terminate()
+ self.fvp_proc.terminate()
+ sleep(1)
+ # List the Zombies
+ # TODO remove debug output
+ for pid in sorted(self.pids):
+ if check_pid_status(pid, ["zombie", ]):
+ pass
+ # print("Warning. Defunc proccess %s" % pid)
+
+ def test(self):
+ """ Parse the output terminal file and evaluate status of tests """
+
+ # read the output file
+ with open(self.term_file, "r") as F:
+ terminal_log = F.read()
+
+ pass_text = "PASSED"
+ # create a filtering regex
+ rex = re.compile(self.test_rex)
+
+ # Extract tests status as a tuple list
+ tests = rex.findall(terminal_log)
+
+ try:
+ if isinstance(tests, list):
+ if len(tests):
+ # when test regex is in format [(test_name, RESULT),...]
+ if isinstance(tests[0], tuple):
+ # Convert result into a dictionary
+ tests = dict(zip(*list(zip(*tests))))
+ # when regex is in format [(test_name, test_name 2),...]
+ # we just need to verify they exist
+ elif isinstance(tests[0], str):
+ pass_text = "PRESENT"
+ tests = dict(zip(tests,
+ [pass_text for n in range(len(tests))]))
+ else:
+ raise Exception("Incompatible Test Format")
+ else:
+ raise Exception("Incompatible Test Format")
+ else:
+ raise Exception("Incompatible Test Format")
+ except Exception:
+
+ if not self.test_complete:
+ print("Warning! Test did not complete.")
+ else:
+ print("Error", "Invalid tests format: %s type: %s" %
+ (tests, type(tests)))
+ # Pass an empty output to test. Do not exit prematurely
+ tests = {}
+
+ # Run the test and store the report
+ self.test_report = test(self.test_list,
+ tests,
+ pass_text=pass_text,
+ test_name=self.name,
+ error_on_failed=self.fvp_test_error,
+ summary=self.fvp_test_summary)
+ return self
+
+ def get_report(self):
+ """ Return the test report object to caller """
+
+ if not self.test_report:
+ raise Exception("Can not create report from incomplete run cycle!")
+ return self.test_report
+
+ def save_report(self, rep_f=None):
+ """ Export report into a file, set by test name but can be overidden by
+ rep_file"""
+
+ if not self.stop_all or not self.test_report:
+ print("Can not create report from incomplete run cycle!")
+ return
+
+ if not rep_f:
+ rep_f = os.path.join(self.work_dir, "report_%s.json" % self.name)
+ rep_f = os.path.abspath(rep_f)
+ save_json(rep_f, self.test_report)
+ print("Exported test report: %s" % rep_f)
+ return self
+
+ def block_wait(self):
+ """ Block execution flow and wait for the monitor to complete """
+ try:
+ while True:
+ for pid in sorted(self.pids):
+
+ if not check_pid_status(pid, ["running",
+ "sleeping",
+ "disk"]):
+ print("Child proccess of pid: %s has died, exitting!" %
+ pid)
+ self.stop()
+ if self.has_stopped():
+ break
+ else:
+ self.monitor_consumer()
+
+ except KeyboardInterrupt:
+ print("User initiated interrupt")
+ self.stop()
+ # Allows method to be chainloaded
+ return self
+
+
+def get_cmd_args():
+ """ Parse command line arguments """
+
+ # Parse command line arguments to override config
+ parser = argparse.ArgumentParser(description="TFM Fastmodel wrapper.")
+ parser.add_argument("--bin",
+ dest="fvp_bin",
+ action="store",
+ help="Fast Model platform binary file")
+ parser.add_argument("--firmware",
+ dest="fvp_firm",
+ action="store",
+ help="Firmware application file to run")
+ parser.add_argument("--boot",
+ dest="fvp_boot",
+ action="store",
+ help="Fast Model bootloader file")
+ parser.add_argument("--fpv-path",
+ dest="fvp_dir",
+ action="store",
+ help="Directory path containing the Fast Models")
+ parser.add_argument("--work-path",
+ dest="work_dir", action="store",
+ default="./",
+ help="Working directory (Where logs are stored)")
+ parser.add_argument("--time-limit",
+ dest="time", action="store",
+ help="Time in seconds to run the simulation")
+ parser.add_argument("--log-file",
+ dest="termf",
+ action="store",
+ help="Set terminal log file name")
+ parser.add_argument("--error",
+ dest="test_err",
+ action="store",
+ help="raise sys.error = 1 if test failed")
+ parser.add_argument("--config-file",
+ dest="config_file",
+ action="store",
+ help="Path of configuration file")
+ parser.add_argument("--print-config",
+ dest="p_config",
+ action="store_true",
+ help="Print the configuration to console")
+ parser.add_argument("--print-command",
+ dest="p_command",
+ action="store_true",
+ help="Print the FPV launch command to console")
+ return parser.parse_args()
+
+
+def main(user_args):
+ """ Main logic """
+
+ # Create FPV handler
+ F = FastmodelWrapper(fvp_cfg=user_args.config_file,
+ work_dir=user_args.work_dir,
+ fvp_dir=user_args.fvp_dir,
+ fvp_binary=user_args.fvp_bin,
+ fvp_boot=user_args.fvp_boot,
+ fvp_app=user_args.fvp_firm,
+ terminal_file=user_args.termf,
+ fvp_time_out=user_args.time,
+ fvp_test_error=user_args.test_err)
+
+ if user_args.p_config:
+ F.show_config()
+ sys.exit(0)
+
+ if user_args.p_command:
+ F.show_cmd()
+ sys.exit(0)
+
+ # Start the wrapper
+ F.start()
+
+ # Wait for the wrapper to complete
+ F.block_wait()
+
+ print("Shutting Down")
+ # Test the output of the system only after a full execution
+ if F.test_complete:
+ F.test()
+
+
+if __name__ == "__main__":
+ main(get_cmd_args())
diff --git a/tfm_ci_pylib/fastmodel_wrapper/fastmodel_wrapper_config.py b/tfm_ci_pylib/fastmodel_wrapper/fastmodel_wrapper_config.py
new file mode 100644
index 0000000..0c2f60a
--- /dev/null
+++ b/tfm_ci_pylib/fastmodel_wrapper/fastmodel_wrapper_config.py
@@ -0,0 +1,267 @@
+#!/usr/bin/env python3
+
+""" fastmodel_wrapper_config.py:
+
+ Using Python clas inheritance model to generate modular and easily to scale
+ configuration models for the run_fpv module. Configuration data is also
+ combined with helper methods. If the file is run as a standalone file,
+ it can save json configuration files to disk if requested by --export
+ directive """
+
+from __future__ import print_function
+from collections import OrderedDict
+from copy import deepcopy
+from pprint import pprint
+
+__copyright__ = """
+/*
+ * Copyright (c) 2018-2019, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+ """
+__author__ = "Minos Galanakis"
+__email__ = "minos.galanakis@linaro.org"
+__project__ = "Trusted Firmware-M Open CI"
+__status__ = "stable"
+__version__ = "1.1"
+
+
+try:
+ from tfm_ci_pylib.utils import save_dict_json
+except ImportError:
+ import os
+ import sys
+ dir_path = os.path.dirname(os.path.realpath(__file__))
+ sys.path.append(os.path.join(dir_path, "../"))
+ from tfm_ci_pylib.utils import save_dict_json
+
+
+# Used in fixed sorting of configuration before generating a json file
+# WARNING modification of this file will fundamentaly change behavior
+config_sort_order = [
+ "directory",
+ "terminal_log",
+ "bin",
+ "error_on_failed",
+ "test_rex", "test_cases",
+ "test_end_string",
+ "application",
+ "data",
+ "simlimit",
+ "parameters"
+]
+
+
+class fpv_wrapper_config(object):
+ """ Controlling Class that wraps around an ARM Fastmodel and controls
+ execution, adding regex flow controls, and headless testing """
+
+ # Ensure the dictionary entries are sorted
+ _cfg = OrderedDict.fromkeys(config_sort_order)
+ _name = "run_fpv"
+
+ def __init__(self,
+ fvp_dir,
+ terminal_file,
+ fvp_binary,
+ eof,
+ test_rex,
+ test_cases,
+ test_end_string,
+ fvp_app,
+ fvp_boot,
+ fvp_sim_limit,
+ params):
+
+ self._cfg["directory"] = fvp_dir
+ self._cfg["terminal_log"] = terminal_file
+ self._cfg["bin"] = fvp_binary
+ self._cfg["error_on_failed"] = eof
+ self._cfg["test_rex"] = test_rex
+ self._cfg["test_cases"] = test_cases
+ self._cfg["test_end_string"] = test_end_string
+ self._cfg["application"] = fvp_app
+ self._cfg["data"] = fvp_boot
+ self._cfg["simlimit"] = fvp_sim_limit
+ self._cfg["parameters"] = params
+
+ @classmethod
+ def get_config(self):
+ """ Return a copy of the fastmodel configuration dictionary """
+ return dict(deepcopy(self._cfg))
+
+ @classmethod
+ def get_variant_metadata(self):
+ """ Return a copy of the class generator variant dictionary """
+ return deepcopy(self._vdict)
+
+ @classmethod
+ def set_variant_metadata(self, vdict):
+ """ Replace the metadata dictionary with user provided one """
+
+ self._vdict = deepcopy(vdict)
+
+ return self
+
+ @classmethod
+ def querry_variant_metadata(self, key, value):
+ """ Verify that metadata dictionary contains value for key entry """
+
+ return self._vdict[key] == value
+
+ @classmethod
+ def rebuild(self):
+ """ Recreate the configuration of a class after metadata has been
+ modified """
+
+ # Reset the configuration entries to the stock ones
+ self._cfg = deepcopy(self._tpl_cfg)
+
+ # recreate a temporary class with proper configuration
+ @config_variant(**self._vdict)
+ class tmp_class(self):
+ pass
+
+ # Copy over the new configuguration from temporary class
+ self._cfg = deepcopy(tmp_class._cfg)
+
+ @classmethod
+ def print(self):
+ """ Print the configuration dictionary in a human readable format """
+ pprint(dict(self._cfg))
+
+ @classmethod
+ def json_to_file(self, outfile=None):
+ """ Create a JSON file with the configration """
+
+ if not outfile:
+ outfile = self.get_name() + ".json"
+ save_dict_json(outfile, self.get_config(), config_sort_order)
+ print("Configuration exported to %s" % outfile)
+
+ @classmethod
+ def get_name(self):
+ """ Return the name of the configuration """
+
+ return self._name.lower()
+
+ def get_sort_order(self):
+ """ Return an ordered list of entries in the configuration """
+
+ return self._cfg.keys()
+
+
+def config_variant(**override_params):
+ """ Class decorator that enables dynamic subclass creation for different
+ configuration combinatins. Override params can be any keyword based
+ argument of template_cfg._vict """
+
+ def class_rebuilder(cls):
+ class TfmFastModelConfig(cls):
+ override = False
+ _cfg = deepcopy(cls._cfg)
+ _tpl_cfg = deepcopy(cls._cfg)
+ _vdict = deepcopy(cls._vdict)
+ for param, value in override_params.items():
+ if param in _vdict.keys():
+ _vdict[param] = value
+ override = True
+
+ if override:
+ _vdict["variant_name_tpl"] = _vdict["variant_name_tpl"] \
+ % _vdict
+
+ # Update the configuration dependant enties
+ _cfg["terminal_log"] = _cfg["terminal_log"] % _vdict
+
+ # Adjust the binaries based on bootloader presense
+ if _vdict["bootloader"] == "BL2":
+ _vdict["app_bin"] = override_params["app_bin"] if \
+ "app_bin" in override_params else "mcuboot.axf"
+ _vdict["data_bin"] = override_params["data_bin"] if \
+ "data_bin" in override_params \
+ else "tfm_s_ns_signed.bin"
+ _vdict["data_bin_offset"] = "0x10080000"
+ else:
+ _vdict["app_bin"] = override_params["app_bin"] if \
+ "app_bin" in override_params else "tfm_s.axf"
+ _vdict["data_bin"] = override_params["data_bin"] if \
+ "data_bin" in override_params else "tfm_ns.bin"
+ _vdict["data_bin_offset"] = "0x00100000"
+
+ # Switching from AN519 requires changing the parameter
+ # cpu0.baseline=0 -> 1
+ if _vdict["platform"] == "AN519":
+ idx = _cfg["parameters"].index("cpu0.baseline=0")
+ cpu_param = _cfg["parameters"].pop(idx).replace("=0", "=1")
+ _cfg["parameters"].append(cpu_param)
+ _cfg["application"] = _cfg["application"] % _vdict
+ _cfg["data"] = _cfg["data"] % _vdict
+
+ _name = cls._name % _vdict
+
+ return TfmFastModelConfig
+
+ return class_rebuilder
+
+
+# =================== Template Classes ===================
+class template_cfg(fpv_wrapper_config):
+ """ Creates a skeleton template configuration that allows creation of
+ configuration variants which set the parameters of:
+ buildpath, config, platform, compiler , as well as the missing test params,
+ test_rex, test_cases, test_end_string """
+
+ _name = fpv_wrapper_config._name + "_%(platform)s_%(compiler)s_" + \
+ "%(config)s_%(build_type)s_%(bootloader)s"
+ # variant dictionary allows indivudal and targeted parameter modification
+ _vdict = {
+ "build_path": "%(build_path)s",
+ "variant_name_tpl": "%(variant_name_tpl)s",
+ "app_bin_path": "%(app_bin_path)s",
+ "app_bin": "%(app_bin)s",
+ "data_bin_path": "%(data_bin_path)s",
+ "data_bin": "%(data_bin)s",
+ "data_bin_offset": "%(data_bin_offset)s",
+ "config": "%(config)s",
+ "platform": "%(platform)s",
+ "compiler": "%(compiler)s",
+ "build_type": "%(build_type)s",
+ "bootloader": "%(bootloader)s"
+ }
+
+ _cfg = deepcopy(fpv_wrapper_config._cfg)
+ _cfg["directory"] = "FVP_MPS2_11.3"
+ _cfg["terminal_log"] = "terminal_%(variant_name_tpl)s.log"
+ _cfg["bin"] = "FVP_MPS2_AEMv8M"
+ _cfg["error_on_failed"] = False
+ _cfg["application"] = (
+ "cpu0=%(build_path)s/%(variant_name_tpl)s/" +
+ "%(app_bin_path)s/%(app_bin)s")
+ _cfg["data"] = (
+ "cpu0=%(build_path)s/%(variant_name_tpl)s/%(data_bin_path)s/" +
+ "%(data_bin)s@%(data_bin_offset)s")
+ _cfg["simlimit"] = "60"
+ _cfg["parameters"] = [
+ "fvp_mps2.platform_type=2",
+ "cpu0.baseline=0",
+ "cpu0.INITVTOR_S=0x10000000",
+ "cpu0.semihosting-enable=0",
+ "fvp_mps2.DISABLE_GATING=0",
+ "fvp_mps2.telnetterminal0.start_telnet=0",
+ "fvp_mps2.telnetterminal1.start_telnet=0",
+ "fvp_mps2.telnetterminal2.start_telnet=0",
+ "fvp_mps2.telnetterminal0.quiet=1",
+ "fvp_mps2.telnetterminal1.quiet=1",
+ "fvp_mps2.telnetterminal2.quiet=1",
+ "fvp_mps2.UART0.out_file=$TERM_FILE",
+ "fvp_mps2.UART0.unbuffered_output=1",
+ "fvp_mps2.UART0.shutdown_on_eot=1",
+ "fvp_mps2.mps2_visualisation.disable-visualisation=1"]
+
+
+if __name__ == "__main__":
+ # Create Json configuration files on user request
+ pass