Phases

Discover how to define and execute test phases using OpenHTF, including phase creation, phase results, and phase options with detailed examples.

Last updated · Verified with OpenHTF 1.6.1

Organize your test flow by breaking it down into multiple phases.

A test executes its phases in order. Each phase returns a PhaseResult: CONTINUE sets the outcome to PASS and moves on, REPEAT runs the phase again, SKIP marks it skipped, and STOP sets the outcome to FAIL and ends the test.

  • Test start
  • phase_boot
  • phase_calibrate
  • phase_optional
  • phase_voltage
  • Test end

A hardware test typically consists of several steps that perform measurements and validation. OpenHTF refers to these steps as phases and allows for precise management of their execution based on the results obtained.

Syntax

Phases are Python functions that take the test object as an argument and must be added to the Test object to be executed. The phase outcome is set either manually with a PhaseResult or automatically through a measurement validator, covered on the Measurements page.

main.py
import openhtf as htf

def phase_one(test):
    return htf.PhaseResult.CONTINUE

def phase_two(test):
    return htf.PhaseResult.CONTINUE

def main():
    test = htf.Test(phase_one, phase_two)
    test.execute(lambda: "SN1234")

if __name__ == '__main__':
    main()

Results

You can choose the next phase's execution by setting the PhaseResult from the following options.

PhaseResult.CONTINUE

Set the phase outcome to PASS and execute the next phase.

PhaseResult.STOP

Set the phase outcome to FAIL and stop executing the test.

PhaseResult.REPEAT

Repeat the phase, ignoring current measurement outcomes. If exceeded the repeat_limit, it triggers a PhaseResult.STOP.

PhaseResult.FAIL_AND_CONTINUE

Set the phase outcome to FAIL and execute the next phase.

PhaseResult.SKIP

Set the phase outcome to SKIP, ignore current measurement outcomes, and execute the next phase.

PhaseResult.FAIL_SUBTEST

Fail the enclosing subtest and continue with the next one. Outside a subtest this is an ERROR.

A phase that returns None (no explicit return) is treated as CONTINUE.

main.py
import openhtf as htf
import random

# Always pass
def phase_pass(test):
    return htf.PhaseResult.CONTINUE

# Retries on failure
def phase_retry(test):
    if random.choice([True, False]):
        return htf.PhaseResult.CONTINUE
    else:
        return htf.PhaseResult.REPEAT

# Fail and stop the test
def phase_fail(test):
    return htf.PhaseResult.STOP

def main():
    test = htf.Test(phase_pass, phase_retry, phase_fail)
    test.execute(lambda: "SN1234")

if __name__ == '__main__':
    main()
Terminal
======================= test: openhtf_test  outcome: FAIL ======================

Options

You can use the @openhtf.PhaseOptions decorator to modify phase execution behavior.

timeout_sfloat

Timeout for the phase, in seconds.

repeat_limitint or None

Maximum number of repeats. Set to None for infinite repeats, as long as PhaseResult.REPEAT is returned.

main.py
import openhtf as htf
import random

@htf.PhaseOptions(timeout_s=5)
def phase_pass(test):
    return htf.PhaseResult.CONTINUE

@htf.PhaseOptions(repeat_limit=3) # Retries up to 3 times in case of failure
def phase_retry(test):
    if random.choice([True, False]):
        return htf.PhaseResult.CONTINUE
    else:
        return htf.PhaseResult.REPEAT

def main():
    test = htf.Test(phase_pass, phase_retry)
    test.execute(lambda: "SN1234")

if __name__ == '__main__':
    main()

For more options, check the advanced use cases.


Advanced use cases

You can leverage advanced OpenHTF options to handle more complex phase execution cases.

Override phase name

You can replace the default phase name or change the case formatting:

namestr

Override for the name of the phase.

phase_name_casePhaseNameCase

PhaseNameCase.KEEP (default) leaves the function name as-is; PhaseNameCase.CAMEL converts measure_voltage to MeasureVoltage.

main.py
import openhtf as htf
from openhtf import PhaseNameCase

@htf.PhaseOptions(name="new_phase_name", phase_name_case=PhaseNameCase.CAMEL)
def example_phase(test):
    return htf.PhaseResult.CONTINUE

def main():
  test = htf.Test(example_phase)
  test.execute(lambda: "SN1234")

if __name__ == '__main__':
  main()

Change repeat behavior

You can repeat or stop phases under specific conditions with these following PhaseOptions:

repeat_limitint

