blob: 226998865842ff04404f917a8e370ddbac5595b8 [file] [log] [blame]
Anas Nashif3ae52622019-04-06 09:08:09 -04001# SPDX-License-Identifier: Apache-2.0
Anas Nashif576be982017-12-23 20:20:27 -05002import re
YouhuaX Zhu965c8b92020-10-22 09:37:27 +08003import os
4import subprocess
Anas Nashif576be982017-12-23 20:20:27 -05005from collections import OrderedDict
YouhuaX Zhu965c8b92020-10-22 09:37:27 +08006import xml.etree.ElementTree as ET
Anas Nashif576be982017-12-23 20:20:27 -05007
Enjia Mai2c5e2c22021-03-07 14:08:31 +08008result_re = re.compile(".*(PASS|FAIL|SKIP) - (test_)?(.*) in")
Andy Rossaa2b8a12019-03-06 09:45:30 -08009
Anas Nashif576be982017-12-23 20:20:27 -050010class Harness:
Anas Nashiff29087e2019-01-25 09:37:38 -050011 GCOV_START = "GCOV_COVERAGE_DUMP_START"
12 GCOV_END = "GCOV_COVERAGE_DUMP_END"
Andrew Boie81ef42d2019-07-16 15:29:46 -070013 FAULT = "ZEPHYR FATAL ERROR"
Anas Nashif83fc06a2019-06-22 11:04:10 -040014 RUN_PASSED = "PROJECT EXECUTION SUCCESSFUL"
15 RUN_FAILED = "PROJECT EXECUTION FAILED"
Anas Nashiff29087e2019-01-25 09:37:38 -050016
Anas Nashif576be982017-12-23 20:20:27 -050017 def __init__(self):
18 self.state = None
19 self.type = None
20 self.regex = []
21 self.matches = OrderedDict()
22 self.ordered = True
23 self.repeat = 1
Anas Nashife0a6a0b2018-02-15 20:07:24 -060024 self.tests = {}
Anas Nashif61e21632018-04-08 13:30:16 -050025 self.id = None
Anas Nashifb20c4842018-06-05 21:36:20 -050026 self.fail_on_fault = True
Anas Nashif77837e82018-08-29 10:05:27 -040027 self.fault = False
Anas Nashiff29087e2019-01-25 09:37:38 -050028 self.capture_coverage = False
Andy Ross5efdd6a2019-06-18 11:57:21 -070029 self.next_pattern = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -040030 self.record = None
31 self.recording = []
32 self.fieldnames = []
Anas Nashif8b425fa2020-08-31 11:50:51 -040033 self.ztest = False
YouhuaX Zhu965c8b92020-10-22 09:37:27 +080034 self.is_pytest = False
Anas Nashif576be982017-12-23 20:20:27 -050035
36 def configure(self, instance):
Anas Nashif83fc06a2019-06-22 11:04:10 -040037 config = instance.testcase.harness_config
38 self.id = instance.testcase.id
39 if "ignore_faults" in instance.testcase.tags:
Anas Nashifb20c4842018-06-05 21:36:20 -050040 self.fail_on_fault = False
41
Anas Nashif576be982017-12-23 20:20:27 -050042 if config:
43 self.type = config.get('type', None)
Ulf Magnusson0d39a102019-09-06 11:13:19 +020044 self.regex = config.get('regex', [])
Anas Nashif576be982017-12-23 20:20:27 -050045 self.repeat = config.get('repeat', 1)
46 self.ordered = config.get('ordered', True)
Anas Nashif83fc06a2019-06-22 11:04:10 -040047 self.record = config.get('record', {})
48
49 def process_test(self, line):
50
51 if self.RUN_PASSED in line:
52 if self.fault:
53 self.state = "failed"
54 else:
55 self.state = "passed"
56
57 if self.RUN_FAILED in line:
58 self.state = "failed"
59
60 if self.fail_on_fault:
61 if self.FAULT == line:
62 self.fault = True
63
64 if self.GCOV_START in line:
65 self.capture_coverage = True
66 elif self.GCOV_END in line:
67 self.capture_coverage = False
Anas Nashif576be982017-12-23 20:20:27 -050068
69class Console(Harness):
70
Andy Rossaa2b8a12019-03-06 09:45:30 -080071 def configure(self, instance):
72 super(Console, self).configure(instance)
73 if self.type == "one_line":
74 self.pattern = re.compile(self.regex[0])
75 elif self.type == "multi_line":
76 self.patterns = []
77 for r in self.regex:
78 self.patterns.append(re.compile(r))
79
Anas Nashif576be982017-12-23 20:20:27 -050080 def handle(self, line):
81 if self.type == "one_line":
Andy Rossaa2b8a12019-03-06 09:45:30 -080082 if self.pattern.search(line):
Anas Nashif576be982017-12-23 20:20:27 -050083 self.state = "passed"
Andy Ross5efdd6a2019-06-18 11:57:21 -070084 elif self.type == "multi_line" and self.ordered:
85 if (self.next_pattern < len(self.patterns) and
86 self.patterns[self.next_pattern].search(line)):
87 self.next_pattern += 1
88 if self.next_pattern >= len(self.patterns):
89 self.state = "passed"
90 elif self.type == "multi_line" and not self.ordered:
Andy Rossaa2b8a12019-03-06 09:45:30 -080091 for i, pattern in enumerate(self.patterns):
92 r = self.regex[i]
Anas Nashif39caff52018-01-24 09:54:08 +053093 if pattern.search(line) and not r in self.matches:
Anas Nashif576be982017-12-23 20:20:27 -050094 self.matches[r] = line
Anas Nashif576be982017-12-23 20:20:27 -050095 if len(self.matches) == len(self.regex):
Ulf Magnussonea4d1d62019-09-02 11:48:44 +020096 self.state = "passed"
Anas Nashiff29087e2019-01-25 09:37:38 -050097
98 if self.fail_on_fault:
Andrew Boie81ef42d2019-07-16 15:29:46 -070099 if self.FAULT in line:
100 self.fault = True
Anas Nashiff29087e2019-01-25 09:37:38 -0500101
102 if self.GCOV_START in line:
103 self.capture_coverage = True
104 elif self.GCOV_END in line:
105 self.capture_coverage = False
Anas Nashif576be982017-12-23 20:20:27 -0500106
Anas Nashif83fc06a2019-06-22 11:04:10 -0400107
108 if self.record:
109 pattern = re.compile(self.record.get("regex", ""))
110 match = pattern.search(line)
111 if match:
112 csv = []
113 if not self.fieldnames:
114 for k,v in match.groupdict().items():
115 self.fieldnames.append(k)
116
117 for k,v in match.groupdict().items():
118 csv.append(v.strip())
119 self.recording.append(csv)
120
Anas Nashifa983a992021-03-16 10:05:37 -0400121 self.process_test(line)
122
Anas Nashiff16e92c2019-03-31 16:58:12 -0400123 if self.state == "passed":
124 self.tests[self.id] = "PASS"
125 else:
126 self.tests[self.id] = "FAIL"
127
YouhuaX Zhu965c8b92020-10-22 09:37:27 +0800128class Pytest(Harness):
129 def configure(self, instance):
130 super(Pytest, self).configure(instance)
131 self.running_dir = instance.build_dir
132 self.source_dir = instance.testcase.source_dir
133 self.pytest_root = 'pytest'
134 self.is_pytest = True
135 config = instance.testcase.harness_config
136
137 if config:
138 self.pytest_root = config.get('pytest_root', 'pytest')
139
140 def handle(self, line):
141 ''' Test cases that make use of pytest more care about results given
142 by pytest tool which is called in pytest_run(), so works of this
143 handle is trying to give a PASS or FAIL to avoid timeout, nothing
144 is writen into handler.log
145 '''
146 self.state = "passed"
147 self.tests[self.id] = "PASS"
148
149 def pytest_run(self, log_file):
150 ''' To keep artifacts of pytest in self.running_dir, pass this directory
151 by "--cmdopt". On pytest end, add a command line option and provide
152 the cmdopt through a fixture function
153 If pytest harness report failure, twister will direct user to see
154 handler.log, this method writes test result in handler.log
155 '''
156 cmd = [
157 'pytest',
158 '-s',
159 os.path.join(self.source_dir, self.pytest_root),
160 '--cmdopt',
161 self.running_dir,
162 '--junit-xml',
163 os.path.join(self.running_dir, 'report.xml'),
164 '-q'
165 ]
166
167 log = open(log_file, "a")
168 outs = []
169 errs = []
170
171 with subprocess.Popen(cmd,
172 stdout = subprocess.PIPE,
173 stderr = subprocess.PIPE) as proc:
174 try:
175 outs, errs = proc.communicate()
176 tree = ET.parse(os.path.join(self.running_dir, "report.xml"))
177 root = tree.getroot()
178 for child in root:
179 if child.tag == 'testsuite':
180 if child.attrib['failures'] != '0':
181 self.state = "failed"
182 elif child.attrib['skipped'] != '0':
183 self.state = "skipped"
184 elif child.attrib['errors'] != '0':
185 self.state = "errors"
186 else:
187 self.state = "passed"
188 except subprocess.TimeoutExpired:
189 proc.kill()
190 self.state = "failed"
191 except ET.ParseError:
192 self.state = "failed"
193 except IOError:
194 log.write("Can't access report.xml\n")
195 self.state = "failed"
196
197 if self.state == "passed":
198 self.tests[self.id] = "PASS"
199 log.write("Pytest cases passed\n")
200 elif self.state == "skipped":
201 self.tests[self.id] = "SKIP"
202 log.write("Pytest cases skipped\n")
203 log.write("Please refer report.xml for detail")
204 else:
205 self.tests[self.id] = "FAIL"
206 log.write("Pytest cases failed\n")
207
208 log.write("\nOutput from pytest:\n")
209 log.write(outs.decode('UTF-8'))
210 log.write(errs.decode('UTF-8'))
211 log.close()
212
213
Anas Nashif576be982017-12-23 20:20:27 -0500214class Test(Harness):
215 RUN_PASSED = "PROJECT EXECUTION SUCCESSFUL"
216 RUN_FAILED = "PROJECT EXECUTION FAILED"
217
218 def handle(self, line):
Andy Rossaa2b8a12019-03-06 09:45:30 -0800219 match = result_re.match(line)
Anas Nashifa4dd49b2019-12-04 16:48:49 -0500220 if match and match.group(2):
Anas Nashif61e21632018-04-08 13:30:16 -0500221 name = "{}.{}".format(self.id, match.group(3))
222 self.tests[name] = match.group(1)
Anas Nashif8b425fa2020-08-31 11:50:51 -0400223 self.ztest = True
Anas Nashife0a6a0b2018-02-15 20:07:24 -0600224
Maciej Perkowskie37d12e2021-03-17 10:34:11 +0100225 self.process_test(line)
Anas Nashifb20c4842018-06-05 21:36:20 -0500226
Anas Nashif8b425fa2020-08-31 11:50:51 -0400227 if not self.ztest and self.state:
228 if self.state == "passed":
229 self.tests[self.id] = "PASS"
230 else:
231 self.tests[self.id] = "FAIL"
Anas Nashifadb6a892020-06-09 09:37:01 -0400232
Anas Nashifce8c12e2020-05-21 09:11:40 -0400233
234class Ztest(Test):
235 pass