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 JSON snapshot of all metrics and events 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 snapshot,.dbwrites a SQLite database loadable viaotto monitor --file.--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
-
class otto.cli.test.TestRunOptions(markers: str =
'', tests: str ='', iterations: int =0, duration: int =0, threshold: float =100.0, results: str ='', cov: bool =False, cov_dir: Path | None =None, cov_clean: bool =True, cov_report: bool =False, cov_report_dir: Path | None =None, overwrite_cov_report_dir: bool =False, project_name: str ='Coverage Report', monitor: bool =False, monitor_interval: float =5.0, monitor_output: Path | None =None, monitor_hosts: str | None =None)¶ Bases:
objectShared
otto testrun options, set by the suite_app callback and read byrun_suite.Stored in Typer
ctx.meta(shared across the whole context chain by click’s design) rather thanctx.obj(whose parent->subcommand propagation broke under click 8.3).
- otto.cli.test.resolve_suite(suite: str, repos: list[Repo]) str¶
Expand a sut_dir-relative suite path to an absolute path for pytest.
- otto.cli.test.run_suite(suite_class: type, suite_file: str, opts_instance: object | None, ctx: Context) None¶
Execute a registered suite via pytest.main().
Runner options (
--markers,--iterations,--duration,--threshold,--results) and coverage options (--cov/--cov-clean) are read from theTestRunOptionstheotto testcallback stored inctx.meta[RUN_OPTIONS_KEY]. The context is passed in by the suite runner (Typer injects it), so this function never reaches into a global context stack.
- otto.cli.test.run_selection(ctx: Context) None¶
Run a suite-less selection (–tests and/or -m) — one session per repo.
- 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 0x730178f48280>] = False, list_markers: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x730178f481f0>] = False, list_tests: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x730178f48160>] = False, markers: ~typing.Annotated[str, <typer.models.OptionInfo object at 0x730178f480d0>] = '', tests: ~typing.Annotated[str, <typer.models.OptionInfo object at 0x730178f48b20>] = '', iterations: ~typing.Annotated[int, <typer.models.OptionInfo object at 0x730178f4bb20>] = 0, duration: ~typing.Annotated[int, <typer.models.OptionInfo object at 0x730178f49690>] = 0, threshold: ~typing.Annotated[float, <typer.models.OptionInfo object at 0x730178f49990>] = 100.0, results: ~typing.Annotated[str, <typer.models.OptionInfo object at 0x730178f49a20>] = '', cov: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x730178f4ab00>] = False, cov_dir: ~types.Annotated[~pathlib.Path | None, <typer.models.OptionInfo object at 0x730178f48070>] | None = None, overwrite_cov_dir: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x730178f48940>] = False, cov_clean: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x730178f48ac0>] = True, cov_report: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x730178f497b0>] = False, cov_report_dir: ~types.Annotated[~pathlib.Path | None, <typer.models.OptionInfo object at 0x730178f49750>] | None = None, overwrite_cov_report_dir: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x730178f498a0>] = False, project_name: ~typing.Annotated[str, <typer.models.OptionInfo object at 0x730178f49450>] = 'Coverage Report', monitor: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x730178f493c0>] = False, monitor_interval: ~typing.Annotated[float, <typer.models.OptionInfo object at 0x730178eb3c40>] = 5.0, monitor_output: ~types.Annotated[~pathlib.Path | None, <typer.models.OptionInfo object at 0x730178f49330>] | None = None, monitor_hosts: ~types.Annotated[str | None, <typer.models.OptionInfo object at 0x730178f49270>] | 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.