Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

After adding a doctest to a file, setup() is called if defined in the same file #10825

Closed
Akuli opened this issue Mar 19, 2023 · 2 comments
Closed
Labels
plugin: doctests related to the doctests builtin plugin plugin: nose related to the nose integration builtin plugin type: bug problem that needs to be addressed

Comments

@Akuli
Copy link
Contributor

Akuli commented Mar 19, 2023

Suppose your project contains a file with a function named setup(). You run tests, everything works. Then you add a doctest to the said file and run your tests again. Now they fail, because pytest decided to call the setup() function automagically.

Minimal reproducer:

def foo():
    """
    >>> foo()
    'bar'
    """
    return "bar"

def setup():
    raise RuntimeError("Setup called!!! Oh No")
$ pip install pytest==6.*
$ pytest -q --doctest-modules asd.py
.                                                                        [100%]
1 passed in 0.02s
$ pip install pytest==7.*
$ pytest -q --doctest-modules asd.py
...very long error message...
The long error message
(e) akuli@akuli-desktop:/tmp$ pytest -q --doctest-modules asd.py
E                                                                        [100%]
==================================== ERRORS ====================================
_____________________ ERROR at setup of [doctest] asd.foo ______________________

cls = <class '_pytest.runner.CallInfo'>
func = <function call_runtest_hook.<locals>.<lambda> at 0x7f7fab9d93a0>
when = 'setup'
reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)

    @classmethod
    def from_call(
        cls,
        func: "Callable[[], TResult]",
        when: "Literal['collect', 'setup', 'call', 'teardown']",
        reraise: Optional[
            Union[Type[BaseException], Tuple[Type[BaseException], ...]]
        ] = None,
    ) -> "CallInfo[TResult]":
        """Call func, wrapping the result in a CallInfo.
    
        :param func:
            The function to call. Called without arguments.
        :param when:
            The phase in which the function is called.
        :param reraise:
            Exception or exceptions that shall propagate if raised by the
            function, instead of being wrapped in the CallInfo.
        """
        excinfo = None
        start = timing.time()
        precise_start = timing.perf_counter()
        try:
>           result: Optional[TResult] = func()

e/lib/python3.9/site-packages/_pytest/runner.py:339: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

>       lambda: ihook(item=item, **kwds), when=when, reraise=reraise
    )

e/lib/python3.9/site-packages/_pytest/runner.py:260: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_HookCaller 'pytest_runtest_setup'>, args = ()
kwargs = {'item': <DoctestItem asd.foo>}, argname = 'item', firstresult = False

    def __call__(self, *args, **kwargs):
        if args:
            raise TypeError("hook calling supports only keyword arguments")
        assert not self.is_historic()
    
        # This is written to avoid expensive operations when not needed.
        if self.spec:
            for argname in self.spec.argnames:
                if argname not in kwargs:
                    notincall = tuple(set(self.spec.argnames) - kwargs.keys())
                    warnings.warn(
                        "Argument(s) {} which are declared in the hookspec "
                        "can not be found in this hook call".format(notincall),
                        stacklevel=2,
                    )
                    break
    
            firstresult = self.spec.opts.get("firstresult")
        else:
            firstresult = False
    
>       return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)

e/lib/python3.9/site-packages/pluggy/_hooks.py:265: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_pytest.config.PytestPluginManager object at 0x7f7fabb45dc0>
hook_name = 'pytest_runtest_setup'
methods = [<HookImpl plugin_name='nose', plugin=<module '_pytest.nose' from '/tmp/e/lib/python3.9/site-packages/_pytest/nose.py'...=None>>, <HookImpl plugin_name='logging-plugin', plugin=<_pytest.logging.LoggingPlugin object at 0x7f7faba02160>>, ...]
kwargs = {'item': <DoctestItem asd.foo>}, firstresult = False

    def _hookexec(self, hook_name, methods, kwargs, firstresult):
        # called from all hookcaller instances.
        # enable_tracing will set its own wrapping function at self._inner_hookexec
>       return self._inner_hookexec(hook_name, methods, kwargs, firstresult)

