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 0x736d6c588730>] = False, list_markers: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x736d6c589420>] = False, list_tests: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x736d6c58a4d0>] = False, markers: ~typing.Annotated[str, <typer.models.OptionInfo object at 0x736d6c588fa0>] = '', tests: ~typing.Annotated[str, <typer.models.OptionInfo object at 0x736d6c589270>] = '', iterations: ~typing.Annotated[int, <typer.models.OptionInfo object at 0x736d6c58ac80>] = 0, duration: ~typing.Annotated[int, <typer.models.OptionInfo object at 0x736d6c58ab90>] = 0, threshold: ~typing.Annotated[float, <typer.models.OptionInfo object at 0x736d6c58ab00>] = 100.0, results: ~typing.Annotated[str, <typer.models.OptionInfo object at 0x736d6c58aa70>] = '', cov: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x736d6c58a2c0>] = False, cov_dir: ~types.Annotated[~pathlib.Path | None, <typer.models.OptionInfo object at 0x736d6c58a200>] | None = None, overwrite_cov_dir: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x736d6c58a170>] = False, cov_clean: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x736d6c5892a0>] = True, cov_report: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x736d6c58a5f0>] = False, cov_report_dir: ~types.Annotated[~pathlib.Path | None, <typer.models.OptionInfo object at 0x736d6c588bb0>] | None = None, overwrite_cov_report_dir: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x736d6c588520>] = False, project_name: ~typing.Annotated[str, <typer.models.OptionInfo object at 0x736d6c5887c0>] = 'Coverage Report', monitor: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x736d6c5886a0>] = False, monitor_interval: ~typing.Annotated[float, <typer.models.OptionInfo object at 0x736d6c58a2f0>] = 5.0, monitor_output: ~types.Annotated[~pathlib.Path | None, <typer.models.OptionInfo object at 0x736d6c58a950>] | None = None, monitor_hosts: ~types.Annotated[str | None, <typer.models.OptionInfo object at 0x736d6c589150>] | 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.