Phases
Organize your test flow by breaking it down into multiple phases.
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 aPhaseResult.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 asPhaseResult.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.
You can click on a phase to monitor its recent performance across the latest runs, like its average duration.
You can access more phase performance indicators on TofuPilot and filter test runs by date, revision, and other criteria.
Learn more in the TofuPilot DocsAdvanced 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()