e/lib/python3.9/site-packages/pluggy/_manager.py:80: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

hook_name = 'pytest_runtest_setup'
hook_impls = [<HookImpl plugin_name='nose', plugin=<module '_pytest.nose' from '/tmp/e/lib/python3.9/site-packages/_pytest/nose.py'...=None>>, <HookImpl plugin_name='logging-plugin', plugin=<_pytest.logging.LoggingPlugin object at 0x7f7faba02160>>, ...]
caller_kwargs = {'item': <DoctestItem asd.foo>}, firstresult = False

    def _multicall(hook_name, hook_impls, caller_kwargs, firstresult):
        """Execute a call into multiple python functions/methods and return the
        result(s).
    
        ``caller_kwargs`` comes from _HookCaller.__call__().
        """
        __tracebackhide__ = True
        results = []
        excinfo = None
        try:  # run impl and wrapper setup functions in a loop
            teardowns = []
            try:
                for hook_impl in reversed(hook_impls):
                    try:
                        args = [caller_kwargs[argname] for argname in hook_impl.argnames]
                    except KeyError:
                        for argname in hook_impl.argnames:
                            if argname not in caller_kwargs:
                                raise HookCallError(
                                    f"hook call must provide argument {argname!r}"
                                )
    
                    if hook_impl.hookwrapper:
                        try:
                            gen = hook_impl.function(*args)
                            next(gen)  # first yield
                            teardowns.append(gen)
                        except StopIteration:
                            _raise_wrapfail(gen, "did not yield")
                    else:
                        res = hook_impl.function(*args)
                        if res is not None:
                            results.append(res)
                            if firstresult:  # halt further impl calls
                                break
            except BaseException:
                excinfo = sys.exc_info()
        finally:
            if firstresult:  # first result hooks return a single value
                outcome = _Result(results[0] if results else None, excinfo)
            else:
                outcome = _Result(results, excinfo)
    
            # run all wrapper post-yield blocks
            for gen in reversed(teardowns):
                try:
                    gen.send(outcome)
                    _raise_wrapfail(gen, "has second yield")
                except StopIteration:
                    pass
    
>           return outcome.get_result()

e/lib/python3.9/site-packages/pluggy/_callers.py:60: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <pluggy._result._Result object at 0x7f7faba14160>

    def get_result(self):
        """Get the result(s) for this hook call.
    
        If the hook was marked as a ``firstresult`` only a single value
        will be returned otherwise a list of results.
        """
        __tracebackhide__ = True
        if self._excinfo is None:
            return self._result
        else:
            ex = self._excinfo
>           raise ex[1].with_traceback(ex[2])

e/lib/python3.9/site-packages/pluggy/_result.py:60: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

hook_name = 'pytest_runtest_setup'
hook_impls = [<HookImpl plugin_name='nose', plugin=<module '_pytest.nose' from '/tmp/e/lib/python3.9/site-packages/_pytest/nose.py'...=None>>, <HookImpl plugin_name='logging-plugin', plugin=<_pytest.logging.LoggingPlugin object at 0x7f7faba02160>>, ...]
caller_kwargs = {'item': <DoctestItem asd.foo>}, firstresult = False

    def _multicall(hook_name, hook_impls, caller_kwargs, firstresult):
        """Execute a call into multiple python functions/methods and return the
        result(s).
    
        ``caller_kwargs`` comes from _HookCaller.__call__().
        """
        __tracebackhide__ = True
        results = []
        excinfo = None
        try:  # run impl and wrapper setup functions in a loop
            teardowns = []
            try:
                for hook_impl in reversed(hook_impls):
                    try:
                        args = [caller_kwargs[argname] for argname in hook_impl.argnames]
                    except KeyError:
                        for argname in hook_impl.argnames:
                            if argname not in caller_kwargs:
                                raise HookCallError(
                                    f"hook call must provide argument {argname!r}"
                                )
    
                    if hook_impl.hookwrapper:
                        try:
                            gen = hook_impl.function(*args)
                            next(gen)  # first yield
                            teardowns.append(gen)
                        except StopIteration:
                            _raise_wrapfail(gen, "did not yield")
                    else:
