Skip to content

Commit c34e3cc

Browse files
authored
Add subtest_previous PhaseFailureCheckpoint (#992)
1 parent 718ff6c commit c34e3cc

File tree

3 files changed

+203
-12
lines changed

3 files changed

+203
-12
lines changed

openhtf/core/phase_branches.py

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@
1414

1515
"""Implements phase node branches.
1616
17-
A BranchSequence is a phase node sequence that runs conditiionally based on the
17+
A BranchSequence is a phase node sequence that runs conditionally based on the
1818
diagnosis results of the test run.
1919
"""
2020

2121
import abc
2222
import enum
23-
from typing import Any, Callable, Dict, Iterator, Text, Tuple, TYPE_CHECKING, Union
23+
from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterator, Optional,
24+
Text, Tuple, Union)
2425

2526
import attr
2627
from openhtf import util
@@ -54,6 +55,9 @@ class PreviousPhases(enum.Enum):
5455
# Check all phases.
5556
ALL = 'ALL'
5657

58+
# Check all previous phases in the current subtest.
59+
SUBTEST = "SUBTEST"
60+
5761

5862
def _not_any(iterable: Iterator[bool]) -> bool:
5963
return not any(iterable)
@@ -179,15 +183,20 @@ def apply_to_all_phases(
179183
return self
180184

181185
def get_result(
182-
self, running_test_state: 'test_state.TestState'
186+
self,
187+
running_test_state: 'test_state.TestState',
188+
subtest_rec: Optional[test_record.SubtestRecord] = None
183189
) -> phase_descriptor.PhaseReturnT:
184-
if self._check_for_action(running_test_state):
190+
if self._check_for_action(running_test_state, subtest_rec):
185191
return self.action
186192
return phase_descriptor.PhaseResult.CONTINUE
187193

188194
@abc.abstractmethod
189-
def _check_for_action(self,
190-
running_test_state: 'test_state.TestState') -> bool:
195+
def _check_for_action(
196+
self,
197+
running_test_state: 'test_state.TestState',
198+
subtest_rec: Optional[test_record.SubtestRecord] = None
199+
) -> bool:
191200
"""Returns True when the action should be taken."""
192201

193202
@abc.abstractmethod
@@ -197,7 +206,7 @@ def record_conditional(self) -> Union[PreviousPhases, DiagnosisCondition]:
197206

198207
@attr.s(slots=True, frozen=True)
199208
class PhaseFailureCheckpoint(Checkpoint):
200-
"""Node that checks if a previous phase or all previous phases failed.
209+
"""Node that checks if the specified previous phase(s) failed.
201210
202211
If the phases fail, this will be resolved as `action`.
203212
@@ -219,6 +228,12 @@ def all_previous(cls, *args, **kwargs) -> 'PhaseFailureCheckpoint':
219228
kwargs['previous_phases_to_check'] = PreviousPhases.ALL
220229
return cls(*args, **kwargs)
221230

231+
@classmethod
232+
def subtest_previous(cls, *args, **kwargs) -> 'PhaseFailureCheckpoint':
233+
"""Checks if any node in the current subtest has failed."""
234+
kwargs['previous_phases_to_check'] = PreviousPhases.SUBTEST
235+
return cls(*args, **kwargs)
236+
222237
def _asdict(self) -> Dict[Text, Any]:
223238
ret = super(PhaseFailureCheckpoint, self)._asdict()
224239
ret.update(previous_phases_to_check=self.previous_phases_to_check)
@@ -228,14 +243,23 @@ def _phase_failed(self, phase_rec: test_record.PhaseRecord) -> bool:
228243
"""Returns True if the phase_rec failed; ignores ERRORs."""
229244
return phase_rec.outcome == test_record.PhaseOutcome.FAIL
230245

231-
def _check_for_action(self,
232-
running_test_state: 'test_state.TestState') -> bool:
246+
def _check_for_action(
247+
self,
248+
running_test_state: 'test_state.TestState',
249+
subtest_rec: Optional[test_record.SubtestRecord] = None
250+
) -> bool:
233251
"""Returns True when the specific set of phases fail."""
234252
phase_records = running_test_state.test_record.phases
235253
if not phase_records:
236254
raise NoPhasesFoundError('No phases found in the test record.')
237255
if self.previous_phases_to_check == PreviousPhases.LAST:
238256
return self._phase_failed(phase_records[-1])
257+
elif (self.previous_phases_to_check == PreviousPhases.SUBTEST and
258+
subtest_rec is not None):
259+
for phase_rec in phase_records:
260+
if (phase_rec.subtest_name == subtest_rec.name and
261+
self._phase_failed(phase_rec)):
262+
return True
239263
else:
240264
for phase_rec in phase_records:
241265
if self._phase_failed(phase_rec):
@@ -261,8 +285,11 @@ def _asdict(self) -> Dict[Text, Any]:
261285
ret.update(diag_condition=self.diag_condition._asdict())
262286
return ret
263287

264-
def _check_for_action(self,
265-
running_test_state: 'test_state.TestState') -> bool:
288+
def _check_for_action(
289+
self,
290+
running_test_state: 'test_state.TestState',
291+
subtest_rec: Optional[test_record.SubtestRecord] = None
292+
) -> bool:
266293
"""Returns True if the condition is true."""
267294
return self.diag_condition.check(running_test_state.diagnoses_manager.store)
268295

openhtf/core/phase_executor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ def evaluate_checkpoint(
362362
subtest_name = None
363363
evaluated_millis = util.time_millis()
364364
try:
365-
outcome = PhaseExecutionOutcome(checkpoint.get_result(self.test_state))
365+
outcome = PhaseExecutionOutcome(checkpoint.get_result(self.test_state, subtest_rec))
366366
self.logger.debug('Checkpoint %s result: %s', checkpoint.name,
367367
outcome.phase_result)
368368
if outcome.is_fail_subtest and not subtest_rec:

test/core/phase_branches_test.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,170 @@ def test_last_fail_subtest__fail_in_subtest(self):
433433
evaluated_millis=htf_test.VALID_TIMESTAMP),
434434
], test_rec.checkpoints)
435435

