Phase Groups

Structure your OpenHTF tests with Phase Groups for guaranteed teardown execution, setup/main/teardown patterns, and nested group composition.

Group phases into setup, main, and teardown blocks so cleanup always runs.

A PhaseGroup runs its setup phases, then its main phases, then its teardown phases. When a main phase fails and stops the group, the remaining main phases do not run but the teardown phases still execute, like a try/finally block.

  • PhaseGroup
  • setup
  • power_on
  • main
  • measure_voltage
  • measure_current
  • teardown
  • power_off

Hardware tests often need guaranteed cleanup: close relays, power down devices, disconnect instruments. A PhaseGroup ensures teardown phases run even if setup or main phases fail, similar to a try/finally block.

Syntax

A PhaseGroup takes three lists of phases: setup, main, and teardown. The rules are:

  • Setup phases run first. If any fails, the group is never entered — main and teardown are skipped entirely.
  • Main phases run after setup succeeds. If any fails, remaining main phases are skipped.
  • Teardown phases run after main, regardless of whether main phases passed or failed. They are guaranteed to execute once setup has succeeded.
main.py
import openhtf as htf

def connect_instrument(test):
    test.logger.info('Connecting to instrument')
    return htf.PhaseResult.CONTINUE

def measure_voltage(test):
    test.logger.info('Measuring voltage')
    return htf.PhaseResult.CONTINUE

def measure_current(test):
    test.logger.info('Measuring current')
    return htf.PhaseResult.CONTINUE

def disconnect_instrument(test):
    test.logger.info('Disconnecting instrument')
    return htf.PhaseResult.CONTINUE

def main():
    test = htf.Test(
        htf.PhaseGroup(
            setup=[connect_instrument],
            main=[measure_voltage, measure_current],
            teardown=[disconnect_instrument],
        )
    )
    test.execute(lambda: "SN1234")

if __name__ == "__main__":
    main()

Execution rules

Setup succeeds

All setup → all main → all teardown phases run in order.

Setup fails

Remaining setup skipped. Main and teardown are skipped entirely — the group was never entered.

Main fails

Remaining main phases are skipped. Teardown still runs because the group was entered during setup.

Teardown fails

Remaining teardown phases still run. Teardown errors are logged but do not prevent other teardown phases from executing.

main.py
import openhtf as htf

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

def failing_main_phase(test):
    return htf.PhaseResult.STOP  # This fails

def skipped_phase(test):
    # This will NOT run because failing_main_phase stopped execution
    return htf.PhaseResult.CONTINUE

def teardown_phase(test):
    # This ALWAYS runs because setup succeeded
    test.logger.info('Cleanup complete')
    return htf.PhaseResult.CONTINUE

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

if __name__ == "__main__":
    main()

Shorthand decorators

You can use factory methods to create groups more concisely when you only need teardown or setup.

main.py
import openhtf as htf

def cleanup(test):
    test.logger.info('Cleaning up')

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

def main():
    # Wrap main phases with a teardown guarantee
    group = htf.PhaseGroup.with_teardown(cleanup)(measure)

    test = htf.Test(group)
    test.execute(lambda: "SN1234")

if __name__ == "__main__":
    main()
PhaseGroup.with_teardown(*teardown_phases)decorator

Returns a decorator that wraps main phases with guaranteed teardown.

PhaseGroup.with_setup(*setup_phases)decorator

Returns a decorator that runs setup phases before main phases.

PhaseGroup.with_context(setup, teardown)decorator

Returns a decorator that wraps main phases with both setup and teardown.

Nested groups

Phase groups can be nested inside other groups. Each group independently guarantees its own teardown. If an inner group's main phase fails, the inner teardown runs, then the outer teardown runs.

main.py
import openhtf as htf

def outer_main(test):
    test.logger.info('Outer main phase')

def inner_main(test):
    test.logger.info('Inner main phase')

def inner_teardown(test):
    test.logger.info('Inner teardown')

def outer_teardown(test):
    test.logger.info('Outer teardown')

def main():
    test = htf.Test(
        htf.PhaseGroup(
            main=[
                outer_main,
                htf.PhaseGroup.with_teardown(inner_teardown)(inner_main),
            ],
            teardown=[outer_teardown],
        )
    )
    test.execute(lambda: "SN1234")

if __name__ == "__main__":
    main()

When inner_main fails, execution order is: inner_teardownouter_teardown. Both teardowns run because both groups were entered.

On this page

First-pass yield
0%4.1
Track with TofuPilot