>                       res = hook_impl.function(*args)

e/lib/python3.9/site-packages/pluggy/_callers.py:39: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

item = <DoctestItem asd.foo>

    def pytest_runtest_setup(item: Item) -> None:
        _update_current_test_var(item, "setup")
>       item.session._setupstate.setup(item)

e/lib/python3.9/site-packages/_pytest/runner.py:155: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_pytest.runner.SetupState object at 0x7f7fabbb0c70>
item = <DoctestItem asd.foo>

    def setup(self, item: Item) -> None:
        """Setup objects along the collector chain to the item."""
        needed_collectors = item.listchain()
    
        # If a collector fails its setup, fail its entire subtree of items.
        # The setup is not retried for each item - the same exception is used.
        for col, (finalizers, exc) in self.stack.items():
            assert col in needed_collectors, "previous item was not torn down properly"
            if exc:
                raise exc
    
        for col in needed_collectors[len(self.stack) :]:
            assert col not in self.stack
            # Push onto the stack.
            self.stack[col] = ([col.teardown], None)
            try:
                col.setup()
            except TEST_OUTCOME as exc:
                self.stack[col] = (self.stack[col][0], exc)
>               raise exc

e/lib/python3.9/site-packages/_pytest/runner.py:495: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_pytest.runner.SetupState object at 0x7f7fabbb0c70>
item = <DoctestItem asd.foo>

    def setup(self, item: Item) -> None:
        """Setup objects along the collector chain to the item."""
        needed_collectors = item.listchain()
    
        # If a collector fails its setup, fail its entire subtree of items.
        # The setup is not retried for each item - the same exception is used.
        for col, (finalizers, exc) in self.stack.items():
            assert col in needed_collectors, "previous item was not torn down properly"
            if exc:
                raise exc
    
        for col in needed_collectors[len(self.stack) :]:
            assert col not in self.stack
            # Push onto the stack.
            self.stack[col] = ([col.teardown], None)
            try:
>               col.setup()

e/lib/python3.9/site-packages/_pytest/runner.py:492: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <DoctestItem asd.foo>

    def setup(self) -> None:
        if self.dtest is not None:
>           self.fixture_request = _setup_fixtures(self)

e/lib/python3.9/site-packages/_pytest/doctest.py:281: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

doctest_item = <DoctestItem asd.foo>

    def _setup_fixtures(doctest_item: DoctestItem) -> FixtureRequest:
        """Used by DoctestTextfile and DoctestItem to setup fixture information."""
    
        def func() -> None:
            pass
    
        doctest_item.funcargs = {}  # type: ignore[attr-defined]
        fm = doctest_item.session._fixturemanager
        doctest_item._fixtureinfo = fm.getfixtureinfo(  # type: ignore[attr-defined]
            node=doctest_item, func=func, cls=None, funcargs=False
        )
        fixture_request = FixtureRequest(doctest_item, _ispytest=True)
>       fixture_request._fillfixtures()

e/lib/python3.9/site-packages/_pytest/doctest.py:587: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <FixtureRequest for <DoctestItem asd.foo>>

    def _fillfixtures(self) -> None:
        item = self._pyfuncitem
        fixturenames = getattr(item, "fixturenames", self.fixturenames)
        for argname in fixturenames:
            if argname not in item.funcargs:
>               item.funcargs[argname] = self.getfixturevalue(argname)

e/lib/python3.9/site-packages/_pytest/fixtures.py:550: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <FixtureRequest for <DoctestItem asd.foo>>
argname = '_xunit_setup_module_fixture_asd'

    def getfixturevalue(self, argname: str) -> Any:
        """Dynamically run a named fixture function.
    
        Declaring fixtures via function argument is recommended where possible.
        But if you can only decide whether to use another fixture at test
        setup time, you may use this function to retrieve it inside a fixture
        or test function body.
    
        This method can be used during the test setup phase or the test run
        phase, but during the test teardown phase a fixture's value may not
        be available.
    
        :param argname:
            The fixture name.
        :raises pytest.FixtureLookupError:
            If the given fixture could not be found.
        """
