Phases

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

OpenHTF Device Under Test (DUT) documentation section header with TofuPilot.

Overview

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.

With the TofuPilot integration, you get precise analytics on phase failure rates and durations across test runs.

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 validation function, which we’ll cover on the next 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: "PCB001")

if __name__ == '__main__':
    main()

Results

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

  • Name
    PhaseResult.CONTINUE
    Description

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

  • Name
    PhaseResult.STOP
    Description

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

  • Name
    PhaseResult.REPEAT
    Description

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

  • Name
    PhaseResult.FAIL_AND_CONTINUE
    Description

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

  • Name
    PhaseResult.SKIP
    Description

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

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: "PCB001")

if __name__ == '__main__':
    main()

Terminal

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

Options

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

  • Name
    timeout_s
    Type
    float
    Description

    Timeout for the phase, in seconds.

  • Name
    repeat_limit
    Type
    int or None
    Description

    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: "PCB001")

if __name__ == '__main__':
    main()

For more options, check the advanced use cases.

TofuPilot integration

The TofuPilot integration is natively compatible with OpenHTF phases.

To integrate TofuPilot, install the open-source client:

pip install tofupilot

Add it to your test like this:

main.py

import openhtf as htf
from tofupilot.openhtf import TofuPilot

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)
    with TofuPilot(test):                  # just works™️
      test.execute(lambda: "PCB001")

if __name__ == '__main__':
    main()

You can visualize the test phases (referred to as steps) on each run page.

Run page showing the different steps performed for a Unit Under Test (UUT) in OpenHTF with TofuPilot.

You can click on a phase to monitor its recent performance across the latest runs, like its average duration.

Procedure page showing the durations for several Unit Under Test (UUT) in OpenHTF with TofuPilot.

You can access more phase performance indicators on TofuPilot and filter test runs by date, revision, and other criteria.

Learn more in the TofuPilot Docs

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:

  • Name
    name
    Type
    str
    Description

    Override for the name of the phase.

  • Name
    phase_name_case
    Type
    PhaseNameCase
    Description

    Change phase name case to CamelCase using CAMEL.

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: "PCB001")

if __name__ == '__main__':
  main()

Change repeat behavior

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

  • Name
    repeat_limit
    Type
    int
    Description

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

  • Name
    force_repeat
    Type
    bool
    Description

    Force the phase to repeat up to repeat_limit times.

  • Name
    repeat_on_timeout
    Type
    bool
    Description

    Repeat phase on timeout.

  • Name
    stop_on_measurement_fail
    Type
    bool
    Description

    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: "PCB001")

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: "PCB001")

if __name__ == "__main__":
    main()

Run if

You can use a callback to determine whether to execute the phase; if skipped, it won't be logged.

main.py

import openhtf as htf
import random

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

# Will always fail, but only runs if a condition is met
@htf.PhaseOptions(run_if=lambda: random.choice([True, False]))
def conditional_fail_phase(test):
    return htf.PhaseResult.STOP

# Will run if the conditional_fail_phase is not executed
def last_phase(test):
    return htf.PhaseResult.CONTINUE

def main():
    test = htf.Test(first_phase, conditional_fail_phase, last_phase)
    test.execute(lambda: "PCB001")

if __name__ == "__main__":
    main()

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: "PCB001")

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.

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: "PCB001")

if __name__ == "__main__":
    main()

Was this page helpful?