Skip to content

Commit

Permalink
Refactor case logic to common dataclass
Browse files Browse the repository at this point in the history
  • Loading branch information
blakeNaccarato committed Jan 24, 2024
1 parent 6b90c68 commit 2b28787
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 70 deletions.
10 changes: 9 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"jupyter.showVariableViewWhenDebugging": true,
//* Pylance
"python.languageServer": "Pylance",
"python.analysis.typeCheckingMode": "strict",
"python.analysis.autoFormatStrings": true,
"python.analysis.autoImportCompletions": true,
"python.analysis.completeFunctionParens": true,
Expand Down Expand Up @@ -130,8 +131,15 @@
"yaml.format.printWidth": 88,
//* ruff (PY, IPYNB)
"ruff.importStrategy": "fromEnvironment",
//? https://github.com/astral-sh/ruff-vscode/issues/232#issuecomment-1866961267
"ruff.lint.args": [
"--extend-exclude=/usr/**/*.py",
"--extend-exclude=/opt/**/*.py",
"--extend-exclude=c:/msys/**/*.py",
"--extend-exclude=c:/program*/**/*.py",
"--extend-exclude=c:Users/*/AppData/Local/Programs/Python/Python*/Lib/**/*.py"
],
"ruff.format.args": [],
"ruff.lint.args": [],
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.wordWrap": "off"
Expand Down
31 changes: 13 additions & 18 deletions tests/boilercv_tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,16 @@
from itertools import chain
from pathlib import Path
from types import SimpleNamespace
from typing import Any, NamedTuple
from typing import Any

import pytest
from _pytest.mark.structures import ParameterSet
from boilercore.notebooks.namespaces import NO_PARAMS, Params, get_nb_ns
from boilercore.notebooks.namespaces import get_nb_ns
from boilercore.paths import get_module_rel, walk_modules


def get_nb(exp: Path, name: str) -> Path:
return exp / f"{name}.ipynb"


class NsArgs(NamedTuple):
"""Indirect parameters for notebook namespace fixture."""

nb: Path
params: Params = NO_PARAMS
return (exp / name).with_suffix(".ipynb")


boilercv_dir = Path("src") / "boilercv"
Expand Down Expand Up @@ -70,19 +63,14 @@ class Case:

path: Path
"""Path to the notebook."""
suffix: str
id: str = "_" # noqa: A003
"""Test ID suffix."""
params: dict[str, Any] = field(default_factory=dict)
"""Parameters to pass to the notebook."""
results: dict[str, Any] = field(default_factory=dict)
"""Variable names to retrieve and optional expectations on their values."""

@property
def id(self) -> str: # noqa: A003 # Okay to shadow in this limited context
"""Test ID."""
return "_".join(
[p for p in (*self.path.with_suffix("").parts, self.suffix) if p]
)
marks: list[pytest.Mark] = field(default_factory=list)
"""Pytest marks."""

@property
def nb(self) -> str:
Expand Down Expand Up @@ -127,3 +115,10 @@ def normalize_cases(*cases: Case) -> Iterable[Case]:
c.results |= {r: None for r in all_results if r not in c.results}
c.results = dict(sorted(c.results.items()))
return cases


def parametrize_by_cases(*cases: Case):
"""Parametrize this test by cases."""
return pytest.mark.parametrize(
"ns", [pytest.param(c, marks=c.marks, id=c.id) for c in cases], indirect=["ns"]
)
34 changes: 25 additions & 9 deletions tests/boilercv_tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@
import pytest
import pytest_harvest
import seaborn as sns
from _pytest.python import Function
from boilercore import WarningFilter, filter_certain_warnings
from boilercore.notebooks.namespaces import get_cached_minimal_nb_ns
from boilercore.testing import get_session_path, unwrap_node
from boilercore.notebooks.namespaces import get_cached_nb_ns, get_ns_attrs
from boilercore.testing import get_session_path
from matplotlib.axis import Axis
from matplotlib.figure import Figure

from boilercv_tests import NsArgs
from boilercv_tests import Case, normalize_cases

# * -------------------------------------------------------------------------------- * #
# * Autouse
Expand Down Expand Up @@ -56,15 +57,28 @@ def _filter_certain_warnings():
# * Notebook namespaces


