Skip to content

Commit

Permalink
Merge pull request #141 from ev-br/alt_checker
Browse files Browse the repository at this point in the history
ENH: make OutputChecker pluggable
  • Loading branch information
ev-br authored Mar 20, 2024
2 parents 4e33c90 + af3d5ae commit 2af7ffa
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 9 deletions.
5 changes: 4 additions & 1 deletion scpdt/impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class DTConfig:
a string. If not empty, the string value is used as the skip reason.
"""
def __init__(self, *, # DTChecker configuration
CheckerKlass=None,
default_namespace=None,
check_namespace=None,
rndm_markers=None,
Expand All @@ -108,6 +109,8 @@ def __init__(self, *, # DTChecker configuration
pytest_extra_xfail=None,
):
### DTChecker configuration ###
self.CheckerKlass = CheckerKlass or DTChecker

# The namespace to run examples in
self.default_namespace = default_namespace or {}

Expand Down Expand Up @@ -340,7 +343,7 @@ def __init__(self, checker=None, verbose=None, optionflags=None, config=None):
if config is None:
config = DTConfig()
if checker is None:
checker = DTChecker(config)
checker = config.CheckerKlass(config)
self.nameerror_after_exception = config.nameerror_after_exception
if optionflags is None:
optionflags = config.optionflags
Expand Down
10 changes: 4 additions & 6 deletions scpdt/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,6 @@ def collect(self):
runner = _get_runner(self.config,
verbose=False,
optionflags=optionflags,
checker=DTChecker(config=self.config.dt_config)
)

# strategy='api': discover doctests in public, non-deprecated objects in module
Expand All @@ -243,7 +242,7 @@ def collect(self):
yield pydoctest.DoctestItem.from_parent(
self, name=test.name, runner=runner, dtest=test
)


class DTTextfile(DoctestTextfile):
"""
Expand All @@ -268,7 +267,6 @@ def collect(self):
runner = _get_runner(self.config,
verbose=False,
optionflags=optionflags,
checker=DTChecker(config=self.config.dt_config)
)

# Plug in an instance of `DTParser` which parses the doctest examples from the text file and
Expand All @@ -283,7 +281,7 @@ def collect(self):
)


def _get_runner(config, checker, verbose, optionflags):
def _get_runner(config, verbose, optionflags):
"""
Override function to return an instance of PytestDTRunner.
Expand Down Expand Up @@ -336,5 +334,5 @@ def report_unexpected_exception(self, out, test, example, exc_info):
out.append(failure)
else:
raise failure
return PytestDTRunner(checker=checker, verbose=verbose, optionflags=optionflags, config=config.dt_config)

return PytestDTRunner(verbose=verbose, optionflags=optionflags, config=config.dt_config)
35 changes: 35 additions & 0 deletions scpdt/tests/test_pytest_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,38 @@ def test_local_file_cases(pytester):
python_file = Path(path_str)
result = pytester.inline_run(python_file, "--doctest-modules")
assert result.ret == pytest.ExitCode.OK


def test_alt_checker(pytester):
"""Test an alternative Checker."""

# create a temporary conftest.py file
pytester.makeconftest(
"""
import doctest
from scpdt.conftest import dt_config
class Vanilla(doctest.OutputChecker):
def __init__(self, config):
pass
dt_config.CheckerKlass = Vanilla
"""
)

# create a temporary pytest test file
f = pytester.makepyfile(
"""
def func():
'''
>>> 2 / 3 # fails with vanilla doctest.OutputChecker
0.667
'''
pass
"""
)

# run all tests with pytest
result = pytester.inline_run(f, '--doctest-modules')
assert result.ret == pytest.ExitCode.TESTS_FAILED

28 changes: 26 additions & 2 deletions scpdt/tests/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

import pytest

from . import failure_cases as module, finder_cases as finder_module
from .. import DTFinder, DTRunner, DebugDTRunner
from . import (failure_cases as module,
finder_cases as finder_module,
module_cases)
from .. import DTFinder, DTRunner, DebugDTRunner, DTConfig


### Smoke test DTRunner methods. Mainly to check that they are runnable.
Expand Down Expand Up @@ -79,3 +81,25 @@ def test_debug_runner_exception(self):
# exception carries the original test
assert orig_exception.test is tests[0]


class VanillaOutputChecker(doctest.OutputChecker):
"""doctest.OutputChecker to drop in for DTChecker.
LSP break: OutputChecker does not have __init__,
here we add it to agree with DTChecker.
"""
def __init__(self, config):
pass

class TestCheckerDropIn:
"""Test DTChecker and vanilla doctest OutputChecker being drop-in replacements.
"""
def test_vanilla_checker(self):
config = DTConfig(CheckerKlass=VanillaOutputChecker)
runner = DebugDTRunner(config=config)
tests = DTFinder().find(module_cases.func)

with pytest.raises(doctest.DocTestFailure) as exc:
for t in tests:
runner.run(t)

0 comments on commit 2af7ffa

Please sign in to comment.