Fixtures & Utilities¶
Built-in pytest fixtures provided by the Zelos SDK plugins.
Core Fixtures¶
check - Assertion Framework¶
def test_example(check):
"""The check fixture is automatically provided."""
check.that(value, "==", expected)
check.that(signal, ">", threshold, within_duration_s=1.0)
Scope: function Auto-use: No (request explicitly)
zelos_session - SDK Initialization¶
# Automatically initializes SDK for all tests
# No need to explicitly use—it's auto-enabled
# Configure via command line:
pytest --zelos-trace --zelos-trace-url=grpc://localhost:2300 --zelos-log-level=debug
Scope: session Auto-use: Yes
Trace Fixtures¶
Recording Fixtures¶
These fixtures automatically manage trace file recording at different scopes (active only when --zelos-trace-file is set and scope matches --zelos-trace-file-scope). They require --zelos-local-artifacts-dir to be configured.
# These are auto-use based on --zelos-trace-file-scope setting
# trace_file_session - Records entire session
# trace_file_module - Records per module
# trace_file_class - Records per class
# trace_file_function - Records per function (default)
# Control via command line:
pytest --zelos-trace-file --zelos-local-artifacts-dir=./artifacts --zelos-trace-file-scope=class
All are auto-use when --zelos-trace-file is enabled.
File naming:
- Default filename:
{artifact_basename}-trace-{sanitized_nodeid}.trz - Override via
pytest_zelos_trace_file_name(request)inconftest.py
trace_logging - Python Log Capture¶
def test_with_logging(trace_logging):
"""Capture Python logging to trace."""
import logging
logging.info("Test started")
logging.error("Something failed")
Scope: session
Auto-use: Yes (when --zelos-trace-logging enabled)
trace_stdout - Console Output¶
# Prints trace events to stdout
# Enable with: pytest --zelos-trace-stdout
def test_debug_output():
"""Events print to console for debugging."""
source.log("event", {"value": 123})
# Prints: [TRACE] source/event: value=123
Scope: session
Auto-use: Yes (when --zelos-trace-stdout enabled)
Utilities¶
Check Configuration¶
from zelos_sdk.pytest.checker import check_config
@check_config(fail_fast=False)
def test_multiple_assertions(check):
"""Continue even if checks fail."""
check.that(1, "==", 1)
check.that(2, "==", 3) # Fails but continues
check.that(3, "==", 3) # Still runs
Range / tolerance with the typed vocabulary¶
The typed Check API ships 21 built-in ops that flow over the wire to the agent. Most range and tolerance assertions are best expressed with these directly:
def test_voltage_in_range(check):
"""Express a range as two bounded checks; both post to the board."""
voltage = 3.85
check.that(voltage, ">=", 3.0)
check.that(voltage, "<=", 4.25)
def test_voltage_settled(check):
"""`is_close` uses math.isclose defaults; `is_approximately`
uses pytest.approx defaults — pick whichever matches your
domain's expected-error model."""
check.that(3.851, "is_close", 3.85, rel_tol=0.01)
check.that(3.851, "is_approximately", 3.85)
Custom local-eval operators¶
For domain-specific predicates that don't fit the typed vocabulary,
the zelos_sdk.pytest.checker.ops registry lets you register a
DescriptiveOp and use it like any other op string. Custom ops are
local-eval only — the callable runs in-process and the result
posts to the board, but the spec never crosses the wire to the
agent (so they pair with literal / cache-field operands, not
Signal handles).
from zelos_sdk.pytest.checker import ops
def within_range(value, bounds):
lo, hi = bounds
return lo <= value <= hi
ops.register_op(ops.DescriptiveOp(within_range, "is within range"))
def test_range(check):
check.that(3.85, "is within range", (3.0, 4.25))
Or pass a callable directly without registration (the function's
__name__ becomes the board label):
Typed ops always win the dispatch race — registering "==" as a
custom op can't shadow the typed fast path. Custom ops paired with
a Signal raise immediately (use the typed Op vocabulary for
agent-side temporal evaluation instead). register_op(..., overwrite=True)
replaces an existing entry; unregister_op("description") removes one.