2020import sys
2121import tempfile
2222import threading
23+ import time
2324import traceback
2425from typing import Iterator , List , Optional , Text , Type , TYPE_CHECKING
2526
2627from openhtf import util
2728from openhtf .core import base_plugs
2829from openhtf .core import diagnoses_lib
2930from openhtf .core import phase_branches
31+ from openhtf .core import phase_child_runner
3032from openhtf .core import phase_collections
3133from openhtf .core import phase_descriptor
3234from openhtf .core import phase_executor
3335from openhtf .core import phase_group
3436from openhtf .core import phase_nodes
37+ from openhtf .core import test_descriptor
3538from openhtf .core import test_record
3639from openhtf .core import test_state
40+ from openhtf .core .results_collector import ResultsCollector
3741from openhtf .plugs import PlugManager
3842from openhtf .util import configuration
3943from openhtf .util import threads
@@ -383,11 +387,48 @@ def _execute_checkpoint(self, checkpoint: phase_branches.Checkpoint,
383387 self .phase_executor .skip_checkpoint (checkpoint , subtest_rec )
384388 return _ExecutorReturn .CONTINUE
385389
386- outcome = self .phase_executor .evaluate_checkpoint (checkpoint , subtest_rec )
390+ def _execute_children (self , child_runner : phase_child_runner .ChildRunnerPhase ,
391+ subtest_rec : Optional [test_record .SubtestRecord ],
392+ in_teardown : bool ) -> _ExecutorReturn :
393+
394+ tests = self ._test_options .child_tests # type: list[test_descriptor.Test]
395+ if len (tests ) == 0 :
396+ self .logger .warning ("There are no child tests to execute." )
397+ return _ExecutorReturn .CONTINUE
398+
399+ results = ResultsCollector ()
400+
401+ def test_runner (child_test : test_descriptor .Test ):
402+ res = child_test .execute ()
403+ self .logger .debug (f"test uid { child_test .uid } finished, with result: { res } " )
404+
405+ execution_threads = []
406+ for test in tests :
407+ test .add_output_callbacks (results .on_test_completed )
408+ thread = threading .Thread (target = test_runner , args = (test ,))
409+ execution_threads .append (thread )
410+ thread .start ()
411+
412+ self .logger .debug ('Waiting for all tests to complete.' )
413+ while any ([not t .state .is_finalized if t .state is not None else False for t in tests ]):
414+ time .sleep (0.1 )
415+
416+ for thread in execution_threads :
417+ thread .join ()
418+
419+ self .logger .debug ('All child tests complete.' )
420+
421+ outcome = phase_executor .PhaseExecutionOutcome (phase_descriptor .PhaseResult .CONTINUE )
422+
423+ if any (res .outcome != test_record .Outcome .PASS for res in results .get_results ()):
424+ if self ._test_options .stop_on_first_failure :
425+ outcome = phase_executor .PhaseExecutionOutcome (
426+ phase_descriptor .PhaseResult .STOP )
427+
387428 if outcome .is_terminal :
388429 if not self ._last_outcome :
389430 self ._last_outcome = outcome
390- self ._last_execution_unit = checkpoint .name
431+ self ._last_execution_unit = child_runner .name
391432 return _ExecutorReturn .TERMINAL
392433
393434 if outcome .is_fail_subtest :
@@ -616,6 +657,8 @@ def _execute_node(self, node: phase_nodes.PhaseNode,
616657 return self ._execute_phase (node , subtest_rec , in_teardown )
617658 if isinstance (node , phase_branches .Checkpoint ):
618659 return self ._execute_checkpoint (node , subtest_rec , in_teardown )
660+ if isinstance (node , phase_child_runner .ChildRunnerPhase ):
661+ return self ._execute_children (node , subtest_rec , in_teardown )
619662 self .logger .error ('Unhandled node type: %s' , node )
620663 return _ExecutorReturn .TERMINAL
621664
@@ -639,4 +682,4 @@ def _execute_test_diagnoser(
639682
640683 def _execute_test_diagnosers (self ) -> None :
641684 for diagnoser in self ._test_options .diagnosers :
642- self ._execute_test_diagnoser (diagnoser )
685+ self ._execute_test_diagnoser (diagnoser )
0 commit comments