suite.selection

Suite-less test selection — resolve --tests / -m against collected tests.

This module holds the pure selection-resolution logic that otto test carried inline in otto.cli.test before it moved here (library-extraction Phase A): matching --tests names against every repo’s collected tests (resolve_selection()) and narrowing repos by a marker expression alone (repos_with_marker_matches()). Extracting it here lets a suite-less selection be resolved as a plain library call, independent of Typer.

This module never imports typer — the library raises library exceptions (UnknownSelectionError for a genuinely unknown test name); the CLI adapter in otto.cli.test owns the translation to typer.BadParameter.

class otto.suite.selection.SelectionMatch(repo: Repo, targets: list[str])

Bases: object

One repo’s resolved selection: the repo plus its matched pytest targets.

targets are absolute nodeids (from resolve_selection()) or absolute test-directory paths (from the -m-alone branch of otto.suite.run.run_selection()) — always non-empty, since callers only construct a SelectionMatch for a repo that matched.

repo : Repo
targets : list[str]
exception otto.suite.selection.UnknownSelectionError(message: str, *, param_hint: str = '--tests')

Bases: ValueError

A requested test name matched nothing despite a non-empty test universe.

Raised by resolve_selection() when at least one requested name went unmatched while there were collected tests to search — i.e. a genuine typo, not an empty selection. The message carries the same did-you-mean suggestions the CLI has always shown; param_hint names the CLI flag the selection came from so the CLI adapter can re-raise as typer.BadParameter with an identical rendering.

Subclasses ValueError, so callers that catch a generic ValueError for “nothing matched” must catch this first to distinguish the typo case.

otto.suite.selection.resolve_selection(repos: list[Repo], names: list[str], markers: str) list[SelectionMatch]

Resolve –tests names to exact nodeids, one entry per matching repo.

A bare name matches every collected test with that function name (all parametrizations); Class::name restricts to one suite. Unknown names raise UnknownSelectionError with did-you-mean suggestions — never a silent empty run — but only when there was an actual test universe to search (at least one collected test across every searched repo). With no repos, or repos that collected nothing, there is nothing to suggest, so this returns an empty list instead: the generic “no tests matched the selection” failure (raised by callers such as otto.suite.run.run_selection()) is the more honest message than a did-you-mean hint with no hints to offer.

otto.suite.selection.repos_with_marker_matches(repos: list[Repo], markers: str) list[Repo]

Filter to repos whose collection has >=1 item matching markers.

Used by the -m-alone branch of run_selection so a repo whose suites don’t carry the given marker never gets a pytest session of its own — such a session would collect nothing and exit 5 (NO_TESTS_COLLECTED), which previously failed the whole multi-repo run via worst = max(worst, rc) even when every other repo matched fine.