cli.test

Run a registered OttoSuite test suite, or a suite-less test selection.

Each Test*-named OttoSuite subclass auto-registers as a subcommand of otto test. Suite-specific options (declared in the suite’s inner Options dataclass) are automatically registered as Typer parameters with full type enforcement and --help documentation.

When no suite subcommand is given, --tests and/or --markers run a suite-less selection instead: exact test names (optionally Class::name qualified) and/or a marker expression are resolved against every repo’s collected tests, and pytest runs once per repo whose selection matched. Plain pytest functions (not just OttoSuite classes) are runnable this way.

Markers

integration

Requires live Vagrant VMs. Skip with --markers "not integration".

timeout(seconds)

Fail the test if it runs longer than seconds.

retry(n)

Retry a failing test up to n times before reporting failure.

Listing tests

--list-suites List test suites with run syntax and exit.

--list-tests List the selected tests (optionally narrowed by a suite name

and/or --markers) and exit.

--list-markers List the markers available to --markers and exit.

Options on ``otto test`` (before the suite name)

--markers / -m EXPRESSION

pytest -m marker expression applied after collection. With no suite subcommand, runs the marker selection across every repo (one pytest session per repo) instead of requiring a suite name.

--tests NAME[,NAME...]

Run specific tests by exact name — no suite subcommand needed. Comma-separated; bare names match every collected test with that function name (all parametrizations, across suites and repos); TestClass::name disambiguates. Combine with --markers to narrow. Unknown names raise a loud error with did-you-mean suggestions.

--iterations / -i N

Repeat each test N times within a single setup/teardown cycle (0 = disabled).

--duration / -d SECONDS

Repeat tests for N seconds within a single setup/teardown cycle (0 = disabled).

--threshold FLOAT

Minimum per-test pass rate percentage required in stability mode (0-100, default: 100).

--results PATH

Write test results (JUnit XML) to PATH (default: auto-written to the log directory).

When both --iterations and --duration are specified, testing stops when either limit is reached first.

--cov

Fetch .gcda files from remote hosts after the suite finishes and place them in a cov/ directory in the suite’s output directory.

--cov-dir PATH

Write coverage data to PATH instead of the default <output_dir>/cov. Implies --cov. The directory is created if missing; if it already exists and is non-empty, the command aborts unless --overwrite-cov-dir is also given.

--overwrite-cov-dir

Clear the contents of the --cov-dir destination before the run so stale data from a previous invocation cannot be mixed with the new results.

--cov-clean / --no-cov-clean

Delete .gcda files on remote hosts before the test run. Enabled by default; use --no-cov-clean to keep stale data.

--cov-report / -r

After coverage collection, render an HTML report. Implies --cov. Default location: <output_dir>/cov_report.

--cov-report-dir PATH

Write the HTML report to PATH instead of the default. Implies --cov-report (and therefore --cov). Empty/overwrite rules match --cov-dir: created if missing, aborts if non-empty unless --overwrite-cov-report-dir is also given.

--overwrite-cov-report-dir

Clear the contents of --cov-report-dir before the report is rendered.

--project-name STR

Title shown in the HTML report header (only used with --cov-report).

--monitor

Enable host performance monitoring for the duration of the run. Samples every host (or those matched by --monitor-hosts) on a fixed interval and emits per-test start/end events automatically. At the end of the run a format:1 monitor export (one session) is written to <output_dir>/monitor.json.

--monitor-interval SECONDS

Sampling interval for --monitor (default: 5).

--monitor-output PATH

Override the destination for the captured monitor data. Format inferred from the suffix: .json (default) writes a self-contained format:1 export, .db writes a SQLite database loadable via otto monitor <path>.

--monitor-hosts REGEX

Restrict --monitor to host IDs matching this regex (re.search).

Examples:

