Skip to content

Commit

Permalink
Fix PyTest hell hopefully
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisrink10 committed Dec 27, 2023
1 parent c09d5f2 commit dea2417
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 33 deletions.
74 changes: 41 additions & 33 deletions src/basilisp/contrib/pytest/testrunner.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import importlib.util
import inspect
import sys
import os
import traceback
from pathlib import Path
from types import GeneratorType
from typing import Callable, Iterable, Iterator, Optional, Tuple

import pytest
from _pytest.nodes import Node

from basilisp import main as basilisp
from basilisp.importer import BasilispImporter
from basilisp.lang import keyword as kw
from basilisp.lang import map as lmap
from basilisp.lang import runtime as runtime
Expand Down Expand Up @@ -137,26 +135,43 @@ def teardown(self) -> None:
self._teardowns = ()


def _is_package(path: Path) -> bool:
"""Return `True` if the given path refers to a Python or Basilisp package."""
_, _, files = next(os.walk(path))
for file in files:
if file in {"__init__.lpy", "__init__.py"} or file.endswith(".lpy"):
return True
return False


def _get_fully_qualified_module_name(file: Path) -> str:
"""Return the fully qualified module name (from the import root) for a module given
its location.
This works by traversing up the filesystem looking for the top-most package. From
there, we derive a Python module name referring to the given module path."""
top = None
for p in file.parents:
if _is_package(p):
top = p
else:
break

if top is None or top == file.parent:
return file.stem

root = top.parent
elems = list(file.with_suffix("").relative_to(root).parts)
if elems[-1] == "__init__":
elems.pop()
return ".".join(elems)


class BasilispFile(pytest.File):
"""Files represent a test module in Python or a test namespace in Basilisp."""

def __init__( # pylint: disable=too-many-arguments
self,
path: Path,
name: Optional[str] = None,
parent: Optional[Node] = None,
config: Optional[pytest.Config] = None,
session: Optional[pytest.Session] = None,
nodeid: Optional[str] = None,
) -> None:
super().__init__(
path=path,
name=name,
parent=parent,
config=config,
session=session,
nodeid=nodeid,
)
def __init__(self, **kwargs) -> None: # pylint: disable=too-many-arguments
super().__init__(**kwargs)
self._fixture_manager: Optional[FixtureManager] = None

@staticmethod
Expand Down Expand Up @@ -202,26 +217,19 @@ def teardown(self) -> None:
assert self._fixture_manager is not None
self._fixture_manager.teardown()

@property
def _importer(self) -> BasilispImporter:
return next(l for l in sys.meta_path if isinstance(l, BasilispImporter))

def _import_module(self) -> runtime.BasilispModule:
spec = self._importer.find_spec(self.path.stem, [str(self.path.parent)])
if spec is None:
raise ImportError(f"Failed to import test module '{self.name}'")
module = importlib.util.module_from_spec(spec)
modname = _get_fully_qualified_module_name(self.path)
module = importlib.import_module(modname)
assert isinstance(module, runtime.BasilispModule)
spec.loader.exec_module(module)
return module

def collect(self):
"""Collect all of the tests in the namespace (module) given.
"""Collect all tests from the namespace (module) given.
Basilisp's test runner imports the namespace which will (as a side effect)
collect all of the test functions in a namespace (represented by `deftest`
forms in Basilisp). BasilispFile.collect fetches those test functions and
generates BasilispTestItems for PyTest to run the tests."""
collect the test functions in a namespace (represented by `deftest` forms in
Basilisp). BasilispFile.collect fetches those test functions and generates
BasilispTestItems for PyTest to run the tests."""
filename = self.path.name
module = self._import_module()
ns = module.__basilisp_namespace__
Expand Down
3 changes: 3 additions & 0 deletions tests/basilisp/testrunner_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def run_result(self, pytester: Pytester) -> RunResult:
(ex-info "This test will count as an error." {})))
"""
pytester.makefile(".lpy", test_testrunner=code)
pytester.syspathinsert()
yield pytester.runpytest()
runtime.Namespace.remove(sym.symbol("test-testrunner"))

Expand Down Expand Up @@ -165,6 +166,7 @@ def test_fixtures(pytester: Pytester):
(is false))
"""
pytester.makefile(".lpy", test_fixtures=code)
pytester.syspathinsert()
result: pytester.RunResult = pytester.runpytest()
result.assert_outcomes(passed=1, failed=1)

Expand Down Expand Up @@ -216,5 +218,6 @@ def test_fixtures_with_errors(
(is false))
"""
pytester.makefile(".lpy", test_fixtures_with_errors=code)
pytester.syspathinsert()
result: pytester.RunResult = pytester.runpytest()
result.assert_outcomes(passed=passes, failed=failures, errors=errors)

0 comments on commit dea2417

Please sign in to comment.