2019-04-06 09:08:09 -04:00
|
|
|
# SPDX-License-Identifier: Apache-2.0
|
2017-12-23 20:20:27 -05:00
|
|
|
import re
|
2020-10-22 09:37:27 +08:00
|
|
|
import os
|
|
|
|
import subprocess
|
2017-12-23 20:20:27 -05:00
|
|
|
from collections import OrderedDict
|
2020-10-22 09:37:27 +08:00
|
|
|
import xml.etree.ElementTree as ET
|
2017-12-23 20:20:27 -05:00
|
|
|
|
2021-03-07 14:08:31 +08:00
|
|
|
result_re = re.compile(".*(PASS|FAIL|SKIP) - (test_)?(.*) in")
|
2019-03-06 09:45:30 -08:00
|
|
|
|
2017-12-23 20:20:27 -05:00
|
|
|
class Harness:
|
2019-01-25 09:37:38 -05:00
|
|
|
GCOV_START = "GCOV_COVERAGE_DUMP_START"
|
|
|
|
GCOV_END = "GCOV_COVERAGE_DUMP_END"
|
2019-07-16 15:29:46 -07:00
|
|
|
FAULT = "ZEPHYR FATAL ERROR"
|
2019-06-22 11:04:10 -04:00
|
|
|
RUN_PASSED = "PROJECT EXECUTION SUCCESSFUL"
|
|
|
|
RUN_FAILED = "PROJECT EXECUTION FAILED"
|
2022-04-07 16:11:01 -04:00
|
|
|
run_id_pattern = r"RunID: (?P<run_id>.*)"
|
|
|
|
|
2019-01-25 09:37:38 -05:00
|
|
|
|
2017-12-23 20:20:27 -05:00
|
|
|
def __init__(self):
|
|
|
|
self.state = None
|
|
|
|
self.type = None
|
|
|
|
self.regex = []
|
|
|
|
self.matches = OrderedDict()
|
|
|
|
self.ordered = True
|
|
|
|
self.repeat = 1
|
2018-02-15 20:07:24 -06:00
|
|
|
self.tests = {}
|
2018-04-08 13:30:16 -05:00
|
|
|
self.id = None
|
2018-06-05 21:36:20 -05:00
|
|
|
self.fail_on_fault = True
|
2018-08-29 10:05:27 -04:00
|
|
|
self.fault = False
|
2019-01-25 09:37:38 -05:00
|
|
|
self.capture_coverage = False
|
2019-06-18 11:57:21 -07:00
|
|
|
self.next_pattern = 0
|
2019-06-22 11:04:10 -04:00
|
|
|
self.record = None
|
|
|
|
self.recording = []
|
|
|
|
self.fieldnames = []
|
2020-08-31 11:50:51 -04:00
|
|
|
self.ztest = False
|
2020-10-22 09:37:27 +08:00
|
|
|
self.is_pytest = False
|
2022-02-02 22:57:43 +01:00
|
|
|
self.detected_suite_names = []
|
2022-04-07 16:11:01 -04:00
|
|
|
self.run_id = None
|
|
|
|
self.matched_run_id = False
|
|
|
|
self.run_id_exists = False
|
2017-12-23 20:20:27 -05:00
|
|
|
|
|
|
|
def configure(self, instance):
|
2019-06-22 11:04:10 -04:00
|
|
|
config = instance.testcase.harness_config
|
|
|
|
self.id = instance.testcase.id
|
2022-04-07 16:11:01 -04:00
|
|
|
self.run_id = instance.run_id
|
2019-06-22 11:04:10 -04:00
|
|
|
if "ignore_faults" in instance.testcase.tags:
|
2018-06-05 21:36:20 -05:00
|
|
|
self.fail_on_fault = False
|
|
|
|
|
2017-12-23 20:20:27 -05:00
|
|
|
if config:
|
|
|
|
self.type = config.get('type', None)
|
2019-09-06 11:13:19 +02:00
|
|
|
self.regex = config.get('regex', [])
|
2017-12-23 20:20:27 -05:00
|
|
|
self.repeat = config.get('repeat', 1)
|
|
|
|
self.ordered = config.get('ordered', True)
|
2019-06-22 11:04:10 -04:00
|
|
|
self.record = config.get('record', {})
|
|
|
|
|
|
|
|
def process_test(self, line):
|
|
|
|
|
2022-04-07 16:11:01 -04:00
|
|
|
runid_match = re.search(self.run_id_pattern, line)
|
|
|
|
if runid_match:
|
|
|
|
run_id = runid_match.group("run_id")
|
|
|
|
self.run_id_exists = True
|
|
|
|
if run_id == str(self.run_id):
|
|
|
|
self.matched_run_id = True
|
|
|
|
|
2019-06-22 11:04:10 -04:00
|
|
|
if self.RUN_PASSED in line:
|
|
|
|
if self.fault:
|
|
|
|
self.state = "failed"
|
|
|
|
else:
|
|
|
|
self.state = "passed"
|
|
|
|
|
|
|
|
if self.RUN_FAILED in line:
|
|
|
|
self.state = "failed"
|
|
|
|
|
|
|
|
if self.fail_on_fault:
|
|
|
|
if self.FAULT == line:
|
|
|
|
self.fault = True
|
|
|
|
|
|
|
|
if self.GCOV_START in line:
|
|
|
|
self.capture_coverage = True
|
|
|
|
elif self.GCOV_END in line:
|
|
|
|
self.capture_coverage = False
|
2017-12-23 20:20:27 -05:00
|
|
|
|
|
|
|
class Console(Harness):
|
|
|
|
|
2019-03-06 09:45:30 -08:00
|
|
|
def configure(self, instance):
|
|
|
|
super(Console, self).configure(instance)
|
|
|
|
if self.type == "one_line":
|
|
|
|
self.pattern = re.compile(self.regex[0])
|
|
|
|
elif self.type == "multi_line":
|
|
|
|
self.patterns = []
|
|
|
|
for r in self.regex:
|
|
|
|
self.patterns.append(re.compile(r))
|
|
|
|
|
2017-12-23 20:20:27 -05:00
|
|
|
def handle(self, line):
|
|
|
|
if self.type == "one_line":
|
2019-03-06 09:45:30 -08:00
|
|
|
if self.pattern.search(line):
|
2017-12-23 20:20:27 -05:00
|
|
|
self.state = "passed"
|
2019-06-18 11:57:21 -07:00
|
|
|
elif self.type == "multi_line" and self.ordered:
|
|
|
|
if (self.next_pattern < len(self.patterns) and
|
|
|
|
self.patterns[self.next_pattern].search(line)):
|
|
|
|
self.next_pattern += 1
|
|
|
|
if self.next_pattern >= len(self.patterns):
|
|
|
|
self.state = "passed"
|
|
|
|
elif self.type == "multi_line" and not self.ordered:
|
2019-03-06 09:45:30 -08:00
|
|
|
for i, pattern in enumerate(self.patterns):
|
|
|
|
r = self.regex[i]
|
2018-01-24 09:54:08 +05:30
|
|
|
if pattern.search(line) and not r in self.matches:
|
2017-12-23 20:20:27 -05:00
|
|
|
self.matches[r] = line
|
|
|
|
if len(self.matches) == len(self.regex):
|
2019-09-02 11:48:44 +02:00
|
|
|
self.state = "passed"
|
2019-01-25 09:37:38 -05:00
|
|
|
|
|
|
|
if self.fail_on_fault:
|
2019-07-16 15:29:46 -07:00
|
|
|
if self.FAULT in line:
|
|
|
|
self.fault = True
|
2019-01-25 09:37:38 -05:00
|
|
|
|
|
|
|
if self.GCOV_START in line:
|
|
|
|
self.capture_coverage = True
|
|
|
|
elif self.GCOV_END in line:
|
|
|
|
self.capture_coverage = False
|
2017-12-23 20:20:27 -05:00
|
|
|
|
2019-06-22 11:04:10 -04:00
|
|
|
|
|
|
|
if self.record:
|
|
|
|
pattern = re.compile(self.record.get("regex", ""))
|
|
|
|
match = pattern.search(line)
|
|
|
|
if match:
|
|
|
|
csv = []
|
|
|
|
if not self.fieldnames:
|
|
|
|
for k,v in match.groupdict().items():
|
|
|
|
self.fieldnames.append(k)
|
|
|
|
|
|
|
|
for k,v in match.groupdict().items():
|
|
|
|
csv.append(v.strip())
|
|
|
|
self.recording.append(csv)
|
|
|
|
|
2021-03-16 10:05:37 -04:00
|
|
|
self.process_test(line)
|
|
|
|
|
2019-03-31 16:58:12 -04:00
|
|
|
if self.state == "passed":
|
|
|
|
self.tests[self.id] = "PASS"
|
|
|
|
else:
|
|
|
|
self.tests[self.id] = "FAIL"
|
|
|
|
|
2020-10-22 09:37:27 +08:00
|
|
|
class Pytest(Harness):
|
|
|
|
def configure(self, instance):
|
|
|
|
super(Pytest, self).configure(instance)
|
|
|
|
self.running_dir = instance.build_dir
|
|
|
|
self.source_dir = instance.testcase.source_dir
|
|
|
|
self.pytest_root = 'pytest'
|
2022-02-24 12:45:29 +01:00
|
|
|
self.pytest_args = []
|
2020-10-22 09:37:27 +08:00
|
|
|
self.is_pytest = True
|
|
|
|
config = instance.testcase.harness_config
|
|
|
|
|
|
|
|
if config:
|
|
|
|
self.pytest_root = config.get('pytest_root', 'pytest')
|
2022-02-24 12:45:29 +01:00
|
|
|
self.pytest_args = config.get('pytest_args', [])
|
2020-10-22 09:37:27 +08:00
|
|
|
|
|
|
|
def handle(self, line):
|
|
|
|
''' Test cases that make use of pytest more care about results given
|
|
|
|
by pytest tool which is called in pytest_run(), so works of this
|
|
|
|
handle is trying to give a PASS or FAIL to avoid timeout, nothing
|
|
|
|
is writen into handler.log
|
|
|
|
'''
|
|
|
|
self.state = "passed"
|
|
|
|
self.tests[self.id] = "PASS"
|
|
|
|
|
|
|
|
def pytest_run(self, log_file):
|
|
|
|
''' To keep artifacts of pytest in self.running_dir, pass this directory
|
|
|
|
by "--cmdopt". On pytest end, add a command line option and provide
|
|
|
|
the cmdopt through a fixture function
|
|
|
|
If pytest harness report failure, twister will direct user to see
|
|
|
|
handler.log, this method writes test result in handler.log
|
|
|
|
'''
|
|
|
|
cmd = [
|
|
|
|
'pytest',
|
|
|
|
'-s',
|
|
|
|
os.path.join(self.source_dir, self.pytest_root),
|
|
|
|
'--cmdopt',
|
|
|
|
self.running_dir,
|
|
|
|
'--junit-xml',
|
|
|
|
os.path.join(self.running_dir, 'report.xml'),
|
|
|
|
'-q'
|
|
|
|
]
|
|
|
|
|
2022-02-24 12:45:29 +01:00
|
|
|
for arg in self.pytest_args:
|
|
|
|
cmd.append(arg)
|
|
|
|
|
2020-10-22 09:37:27 +08:00
|
|
|
log = open(log_file, "a")
|
|
|
|
outs = []
|
|
|
|
errs = []
|
|
|
|
|
|
|
|
with subprocess.Popen(cmd,
|
|
|
|
stdout = subprocess.PIPE,
|
|
|
|
stderr = subprocess.PIPE) as proc:
|
|
|
|
try:
|
|
|
|
outs, errs = proc.communicate()
|
|
|
|
tree = ET.parse(os.path.join(self.running_dir, "report.xml"))
|
|
|
|
root = tree.getroot()
|
|
|
|
for child in root:
|
|
|
|
if child.tag == 'testsuite':
|
|
|
|
if child.attrib['failures'] != '0':
|
|
|
|
self.state = "failed"
|
|
|
|
elif child.attrib['skipped'] != '0':
|
|
|
|
self.state = "skipped"
|
|
|
|
elif child.attrib['errors'] != '0':
|
|
|
|
self.state = "errors"
|
|
|
|
else:
|
|
|
|
self.state = "passed"
|
|
|
|
except subprocess.TimeoutExpired:
|
|
|
|
proc.kill()
|
|
|
|
self.state = "failed"
|
|
|
|
except ET.ParseError:
|
|
|
|
self.state = "failed"
|
|
|
|
except IOError:
|
|
|
|
log.write("Can't access report.xml\n")
|
|
|
|
self.state = "failed"
|
|
|
|
|
|
|
|
if self.state == "passed":
|
|
|
|
self.tests[self.id] = "PASS"
|
|
|
|
log.write("Pytest cases passed\n")
|
|
|
|
elif self.state == "skipped":
|
|
|
|
self.tests[self.id] = "SKIP"
|
|
|
|
log.write("Pytest cases skipped\n")
|
|
|
|
log.write("Please refer report.xml for detail")
|
|
|
|
else:
|
|
|
|
self.tests[self.id] = "FAIL"
|
|
|
|
log.write("Pytest cases failed\n")
|
|
|
|
|
|
|
|
log.write("\nOutput from pytest:\n")
|
|
|
|
log.write(outs.decode('UTF-8'))
|
|
|
|
log.write(errs.decode('UTF-8'))
|
|
|
|
log.close()
|
|
|
|
|
|
|
|
|
2017-12-23 20:20:27 -05:00
|
|
|
class Test(Harness):
|
|
|
|
RUN_PASSED = "PROJECT EXECUTION SUCCESSFUL"
|
|
|
|
RUN_FAILED = "PROJECT EXECUTION FAILED"
|
2022-02-02 22:57:43 +01:00
|
|
|
test_suite_start_pattern = r"Running test suite (?P<suite_name>.*)"
|
2017-12-23 20:20:27 -05:00
|
|
|
|
|
|
|
def handle(self, line):
|
2022-02-02 22:57:43 +01:00
|
|
|
test_suite_match = re.search(self.test_suite_start_pattern, line)
|
|
|
|
if test_suite_match:
|
|
|
|
suite_name = test_suite_match.group("suite_name")
|
|
|
|
self.detected_suite_names.append(suite_name)
|
|
|
|
|
2019-03-06 09:45:30 -08:00
|
|
|
match = result_re.match(line)
|
2019-12-04 16:48:49 -05:00
|
|
|
if match and match.group(2):
|
2018-04-08 13:30:16 -05:00
|
|
|
name = "{}.{}".format(self.id, match.group(3))
|
|
|
|
self.tests[name] = match.group(1)
|
2020-08-31 11:50:51 -04:00
|
|
|
self.ztest = True
|
2018-02-15 20:07:24 -06:00
|
|
|
|
2021-03-17 10:34:11 +01:00
|
|
|
self.process_test(line)
|
2018-06-05 21:36:20 -05:00
|
|
|
|
2020-08-31 11:50:51 -04:00
|
|
|
if not self.ztest and self.state:
|
|
|
|
if self.state == "passed":
|
|
|
|
self.tests[self.id] = "PASS"
|
|
|
|
else:
|
|
|
|
self.tests[self.id] = "FAIL"
|
2020-06-09 09:37:01 -04:00
|
|
|
|
2020-05-21 09:11:40 -04:00
|
|
|
|
|
|
|
class Ztest(Test):
|
|
|
|
pass
|