Skip to content

Built-in fixtures

voci ships a handful of fixtures for the things most suites need from a runner. Use them just like any other fixture: tmp: Annotated[Path, Depends(voci.tmp_path)].

Temporary directories

Every directory these allocate lives under one root per run, a fresh numbered directory in the platform temp directory that keeps the last three runs' contents for a post-mortem. The --basetemp flag puts the root somewhere else.

voci.tmp_path module-attribute

tmp_path: Path

A directory unique to this test, by construction: basetemp/<sanitized-test-id>.

voci.tmp_path_factory module-attribute

tmp_path_factory: TmpPathFactory

voci.TmpPathFactory

Session-scoped factory for temporary directories under the run's basetemp root.

mktemp numbers from a per-basename counter rather than by scanning for a free number, so two tests calling it concurrently never collide.

getbasetemp

getbasetemp() -> Path

The root directory every path this factory hands out lives under.

mktemp

mktemp(basename: str, *, numbered: bool = True) -> Path

Create and return a fresh directory under the basetemp root.

numbered appends a per-basename counter, so repeated calls with one basename each get their own directory; numbered=False uses basename as given and raises FileExistsError if that directory is already there.

py.path compatibility

tmpdir and tmpdir_factory are tmp_path and tmp_path_factory behind the py.path.local surface, for suites whose helpers call .join, .strpath or .write on the directory they are given. A test asking for both a tmp_path and a tmpdir gets the same directory under two types.

voci.tmpdir module-attribute

tmpdir: LegacyPath

This test's tmp_path, wrapped as a LegacyPath.

voci.tmpdir_factory module-attribute

tmpdir_factory: LegacyTmpPathFactory

The session's tmp_path_factory, wrapped as a LegacyTmpPathFactory.

voci.LegacyPath

A pathlib.Path wrapped in the py.path.local surface pytest's own tmpdir hands out: .join, .strpath, /, .write and .mkdir.

Any other attribute resolves on the wrapped Path, so one the two types share (.exists(), .read_text()) behaves as Path's and one that only py.path.local had raises AttributeError.

strpath property

strpath: str

join

join(*args: str) -> LegacyPath

mkdir

mkdir(*args: str) -> LegacyPath

Create and return the directory .join(*args) names.

Shadows Path.mkdir, whose mode/parents/exist_ok keyword arguments this class does not carry over.

write

write(
    data: str | bytes,
    mode: str = "w",
    *,
    ensure: bool = False,
) -> None

Write data to the path in mode, creating parent directories first if ensure.

mode is open()'s own mode string -- "w" truncates, "a" appends, a "b" in it picks binary -- and data's type has to agree with it, exactly as py.path.local.write requires.

voci.LegacyTmpPathFactory

TmpPathFactory, handing back LegacyPath instead of Path.

getbasetemp

getbasetemp() -> LegacyPath

mktemp

mktemp(
    basename: str, *, numbered: bool = True
) -> LegacyPath

Captured output

voci.capture module-attribute

capture: Capture

voci.Capture

The current test's captured stdout and stderr.

A live view, not a snapshot: .out/.err read the capture buffers on every access, so text written after this fixture was injected is visible immediately.

err property

err: str

out property

out: str

Captured log records

voci.log_records module-attribute

log_records: LogRecords

voci.LogRecords

The logging records captured for the current test.

A live view, not a copy: .records/.messages/.text/.record_tuples reflect records logged after this fixture was injected, and .clear() empties the container the capture handler goes on appending to.

messages property

messages: Sequence[str]

record_tuples property

record_tuples: Sequence[tuple[str, int, str]]

(logger name, level, message) for each captured record, for assertion comparison.

records property

records: Sequence[LogRecord]

text property

text: str

Every captured record formatted, one per line.

clear

clear() -> None

Empty the captured records. A record logged after this call is captured as normal.

set_level

set_level(
    level: int | str, *, logger: str | None = None
) -> AbstractContextManager[None]

Raise or lower a logger's level for the duration of the block, then restore it.

logger=None targets the root logger, which every logger without its own explicit level inherits from (Logger.getEffectiveLevel's walk up .parent). level and logger are validated before the context manager is constructed or entered.

Logger levels are process-global, so a concurrent test logging to a logger of the same name is affected too: raising a level lets it capture more than it asked for, and lowering one can leave its own set_level block empty.

Test metadata

voci.test_info module-attribute

test_info: TestInfo

voci.TestInfo dataclass

Read-only facts about the test that is running.

id instance-attribute

id: str

relative/path/test_file.py::test_name[param-id].

tags instance-attribute

tags: tuple[str, ...]

Every name attached to this test by @voci.tag(...).

timeout instance-attribute

timeout: float | None

The budget this test is actually held to, or None for no limit: a per-test @voci.timeout(...) mark if it carries one, else the suite's --timeout.

worker instance-attribute

worker: int

Which concurrency slot (0..concurrency-1) this test is occupying.