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

xdoctest is breaking with TypeError: getfixtureinfo() got an unexpected keyword argument 'funcargs' #151

Closed
Saransh-cpp opened this issue Jan 29, 2024 · 6 comments

Comments

@Saransh-cpp
Copy link

Python version: all

xdoctest throws an error in CI just at setup. This same CI pipeline was working fine the last week and no changes have been made since then. The error seems to be originating from within xdoctest.

Command used:

python -m pytest -ra --cov=vector --xdoctest --ignore tests/test_notebooks.py .

See the failing logs for the complete error.

Stacktrace for a single doctest error:

==================================== ERRORS ====================================
_____________ ERROR at setup of [xdoctest] Vector2D.to_Vector3D:0 ______________

cls = <class '_pytest.runner.CallInfo'>
func = <function call_runtest_hook.<locals>.<lambda> at 0x7f56aef1d670>
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()

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/runner.py:345: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

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

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/runner.py:266: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <HookCaller 'pytest_runtest_setup'>
kwargs = {'item': <XDoctestItem Vector2D.to_Vector3D:0>}, firstresult = False

    def __call__(self, **kwargs: object) -> Any:
        """Call the hook.
    
        Only accepts keyword arguments, which should match the hook
        specification.
    
        Returns the result(s) of calling all registered plugins, see
        :ref:`calling`.
        """
        assert (
            not self.is_historic()
        ), "Cannot directly call a historic hook - use call_historic instead."
        self._verify_all_args_are_provided(kwargs)
        firstresult = self.spec.opts.get("firstresult", False) if self.spec else False
        # Copy because plugins may register other plugins during iteration (#438).
