"""
Pytest configuration module.
pytest internals:
pytest_addoption -- add custom options to test a single subtask at a time
pytest_generate_tests -- generate tests programmatically from `tests.pickle`
pytest fixtures:
verbosity -- provide verbosity level for outputs
submission -- provide student submission
PYTEST_DONT_REWRITE
"""
from __future__ import annotations
__all__ = [
"pytest_addoption",
"pytest_generate_tests",
"pytest_sessionfinish",
"pytest_sessionstart",
"submission",
"verbosity",
]
import pathlib
import warnings
import pytest
import pytest_nbgrader
[docs]
def pytest_addoption(parser: pytest.Parser) -> None:
"""
Add custom options for test case location and auto-generation.
Parameters
----------
parser : _pytest.config.argparsing.Parser
The pytest argument parser.
"""
parser.addoption(
"--cases",
action="store",
dest="cases",
default=None,
help="specify location of test cases in yml format",
)
parser.addoption(
"--noauto",
action="store_false",
dest="auto",
default=True,
help="Do not generate tests with pytest-nbgrader",
)
[docs]
def pytest_sessionstart(session: pytest.Session) -> None:
"""
Collect manual and automatic test cases.
Parameters
----------
session : pytest.Session
The pytest session object.
"""
import yaml
# TODO: Collect yaml files with pytest_collection instead
cases = session.config.getoption("cases")
if cases is not None:
# load cases from yaml
if not pathlib.Path(cases).is_file():
raise FileNotFoundError(f"{cases!r} is not a file.")
with pathlib.Path(cases).open("rb") as f:
test_cases = yaml.unsafe_load(f)
session.config.option.test_cases = test_cases
if session.config.option.auto:
import uuid
test_file = pathlib.Path(f"test_auto_{uuid.uuid4()}.py")
test_file.symlink_to(pytest_nbgrader.harness.__file__)
session.config.option.auto = test_file
else:
session.config.option.test_cases = None
[docs]
def pytest_sessionfinish(session: pytest.Session) -> None:
"""
Unlink the previously generated tests file at session finish.
Parameters
----------
session : pytest.Session
The pytest session object.
"""
if isinstance(session.config.option.auto, pathlib.Path):
session.config.option.auto.unlink()
[docs]
def pytest_generate_tests(metafunc: pytest.Metafunc) -> None:
"""
Programmatically generate tests from deserialized test cases.
Parameters
----------
metafunc : pytest.Metafunc
The metafunc object for parametrizing test functions.
"""
cases = metafunc.config.getoption("test_cases")
if cases:
for fixture in ["prerequisites", "assertions", "cases"]:
if fixture in metafunc.fixturenames:
parameters = getattr(cases, fixture)
if isinstance(parameters, dict):
parameters = parameters.items()
metafunc.parametrize(fixture, parameters)
else:
warnings.warn(UserWarning("pytest-nbgrader: No data for automatic tests found."), stacklevel=2)
[docs]
@pytest.fixture
def verbosity(request: pytest.FixtureRequest) -> int:
"""
Inject verbosity from global config.
Parameters
----------
request : pytest.FixtureRequest
The pytest fixture request object.
Returns
-------
int
The verbosity level.
"""
return request.config.getoption("verbose")
[docs]
@pytest.fixture
def submission() -> object:
"""
Inject submission object into pytest as fixture.
Returns
-------
object
The stored student submission.
"""
return pytest_nbgrader.loader.Submission.submission