@@ -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 :
0 commit comments