>       return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_hooks.py:501: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_pytest.config.PytestPluginManager object at 0x7f56b0d146a0>
hook_name = 'pytest_runtest_setup'
methods = [<HookImpl plugin_name='nose', plugin=<module '_pytest.nose' from '/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3....pper name='/dev/null' mode='r' encoding='utf-8'>> _state='suspended' _in_suspended=False> _capture_fixture=None>>, ...]
kwargs = {'item': <XDoctestItem Vector2D.to_Vector3D:0>}, firstresult = False

    def _hookexec(
        self,
        hook_name: str,
        methods: Sequence[HookImpl],
        kwargs: Mapping[str, object],
        firstresult: bool,
    ) -> object | list[object]:
        # 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)

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_manager.py:119: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

hook_name = 'pytest_runtest_setup'
hook_impls = [<HookImpl plugin_name='nose', plugin=<module '_pytest.nose' from '/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3....pper name='/dev/null' mode='r' encoding='utf-8'>> _state='suspended' _in_suspended=False> _capture_fixture=None>>, ...]
caller_kwargs = {'item': <XDoctestItem Vector2D.to_Vector3D:0>}
firstresult = False

    def _multicall(
        hook_name: str,
        hook_impls: Sequence[HookImpl],
        caller_kwargs: Mapping[str, object],
        firstresult: bool,
    ) -> object | list[object]:
        """Execute a call into multiple python functions/methods and return the
        result(s).
    
        ``caller_kwargs`` comes from HookCaller.__call__().
        """
        __tracebackhide__ = True
        results: list[object] = []
        exception = None
        only_new_style_wrappers = True
        try:  # run impl and wrapper setup functions in a loop
            teardowns: list[Teardown] = []
            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:
                        only_new_style_wrappers = False
                        try:
                            # If this cast is not valid, a type error is raised below,
                            # which is the desired response.
                            res = hook_impl.function(*args)
                            wrapper_gen = cast(Generator[None, Result[object], None], res)
                            next(wrapper_gen)  # first yield
                            teardowns.append((wrapper_gen, hook_impl))
                        except StopIteration:
                            _raise_wrapfail(wrapper_gen, "did not yield")
                    elif hook_impl.wrapper:
                        try:
                            # If this cast is not valid, a type error is raised below,
                            # which is the desired response.
                            res = hook_impl.function(*args)
                            function_gen = cast(Generator[None, object, object], res)
                            next(function_gen)  # first yield
                            teardowns.append(function_gen)
                        except StopIteration:
                            _raise_wrapfail(function_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 as exc:
                exception = exc
        finally:
            # Fast path - only new-style wrappers, no Result.
            if only_new_style_wrappers:
                if firstresult:  # first result hooks return a single value
                    result = results[0] if results else None
                else:
                    result = results
    
                # run all wrapper post-yield blocks
                for teardown in reversed(teardowns):
                    try:
                        if exception is not None:
                            teardown.throw(exception)  # type: ignore[union-attr]
                        else:
                            teardown.send(result)  # type: ignore[union-attr]
                        # Following is unreachable for a well behaved hook wrapper.
                        # Try to force finalizers otherwise postponed till GC action.
                        # Note: close() may raise if generator handles GeneratorExit.
                        teardown.close()  # type: ignore[union-attr]
                    except StopIteration as si:
                        result = si.value
                        exception = None
                        continue
                    except BaseException as e:
                        exception = e
                        continue
                    _raise_wrapfail(teardown, "has second yield")  # type: ignore[arg-type]
    
                if exception is not None:
>                   raise exception.with_traceback(exception.__traceback__)

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_callers.py:138: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

hook_name = 'pytest_runtest_setup'
hook_impls = [<HookImpl plugin_name='nose', plugin=<module '_pytest.nose' from '/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3....pper name='/dev/null' mode='r' encoding='utf-8'>> _state='suspended' _in_suspended=False> _capture_fixture=None>>, ...]
caller_kwargs = {'item': <XDoctestItem Vector2D.to_Vector3D:0>}
firstresult = False

    def _multicall(
        hook_name: str,
        hook_impls: Sequence[HookImpl],
        caller_kwargs: Mapping[str, object],
        firstresult: bool,
    ) -> object | list[object]:
        """Execute a call into multiple python functions/methods and return the
        result(s).
    
        ``caller_kwargs`` comes from HookCaller.__call__().
        """
        __tracebackhide__ = True
        results: list[object] = []
        exception = None
        only_new_style_wrappers = True
        try:  # run impl and wrapper setup functions in a loop
            teardowns: list[Teardown] = []
            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:
                        only_new_style_wrappers = False
                        try:
                            # If this cast is not valid, a type error is raised below,
                            # which is the desired response.
                            res = hook_impl.function(*args)
                            wrapper_gen = cast(Generator[None, Result[object], None], res)
                            next(wrapper_gen)  # first yield
                            teardowns.append((wrapper_gen, hook_impl))
                        except StopIteration:
                            _raise_wrapfail(wrapper_gen, "did not yield")
                    elif hook_impl.wrapper:
                        try:
                            # If this cast is not valid, a type error is raised below,
                            # which is the desired response.
                            res = hook_impl.function(*args)
                            function_gen = cast(Generator[None, object, object], res)
                            next(function_gen)  # first yield
                            teardowns.append(function_gen)
                        except StopIteration:
                            _raise_wrapfail(function_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 as exc:
                exception = exc
        finally:
            # Fast path - only new-style wrappers, no Result.
            if only_new_style_wrappers:
                if firstresult:  # first result hooks return a single value
                    result = results[0] if results else None
                else:
                    result = results
    
                # run all wrapper post-yield blocks
                for teardown in reversed(teardowns):
                    try:
                        if exception is not None:
>                           teardown.throw(exception)  # type: ignore[union-attr]

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_callers.py:121: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    @pytest.hookimpl(wrapper=True, tryfirst=True)
    def pytest_runtest_setup() -> Generator[None, None, None]:
>       yield from unraisable_exception_runtest_hook()

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/unraisableexception.py:85: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def unraisable_exception_runtest_hook() -> Generator[None, None, None]:
        with catch_unraisable_exception() as cm:
            try:
>               yield

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/unraisableexception.py:65: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

hook_name = 'pytest_runtest_setup'
hook_impls = [<HookImpl plugin_name='nose', plugin=<module '_pytest.nose' from '/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3....pper name='/dev/null' mode='r' encoding='utf-8'>> _state='suspended' _in_suspended=False> _capture_fixture=None>>, ...]
caller_kwargs = {'item': <XDoctestItem Vector2D.to_Vector3D:0>}
firstresult = False

    def _multicall(
        hook_name: str,
        hook_impls: Sequence[HookImpl],
        caller_kwargs: Mapping[str, object],
        firstresult: bool,
    ) -> object | list[object]:
        """Execute a call into multiple python functions/methods and return the
        result(s).
    
        ``caller_kwargs`` comes from HookCaller.__call__().
        """
        __tracebackhide__ = True
        results: list[object] = []
        exception = None
        only_new_style_wrappers = True
        try:  # run impl and wrapper setup functions in a loop
            teardowns: list[Teardown] = []
            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:
                        only_new_style_wrappers = False
                        try:
                            # If this cast is not valid, a type error is raised below,
                            # which is the desired response.
                            res = hook_impl.function(*args)
                            wrapper_gen = cast(Generator[None, Result[object], None], res)
                            next(wrapper_gen)  # first yield
                            teardowns.append((wrapper_gen, hook_impl))
                        except StopIteration:
                            _raise_wrapfail(wrapper_gen, "did not yield")
                    elif hook_impl.wrapper:
                        try:
                            # If this cast is not valid, a type error is raised below,
                            # which is the desired response.
                            res = hook_impl.function(*args)
                            function_gen = cast(Generator[None, object, object], res)
                            next(function_gen)  # first yield
                            teardowns.append(function_gen)
                        except StopIteration:
                            _raise_wrapfail(function_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 as exc:
                exception = exc
        finally:
            # Fast path - only new-style wrappers, no Result.
            if only_new_style_wrappers:
                if firstresult:  # first result hooks return a single value
                    result = results[0] if results else None
                else:
                    result = results
    
                # run all wrapper post-yield blocks
                for teardown in reversed(teardowns):
                    try:
                        if exception is not None:
>                           teardown.throw(exception)  # type: ignore[union-attr]

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_callers.py:121: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_pytest.logging.LoggingPlugin object at 0x7f56af368460>
item = <XDoctestItem Vector2D.to_Vector3D:0>

    @hookimpl(wrapper=True)
    def pytest_runtest_setup(self, item: nodes.Item) -> Generator[None, None, None]:
        self.log_cli_handler.set_when("setup")
    
        empty: Dict[str, List[logging.LogRecord]] = {}
        item.stash[caplog_records_key] = empty
>       yield from self._runtest_for(item, "setup")

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/logging.py:833: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_pytest.logging.LoggingPlugin object at 0x7f56af368460>
item = <XDoctestItem Vector2D.to_Vector3D:0>, when = 'setup'

    def _runtest_for(self, item: nodes.Item, when: str) -> Generator[None, None, None]:
        """Implement the internals of the pytest_runtest_xxx() hooks."""
        with catching_logs(
            self.caplog_handler,
            level=self.log_level,
        ) as caplog_handler, catching_logs(
            self.report_handler,
            level=self.log_level,
        ) as report_handler:
            caplog_handler.reset()
            report_handler.reset()
            item.stash[caplog_records_key][when] = caplog_handler.records
            item.stash[caplog_handler_key] = caplog_handler
    
            try:
>               yield

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/logging.py:822: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

hook_name = 'pytest_runtest_setup'
hook_impls = [<HookImpl plugin_name='nose', plugin=<module '_pytest.nose' from '/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3....pper name='/dev/null' mode='r' encoding='utf-8'>> _state='suspended' _in_suspended=False> _capture_fixture=None>>, ...]
caller_kwargs = {'item': <XDoctestItem Vector2D.to_Vector3D:0>}
firstresult = False

    def _multicall(
        hook_name: str,
        hook_impls: Sequence[HookImpl],
        caller_kwargs: Mapping[str, object],
        firstresult: bool,
    ) -> object | list[object]:
        """Execute a call into multiple python functions/methods and return the
        result(s).
    
        ``caller_kwargs`` comes from HookCaller.__call__().
        """
        __tracebackhide__ = True
        results: list[object] = []
        exception = None
        only_new_style_wrappers = True
        try:  # run impl and wrapper setup functions in a loop
            teardowns: list[Teardown] = []
            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:
                        only_new_style_wrappers = False
                        try:
                            # If this cast is not valid, a type error is raised below,
                            # which is the desired response.
                            res = hook_impl.function(*args)
                            wrapper_gen = cast(Generator[None, Result[object], None], res)
                            next(wrapper_gen)  # first yield
                            teardowns.append((wrapper_gen, hook_impl))
                        except StopIteration:
                            _raise_wrapfail(wrapper_gen, "did not yield")
                    elif hook_impl.wrapper:
                        try:
                            # If this cast is not valid, a type error is raised below,
                            # which is the desired response.
                            res = hook_impl.function(*args)
                            function_gen = cast(Generator[None, object, object], res)
                            next(function_gen)  # first yield
                            teardowns.append(function_gen)
                        except StopIteration:
                            _raise_wrapfail(function_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 as exc:
                exception = exc
        finally:
            # Fast path - only new-style wrappers, no Result.
            if only_new_style_wrappers:
                if firstresult:  # first result hooks return a single value
                    result = results[0] if results else None
                else:
                    result = results
    
                # run all wrapper post-yield blocks
                for teardown in reversed(teardowns):
                    try:
                        if exception is not None:
>                           teardown.throw(exception)  # type: ignore[union-attr]

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_callers.py:121: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <CaptureManager _method='fd' _global_capturing=<MultiCapture out=<FDCapture 1 oldfd=5 _state='suspended' tmpfile=<_io....xtIOWrapper name='/dev/null' mode='r' encoding='utf-8'>> _state='suspended' _in_suspended=False> _capture_fixture=None>
item = <XDoctestItem Vector2D.to_Vector3D:0>

    @hookimpl(wrapper=True)
    def pytest_runtest_setup(self, item: Item) -> Generator[None, None, None]:
        with self.item_capture("setup", item):
>           return (yield)

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/capture.py:877: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

hook_name = 'pytest_runtest_setup'
hook_impls = [<HookImpl plugin_name='nose', plugin=<module '_pytest.nose' from '/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3....pper name='/dev/null' mode='r' encoding='utf-8'>> _state='suspended' _in_suspended=False> _capture_fixture=None>>, ...]
caller_kwargs = {'item': <XDoctestItem Vector2D.to_Vector3D:0>}
firstresult = False

    def _multicall(
        hook_name: str,
        hook_impls: Sequence[HookImpl],
        caller_kwargs: Mapping[str, object],
        firstresult: bool,
    ) -> object | list[object]:
        """Execute a call into multiple python functions/methods and return the
        result(s).
    
        ``caller_kwargs`` comes from HookCaller.__call__().
        """
        __tracebackhide__ = True
        results: list[object] = []
        exception = None
        only_new_style_wrappers = True
        try:  # run impl and wrapper setup functions in a loop
            teardowns: list[Teardown] = []
            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:
                        only_new_style_wrappers = False
                        try:
                            # If this cast is not valid, a type error is raised below,
                            # which is the desired response.
                            res = hook_impl.function(*args)
                            wrapper_gen = cast(Generator[None, Result[object], None], res)
                            next(wrapper_gen)  # first yield
                            teardowns.append((wrapper_gen, hook_impl))
                        except StopIteration:
                            _raise_wrapfail(wrapper_gen, "did not yield")
                    elif hook_impl.wrapper:
                        try:
                            # If this cast is not valid, a type error is raised below,
                            # which is the desired response.
                            res = hook_impl.function(*args)
                            function_gen = cast(Generator[None, object, object], res)
                            next(function_gen)  # first yield
                            teardowns.append(function_gen)
                        except StopIteration:
                            _raise_wrapfail(function_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 as exc:
                exception = exc
        finally:
            # Fast path - only new-style wrappers, no Result.
            if only_new_style_wrappers:
                if firstresult:  # first result hooks return a single value
                    result = results[0] if results else None
                else:
                    result = results
    
                # run all wrapper post-yield blocks
                for teardown in reversed(teardowns):
                    try:
                        if exception is not None:
>                           teardown.throw(exception)  # type: ignore[union-attr]

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_callers.py:121: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    @pytest.hookimpl(wrapper=True, trylast=True)
    def pytest_runtest_setup() -> Generator[None, None, None]:
>       yield from thread_exception_runtest_hook()

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/threadexception.py:82: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def thread_exception_runtest_hook() -> Generator[None, None, None]:
        with catch_threading_exception() as cm:
            try:
>               yield

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/threadexception.py:63: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

hook_name = 'pytest_runtest_setup'
hook_impls = [<HookImpl plugin_name='nose', plugin=<module '_pytest.nose' from '/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3....pper name='/dev/null' mode='r' encoding='utf-8'>> _state='suspended' _in_suspended=False> _capture_fixture=None>>, ...]
caller_kwargs = {'item': <XDoctestItem Vector2D.to_Vector3D:0>}
firstresult = False

    def _multicall(
        hook_name: str,
        hook_impls: Sequence[HookImpl],
        caller_kwargs: Mapping[str, object],
        firstresult: bool,
    ) -> object | list[object]:
        """Execute a call into multiple python functions/methods and return the
        result(s).
    
        ``caller_kwargs`` comes from HookCaller.__call__().
        """
        __tracebackhide__ = True
        results: list[object] = []
        exception = None
        only_new_style_wrappers = True
        try:  # run impl and wrapper setup functions in a loop
            teardowns: list[Teardown] = []
            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:
                        only_new_style_wrappers = False
                        try:
                            # If this cast is not valid, a type error is raised below,
                            # which is the desired response.
                            res = hook_impl.function(*args)
                            wrapper_gen = cast(Generator[None, Result[object], None], res)
                            next(wrapper_gen)  # first yield
                            teardowns.append((wrapper_gen, hook_impl))
                        except StopIteration:
                            _raise_wrapfail(wrapper_gen, "did not yield")
                    elif hook_impl.wrapper:
                        try:
                            # If this cast is not valid, a type error is raised below,
                            # which is the desired response.
                            res = hook_impl.function(*args)
                            function_gen = cast(Generator[None, object, object], res)
                            next(function_gen)  # first yield
                            teardowns.append(function_gen)
                        except StopIteration:
                            _raise_wrapfail(function_gen, "did not yield")
                    else:
>                       res = hook_impl.function(*args)

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_callers.py:102: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

item = <XDoctestItem Vector2D.to_Vector3D:0>

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

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/runner.py:161: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_pytest.runner.SetupState object at 0x7f56af05bbb0>
item = <XDoctestItem Vector2D.to_Vector3D:0>

    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

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/runner.py:517: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_pytest.runner.SetupState object at 0x7f56af05bbb0>
item = <XDoctestItem Vector2D.to_Vector3D:0>

    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()

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/runner.py:514: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <XDoctestItem Vector2D.to_Vector3D:0>

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

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/xdoctest/plugin.py:203: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

xdoctest_item = <XDoctestItem Vector2D.to_Vector3D:0>

    def _setup_fixtures(xdoctest_item):
        """
        Used by XDoctestTextfile and XDoctestItem to setup fixture information.
    
        Args:
            xdoctest_item (XDoctestItem):
    
        Returns:
            fixtures.FixtureRequest
        """
        def func():
            pass
    
        xdoctest_item.funcargs = {}
        fm = xdoctest_item.session._fixturemanager
>       xdoctest_item._fixtureinfo = fm.getfixtureinfo(
            node=xdoctest_item, func=func, cls=None, funcargs=False)
E       TypeError: getfixtureinfo() got an unexpected keyword argument 'funcargs'

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/xdoctest/plugin.py:340: TypeError
@Zeitsperre
Copy link

I'm also seeing in this in my CI builds. Are there any easy ways to resolve this via version pinning?

@Zeitsperre
Copy link

Looks as though the changes are likely due to pytest>=8.0 (released two days ago).

Zeitsperre added a commit to Ouranosinc/xclim that referenced this issue Jan 29, 2024
### What kind of change does this PR introduce?

* Pins `pytest` below v8.0 due to breakage with `xdoctest`
* Now using the `bump-my-version` Conda-forge package

### Does this PR introduce a breaking change?

No.

### Other information:

Erotemic/xdoctest#151
@Erotemic
Copy link
Owner

I have a patch in #150 that should handle pytest 8.x as well as previous versions. Will merge and release a new branch once CI passes. For now ensure pytest is < 8.0.

@Erotemic
Copy link
Owner

I'm having issues getting the dashboards to work on windows. I've verified that it only happens with pytest 8.0 on windows other platforms seem fine.

https://github.com/Erotemic/xdoctest/actions/runs/7704989009/job/20998236140

It's just 2 tests that are failing, both having to do with running the pytest CLI: test_simple_pytest_cli and test_simple_pytest_import_error_cli. What these tests basically do is setup a dummy module in a temporary directory and then call pytest on it via the command line. For some reason on windows it no longer works. An example of the command it tries to run is:

C:\hostedtoolcache\windows\Python\3.12.1\x64\python.exe -m pytest -v -s --xdoctest-verbose=3 --xdoctest-supress-import-errors --xdoctest C:\Users\RUNNER~1\AppData\Local\Temp\tmpcttcs8zz

and pytest prints:

============================= test session starts =============================
platform win32 -- Python 3.12.1, pytest-8.0.0, pluggy-1.4.0 -- C:\hostedtoolcache\windows\Python\3.12.1\x64\python.exe
cachedir: .pytest_cache
rootdir: D:\a\xdoctest\xdoctest\testdir_py3.12_7704989009_Windows
plugins: cov-4.1.0, xdoctest-1.1.3
collecting ... collected 0 items

============================ no tests ran in 0.05s ============================

So it's not recognizing that the directory has a python file with a doctest in it. I'm wondering if the RUNNER~1 is causing an issue with windows short-vs-long paths. If I run dir on the directory I get:

 Volume in drive C is Windows
 Volume Serial Number is 9CBA-7E40

 Directory of C:\Users\RUNNER~1\AppData\Local\Temp\tmpcttcs8zz

01/30/2024  01:36 AM    <DIR>          .
01/30/2024  01:36 AM    <DIR>          ..
01/30/2024  01:36 AM               205 imperr_test_mod.py
               1 File(s)            205 bytes
               2 Dir(s)  80,790,720,512 bytes free

which seems fine.

For the other test, which calls pytest directly on a file, it gives this output and error:

============================= test session starts =============================
platform win32 -- Python 3.12.1, pytest-8.0.0, pluggy-1.4.0
rootdir: D:\a\xdoctest\xdoctest\testdir_py3.12_7704989009_Windows
plugins: cov-4.1.0, xdoctest-1.1.3
collected 0 items

============================ no tests ran in 0.06s ============================

ERROR: not found: C:\Users\RUNNER~1\AppData\Local\Temp\tmp31o2lm30\bokmdnkn.py
(no match in any of [<Dir Users>])

Which indicates to me pytest itself might be having a hard time resolving this short path.

If anyone has any ideas I could use some help on this.

edgarrmondragon added a commit to meltano/sdk that referenced this issue Jan 30, 2024
github-merge-queue bot pushed a commit to meltano/sdk that referenced this issue Jan 30, 2024
* chore(deps): bump furo from 2023.9.10 to 2024.1.29

Bumps [furo](https://github.com/pradyunsg/furo) from 2023.9.10 to 2024.1.29.
- [Release notes](https://github.com/pradyunsg/furo/releases)
- [Changelog](https://github.com/pradyunsg/furo/blob/main/docs/changelog.md)
- [Commits](pradyunsg/furo@2023.09.10...2024.01.29)

---
updated-dependencies:
- dependency-name: furo
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Run `poetry lock`

* Fix env var tests

* Revert pytest bump

Erotemic/xdoctest#151

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Edgar Ramírez-Mondragón <edgarrm358@gmail.com>
@Erotemic
Copy link
Owner

I believe that I've been able to resolve the issue. It seems like ensuring that the tests use long paths intead of short paths resolves the issue. I made an issue in pytest to make them aware of the problem here: pytest-dev/pytest#11895

But as far as xdoctest is concerned, everything should be good to go. I'm going to merge and make a release and the new version should fix most pytest 8.0 issues (unless you are using short paths on windows).

@Erotemic
Copy link
Owner

new release is live

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants