Examples

Phase Branches Example

The upstream phase_branches.py example — an operator picks a device stage, a PhaseDiagnoser turns the answer into a Diagnosis, and BranchSequence runs only the matching phases, including a nested branch.

Last updated · Verified with OpenHTF 1.6.1

An interactive test whose path depends on operator input. A diagnoser converts a measurement into a Diagnosis; BranchSequence nodes run only when their DiagnosisCondition holds. Based on examples/phase_branches.py, lightly shortened.

phase_branches.py
import os.path

import openhtf as htf
from openhtf.output.callbacks import json_factory
from openhtf.plugs import user_input


class DeviceType(htf.DiagResultEnum):
    PROTOTYPE = 'prototype'
    EVT = 'evt'
    DVT = 'dvt'


class OperatorError(Exception):
    pass


# --- Step 1: a diagnoser turns the operator's answer into a Diagnosis ---

@htf.PhaseDiagnoser(DeviceType)
def diagnose_test_branch(phase_rec: htf.PhaseRecord):
    answer = phase_rec.measurements['test_branch'].measured_value.value.lower()
    try:
        branch = DeviceType(answer)
    except ValueError as e:
        options = ', '.join(d.value for d in DeviceType)
        raise OperatorError(f"Input {answer!r} not recognized. Expected one of: {options}.") from e
    return htf.Diagnosis(branch)


@htf.diagnose(diagnose_test_branch)
@htf.measures(htf.Measurement('test_branch'))
@htf.plug(prompts=user_input.UserInput)
@htf.PhaseOptions(phase_name_case=htf.PhaseNameCase.CAMEL)
def select_testing_branch_phase(test: htf.TestApi, prompts: user_input.UserInput):
    options = ', '.join(f'"{d.value}"' for d in DeviceType)
    test.measurements.test_branch = prompts.prompt(
        f'Which branch is this unit? {options}', text_input=True)


# --- EVT and DVT: a prompt and a validation phase each ---

@htf.plug(prompts=user_input.UserInput)
def evt_observation_phase(test, prompts):
    test.logger.info('Operator observations: %s', prompts.prompt('EVT observations?', text_input=True))

def evt_validation_phase(test):
    test.logger.info('EVT parameters within limits.')

@htf.plug(prompts=user_input.UserInput)
def dvt_observation_phase(test, prompts):
    test.logger.info('Operator observations: %s', prompts.prompt('DVT observations?', text_input=True))

def dvt_validation_phase(test):
    test.logger.info('DVT validation complete.')


# --- Prototype: a second diagnoser drives a nested branch ---

class PrototypeOutcome(htf.DiagResultEnum):
    INCINERATE = 'incinerate'
    PROMISE_CAKE = 'promise_cake'


@htf.PhaseDiagnoser(PrototypeOutcome)
def diagnose_prototype_observation(phase_rec: htf.PhaseRecord):
    observation = phase_rec.measurements['prototype_observation'].measured_value.value.lower()
    if any(word in observation for word in ('fire', 'smoke', 'fail', 'bad')):
        return htf.Diagnosis(PrototypeOutcome.INCINERATE)
    return htf.Diagnosis(PrototypeOutcome.PROMISE_CAKE)


@htf.diagnose(diagnose_prototype_observation)
@htf.measures(htf.Measurement('prototype_observation'))
@htf.plug(prompts=user_input.UserInput)
def prototype_observation_phase(test, prompts):
    test.measurements.prototype_observation = prompts.prompt('Prototype observations?', text_input=True)

@htf.plug(prompts=user_input.UserInput)
def incinerate_phase(test, prompts):
    test.logger.warning('Dangerous anomalies detected.')
    prompts.prompt('Incineration sequence initiated. Press Okay.')
    return htf.PhaseResult.FAIL_AND_CONTINUE

def promise_cake_phase(test):
    test.logger.info('No failures detected. You remain eligible for cake.')


# --- Step 2: assemble the branches ---

def create_and_run_test(output_dir: str = '.'):
    prototype_branch = htf.BranchSequence(
        htf.DiagnosisCondition.on_all(DeviceType.PROTOTYPE),
        prototype_observation_phase,
        htf.BranchSequence(htf.DiagnosisCondition.on_all(PrototypeOutcome.PROMISE_CAKE), promise_cake_phase),
        htf.BranchSequence(htf.DiagnosisCondition.on_all(PrototypeOutcome.INCINERATE), incinerate_phase),
    )
    evt_branch = htf.BranchSequence(
        htf.DiagnosisCondition.on_all(DeviceType.EVT), evt_observation_phase, evt_validation_phase)
    dvt_branch = htf.BranchSequence(
        htf.DiagnosisCondition.on_all(DeviceType.DVT), dvt_observation_phase, dvt_validation_phase)

    test = htf.Test(select_testing_branch_phase, prototype_branch, evt_branch, dvt_branch)
    test.add_output_callbacks(
        json_factory.OutputToJSON(os.path.join(output_dir, '{dut_id}.phase_branches.json'), indent=2))
    test.execute(test_start=user_input.prompt_for_test_start())


if __name__ == '__main__':
    create_and_run_test()
Terminal
$ python phase_branches.py -v
Enter a DUT ID in order to start the test.
--> SN1234
Which branch is this unit? "prototype", "evt", "dvt"
--> prototype
Prototype observations?
--> smoke from U7
W ... <phase: incinerate_phase> - Dangerous anomalies detected.
Incineration sequence initiated. Press Okay.
-->

======================= test: openhtf_test  outcome: FAIL ======================

Answer evt instead and only the two EVT phases run; the record's branches[] lists each BranchSequence with whether its condition was taken.

What it shows

htf.DiagResultEnum

The set of possible diagnoses. Values must be unique across all diagnosers in a test. Diagnoses →

@htf.PhaseDiagnoser(Enum) + @htf.diagnose(fn)

The diagnoser receives the finished PhaseRecord and returns zero or more Diagnosis objects; @htf.diagnose attaches it to a phase.

htf.BranchSequence(condition, *nodes)

Runs its nodes only when condition.check(store) is true at that point in the sequence. Branches nest, as the prototype branch shows. Branch sequences →

DiagnosisCondition.on_all / on_any / on_not_all / on_not_any

Boolean combinations over the diagnoses recorded so far. Condition types →

Raising from a diagnoser

OperatorError on bad input ends the test with outcome ERROR. Add it to failure_exceptions in Test Options if you would rather record a FAIL.

PhaseNameCase.CAMEL

Records the phase as SelectTestingBranchPhase. Override phase name →

Next

On this page

First-pass yield
0%4.1
Track with TofuPilot