blob: fb2c5bd0149beddd19c53dd0e63b3173fdd5b189 [file] [log] [blame]
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
"""
Pytest auto configuration.
This module is run automatically by pytest, to define and enable fixtures.
"""
from __future__ import annotations
import os
import sys
import sysconfig
import warnings
from pathlib import Path
from typing import Iterator
import pytest
from coverage import env
from coverage.files import set_relative_directory
# Pytest will rewrite assertions in test modules, but not elsewhere.
# This tells pytest to also rewrite assertions in these files:
pytest.register_assert_rewrite("tests.coveragetest")
pytest.register_assert_rewrite("tests.helpers")
# Pytest can take additional options:
# $set_env.py: PYTEST_ADDOPTS - Extra arguments to pytest.
pytest_plugins = [
"tests.balance_xdist_plugin",
"tests.select_plugin",
]
@pytest.fixture(autouse=True)
def set_warnings() -> None:
"""Configure warnings to show while running tests."""
warnings.simplefilter("default")
warnings.simplefilter("once", DeprecationWarning)
# Warnings to suppress:
# How come these warnings are successfully suppressed here, but not in pyproject.toml??
if env.PYPY:
# pypy3 warns about unclosed files a lot.
warnings.filterwarnings("ignore", r".*unclosed file", category=ResourceWarning)
# Don't warn about unclosed SQLite connections.
# We don't close ":memory:" databases because we don't have a way to connect
# to them more than once if we close them. In real coverage.py uses, there
# are only a couple of them, but our test suite makes many and we get warned
# about them all.
# Python3.13 added this warning, but the behavior has been the same all along,
# without any reported problems, so just quiet the warning.
# https://github.com/python/cpython/issues/105539
warnings.filterwarnings("ignore", r"unclosed database", category=ResourceWarning)
@pytest.fixture(autouse=True)
def reset_sys_path() -> Iterator[None]:
"""Clean up sys.path changes around every test."""
sys_path = list(sys.path)
yield
sys.path[:] = sys_path
@pytest.fixture(autouse=True)
def reset_environment() -> Iterator[None]:
"""Make sure a test setting an envvar doesn't leak into another test."""
old_environ = os.environ.copy()
yield
os.environ.clear()
os.environ.update(old_environ)
@pytest.fixture(autouse=True)
def reset_filesdotpy_globals() -> Iterator[None]:
"""coverage/files.py has some unfortunate globals. Reset them every test."""
set_relative_directory()
yield
WORKER = os.getenv("PYTEST_XDIST_WORKER", "none")
def pytest_sessionstart() -> None:
"""Run once at the start of the test session."""
# Only in the main process...
if WORKER == "none":
# Create a .pth file for measuring subprocess coverage.
pth_dir = find_writable_pth_directory()
assert pth_dir
(pth_dir / "subcover.pth").write_text("import coverage; coverage.process_startup()\n")
# subcover.pth is deleted by pytest_sessionfinish below.
def pytest_sessionfinish() -> None:
"""Hook the end of a test session, to clean up."""
# This is called by each of the workers and by the main process.
if WORKER == "none":
for pth_dir in possible_pth_dirs(): # pragma: part covered
pth_file = pth_dir / "subcover.pth"
if pth_file.exists():
pth_file.unlink()
def possible_pth_dirs() -> Iterator[Path]:
"""Produce a sequence of directories for trying to write .pth files."""
# First look through sys.path, and if we find a .pth file, then it's a good
# place to put ours.
for pth_dir in map(Path, sys.path): # pragma: part covered
pth_files = list(pth_dir.glob("*.pth"))
if pth_files:
yield pth_dir
# If we're still looking, then try the Python library directory.
# https://github.com/nedbat/coveragepy/issues/339
yield Path(sysconfig.get_path("purelib")) # pragma: cant happen
def find_writable_pth_directory() -> Path | None:
"""Find a place to write a .pth file."""
for pth_dir in possible_pth_dirs(): # pragma: part covered
try_it = pth_dir / f"touch_{WORKER}.it"
try:
try_it.write_text("foo")
except OSError: # pragma: cant happen
continue
os.remove(try_it)
return pth_dir
return None # pragma: cant happen