>       fixturedef = self._get_active_fixturedef(argname)

e/lib/python3.9/site-packages/_pytest/fixtures.py:569: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <FixtureRequest for <DoctestItem asd.foo>>
argname = '_xunit_setup_module_fixture_asd'

    def _get_active_fixturedef(
        self, argname: str
    ) -> Union["FixtureDef[object]", PseudoFixtureDef[object]]:
        try:
            return self._fixture_defs[argname]
        except KeyError:
            try:
                fixturedef = self._getnextfixturedef(argname)
            except FixtureLookupError:
                if argname == "request":
                    cached_result = (self, [0], None)
                    return PseudoFixtureDef(cached_result, Scope.Function)
                raise
        # Remove indent to prevent the python3 exception
        # from leaking into the call.
>       self._compute_fixture_value(fixturedef)

e/lib/python3.9/site-packages/_pytest/fixtures.py:591: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <FixtureRequest for <DoctestItem asd.foo>>
fixturedef = <FixtureDef argname='_xunit_setup_module_fixture_asd' scope='module' baseid='asd.py'>

    def _compute_fixture_value(self, fixturedef: "FixtureDef[object]") -> None:
        """Create a SubRequest based on "self" and call the execute method
        of the given FixtureDef object.
    
        This will force the FixtureDef object to throw away any previous
        results and compute a new fixture value, which will be stored into
        the FixtureDef object itself.
        """
        # prepare a subrequest object before calling fixture function
        # (latter managed by fixturedef)
        argname = fixturedef.argname
        funcitem = self._pyfuncitem
        scope = fixturedef._scope
        try:
            callspec = funcitem.callspec
        except AttributeError:
            callspec = None
        if callspec is not None and argname in callspec.params:
            param = callspec.params[argname]
            param_index = callspec.indices[argname]
            # If a parametrize invocation set a scope it will override
            # the static scope defined with the fixture function.
            with suppress(KeyError):
                scope = callspec._arg2scope[argname]
        else:
            param = NOTSET
            param_index = 0
            has_params = fixturedef.params is not None
            fixtures_not_supported = getattr(funcitem, "nofuncargs", False)
            if has_params and fixtures_not_supported:
                msg = (
                    "{name} does not support fixtures, maybe unittest.TestCase subclass?\n"
                    "Node id: {nodeid}\n"
                    "Function type: {typename}"
                ).format(
                    name=funcitem.name,
                    nodeid=funcitem.nodeid,
                    typename=type(funcitem).__name__,
                )
                fail(msg, pytrace=False)
            if has_params:
                frame = inspect.stack()[3]
                frameinfo = inspect.getframeinfo(frame[0])
                source_path = absolutepath(frameinfo.filename)
                source_lineno = frameinfo.lineno
                try:
                    source_path_str = str(
                        source_path.relative_to(funcitem.config.rootpath)
                    )
                except ValueError:
                    source_path_str = str(source_path)
                msg = (
                    "The requested fixture has no parameter defined for test:\n"
                    "    {}\n\n"
                    "Requested fixture '{}' defined in:\n{}"
                    "\n\nRequested here:\n{}:{}".format(
                        funcitem.nodeid,
                        fixturedef.argname,
                        getlocation(fixturedef.func, funcitem.config.rootpath),
                        source_path_str,
                        source_lineno,
                    )
                )
                fail(msg, pytrace=False)
    
        subrequest = SubRequest(
            self, scope, param, param_index, fixturedef, _ispytest=True
        )
    
        # Check if a higher-level scoped fixture accesses a lower level one.
        subrequest._check_scope(argname, self._scope, scope)
        try:
            # Call the fixture function.
>           fixturedef.execute(request=subrequest)

