conftest.py - pytest fixtures for testing

Imports

These are listed in the order prescribed by PEP 8.

Standard library

import subprocess
import sys
import io
 

Third-party imports

import coverage
import pytest
 

Local imports

Start code coverage here. The imports below load code that must be covered. This seems cleaner than other solutions (create a separate pytest plugin just for coverage, put coverage code in a conftest.py that’s imported before this one.)

cov = coverage.Coverage()
cov.start()

from CodeChat_Server.__main__ import get_client  # noqa: E402
 
 

Code coverage

Getting code coverage to work in tricky. This is because code coverage must be collected while running pytest and while running the webserver. Since these run in parallel, trying to create a single coverage data file doesn’t work. Therefore, we must set coverage’s parallel flag to True, so that each data file will be uniquely named. After pytest finishes, combine these two data files to produce a coverage result. While pytest-cov would be ideal, it overrides the parallel flag (sigh).

A simpler solution: invoke coverage run -m pytest, then coverage combine, then coverage report. I opted for this complexity, to make it easy to just invoke pytest and get coverage with no further steps. Output a coverage report when testing is done. See the docs.pytest_terminal_summary.

def pytest_terminal_summary(terminalreporter):
    cov.stop()
    cov.save()

Combine this (pytest) coverage with the webserver coverage. Use a new object, since the cov object is tried to the data file produced by the pytest run. Otherwise, the report is correct, but the resulting .coverage data file is empty.

    cov_all = coverage.Coverage()
    cov_all.combine()
 

Report on this combined data.

    f = io.StringIO()
    cov_all.report(file=f)
    terminalreporter.write(f.getvalue())
 
 

Fixtures

SUBPROCESS_SERVER_ARGS = [
    sys.executable,
    "-m",
    "coverage",
    "run",
    "-m",
    "CodeChat_Server",
]


@pytest.fixture
def run_server():
    subprocess.run(SUBPROCESS_SERVER_ARGS + ["start", "--coverage"], check=True)
    yield
    subprocess.run(SUBPROCESS_SERVER_ARGS + ["stop"], check=True)


@pytest.fixture
def editor_plugin(run_server):
    return get_client()