diff --git a/scpdt/impl.py b/scpdt/impl.py index e3ed910..b5eb241 100644 --- a/scpdt/impl.py +++ b/scpdt/impl.py @@ -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, @@ -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 {} @@ -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 diff --git a/scpdt/plugin.py b/scpdt/plugin.py index d6a5ed7..c888f53 100644 --- a/scpdt/plugin.py +++ b/scpdt/plugin.py @@ -213,7 +213,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 @@ -228,7 +227,7 @@ def collect(self): yield pydoctest.DoctestItem.from_parent( self, name=test.name, runner=runner, dtest=test ) - + class DTTextfile(DoctestTextfile): """ @@ -253,7 +252,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 @@ -268,7 +266,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. @@ -321,5 +319,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) diff --git a/scpdt/tests/test_pytest_configuration.py b/scpdt/tests/test_pytest_configuration.py index 797501d..f5d24ab 100644 --- a/scpdt/tests/test_pytest_configuration.py +++ b/scpdt/tests/test_pytest_configuration.py @@ -42,3 +42,38 @@ def test_local_file_cases(pytester): python_file = PosixPath(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 + diff --git a/scpdt/tests/test_runner.py b/scpdt/tests/test_runner.py index d2893dd..742efb3 100644 --- a/scpdt/tests/test_runner.py +++ b/scpdt/tests/test_runner.py @@ -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. @@ -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) +