Skip to content

Commit 190edf1

Browse files
authored
Upgrade to using PyTest 7 (#738)
Fixes #660
1 parent 67188c4 commit 190edf1

File tree

6 files changed

+66
-32
lines changed

6 files changed

+66
-32
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
* Added rudimentary support for `clojure.stacktrace` with `print-cause-trace` (part of #721)
1010
* Added support for `bytes` literals using a `#b` prefix (#732)
1111

12+
### Changed
13+
* Basilisp now supports PyTest 7.0+ (#660)
14+
1215
### Fixed
1316
* Fix issue with `case` evaluating all of its clauses expressions (#699)
1417
* Fix issue with relative paths dropping their first character on MS-Windows (#703)

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,15 @@ python-dateutil = "^2.8.1"
3838
readerwriterlock = "^1.0.8"
3939

4040
astor = { version = "^0.8.1", python = "<3.9", optional = true }
41-
pytest = { version = "^6.2.5", optional = true }
41+
pytest = { version = "^7.0.0", optional = true }
4242
pygments = { version = "^2.9.0", optional = true }
4343

4444
[tool.poetry.dev-dependencies]
4545
black = "*"
4646
docutils = "*"
4747
isort = "*"
4848
pygments = "*"
49-
pytest = "^6.2.5"
49+
pytest = "^7.0.0"
5050
pytest-pycharm = "*"
5151
sphinx = "*"
5252
sphinx-rtd-theme = "*"

src/basilisp/contrib/pytest/testrunner.py

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1+
import importlib.util
12
import inspect
3+
import os
24
import traceback
5+
from pathlib import Path
36
from types import GeneratorType
47
from typing import Callable, Iterable, Iterator, Optional, Tuple
58

6-
import py
79
import pytest
8-
from _pytest.config import Config
9-
from _pytest.main import Session
1010

1111
from basilisp import main as basilisp
1212
from basilisp.lang import keyword as kw
@@ -26,11 +26,11 @@ def pytest_configure(config):
2626
basilisp.bootstrap("basilisp.test")
2727

2828

29-
def pytest_collect_file(parent, path):
29+
def pytest_collect_file(file_path: Path, path, parent):
3030
"""Primary PyTest hook to identify Basilisp test files."""
31-
if path.ext == ".lpy":
32-
if path.basename.startswith("test_") or path.purebasename.endswith("_test"):
33-
return BasilispFile.from_parent(parent, fspath=path)
31+
if file_path.suffix == ".lpy":
32+
if file_path.name.startswith("test_") or file_path.name.endswith("_test"):
33+
return BasilispFile.from_parent(parent, fspath=path, path=file_path)
3434
return None
3535

3636

@@ -135,18 +135,43 @@ def teardown(self) -> None:
135135
self._teardowns = ()
136136

137137

138+
def _is_package(path: Path) -> bool:
139+
"""Return `True` if the given path refers to a Python or Basilisp package."""
140+
_, _, files = next(os.walk(path))
141+
for file in files:
142+
if file in {"__init__.lpy", "__init__.py"} or file.endswith(".lpy"):
143+
return True
144+
return False
145+
146+
147+
def _get_fully_qualified_module_name(file: Path) -> str:
148+
"""Return the fully qualified module name (from the import root) for a module given
149+
its location.
150+
151+
This works by traversing up the filesystem looking for the top-most package. From
152+
there, we derive a Python module name referring to the given module path."""
153+
top = None
154+
for p in file.parents:
155+
if _is_package(p):
156+
top = p
157+
else:
158+
break
159+
160+
if top is None or top == file.parent:
161+
return file.stem
162+
163+
root = top.parent
164+
elems = list(file.with_suffix("").relative_to(root).parts)
165+
if elems[-1] == "__init__":
166+
elems.pop()
167+
return ".".join(elems)
168+
169+
138170
class BasilispFile(pytest.File):
139171
"""Files represent a test module in Python or a test namespace in Basilisp."""
140172

141-
def __init__( # pylint: disable=too-many-arguments
142-
self,
143-
fspath: py.path.local,
144-
parent=None,
145-
config: Optional[Config] = None,
146-
session: Optional["Session"] = None,
147-
nodeid: Optional[str] = None,
148-
) -> None:
149-
super().__init__(fspath, parent, config, session, nodeid)
173+
def __init__(self, **kwargs) -> None:
174+
super().__init__(**kwargs)
150175
self._fixture_manager: Optional[FixtureManager] = None
151176

152177
@staticmethod
@@ -192,16 +217,21 @@ def teardown(self) -> None:
192217
assert self._fixture_manager is not None
193218
self._fixture_manager.teardown()
194219

220+
def _import_module(self) -> runtime.BasilispModule:
221+
modname = _get_fully_qualified_module_name(self.path)
222+
module = importlib.import_module(modname)
223+
assert isinstance(module, runtime.BasilispModule)
224+
return module
225+
195226
def collect(self):
196-
"""Collect all of the tests in the namespace (module) given.
227+
"""Collect all tests from the namespace (module) given.
197228
198229
Basilisp's test runner imports the namespace which will (as a side effect)
199-
collect all of the test functions in a namespace (represented by `deftest`
200-
forms in Basilisp). BasilispFile.collect fetches those test functions and
201-
generates BasilispTestItems for PyTest to run the tests."""
202-
filename = self.fspath.basename
203-
module = self.fspath.pyimport()
204-
assert isinstance(module, runtime.BasilispModule)
230+
collect the test functions in a namespace (represented by `deftest` forms in
231+
Basilisp). BasilispFile.collect fetches those test functions and generates
232+
BasilispTestItems for PyTest to run the tests."""
233+
filename = self.path.name
234+
module = self._import_module()
205235
ns = module.__basilisp_namespace__
206236
once_fixtures, each_fixtures = self._collected_fixtures(ns)
207237
self._fixture_manager = FixtureManager(once_fixtures)

tests/basilisp/importer_test.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
from unittest.mock import patch
88

99
import pytest
10-
from _pytest.monkeypatch import MonkeyPatch
11-
from _pytest.pytester import Testdir
1210

1311
from basilisp import importer as importer
1412
from basilisp.lang import runtime as runtime
@@ -35,7 +33,7 @@ def test_hook_imports():
3533
assert 1 == importer_counter()
3634

3735

38-
def test_demunged_import(testdir: Testdir):
36+
def test_demunged_import(pytester: pytest.Pytester):
3937
with TemporaryDirectory() as tmpdir:
4038
tmp_module = os.path.join(
4139
tmpdir, "long__AMP__namespace_name__PLUS__with___LT__punctuation__GT__.lpy"
@@ -85,19 +83,19 @@ def _ns_and_module(filename: str) -> Tuple[str, str]:
8583

8684
class TestImporter:
8785
@pytest.fixture
88-
def do_cache_namespaces(self, monkeypatch):
86+
def do_cache_namespaces(self, monkeypatch: pytest.MonkeyPatch):
8987
monkeypatch.setenv(importer._NO_CACHE_ENVVAR, "false")
9088

9189
@pytest.fixture
92-
def do_not_cache_namespaces(self, monkeypatch):
90+
def do_not_cache_namespaces(self, monkeypatch: pytest.MonkeyPatch):
9391
monkeypatch.setenv(importer._NO_CACHE_ENVVAR, "true")
9492

9593
@pytest.fixture
9694
def module_cache(self):
9795
return {name: module for name, module in sys.modules.items()}
9896

9997
@pytest.fixture
100-
def module_dir(self, monkeypatch: MonkeyPatch, module_cache):
98+
def module_dir(self, monkeypatch: pytest.MonkeyPatch, module_cache):
10199
with TemporaryDirectory() as tmpdir:
102100
cwd = os.getcwd()
103101
monkeypatch.chdir(tmpdir)

tests/basilisp/testrunner_test.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def run_result(self, pytester: Pytester) -> RunResult:
4040
(ex-info "This test will count as an error." {})))
4141
"""
4242
pytester.makefile(".lpy", test_testrunner=code)
43+
pytester.syspathinsert()
4344
yield pytester.runpytest()
4445
runtime.Namespace.remove(sym.symbol("test-testrunner"))
4546

@@ -165,6 +166,7 @@ def test_fixtures(pytester: Pytester):
165166
(is false))
166167
"""
167168
pytester.makefile(".lpy", test_fixtures=code)
169+
pytester.syspathinsert()
168170
result: pytester.RunResult = pytester.runpytest()
169171
result.assert_outcomes(passed=1, failed=1)
170172

@@ -216,5 +218,6 @@ def test_fixtures_with_errors(
216218
(is false))
217219
"""
218220
pytester.makefile(".lpy", test_fixtures_with_errors=code)
221+
pytester.syspathinsert()
219222
result: pytester.RunResult = pytester.runpytest()
220223
result.assert_outcomes(passed=passes, failed=failures, errors=errors)

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ setenv =
88
BASILISP_DO_NOT_CACHE_NAMESPACES = true
99
deps =
1010
coverage[toml]
11-
pytest~=6.2.5
11+
pytest >=7.0.0,<8.0.0
1212
pygments
1313
commands =
1414
coverage run \

0 commit comments

Comments
 (0)