blob: 7d1ca46bacbe5bca35f022297eb3daef7b2f1137 [file] [log] [blame]
#!/usr/bin/env python3
""" utils.py:
various simple and commonly used methods and classes shared by the scripts
in the CI environment """
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.0"
import os
import sys
import yaml
import argparse
import json
import itertools
from collections import OrderedDict, namedtuple
from subprocess import Popen, PIPE, STDOUT
def detect_python3():
""" Return true if script is run with Python3 interpreter """
return sys.version_info > (3, 0)
def print_test_dict(data_dict,
pad_space=80,
identation=5,
titl="Summary",
pad_char="*"):
""" Configurable print formatter aimed for dictionaries of the type
{"TEST NAME": "RESULT"} used in CI systems. It will also return
the string which is printing """
# Calculate pad space bewteen variables x, y t achieve alignment on y
# taking into consideration a maximum aligment boundary p and
# possible indentation i
def flex_pad(x, y, p, i):
return " " * (p - i * 2 - len(x) - len(y)) + "-> "
# Calculate the padding for the dataset
tests = [k + flex_pad(k,
v,
pad_space,
identation) + v for k, v in data_dict.items()]
# Add the identation
tests = map(lambda x: " " * identation + x, tests)
# Convert to string
tests = "\n".join(tests)
# Calcuate the top header padding ceiling any rounding errors
hdr_pad = (pad_space - len(titl) - 3) / 2
if detect_python3():
hdr_pad = int(hdr_pad)
# Generate a print formatting dictionary
print_dict = {"pad0": pad_char * (hdr_pad),
"pad1": pad_char * (hdr_pad + 1 if len(titl) % 2
else hdr_pad),
"sumry": tests,
"pad2": pad_char * pad_space,
"titl": titl}
# Compose & print the report
r = "\n%(pad0)s %(titl)s %(pad1)s\n\n%(sumry)s\n\n%(pad2)s\n" % print_dict
print(r)
return r
def print_test(t_name=None, t_list=None, status="failed", tname="Tests"):
""" Print a list of tests in a stuctured ascii table format """
gfx_line1 = "=" * 80
gfx_line2 = "\t" + "-" * 70
if t_name:
print("%(line)s\n%(name)s\n%(line)s" % {"line": gfx_line1,
"name": t_name})
print("%s %s:" % (tname, status))
print(gfx_line2 + "\n" +
"\n".join(["\t| %(key)s%(pad)s|\n%(line)s" % {
"key": n,
"pad": (66 - len(n)) * " ",
"line": gfx_line2} for n in t_list]))
def test(test_list,
test_dict,
test_name="TF-M Test",
pass_text=["PASSED", "PRESENT"],
error_on_failed=True,
summary=True):
""" Using input of a test_lst and a test results dictionary in the format
of test_name: resut key-value pairs, test() method will verify that Every
single method in the test_list has been tested and passed. Pass and Failed,
status tests can be overriden and error_on_failed flag, exits the script
with failure if a single test fails or is not detected. Returns a json
containing status and fields for each test passed/failed/missing, if error
on failed is not set.
"""
t_report = {"name": test_name,
"success": None,
"passed": [],
"failed": [],
"missing": []}
# Clean-up tests that are not requested by test_list
test_dict = {k: v for k, v in test_dict.items() if k in test_list}
# Calculate the difference of the two sets to find missing tests
t_report["missing"] = list(set(test_list) - set(test_dict.keys()))
# Sor the items into the apropriate lists (failed or passed)
# based on their status.
for k, v in test_dict.items():
# print(k, v)
key = "passed" if v in pass_text else "failed"
t_report[key] += [k]
# For the test to pass every singe test in test_list needs to be present
# and be in the passed list
if len(test_list) == len(t_report["passed"]):
t_report["success"] = True
else:
t_report["success"] = False
# Print a summary
if summary:
if t_report["passed"]:
print_test(test_name, t_report["passed"], status="passed")
if t_report["missing"]:
print_test(test_name, t_report["missing"], status="missing")
if t_report["failed"]:
print_test(test_name, t_report["failed"], status="Failed")
print("\nTest %s has %s!" % (t_report["name"],
" been successful" if t_report["success"]
else "failed"))
print("-" * 80)
if error_on_failed:
syscode = 0 if t_report["success"] else 1
sys.exit(syscode)
return t_report
def save_json(f_name, data_object):
""" Save object to json file """
with open(f_name, "w") as F:
F.write(json.dumps(data_object, indent=2))
def save_dict_json(f_name, data_dict, sort_list=None):
""" Save a dictionary object to file with optional sorting """
if sort_list:
data_object = (sort_dict(data_dict, sort_list))
save_json(f_name, data_object)
def sort_dict(config_dict, sort_order_list=None):
""" Create a fixed order disctionary out of a config dataset """
if sort_order_list:
ret = OrderedDict([(k, config_dict[k]) for k in sort_order_list])
else:
ret = OrderedDict([(k, config_dict[k]) for k in sorted(config_dict)])
return ret
def load_json(f_name):
""" Load object from json file """
with open(f_name, "r") as F:
try:
return json.loads(F.read())
except ValueError as exc:
print("No JSON object could be decoded from file: %s" % f_name)
except IOError:
print("Error opening file: %s" % f_name)
raise Exception("Failed to load file")
def load_yaml(f_name):
# Parse command line arguments to override config
with open(f_name, "r") as F:
try:
return yaml.load(F.read())
except yaml.YAMLError as exc:
print("Error parsing file: %s" % f_name)
except IOError:
print("Error opening file: %s" % f_name)
raise Exception("Failed to load file")
def subprocess_log(cmd, log_f, prefix=None, append=False, silent=False):
""" Run a command as subproccess an log the output to stdout and fileself.
If prefix is spefified it will be added as the first line in file """
with open(log_f, 'a' if append else "w") as F:
if prefix:
F.write(prefix + "\n")
pcss = Popen(cmd,
stdout=PIPE,
stderr=STDOUT,
shell=True,
env=os.environ)
for line in pcss.stdout:
if detect_python3():
line = line.decode("utf-8")
if not silent:
sys.stdout.write(line)
F.write(line)
pcss.communicate()
return pcss.returncode
return
def run_proccess(cmd):
""" Run a command as subproccess an log the output to stdout and file.
If prefix is spefified it will be added as the first line in file """
pcss = Popen(cmd,
stdout=PIPE,
stderr=PIPE,
shell=True,
env=os.environ)
pcss.communicate()
return pcss.returncode
def list_chunks(l, n):
""" Yield successive n-sized chunks from l. """
for i in range(0, len(l), n):
yield l[i:i + n]
def export_config_map(config_m, dir=None):
""" Will export a dictionary of configurations to a group of JSON files """
_dir = dir if dir else os.getcwd()
for _cname, _cfg in config_m.items():
_cname = _cname.lower()
_fname = os.path.join(_dir, _cname + ".json")
print("Exporting config %s" % _fname)
save_json(_fname, _cfg)
def gen_cfg_combinations(name, categories, *args):
""" Create a list of named tuples of `name`, with elements defined in a
space separated string `categories` and equal ammount of lists for said
categories provided as arguments. Order of arguments should match the
order of the categories lists """
build_config = namedtuple(name, categories)
return [build_config(*x) for x in itertools.product(*args)]
def get_cmd_args(descr="", parser=None):
""" Parse command line arguments """
# Parse command line arguments to override config
if not parser:
parser = argparse.ArgumentParser(description=descr)
return parser.parse_args()