Skip to content

Marks

A mark is a decorator attaching a fixed value to the underlying function: fixture or test.

For clarity, skip, xfail or timeout applied twice will raise TypeError. Othr marks can be stacked freely.

Skipping and expected failures

voci.skip

skip(reason: str) -> Callable[[F], F]

Always skip this test.

voci.skipif

skipif(
    condition: bool | Callable[[], bool], *, reason: str
) -> Callable[[F], F]

Skip when condition holds. Stacks; any one truthy condition skips.

voci.xfail

xfail(
    reason: str,
    *,
    condition: bool | Callable[[], bool] = True,
    strict: bool = False,
    raises: ExcTypes | None = None,
) -> Callable[[F], F]

Record an expected-failure mark, with reason. A call phase that raises reports XFAILED instead of FAILED; one that passes reports XPASSED, or fails the test outright if strict is set. raises, if given, narrows which exception type counts as the expected failure -- any other exception still reports FAILED. condition expects the failure only where it holds, read the way skipif's is: a bool, or a zero-argument callable for one that must not be computed during import.

Selection and execution

You can assign string labels to tests, which can then be selected via the CLI.

voci.tag

tag(*names: str) -> Callable[[F], F]

Attach selection tags to a test, exposed as TestInfo.tags and selectable with -m.

Execution isolation

For tests that may not play nicely with concurrency, voci provides isolated execution marks.

voci.timeout

timeout(seconds: float) -> Callable[[F], F]

Record a per-test timeout, in seconds, overriding the suite-wide --timeout budget for this test alone.

voci.solo

solo(fn: F) -> F

Mark this test to run alone, with nothing else scheduled alongside it.

Applied bare, with no parentheses. Such a test is admitted only once nothing else is running, and blocks every other test's admission until it finishes.

voci.isolated

isolated(fn: F) -> F

Mark this test to run alone in a subprocess, on its own fresh interpreter and loop.

Applied bare, with no parentheses. Admitted through the same concurrency/exclusive=/solo gate as every other test -- the subprocess is what's fresh, not the scheduling. A module-scope fixture this test shares with in-process siblings is set up and torn down separately inside the subprocess, not shared with them.

Warnings

voci.filterwarnings

filterwarnings(*specs: str) -> Callable[[F], F]

Filter the warnings this test raises, with action:message:category:module:lineno specs -- "error", "ignore::DeprecationWarning", "error:.*legacy:UserWarning".

Each spec is layered over the run's own filterwarnings/-W filters, and the last one to match a warning is the one that decides it: later specs win over earlier ones, and an outer decorator's win over an inner one's. error raises the warning where it was warned, failing the phase that raised it. Every spec is parsed here, so a bad one is an error at import.

See Warnings for the filter grammar and how the three tiers of filters are layered.

Parametrization

voci.parametrize

parametrize(
    argnames: str | Sequence[str],
    argvalues: Sequence[object],
    *,
    ids: Sequence[str]
    | Callable[[object], str | None]
    | None = None,
) -> Callable[[F], F]

Record a parametrize mark. argnames is "a,b" or ["a", "b"]; with one name, each entry of argvalues is that value, with several, each entry is a tuple aligned to the names. Stacked decorators combine in a stable, defined order: outermost varies slowest. An entry written as case(value, marks=...) carries marks for that one case, folded into the test's own for the record that case expands into. Expanded into one test per case at collection.

voci.case

case(
    *values: object,
    marks: MarkDecorator | Sequence[MarkDecorator] = (),
) -> ParamCase

One @parametrize case, carrying marks that reach that case alone.

marks are the very decorators a test carries -- case(2, marks=voci.skip("flaky")), case(3, marks=[voci.xfail("known"), voci.tag("slow")]) -- and mean for this case what they would mean written above the def. Every mark but parametrize works this way; the test's own marks and the case's are folded together, the case's winning where only one of skip, xfail or timeout can stand.

values are that case's values, one per argname, exactly as they would be written without the wrapper: case(1, 2) for two argnames, case((1, 2)) for one that takes a tuple.

Stopping a test from within

Two exception classes are provided to force a test result from within the body.

voci.Skipped

Bases: BaseException

Raise to skip the running test immediately -- the runtime counterpart of @voci.skip/@voci.skipif, which decide once at collection instead.

Raised from a fixture or from the test body, it reports the test as skipped with str(self) as the reason; whatever ran before the raise already ran, and fixtures already acquired are torn down normally. A BaseException, not an Exception, so a test or fixture body's except Exception: does not swallow the skip signal.

voci.Failed

Bases: BaseException

Raise to fail the running test immediately, with a message rather than an assertion -- a named spelling of assert False, msg.

A BaseException, not an Exception, for the same reason Skipped is: a broad except Exception: around a test's own code must not swallow a deliberate failure signal.