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
integrationRequires 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-testsList the selected tests (optionally narrowed by a suite nameand/or
--markers) and exit.
--list-markers List the markers available to --markers and exit.
Options on ``otto test`` (before the suite name)
--markers / -m EXPRESSIONpytest
-mmarker 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::namedisambiguates. Combine with--markersto narrow. Unknown names raise a loud error with did-you-mean suggestions.--iterations / -i NRepeat each test N times within a single setup/teardown cycle (0 = disabled).
--duration / -d SECONDSRepeat tests for N seconds within a single setup/teardown cycle (0 = disabled).
--threshold FLOATMinimum per-test pass rate percentage required in stability mode (0-100, default: 100).
--results PATHWrite 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.
--covFetch
.gcdafiles from remote hosts after the suite finishes and place them in acov/directory in the suite’s output directory.--cov-dir PATHWrite coverage data to
PATHinstead 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-diris also given.--overwrite-cov-dirClear the contents of the
--cov-dirdestination before the run so stale data from a previous invocation cannot be mixed with the new results.--cov-clean / --no-cov-cleanDelete
.gcdafiles on remote hosts before the test run. Enabled by default; use--no-cov-cleanto keep stale data.--cov-report / -rAfter coverage collection, render an HTML report. Implies
--cov. Default location:<output_dir>/cov_report.--cov-report-dir PATHWrite the HTML report to
PATHinstead 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-diris also given.--overwrite-cov-report-dirClear the contents of
--cov-report-dirbefore the report is rendered.--project-name STRTitle shown in the HTML report header (only used with
--cov-report).--monitorEnable 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 SECONDSSampling interval for
--monitor(default: 5).--monitor-output PATHOverride the destination for the captured monitor data. Format inferred from the suffix:
.json(default) writes a self-contained format:1 export,.dbwrites a SQLite database loadable viaotto monitor <path>.--monitor-hosts REGEXRestrict
--monitorto 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 ofctx.meta, calls the library engine, and maps its library exceptions onto Typer’s conventions. An unknown--testsname (libraryUnknownSelectionError, carrying the did-you-mean message) becomestyper.BadParameterwith the same message andparam_hint. The “nothing matched” case (libraryNoTestsMatchedError) becomes the historical redrprint+ exit 1. Both subclassValueError; catching them by their specific types (UnknownSelectionErrorfirst, thenNoTestsMatchedError) keeps an unrelated pipelineValueErrorfrom 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 testrun options and store them inctx.metafor suite runners.Validates coverage and report directories and resolves implied flags (e.g.
--cov-dirimplies--cov). Output-directory creation and the reservation gate happen later, in the shared leaf-invoke command preamble.