e/lib/python3.9/site-packages/_pytest/fixtures.py:677: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <FixtureDef argname='_xunit_setup_module_fixture_asd' scope='module' baseid='asd.py'>
request = <SubRequest '_xunit_setup_module_fixture_asd' for <DoctestItem asd.foo>>

    def execute(self, request: SubRequest) -> FixtureValue:
        # Get required arguments and register our own finish()
        # with their finalization.
        for argname in self.argnames:
            fixturedef = request._get_active_fixturedef(argname)
            if argname != "request":
                # PseudoFixtureDef is only for "request".
                assert isinstance(fixturedef, FixtureDef)
                fixturedef.addfinalizer(functools.partial(self.finish, request=request))
    
        my_cache_key = self.cache_key(request)
        if self.cached_result is not None:
            # note: comparison with `==` can fail (or be expensive) for e.g.
            # numpy arrays (#6497).
            cache_key = self.cached_result[1]
            if my_cache_key is cache_key:
                if self.cached_result[2] is not None:
                    _, val, tb = self.cached_result[2]
                    raise val.with_traceback(tb)
                else:
                    result = self.cached_result[0]
                    return result
            # We have a previous but differently parametrized fixture instance
            # so we need to tear it down before creating a new one.
            self.finish(request)
            assert self.cached_result is None
    
        ihook = request.node.ihook
>       result = ihook.pytest_fixture_setup(fixturedef=self, request=request)

e/lib/python3.9/site-packages/_pytest/fixtures.py:1075: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_HookCaller 'pytest_fixture_setup'>, args = ()
kwargs = {'fixturedef': <FixtureDef argname='_xunit_setup_module_fixture_asd' scope='module' baseid='asd.py'>, 'request': <SubRequest '_xunit_setup_module_fixture_asd' for <DoctestItem asd.foo>>}
argname = 'request', firstresult = True

    def __call__(self, *args, **kwargs):
        if args:
            raise TypeError("hook calling supports only keyword arguments")
        assert not self.is_historic()
    
        # This is written to avoid expensive operations when not needed.
        if self.spec:
            for argname in self.spec.argnames:
                if argname not in kwargs:
                    notincall = tuple(set(self.spec.argnames) - kwargs.keys())
                    warnings.warn(
                        "Argument(s) {} which are declared in the hookspec "
                        "can not be found in this hook call".format(notincall),
                        stacklevel=2,
                    )
                    break
    
            firstresult = self.spec.opts.get("firstresult")
        else:
            firstresult = False
    
>       return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)

