Skip to content

Commit 50767a6

Browse files
committed
fix: prevent multi-stage tests from passing when earlier stages fail
1 parent 5a9f318 commit 50767a6

File tree

11 files changed

+1492
-29
lines changed

11 files changed

+1492
-29
lines changed

pytest-embedded-idf/pytest_embedded_idf/unity_tester.py

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -262,11 +262,15 @@ def _hard_reset(self) -> None:
262262
self._ignore_first_ready_pattern = True
263263
pass
264264

265-
def _get_ready(self, timeout: float = 30) -> None:
265+
def _get_ready(self, timeout: float = 30, *, return_before: bool = False) -> bytes | None:
266266
if self._ignore_first_ready_pattern:
267267
self._ignore_first_ready_pattern = False
268268
else:
269269
self.expect_exact(READY_PATTERN_LIST, timeout=timeout)
270+
if return_before:
271+
return self.pexpect_proc.before
272+
273+
return None
270274

271275
@property
272276
def test_menu(self) -> list[UnittestMenuCase]:
@@ -301,6 +305,11 @@ def _analyze_test_case_result(
301305
self._add_test_case_to_suite(attrs)
302306
return
303307

308+
attrs = self._read_result_and_parse_attrs(case, start_time, timeout)
309+
310+
self._add_test_case_to_suite(attrs)
311+
312+
def _read_result_and_parse_attrs(self, case: UnittestMenuCase, start_time: float, timeout: float) -> dict:
304313
log = ''
305314
try:
306315
remaining_timeout = timeout - (time.perf_counter() - start_time)
@@ -311,16 +320,39 @@ def _analyze_test_case_result(
311320
pass
312321
else: # result block exists
313322
log = remove_asci_color_code(self.pexpect_proc.before)
314-
finally:
315-
attrs = _parse_unity_test_output(log, case.name, self.pexpect_proc.buffer_debug_str)
316-
attrs.update(
317-
{
318-
'app_path': self.app.app_path,
319-
'time': round(time.perf_counter() - start_time, 3),
320-
}
321-
)
322323

323-
self._add_test_case_to_suite(attrs)
324+
attrs = _parse_unity_test_output(log, case.name, self.pexpect_proc.buffer_debug_str)
325+
attrs.update(
326+
{
327+
'app_path': self.app.app_path,
328+
'time': round(time.perf_counter() - start_time, 3),
329+
}
330+
)
331+
return attrs
332+
333+
def _prepare_and_start_case(self, case: UnittestMenuCase, reset: bool, timeout: float) -> float:
334+
if reset:
335+
self._hard_reset()
336+
337+
_start_at = time.perf_counter()
338+
self._get_ready(timeout)
339+
self.confirm_write(case.index, expect_str=f'Running {case.name}...')
340+
return _start_at
341+
342+
def _squash_failed_subcases(self, failed_subcases: list[dict], start_time: float) -> dict:
343+
squashed_attrs = failed_subcases[0].copy()
344+
if len(failed_subcases) > 1:
345+
for key in ('stdout', 'message'):
346+
if key in squashed_attrs:
347+
squashed_attrs[key] = '\n---\n'.join(f.get(key, '') for f in failed_subcases)
348+
349+
squashed_attrs.update(
350+
{
351+
'app_path': self.app.app_path,
352+
'time': round(time.perf_counter() - start_time, 3),
353+
}
354+
)
355+
return squashed_attrs
324356

325357
def _run_normal_case(
326358
self,
@@ -345,12 +377,7 @@ def _run_normal_case(
345377
return
346378

347379
try:
348-
if reset:
349-
self._hard_reset()
350-
351-
_start_at = time.perf_counter()
352-
self._get_ready(timeout)
353-
self.confirm_write(case.index, expect_str=f'Running {case.name}...')
380+
_start_at = self._prepare_and_start_case(case, reset, timeout)
354381
except Exception as e:
355382
self._analyze_test_case_result(case, e)
356383
else:
@@ -379,19 +406,22 @@ def _run_multi_stage_case(
379406
return
380407

381408
try:
382-
if reset:
383-
self._hard_reset()
384-
385-
_start_at = time.perf_counter()
386-
self._get_ready(timeout)
387-
self.confirm_write(case.index, expect_str=f'Running {case.name}...')
409+
_start_at = self._prepare_and_start_case(case, reset, timeout)
388410
except Exception as e:
389411
self._analyze_test_case_result(case, e)
390412
else:
413+
failed_subcases = []
391414
try:
392415
for sub_case in case.subcases:
393416
if sub_case != case.subcases[0]:
394-
self._get_ready(timeout)
417+
ready_before = self._get_ready(timeout, return_before=True)
418+
if ready_before and UNITY_SUMMARY_LINE_REGEX.search(ready_before):
419+
attrs = _parse_unity_test_output(
420+
remove_asci_color_code(ready_before), case.name, self.pexpect_proc.buffer_debug_str
421+
)
422+
if attrs['result'] == 'FAIL':
423+
failed_subcases.append(attrs)
424+
395425
self.confirm_write(case.index, expect_str=f'Running {case.name}...')
396426

397427
self.write(str(sub_case['index']))
@@ -400,7 +430,15 @@ def _run_multi_stage_case(
400430
# We'll stop sending commands and let the result recorder handle the failure.
401431
pass
402432
finally:
403-
self._analyze_test_case_result(case, None, start_time=_start_at, timeout=timeout)
433+
attrs = self._read_result_and_parse_attrs(case, _start_at, timeout)
434+
435+
if attrs['result'] == 'FAIL':
436+
failed_subcases.append(attrs)
437+
438+
if failed_subcases:
439+
self._add_test_case_to_suite(self._squash_failed_subcases(failed_subcases, _start_at))
440+
else:
441+
self._add_test_case_to_suite(attrs)
404442

405443
def run_single_board_case(self, name: str, reset: bool = False, timeout: float = 30) -> None:
406444
for case in self.test_menu:

pytest-embedded-qemu/tests/test_qemu.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -201,15 +201,15 @@ def test_qemu_use_idf_mixin_methods(testdir):
201201
import pytest
202202
203203
def test_qemu_use_idf_mixin_methods(dut):
204-
dut.run_all_single_board_cases(timeout=10)
204+
dut.run_all_single_board_cases(timeout=3)
205205
""")
206206

207207
result = testdir.runpytest(
208208
'-s',
209209
'--embedded-services',
210210
'idf,qemu',
211211
'--app-path',
212-
f'{os.path.join(testdir.tmpdir, "unit_test_app_esp32")}',
212+
f'{os.path.join(testdir.tmpdir, "unit_test_app_qemu")}',
213213
'--junitxml',
214214
'report.xml',
215215
)
@@ -219,6 +219,6 @@ def test_qemu_use_idf_mixin_methods(dut):
219219
junit_report = ET.parse('report.xml').getroot()[0]
220220

221221
assert junit_report.attrib['errors'] == '0'
222-
assert junit_report.attrib['failures'] == '2'
223-
assert junit_report.attrib['skipped'] == '2'
224-
assert junit_report.attrib['tests'] == '1'
222+
assert junit_report.attrib['failures'] == '5'
223+
assert junit_report.attrib['skipped'] == '0'
224+
assert junit_report.attrib['tests'] == '3'
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
cmake_minimum_required(VERSION 3.16)
2+
3+
set(EXTRA_COMPONENT_DIRS
4+
"$ENV{IDF_PATH}/tools/test_apps/components")
5+
6+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
7+
project(case_tester_example)
Binary file not shown.
139 KB
Binary file not shown.

0 commit comments

Comments
 (0)