otto test --list-suites
otto test --list-markers
otto test --list-tests
otto test --list-tests --markers slow
otto test --list-tests TestMyDevice
otto test --tests test_login
otto test --tests TestB::test_login,test_plain
otto test -m slow
otto test TestMyDevice --help
otto test TestMyDevice --device-type switch --firmware 2.1
otto test --iterations 50 --threshold 95 TestMyDevice
otto test --duration 300 --threshold 90 TestMyDevice
otto test --iterations 100 --duration 60 TestMyDevice
otto test --cov TestMyDevice
otto test --cov-dir /tmp/myrun TestMyDevice
otto test --cov-dir /tmp/myrun --overwrite-cov-dir TestMyDevice
otto test --cov --no-cov-clean TestMyDevice
otto test --cov --cov-report TestMyDevice
otto test -r --cov-report-dir /tmp/myreport TestMyDevice
otto.cli.test.run_selection(ctx: Context) None

Run a suite-less selection (–tests and/or -m) — one session per repo.

Thin CLI adapter over otto.suite.run.run_selection(): lifts run options out of ctx.meta, calls the library engine, and maps its library exceptions onto Typer’s conventions. An unknown --tests name (library UnknownSelectionError, carrying the did-you-mean message) becomes typer.BadParameter with the same message and param_hint. The “nothing matched” case (library NoTestsMatchedError) becomes the historical red rprint + exit 1. Both subclass ValueError; catching them by their specific types (UnknownSelectionError first, then NoTestsMatchedError) keeps an unrelated pipeline ValueError from being misreported as a no-match — it propagates untouched.

otto.cli.test.list_suites_callback(value: bool) None

Print all available test suites (one panel per repo) and exit when the flag is set.

otto.cli.test.list_markers_callback(value: bool) None

Print the markers available to –markers (one panel per repo) and exit.

otto.cli.test.main(ctx: ~typer.models.Context, list_suites: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x738000be0970>] = False, list_markers: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x738000be1a50>] = False, list_tests: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x738000be2380>] = False, markers: ~typing.Annotated[str, <typer.models.OptionInfo object at 0x738000be1600>] = '', tests: ~typing.Annotated[str, <typer.models.OptionInfo object at 0x738000be18d0>] = '', iterations: ~typing.Annotated[int, <typer.models.OptionInfo object at 0x738000be2b60>] = 0, duration: ~typing.Annotated[int, <typer.models.OptionInfo object at 0x738000be29e0>] = 0, threshold: ~typing.Annotated[float, <typer.models.OptionInfo object at 0x738000be2a10>] = 100.0, results: ~typing.Annotated[str, <typer.models.OptionInfo object at 0x738000be28c0>] = '', cov: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x738000be2170>] = False, cov_dir: ~types.Annotated[~pathlib.Path | None, <typer.models.OptionInfo object at 0x738000be21d0>] | None = None, overwrite_cov_dir: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x738000be2080>] = False, cov_clean: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x738000be1930>] = True, cov_report: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x738000be2530>] = False, cov_report_dir: ~types.Annotated[~pathlib.Path | None, <typer.models.OptionInfo object at 0x738000be0f40>] | None = None, overwrite_cov_report_dir: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x738000be0940>] = False, project_name: ~typing.Annotated[str, <typer.models.OptionInfo object at 0x738000be09d0>] = 'Coverage Report', monitor: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x738000be0820>] = False, monitor_interval: ~typing.Annotated[float, <typer.models.OptionInfo object at 0x738000be20b0>] = 5.0, monitor_output: ~types.Annotated[~pathlib.Path | None, <typer.models.OptionInfo object at 0x738000be27a0>] | None = None, monitor_hosts: ~types.Annotated[str | None, <typer.models.OptionInfo object at 0x738000be17e0>] | None = None) None

Collect otto test run options and store them in ctx.meta for suite runners.

Validates coverage and report directories and resolves implied flags (e.g. --cov-dir implies --cov). Output-directory creation and the reservation gate happen later, in the shared leaf-invoke command preamble.