e/lib/python3.9/site-packages/pluggy/_hooks.py:265: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_pytest.config.PytestPluginManager object at 0x7f7fabb45dc0>
hook_name = 'pytest_fixture_setup'
methods = [<HookImpl plugin_name='fixtures', plugin=<module '_pytest.fixtures' from '/tmp/e/lib/python3.9/site-packages/_pytest/..._name='setuponly', plugin=<module '_pytest.setuponly' from '/tmp/e/lib/python3.9/site-packages/_pytest/setuponly.py'>>]
kwargs = {'fixturedef': <FixtureDef argname='_xunit_setup_module_fixture_asd' scope='module' baseid='asd.py'>, 'request': <SubRequest '_xunit_setup_module_fixture_asd' for <DoctestItem asd.foo>>}
firstresult = True

    def _hookexec(self, hook_name, methods, kwargs, firstresult):
        # called from all hookcaller instances.
        # enable_tracing will set its own wrapping function at self._inner_hookexec
>       return self._inner_hookexec(hook_name, methods, kwargs, firstresult)

e/lib/python3.9/site-packages/pluggy/_manager.py:80: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

hook_name = 'pytest_fixture_setup'
hook_impls = [<HookImpl plugin_name='fixtures', plugin=<module '_pytest.fixtures' from '/tmp/e/lib/python3.9/site-packages/_pytest/..._name='setuponly', plugin=<module '_pytest.setuponly' from '/tmp/e/lib/python3.9/site-packages/_pytest/setuponly.py'>>]
caller_kwargs = {'fixturedef': <FixtureDef argname='_xunit_setup_module_fixture_asd' scope='module' baseid='asd.py'>, 'request': <SubRequest '_xunit_setup_module_fixture_asd' for <DoctestItem asd.foo>>}
firstresult = True

    def _multicall(hook_name, hook_impls, caller_kwargs, firstresult):
        """Execute a call into multiple python functions/methods and return the
        result(s).
    
        ``caller_kwargs`` comes from _HookCaller.__call__().
        """
        __tracebackhide__ = True
        results = []
        excinfo = None
        try:  # run impl and wrapper setup functions in a loop
            teardowns = []
            try:
                for hook_impl in reversed(hook_impls):
                    try:
                        args = [caller_kwargs[argname] for argname in hook_impl.argnames]
                    except KeyError:
                        for argname in hook_impl.argnames:
                            if argname not in caller_kwargs:
                                raise HookCallError(
                                    f"hook call must provide argument {argname!r}"
                                )
    
                    if hook_impl.hookwrapper:
                        try:
                            gen = hook_impl.function(*args)
                            next(gen)  # first yield
                            teardowns.append(gen)
                        except StopIteration:
                            _raise_wrapfail(gen, "did not yield")
                    else:
                        res = hook_impl.function(*args)
                        if res is not None:
                            results.append(res)
                            if firstresult:  # halt further impl calls
                                break
            except BaseException:
                excinfo = sys.exc_info()
        finally:
            if firstresult:  # first result hooks return a single value
                outcome = _Result(results[0] if results else None, excinfo)
            else:
                outcome = _Result(results, excinfo)
    
            # run all wrapper post-yield blocks
            for gen in reversed(teardowns):
                try:
                    gen.send(outcome)
                    _raise_wrapfail(gen, "has second yield")
                except StopIteration:
                    pass
    
>           return outcome.get_result()

e/lib/python3.9/site-packages/pluggy/_callers.py:60: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <pluggy._result._Result object at 0x7f7fab9ea100>

    def get_result(self):
        """Get the result(s) for this hook call.
    
        If the hook was marked as a ``firstresult`` only a single value
        will be returned otherwise a list of results.
        """
        __tracebackhide__ = True
        if self._excinfo is None:
            return self._result
        else:
            ex = self._excinfo
>           raise ex[1].with_traceback(ex[2])

e/lib/python3.9/site-packages/pluggy/_result.py:60: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

hook_name = 'pytest_fixture_setup'
hook_impls = [<HookImpl plugin_name='fixtures', plugin=<module '_pytest.fixtures' from '/tmp/e/lib/python3.9/site-packages/_pytest/..._name='setuponly', plugin=<module '_pytest.setuponly' from '/tmp/e/lib/python3.9/site-packages/_pytest/setuponly.py'>>]
caller_kwargs = {'fixturedef': <FixtureDef argname='_xunit_setup_module_fixture_asd' scope='module' baseid='asd.py'>, 'request': <SubRequest '_xunit_setup_module_fixture_asd' for <DoctestItem asd.foo>>}
firstresult = True

    def _multicall(hook_name, hook_impls, caller_kwargs, firstresult):
        """Execute a call into multiple python functions/methods and return the
        result(s).
    
        ``caller_kwargs`` comes from _HookCaller.__call__().
        """
        __tracebackhide__ = True
        results = []
        excinfo = None
        try:  # run impl and wrapper setup functions in a loop
            teardowns = []
            try:
                for hook_impl in reversed(hook_impls):
                    try:
                        args = [caller_kwargs[argname] for argname in hook_impl.argnames]
                    except KeyError:
                        for argname in hook_impl.argnames:
                            if argname not in caller_kwargs:
                                raise HookCallError(
                                    f"hook call must provide argument {argname!r}"
                                )
    
                    if hook_impl.hookwrapper:
                        try:
                            gen = hook_impl.function(*args)
                            next(gen)  # first yield
                            teardowns.append(gen)
                        except StopIteration:
                            _raise_wrapfail(gen, "did not yield")
                    else:
>                       res = hook_impl.function(*args)

e/lib/python3.9/site-packages/pluggy/_callers.py:39: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

fixturedef = <FixtureDef argname='_xunit_setup_module_fixture_asd' scope='module' baseid='asd.py'>
request = <SubRequest '_xunit_setup_module_fixture_asd' for <DoctestItem asd.foo>>

    def pytest_fixture_setup(
        fixturedef: FixtureDef[FixtureValue], request: SubRequest
    ) -> FixtureValue:
        """Execution of fixture setup."""
        kwargs = {}
        for argname in fixturedef.argnames:
            fixdef = request._get_active_fixturedef(argname)
            assert fixdef.cached_result is not None
            result, arg_cache_key, exc = fixdef.cached_result
            request._check_scope(argname, request._scope, fixdef._scope)
            kwargs[argname] = result
    
        fixturefunc = resolve_fixture_function(fixturedef, request)
        my_cache_key = fixturedef.cache_key(request)
        try:
>           result = call_fixture_func(fixturefunc, request, kwargs)

e/lib/python3.9/site-packages/_pytest/fixtures.py:1129: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

fixturefunc = <function Module._inject_setup_module_fixture.<locals>.xunit_setup_module_fixture at 0x7f7fab9d9280>
request = <SubRequest '_xunit_setup_module_fixture_asd' for <DoctestItem asd.foo>>
kwargs = {'request': <SubRequest '_xunit_setup_module_fixture_asd' for <DoctestItem asd.foo>>}

    def call_fixture_func(
        fixturefunc: "_FixtureFunc[FixtureValue]", request: FixtureRequest, kwargs
    ) -> FixtureValue:
        if is_generator(fixturefunc):
            fixturefunc = cast(
                Callable[..., Generator[FixtureValue, None, None]], fixturefunc
            )
            generator = fixturefunc(**kwargs)
            try:
>               fixture_result = next(generator)

e/lib/python3.9/site-packages/_pytest/fixtures.py:901: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <SubRequest '_xunit_setup_module_fixture_asd' for <DoctestItem asd.foo>>

    @fixtures.fixture(
        autouse=True,
        scope="module",
        # Use a unique name to speed up lookup.
        name=f"_xunit_setup_module_fixture_{self.obj.__name__}",
    )
    def xunit_setup_module_fixture(request) -> Generator[None, None, None]:
        if setup_module is not None:
>           _call_with_optional_argument(setup_module, request.module)

e/lib/python3.9/site-packages/_pytest/python.py:573: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

func = <function setup at 0x7f7fab9d5dc0>
arg = <module 'asd' from '/tmp/asd.py'>

    def _call_with_optional_argument(func, arg) -> None:
        """Call the given function with the given argument if func accepts one argument, otherwise
        calls func without arguments."""
        arg_count = func.__code__.co_argcount
        if inspect.ismethod(func):
            arg_count -= 1
        if arg_count:
            func(arg)
        else:
>           func()

e/lib/python3.9/site-packages/_pytest/python.py:788: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def setup():
>       raise RuntimeError("Setup called!!! Oh No")
E       RuntimeError: Setup called!!! Oh No

asd.py:9: RuntimeError
=========================== short test summary info ============================
ERROR asd.py::asd.foo - RuntimeError: Setup called!!! Oh No
1 error in 0.27s

To prevent this from happening, you can pass -p no:nose to disable the nose plugin.

This behavior first appeared in 7.0.0rc1 and I'm pretty sure it was introduced in #9273 (or at least I figured out how to work around it by reading the diff in #9273)

Please just close this if it is a feature and not a bug. I'm not sure, and it's easy to work around anyway :)

@Zac-HD Zac-HD added type: bug problem that needs to be addressed plugin: doctests related to the doctests builtin plugin plugin: nose related to the nose integration builtin plugin labels Apr 8, 2023
@bluetech
Copy link
Member

We have deprecated the nose plugin some time ago, and it is slated for complete removal in pytest 8.1. Until then -p no:nose is probably the best solution, there's not much we can do about it for backward compat reasons.

@bluetech
Copy link
Member

bluetech commented Jan 4, 2024

Fixed by #11757 (will be part of pytest 8.1).

@bluetech bluetech closed this as completed Jan 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
plugin: doctests related to the doctests builtin plugin plugin: nose related to the nose integration builtin plugin type: bug problem that needs to be addressed
Projects
None yet
Development

No branches or pull requests

3 participants