@pytest.fixture(scope="module", autouse=True)
def _get_ns_attrs(request):
module = request.module
cases = getattr(module, "CASES", [])
notebook_namespace_tests = (
node
for node in request.node.collect()
if isinstance(node, Function) and "ns" in node.fixturenames
)
for case, test in zip(cases, notebook_namespace_tests, strict=True):
name = getattr(module, test.originalname)
case.results |= {r: None for r in get_ns_attrs(name)}
normalize_cases(*cases)


@pytest.fixture()
@pytest_harvest.saved_fixture
def ns(request, fixture_stores) -> Iterator[SimpleNamespace]:
"""Notebook namespace."""
namespace: NsArgs = request.param
yield get_cached_minimal_nb_ns(
nb=namespace.nb.read_text(encoding="utf-8"),
receiver=unwrap_node(request.node),
params=namespace.params,
case: Case = request.param
yield get_cached_nb_ns(
nb=case.nb, params=case.params, attributes=case.results.keys()
)
update_fixture_stores(
fixture_stores,
Expand Down Expand Up @@ -149,7 +163,9 @@ def fixture_stores(fixture_store) -> FixtureStores:
# # https://github.com/smarie/python-pytest-harvest/issues/46#issuecomment-742367746
# # https://smarie.github.io/python-pytest-harvest/#pytest-x-dist

RESULTS_PATH = Path(f".xdist_harvested/{getpid()}")
HARVEST_ROOT = Path(".xdist_harvested")
HARVEST_ROOT.mkdir(exist_ok=True)
RESULTS_PATH = HARVEST_ROOT / str(getpid())
RESULTS_PATH.mkdir(exist_ok=True)


Expand Down
60 changes: 18 additions & 42 deletions tests/boilercv_tests/test_e230920_subcool.py
Original file line number Diff line number Diff line change
@@ -1,57 +1,26 @@
"""Tests for experiment `e230920_subcool`."""

from pathlib import Path
from typing import NamedTuple

import pytest
from boilercore.notebooks.namespaces import Params
from pandas.testing import assert_index_equal

from boilercv_tests import NsArgs, get_nb

EXP = "e230920_subcool"
from boilercv_tests import Case, get_nb, parametrize_by_cases

pytestmark = pytest.mark.slow


@pytest.fixture(scope="module")
def nss(fixtures):
return fixtures.ns.test_e230920_subcool
EXP = Path("src/boilercv/stages/experiments/e230920_subcool")
CASES: list[Case] = []


NO_PARAMS = {}
NO_MARKS = []
def C(name: str, *args, **kwds) -> Case: # noqa: N802 # A classy function
"""Shorthand for cases."""
case = Case(get_nb(EXP, name), *args, **kwds)
CASES.append(case)
return case


class P(NamedTuple):
"""Local test parameter shorthand."""

nb: str
params: Params = NO_PARAMS
id: str = "_" # noqa: A003
marks: list[pytest.Mark] = NO_MARKS


def _parametrize(*params: P):
"""Local test parametrization shorthand."""
return pytest.mark.parametrize(
"ns",
[
pytest.param(
NsArgs(
nb=get_nb(Path(f"src/boilercv/stages/experiments/{EXP}"), p.nb),
params=p.params,
),
marks=p.marks,
id=p.id,
)
for p in params
],
indirect=["ns"],
)


@_parametrize(P("find_centers"))
@parametrize_by_cases(C("find_centers"))
def test_centers_index(ns):
assert_index_equal(
ns.trackpy_centers.columns,
Expand All @@ -64,17 +33,24 @@ def test_centers_index(ns):
raises=AssertionError,
reason="Radius estimate cannot account for large and small bubbles alike",
)
@_parametrize(P("find_collapse", {"TIME": "2023-09-20T17:14:18"}, "small_bubbles"))
@parametrize_by_cases(
C("find_collapse", "small_bubbles", {"TIME": "2023-09-20T17:14:18"})
)
def test_find_collapse(ns):
objects = ns.nondimensionalized_departing_long_lived_objects
assert objects["Dimensionless bubble diameter"].min() < 0.2


@_parametrize(P("get_thermal_data"))
@parametrize_by_cases(C("get_thermal_data"))
def test_get_thermal_data(ns):
assert ns.data.subcool.mean() == pytest.approx(3.65, abs=0.01, rel=0.01)


@pytest.fixture(scope="module")
def nss(fixtures):
return fixtures.ns.test_e230920_subcool


def test_synthesis(nss, plt):
_, axes = plt.subplots(1, 3)
axes = iter(axes)
Expand Down

0 comments on commit 2b28787

Please sign in to comment.