suite.plugin

OttoPlugin — internal pytest plugin registered when otto invokes pytest.main().

Provides the pytest_runtest_makereport hook that attaches the per-phase test report to each item (as item.rep_setup, item.rep_call, item.rep_teardown). This makes pass/fail status available to fixtures (including OttoSuite._test_lifecycle) during the teardown phase.

When sut_test_dirs is supplied, the pytest_ignore_collect hook restricts collection to only those directories and their descendants, ensuring that only tests defined in OTTO_SUT_DIRS repos are run.

Additional hooks:

pytest_runtest_protocol

Implements stability testing (--iterations / --duration). Repeats each test item within a single setup/teardown cycle, stopping when the iteration or time limit is reached.

pytest_runtest_call

Implements @pytest.mark.retry(n) — retries the test body up to n times on failure, stopping on the first success.

pytest_runtest_logreport

In stability mode, accumulates per-test pass/fail counts into the StabilityCollector attached to the plugin instance.

otto.suite.plugin.otto_cov_key : StashKey[bool] = <_pytest.stash.StashKey object>

Stash key indicating that --cov was passed to otto test. Fixtures can read this to decide whether to preserve .gcda files on remote hosts for post-run collection.

class otto.suite.plugin.StabilityCollector

Bases: object

Accumulates per-test pass/fail counts across multiple stability runs.

record(nodeid, passed)
Return type:

None

class otto.suite.plugin.OttoPlugin(sut_test_dirs=None, stability_collector=None, cov=False, iterations=0, duration=0, monitor=False, monitor_interval=5.0, monitor_output=None, monitor_hosts=None)

Bases: object

Internal pytest plugin used by otto test to instrument test runs.

Parameters

sut_test_dirs :

Resolved test directories from all configured OTTO_SUT_DIRS repos (i.e. the union of Repo.tests for every repo). When provided, collection is restricted to these directories. Pass an empty list or omit to disable filtering.

stability_collector :

When running in stability mode, pass a StabilityCollector instance here to accumulate pass/fail counts across repeated runs.

pytest_configure(config)

Register the shared async timeout fixture and enforce auto asyncio mode.

OttoSuites always run with asyncio_mode=auto so that async fixtures and test methods work without explicit @pytest.mark.asyncio markers. This is distinct from otto’s own unit tests which use asyncio_mode=strict (set in pyproject.toml).

Return type:

None

pytest_ignore_collect(collection_path, config)

Ignore any path not under a configured SUT test directory.

Returns True (ignore) for paths outside all SUT test dirs. Returns None (collect normally) for paths inside a SUT test dir or for ancestor directories that need to be traversed to reach one. When no SUT test dirs are configured, all paths are collected normally.

Return type:

bool | None

pytest_runtest_protocol(item, nextitem)

Repeat each test item when stability mode is active.

When --iterations or --duration (or both) are specified, each collected test is executed multiple times within a single pytest session. Class-scoped fixtures (setup_class / teardown_class) remain cached by pytest for the lifetime of the class and fire only once. Method-scoped fixtures fire on every iteration.

Unlike calling runtestprotocol in a loop (which tears down all fixtures including class-scoped ones after each call), this hook runs setup once, repeats the call phase N times, then runs teardown once. This keeps class-scoped resources (SSH connections, deployed artifacts, etc.) alive across iterations.

Returns True to signal that this hook handled the item, or None to fall through to default behaviour.

Return type:

bool | None

pytest_runtest_call(item)

Implement @pytest.mark.retry(n) — retry the test body up to n times.

Stops on the first success. Re-raises the last exception if all attempts fail. Each failed attempt is logged at WARNING level.

Return type:

None

pytest_report_teststatus(report, config)

Suppress pytest’s per-test progress characters.

otto’s RichHandler streams log output to the console in real time, so pytest’s dot/F/E column adds no information and races with log records when capture is disabled. Returning an empty short-letter keeps the category and verbose word intact (so failure summaries and the final pass/fail counts still render) while stopping the terminal reporter from writing anything per test.

Return type:

tuple[str, str, str] | None

pytest_runtest_logreport(report)

In stability mode, accumulate per-test pass/fail counts.

Return type:

None

pytest_runtest_makereport(item, call)
Return type:

Generator[None, None, None]