436+
@htf_test.yields_phases
437+
def test_subtest_previous_fail__fail(self):
438+
test_rec = yield htf.Test(
439+
fail_phase,
440+
phase0,
441+
phase_branches.PhaseFailureCheckpoint.subtest_previous(
442+
'subtest_previous_fail', action=htf.PhaseResult.STOP), error_phase)
443+
444+
self.assertTestFail(test_rec)
445+
self.assertPhasesOutcomeByName(test_record.PhaseOutcome.PASS, test_rec,
446+
'phase0')
447+
self.assertPhasesOutcomeByName(test_record.PhaseOutcome.FAIL, test_rec,
448+
'fail_phase')
449+
450+
self.assertEqual([
451+
test_record.CheckpointRecord(
452+
name='subtest_previous_fail',
453+
action=htf.PhaseResult.STOP,
454+
conditional=phase_branches.PreviousPhases.SUBTEST,
455+
subtest_name=None,
456+
result=phase_executor.PhaseExecutionOutcome(
457+
htf.PhaseResult.STOP),
458+
evaluated_millis=htf_test.VALID_TIMESTAMP),
459+
], test_rec.checkpoints)
460+
461+
@htf_test.yields_phases
462+
def test_subtest_previous_fail__pass(self):
463+
test_rec = yield htf.Test(
464+
phase0,
465+
phase1,
466+
phase_branches.PhaseFailureCheckpoint.subtest_previous(
467+
'subtest_previous_pass', action=htf.PhaseResult.STOP), phase2)
468+
469+
self.assertTestPass(test_rec)
470+
self.assertPhasesOutcomeByName(test_record.PhaseOutcome.PASS, test_rec,
471+
'phase0', 'phase1', 'phase2')
472+
473+
self.assertEqual([
474+
test_record.CheckpointRecord(
475+
name='subtest_previous_pass',
476+
action=htf.PhaseResult.STOP,
477+
conditional=phase_branches.PreviousPhases.SUBTEST,
478+
subtest_name=None,
479+
result=phase_executor.PhaseExecutionOutcome(
480+
htf.PhaseResult.CONTINUE),
481+
evaluated_millis=htf_test.VALID_TIMESTAMP),
482+
], test_rec.checkpoints)
483+
484+
@htf_test.yields_phases
485+
def test_subtest_previous_fail__fail_in_subtest(self):
486+
test_rec = yield htf.Test(
487+
phase0,
488+
htf.Subtest(
489+
'sub', fail_phase, phase1,
490+
phase_branches.PhaseFailureCheckpoint.subtest_previous(
491+
'subtest_previous_fail_in_subtest', action=htf.PhaseResult.STOP),
492+
),
493+
error_phase)
494+
495+
self.assertTestFail(test_rec)
496+
self.assertPhasesOutcomeByName(test_record.PhaseOutcome.PASS, test_rec,
497+
'phase0', 'phase1')
498+
self.assertPhasesOutcomeByName(test_record.PhaseOutcome.FAIL, test_rec,
499+
'fail_phase')
500+
501+
self.assertEqual([
502+
test_record.CheckpointRecord(
503+
name='subtest_previous_fail_in_subtest',
504+
action=htf.PhaseResult.STOP,
505+
conditional=phase_branches.PreviousPhases.SUBTEST,
506+
subtest_name='sub',
507+
result=phase_executor.PhaseExecutionOutcome(
508+
htf.PhaseResult.STOP),
509+
evaluated_millis=htf_test.VALID_TIMESTAMP),
510+
], test_rec.checkpoints)
511+
512+
@htf_test.yields_phases
513+
def test_subtest_previous_fail__fail_out_of_subtest(self):
514+
test_rec = yield htf.Test(
515+
fail_phase,
516+
htf.Subtest(
517+
'sub', phase0,
518+
phase_branches.PhaseFailureCheckpoint.subtest_previous(
519+
'subtest_previous_fail_out_of_subtest', action=htf.PhaseResult.STOP),
520+
phase1,
521+
),
522+
phase2)
523+
524+
self.assertTestFail(test_rec)
525+
self.assertPhasesOutcomeByName(test_record.PhaseOutcome.PASS, test_rec,
526+
'phase0', 'phase1', 'phase2')
527+
self.assertPhasesOutcomeByName(test_record.PhaseOutcome.FAIL, test_rec,
528+
'fail_phase')
529+
530+
self.assertEqual([
531+
test_record.CheckpointRecord(
532+
name='subtest_previous_fail_out_of_subtest',
533+
action=htf.PhaseResult.STOP,
534+
conditional=phase_branches.PreviousPhases.SUBTEST,
535+
subtest_name='sub',
536+
result=phase_executor.PhaseExecutionOutcome(
537+
htf.PhaseResult.CONTINUE),
538+
evaluated_millis=htf_test.VALID_TIMESTAMP),
539+
], test_rec.checkpoints)
540+
541+
@htf_test.yields_phases
542+
def test_subtest_previous_fail__pass_in_subtest(self):
543+
test_rec = yield htf.Test(
544+
phase0,
545+
htf.Subtest(
546+
'sub', phase1,
547+
phase_branches.PhaseFailureCheckpoint.subtest_previous(
548+
'subtest_previous_pass_in_subtest', action=htf.PhaseResult.STOP),
549+
phase2,
550+
), phase3)
551+
552+
self.assertTestPass(test_rec)
553+
self.assertPhasesOutcomeByName(test_record.PhaseOutcome.PASS, test_rec,
554+
'phase0', 'phase1', 'phase2', 'phase3')
555+
self.assertPhasesOutcomeByName(test_record.PhaseOutcome.FAIL, test_rec,
556+
'fail_phase')
557+
558+
self.assertEqual([
559+
test_record.CheckpointRecord(
560+
name='subtest_previous_pass_in_subtest',
561+
action=htf.PhaseResult.STOP,
562+
conditional=phase_branches.PreviousPhases.SUBTEST,
563+
subtest_name='sub',
564+
result=phase_executor.PhaseExecutionOutcome(
565+
htf.PhaseResult.CONTINUE),
566+
evaluated_millis=htf_test.VALID_TIMESTAMP),
567+
], test_rec.checkpoints)
568+
569+
@htf_test.yields_phases
570+
def test_subtest_previous_fail_subtest__fail_in_subtest(self):
571+
test_rec = yield htf.Test(
572+
phase0,
573+
htf.Subtest(
574+
'sub', fail_phase, phase1,
575+
phase_branches.PhaseFailureCheckpoint.subtest_previous(
576+
'subtest_previous_fail_subtest_in_subtest', action=htf.PhaseResult.FAIL_SUBTEST),
577+
skip0,
578+
),
579+
phase2)
580+
581+
self.assertTestFail(test_rec)
582+
self.assertPhasesOutcomeByName(test_record.PhaseOutcome.PASS, test_rec,
583+
'phase0', 'phase1', 'phase2')
584+
self.assertPhasesOutcomeByName(test_record.PhaseOutcome.FAIL, test_rec,
585+
'fail_phase')
586+
self.assertPhasesOutcomeByName(test_record.PhaseOutcome.SKIP, test_rec, 'skip0')
587+
588+
self.assertEqual([
589+
test_record.CheckpointRecord(
590+
name='subtest_previous_fail_subtest_in_subtest',
591+
action=htf.PhaseResult.FAIL_SUBTEST,
592+
conditional=phase_branches.PreviousPhases.SUBTEST,
593+
subtest_name='sub',
594+
result=phase_executor.PhaseExecutionOutcome(
595+
htf.PhaseResult.FAIL_SUBTEST),
596+
evaluated_millis=htf_test.VALID_TIMESTAMP),
597+
], test_rec.checkpoints)
598+
599+
436600
@htf_test.yields_phases
437601
def test_all__no_previous_phases(self):
438602
self.test_start_function = None

0 commit comments

Comments
 (0)