mbed-os/tools/python/mbed_os_tools/test/mbed_greentea_hooks.py

247 lines
9.9 KiB
Python

# Copyright (c) 2018, Arm Limited and affiliates.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import re
import json
from subprocess import Popen, PIPE
from .mbed_greentea_log import gt_logger
"""
List of available hooks:
"""
class GreenteaTestHook(object):
"""! Class used to define
"""
name = None
def __init__(self, name):
self.name = name
def run(self, format=None):
pass
class GreenteaCliTestHook(GreenteaTestHook):
"""! Class used to define a hook which will call command line program
"""
cmd = None
def __init__(self, name, cmd):
GreenteaTestHook.__init__(self, name)
self.cmd = cmd
def run_cli_process(self, cmd):
"""! Runs command as a process and return stdout, stderr and ret code
@param cmd Command to execute
@return Tuple of (stdout, stderr, returncode)
"""
_stdout, _stderr, ret = None, None, -1
try:
p = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True)
_stdout, _stderr = p.communicate()
ret = p.returncode
except OSError as e:
gt_logger.gt_log_err(str(e))
ret = -1
return _stdout, _stderr, ret
def run(self, format=None):
"""! Runs hook after command is formated with in-place {tags}
@format Pass format dictionary to replace hook {tags} with real values
@param format Used to format string with cmd, notation used is e.g: {build_name}
"""
gt_logger.gt_log("hook '%s' execution"% self.name)
cmd = self.format_before_run(self.cmd, format)
gt_logger.gt_log_tab("hook command: %s"% cmd)
(_stdout, _stderr, ret) = self.run_cli_process(cmd)
if _stdout:
print(_stdout)
if ret:
gt_logger.gt_log_err("hook exited with error: %d, dumping stderr..."% ret)
print(_stderr)
return ret
@staticmethod
def format_before_run(cmd, format, verbose=False):
if format:
# We will expand first
cmd_expand = GreenteaCliTestHook.expand_parameters(cmd, format)
if cmd_expand:
cmd = cmd_expand
if verbose:
gt_logger.gt_log_tab("hook expanded: %s"% cmd)
cmd = cmd.format(**format)
if verbose:
gt_logger.gt_log_tab("hook formated: %s"% cmd)
return cmd
@staticmethod
def expand_parameters(expr, expandables, delimiter=' '):
"""! Expands lists for multiple parameters in hook command
@param expr Expression to expand
@param expandables Dictionary of token: list_to_expand See details for more info
@param delimiter Delimiter used to combine expanded strings, space by default
@details
test_name_list = ['mbed-drivers-test-basic', 'mbed-drivers-test-hello', 'mbed-drivers-test-time_us']
build_path_list = ['./build/frdm-k64f-gcc', './build/frdm-k64f-armcc']
expandables = {
"{test_name_list}": test_name_list,
"{build_path_list}": build_path_list
}
expr = "lcov --gcov-tool arm-none-eabi-gcov [-a {build_path_list}/test/{test_name_list}.info] --output-file result.info"
'expr' expression [-a {build_path_list}/test/{test_name_list}.info] will expand to:
[
"-a ./build/frdm-k64f-gcc/test/mbed-drivers-test-basic.info",
"-a ./build/frdm-k64f-armcc/test/mbed-drivers-test-basic.info",
"-a ./build/frdm-k64f-gcc/test/mbed-drivers-test-hello.info",
"-a ./build/frdm-k64f-armcc/test/mbed-drivers-test-hello.info",
"-a ./build/frdm-k64f-gcc/test/mbed-drivers-test-time_us.info",
"-a ./build/frdm-k64f-armcc/test/mbed-drivers-test-time_us.info"
]
"""
result = None
if expandables:
expansion_result = []
m = re.search(r'\[.*?\]', expr)
if m:
expr_str_orig = m.group(0)
expr_str_base = m.group(0)[1:-1]
expr_str_list = [expr_str_base]
for token in expandables:
# We will expand only values which are lists (of strings)
if type(expandables[token]) is list:
# Use tokens with curly braces (Python string format like)
format_token = '{' + token + '}'
for expr_str in expr_str_list:
if format_token in expr_str:
patterns = expandables[token]
for pattern in patterns:
s = expr_str
s = s.replace(format_token, pattern)
expr_str_list.append(s)
# Nothing to extend/change in this string
if not any('{' + p + '}' in s for p in expandables.keys() if type(expandables[p]) is list):
expansion_result.append(s)
expansion_result.sort()
result = expr.replace(expr_str_orig, delimiter.join(expansion_result))
return result
class LcovHook(GreenteaCliTestHook):
"""! Class used to define a LCOV hook
"""
lcov_hooks = {
"hooks": {
"hook_test_end": "$lcov --gcov-tool gcov --capture --directory ./build --output-file {build_path}/{test_name}.info",
"hook_post_all_test_end": "$lcov --gcov-tool gcov [(-a << {build_path}/{test_name_list}.info>>)] --output-file result.info"
}
}
def __init__(self, name, cmd):
GreenteaCliTestHook.__init__(self, name, cmd)
@staticmethod
def format_before_run(cmd, format, verbose=False):
if format:
# We will expand first
cmd_expand = GreenteaCliTestHook.expand_parameters(cmd, format)
if cmd_expand:
cmd = cmd_expand
if verbose:
gt_logger.gt_log_tab("hook expanded: %s"% cmd)
cmd = cmd.format(**format)
cmd = LcovHook.check_if_file_exists_or_is_empty(cmd)
if verbose:
gt_logger.gt_log_tab("hook formated: %s"% cmd)
return cmd
@staticmethod
def check_if_file_exists_or_is_empty(expr):
"""! Check Expression for specific characters in hook command
@param expr Expression to check
@details
expr = "lcov --gcov-tool gcov (-a <<{build_path}/test/{test_name}.info>>) --output-file result.info"
where:
(...) -> specify part to check
<<...>> -> specify part which is a path to a file
'expr' expression (-a <<{build_path}/test/{test_name}.info>>) will be either:
"-a ./build/frdm-k64f-gcc/test/test_name.info"
or will be removed from command
It is also possible to use it in combination with expand_parameters:
expr = "lcov --gcov-tool gcov [(-a <<./build/{yotta_target_name}/{test_name_list}.info>>)] --output-file result.info"
"""
result = expr
expr_strs_orig = re.findall(r'\(.*?\)', expr)
for expr_str_orig in expr_strs_orig:
expr_str_base = expr_str_orig[1:-1]
result = result.replace(expr_str_orig, expr_str_base)
m = re.search(r'\<<.*?\>>', expr_str_base)
if m:
expr_str_path = m.group(0)[2:-2]
# Remove option if file not exists OR if file exists but empty
if not os.path.exists(expr_str_path):
result = result.replace(expr_str_base, '')
elif os.path.getsize(expr_str_path) == 0:
result = result.replace(expr_str_base, '')
# Remove path limiter
result = result.replace('<<', '')
result = result.replace('>>', '')
return result
class GreenteaHooks(object):
"""! Class used to store all hooks
@details Hooks command starts with '$' dollar sign
"""
HOOKS = {}
def __init__(self, path_to_hooks):
"""! Opens JSON file with
"""
try:
if path_to_hooks == 'lcov':
hooks = LcovHook.lcov_hooks
for hook in hooks['hooks']:
hook_name = hook
hook_expression = hooks['hooks'][hook]
self.HOOKS[hook_name] = LcovHook(hook_name, hook_expression[1:])
else:
with open(path_to_hooks, 'r') as data_file:
hooks = json.load(data_file)
if 'hooks' in hooks:
for hook in hooks['hooks']:
hook_name = hook
hook_expression = hooks['hooks'][hook]
# This is a command line hook
if hook_expression.startswith('$'):
self.HOOKS[hook_name] = GreenteaCliTestHook(hook_name, hook_expression[1:])
except IOError as e:
print(str(e))
self.HOOKS = None
def is_hooked_to(self, hook_name):
return hook_name in self.HOOKS
def run_hook(self, hook_name, format):
if hook_name in self.HOOKS:
return self.HOOKS[hook_name].run(format)
def run_hook_ext(self, hook_name, format):
if self.is_hooked_to(hook_name):
# We can execute this test hook just after all tests are finished ('hook_post_test_end')
self.run_hook(hook_name, format)