Define a maximum number of repeats. None indicates that a phase will be repeated infinitely as long as PhaseResult.REPEAT is returned.

force_repeatbool

Force the phase to repeat up to repeat_limit times.

repeat_on_timeoutbool

Repeat phase on timeout.

repeat_on_measurement_failbool

Repeat the phase (up to repeat_limit) when any of its measurements fails validation, instead of failing the phase.

stop_on_measurement_failbool

Stop the test if any measurements fail.

main.py
import openhtf as htf
import random

@htf.PhaseOptions(force_repeat=True, repeat_limit=3)
def repeat_phase(test):
    return htf.PhaseResult.CONTINUE

@htf.PhaseOptions(repeat_on_timeout=True)
def timeout_phase(test):
    return htf.PhaseResult.CONTINUE

@htf.PhaseOptions(stop_on_measurement_fail=True)
def random_fail_phase(test):
    if random.choice([True, False]):
        return htf.PhaseResult.CONTINUE
    else:
        return htf.PhaseResult.STOP

# This test won't be run if random_fail_phase is FAIL.
def always_true_phase(test):
    return htf.PhaseResult.CONTINUE

def main():
    test = htf.Test(repeat_phase, timeout_phase, random_fail_phase, always_true_phase)
    test.execute(lambda: "SN1234")

if __name__ == "__main__":
    main()

Python Debugger

You can run the phase under the Python Debugger. When setting this option, increase the phase timeout_s as well because the timeout will still apply when under the debugger.

main.py
import openhtf as htf

@htf.PhaseOptions(run_under_pdb=True, timeout_s=20)
def first_phase(test):
    return htf.PhaseResult.CONTINUE

def main():
    test = htf.Test(first_phase)
    test.execute(lambda: "SN1234")

if __name__ == "__main__":
    main()

Run if

You can use a callback to decide whether a phase executes at all. The typical use is gating a subset of phases on an environment variable or a configuration flag, so the same test file serves several station setups (a full end-of-line run on one bench, a quick subset on another) without maintaining separate copies of the test.

main.py
import os
import openhtf as htf

# One flag per optional block, driven by the station's environment
RUN_BURNIN = os.environ.get("RUN_BURNIN", "0") == "1"

def measure_voltage(test):
    return htf.PhaseResult.CONTINUE

# Only runs when the station sets RUN_BURNIN=1
@htf.PhaseOptions(run_if=lambda: RUN_BURNIN)
def thermal_burnin(test):
    return htf.PhaseResult.CONTINUE

def main():
    test = htf.Test(measure_voltage, thermal_burnin)
    test.execute(lambda: "SN1234")

if __name__ == "__main__":
    main()

Two things to keep in mind:

  • The callback takes no arguments, so it cannot see the test state or earlier measurements; it can only read module globals, environment variables, or configuration values. To skip based on something measured during the run, return PhaseResult.SKIP from inside the phase instead.
  • A phase skipped by run_if is not logged: it leaves no phase record in the test output, as if it were never part of the test. PhaseResult.SKIP on the other hand records the phase with a skip outcome.

Requires state

You can use this option when a phase needs to manage internal test details, such as wrapping or controlling other phases. The complete TestState object is passed instead of default TestApi.

main.py
import openhtf as htf

@htf.PhaseOptions()
def check_condition(test):
    return htf.PhaseResult.CONTINUE

@htf.PhaseOptions(requires_state=True)
def conditional_phase(test_state):
    check_condition(test_state)  # Manually invoke another phase

def main():
    test = htf.Test(conditional_phase)
    test.execute(lambda: "SN1234")

if __name__ == "__main__":
    main()

Phase Groups

You can group phases into setup, main, and teardown. If a failure occurs during the setup or main phases, the system will automatically ensure that the teardown phase is always executed. The Phase Groups page covers nesting and the shorthand decorators.

main.py
import openhtf as htf
from openhtf.util import units

def setup_phase(test):
    return htf.PhaseResult.CONTINUE

def first_measurement_phase(test):
    return htf.PhaseResult.CONTINUE

def second_measurement_phase(test):
    return htf.PhaseResult.STOP

def teardown_phase(test):
    return htf.PhaseResult.CONTINUE

def main():
    test = htf.Test(
        htf.PhaseGroup(
            setup=[setup_phase],
            main=[first_measurement_phase, second_measurement_phase],
            teardown=[teardown_phase],
        )
    )
    test.execute(lambda: "SN1234")

if __name__ == "__main__":
    main()

On this page

First-pass yield
0%4.1
Track with TofuPilot