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

pytest 3.3: fixture-related breakage #2979

Closed
cjw296 opened this issue Nov 29, 2017 · 9 comments · Fixed by simplistix/sybil#5
Closed

pytest 3.3: fixture-related breakage #2979

cjw296 opened this issue Nov 29, 2017 · 9 comments · Fixed by simplistix/sybil#5
Labels
topic: fixtures anything involving fixtures directly or indirectly type: question general question, might be closed after 2 weeks of inactivity type: regression indicates a problem that was introduced in a release which was working previously

Comments

@cjw296
Copy link

cjw296 commented Nov 29, 2017

First spotted here:
https://travis-ci.org/Simplistix/testfixtures/builds/308659341?utm_source=email&utm_medium=notification

Confirmed as sybil, a pytest plugin, problem here:
https://travis-ci.org/cjw296/sybil/builds/308116822?utm_source=email&utm_medium=notification

Here's a short failure:

(sybil_35) tweedledee:sybil chris$ git remote -v
origin	[email protected]:cjw296/sybil.git (fetch)
origin	[email protected]:cjw296/sybil.git (push)
(sybil_35) tweedledee:sybil chris$ pytest -x /Users/chris/vcs/git/sybil/tests/functional/pytest
========================================================================= test session starts ==========================================================================
platform darwin -- Python 3.5.0, pytest-3.3.0, py-1.5.2, pluggy-0.6.0
rootdir: /Users/chris/vcs/git/sybil/tests/functional/pytest, inifile: pytest.ini
collected 10 items                                                                                                                                                     

tests/functional/pytest/fail.rst Emodule_fixture teardown
sybil teardown 0


================================================================================ ERRORS ================================================================================
______________________________________________________________ ERROR at setup of fail.rst line=1 column=1 ______________________________________________________________

self = <FixtureRequest for <SybilItem 'line:1,column:1'>>, fixturedef = <FixtureDef name='session_fixture' scope='session' baseid='' >

    def _getfixturevalue(self, fixturedef):
        # prepare a subrequest object before calling fixture function
        # (latter managed by fixturedef)
        argname = fixturedef.argname
        funcitem = self._pyfuncitem
        scope = fixturedef.scope
        try:
            param = funcitem.callspec.getparam(argname)
        except (AttributeError, ValueError):
            param = NOTSET
            param_index = 0
            if fixturedef.params is not None:
                frame = inspect.stack()[3]
                frameinfo = inspect.getframeinfo(frame[0])
                source_path = frameinfo.filename
                source_lineno = frameinfo.lineno
                source_path = py.path.local(source_path)
                if source_path.relto(funcitem.config.rootdir):
                    source_path = source_path.relto(funcitem.config.rootdir)
                msg = (
                    "The requested fixture has no parameter defined for the "
                    "current test.\n\nRequested fixture '{0}' defined in:\n{1}"
                    "\n\nRequested here:\n{2}:{3}".format(
                        fixturedef.argname,
                        getlocation(fixturedef.func, funcitem.config.rootdir),
                        source_path,
                        source_lineno,
                    )
                )
                fail(msg)
        else:
            # indices might not be set if old-style metafunc.addcall() was used
            param_index = funcitem.callspec.indices.get(argname, 0)
            # if a parametrize invocation set a scope it will override
            # the static scope defined with the fixture function
            paramscopenum = funcitem.callspec._arg2scopenum.get(argname)
            if paramscopenum is not None:
                scope = scopes[paramscopenum]
    
        subrequest = SubRequest(self, scope, param, param_index, fixturedef)
    
        # check if a higher-level scoped fixture accesses a lower level one
        subrequest._check_scope(argname, self.scope, scope)
    
        # clear sys.exc_info before invoking the fixture (python bug?)
        # if its not explicitly cleared it will leak into the call
        exc_clear()
        try:
            # call the fixture function
>           val = fixturedef.execute(request=subrequest)

../../../virtualenvs/sybil_35/lib/python3.5/site-packages/_pytest/fixtures.py:518: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <FixtureDef name='session_fixture' scope='session' baseid='' >, request = <SubRequest 'session_fixture' for <SybilItem 'line:1,column:1'>>

    def execute(self, request):
        # 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":
                fixturedef.addfinalizer(functools.partial(self.finish, request=request))
    
        my_cache_key = request.param_index
        cached_result = getattr(self, "cached_result", None)
        if cached_result is not None:
            result, cache_key, err = cached_result
            if my_cache_key == cache_key:
                if err is not None:
                    py.builtin._reraise(*err)
                else:
                    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 not hasattr(self, "cached_result")
    
>       hook = self._fixturemanager.session.gethookproxy(request.node.fspath)

../../../virtualenvs/sybil_35/lib/python3.5/site-packages/_pytest/fixtures.py:790: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <SubRequest 'session_fixture' for <SybilItem 'line:1,column:1'>>

    @property
    def node(self):
        """ underlying collection node (depends on current request scope)"""
>       return self._getscopeitem(self.scope)

../../../virtualenvs/sybil_35/lib/python3.5/site-packages/_pytest/fixtures.py:285: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <SubRequest 'session_fixture' for <SybilItem 'line:1,column:1'>>, scope = 'session'

    def _getscopeitem(self, scope):
        if scope == "function":
            # this might also be a non-function Item despite its attribute name
            return self._pyfuncitem
        node = get_scope_node(self._pyfuncitem, scope)
        if node is None and scope == "class":
            # fallback to function item itself
            node = self._pyfuncitem
>       assert node
E       AssertionError

../../../virtualenvs/sybil_35/lib/python3.5/site-packages/_pytest/fixtures.py:556: AssertionError

During handling of the above exception, another exception occurred:

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

../../../virtualenvs/sybil_35/lib/python3.5/site-packages/_pytest/runner.py:177: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_HookCaller 'pytest_runtest_setup'>, args = (), kwargs = {'item': <SybilItem 'line:1,column:1'>}, notincall = set()

    def __call__(self, *args, **kwargs):
        if args:
            raise TypeError("hook calling supports only keyword arguments")
        assert not self.is_historic()
        if self.argnames:
            notincall = set(self.argnames) - set(['__multicall__']) - set(
                kwargs.keys())
            if notincall:
                warnings.warn(
                    "Argument(s) {} which are declared in the hookspec "
                    "can not be found in this hook call"
                    .format(tuple(notincall)),
                    stacklevel=2,
                )
>       return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)

../../../virtualenvs/sybil_35/lib/python3.5/site-packages/pluggy/__init__.py:617: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_pytest.config.PytestPluginManager object at 0x101c086d8>, hook = <_HookCaller 'pytest_runtest_setup'>
methods = [<pluggy.HookImpl object at 0x10325b5f8>, <pluggy.HookImpl object at 0x1030bf240>, <pluggy.HookImpl object at 0x10325b...uggy.HookImpl object at 0x103241a90>, <pluggy.HookImpl object at 0x10383a668>, <pluggy.HookImpl object at 0x103850048>]
kwargs = {'item': <SybilItem 'line:1,column:1'>}

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

../../../virtualenvs/sybil_35/lib/python3.5/site-packages/pluggy/__init__.py:222: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

hook = <_HookCaller 'pytest_runtest_setup'>
methods = [<pluggy.HookImpl object at 0x10325b5f8>, <pluggy.HookImpl object at 0x1030bf240>, <pluggy.HookImpl object at 0x10325b...uggy.HookImpl object at 0x103241a90>, <pluggy.HookImpl object at 0x10383a668>, <pluggy.HookImpl object at 0x103850048>]
kwargs = {'item': <SybilItem 'line:1,column:1'>}

    self._inner_hookexec = lambda hook, methods, kwargs: \
        hook.multicall(
            methods, kwargs,
>           firstresult=hook.spec_opts.get('firstresult'),
        )

../../../virtualenvs/sybil_35/lib/python3.5/site-packages/pluggy/__init__.py:216: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

hook_impls = [<pluggy.HookImpl object at 0x10325b5f8>, <pluggy.HookImpl object at 0x1030bf240>, <pluggy.HookImpl object at 0x10325b...uggy.HookImpl object at 0x103241a90>, <pluggy.HookImpl object at 0x10383a668>, <pluggy.HookImpl object at 0x103850048>]
caller_kwargs = {'item': <SybilItem 'line:1,column:1'>}, firstresult = False

    def _multicall(hook_impls, caller_kwargs, firstresult=False):
        """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(
                                    "hook call must provide argument %r" % (argname,))
    
                    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()

../../../virtualenvs/sybil_35/lib/python3.5/site-packages/pluggy/callers.py:201: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <pluggy.callers._Result object at 0x103881ba8>

    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
            if _py3:
>               raise ex[1].with_traceback(ex[2])

../../../virtualenvs/sybil_35/lib/python3.5/site-packages/pluggy/callers.py:76: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

hook_impls = [<pluggy.HookImpl object at 0x10325b5f8>, <pluggy.HookImpl object at 0x1030bf240>, <pluggy.HookImpl object at 0x10325b...uggy.HookImpl object at 0x103241a90>, <pluggy.HookImpl object at 0x10383a668>, <pluggy.HookImpl object at 0x103850048>]
caller_kwargs = {'item': <SybilItem 'line:1,column:1'>}, firstresult = False

    def _multicall(hook_impls, caller_kwargs, firstresult=False):
        """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(
                                    "hook call must provide argument %r" % (argname,))
    
                    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)

../../../virtualenvs/sybil_35/lib/python3.5/site-packages/pluggy/callers.py:180: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

item = <SybilItem 'line:1,column:1'>

    def pytest_runtest_setup(item):
        _update_current_test_var(item, 'setup')
>       item.session._setupstate.prepare(item)

../../../virtualenvs/sybil_35/lib/python3.5/site-packages/_pytest/runner.py:101: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_pytest.runner.SetupState object at 0x103858940>, colitem = <SybilItem 'line:1,column:1'>

    def prepare(self, colitem):
        """ setup objects along the collector chain to the test-method
                and teardown previously setup objects."""
        needed_collectors = colitem.listchain()
        self._teardown_towards(needed_collectors)
    
        # check if the last collection node has raised an error
        for col in self.stack:
            if hasattr(col, '_prepare_exc'):
                py.builtin._reraise(*col._prepare_exc)
        for col in needed_collectors[len(self.stack):]:
            self.stack.append(col)
            try:
>               col.setup()

../../../virtualenvs/sybil_35/lib/python3.5/site-packages/_pytest/runner.py:495: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <SybilItem 'line:1,column:1'>

    def setup(self):
>       fixtures.fillfixtures(self)

sybil/integration/pytest.py:61: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

function = <SybilItem 'line:1,column:1'>

    def fillfixtures(function):
        """ fill missing funcargs for a test function. """
        try:
            request = function._request
        except AttributeError:
            # XXX this special code path is only expected to execute
            # with the oejskit plugin.  It uses classes with funcargs
            # and we thus have to work a bit to allow this.
            fm = function.session._fixturemanager
            fi = fm.getfixtureinfo(function.parent, function.obj, None)
            function._fixtureinfo = fi
            request = function._request = FixtureRequest(function)
            request._fillfixtures()
            # prune out funcargs for jstests
            newfuncargs = {}
            for name in fi.argnames:
                newfuncargs[name] = function.funcargs[name]
            function.funcargs = newfuncargs
        else:
>           request._fillfixtures()

../../../virtualenvs/sybil_35/lib/python3.5/site-packages/_pytest/fixtures.py:242: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <FixtureRequest for <SybilItem 'line:1,column:1'>>

    def _fillfixtures(self):
        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)

../../../virtualenvs/sybil_35/lib/python3.5/site-packages/_pytest/fixtures.py:385: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <FixtureRequest for <SybilItem 'line:1,column:1'>>, argname = 'session_fixture'

    def getfixturevalue(self, argname):
        """ 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.
            """
>       return self._get_active_fixturedef(argname).cached_result[0]

../../../virtualenvs/sybil_35/lib/python3.5/site-packages/_pytest/fixtures.py:427: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <FixtureRequest for <SybilItem 'line:1,column:1'>>, argname = 'session_fixture'

    def _get_active_fixturedef(self, argname):
        try:
            return self._fixture_defs[argname]
        except KeyError:
            try:
                fixturedef = self._getnextfixturedef(argname)
            except FixtureLookupError:
                if argname == "request":
                    class PseudoFixtureDef:
                        cached_result = (self, [0], None)
                        scope = "function"
                    return PseudoFixtureDef
                raise
        # remove indent to prevent the python3 exception
        # from leaking into the call
>       result = self._getfixturevalue(fixturedef)

../../../virtualenvs/sybil_35/lib/python3.5/site-packages/_pytest/fixtures.py:453: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <FixtureRequest for <SybilItem 'line:1,column:1'>>, fixturedef = <FixtureDef name='session_fixture' scope='session' baseid='' >

    def _getfixturevalue(self, fixturedef):
        # prepare a subrequest object before calling fixture function
        # (latter managed by fixturedef)
        argname = fixturedef.argname
        funcitem = self._pyfuncitem
        scope = fixturedef.scope
        try:
            param = funcitem.callspec.getparam(argname)
        except (AttributeError, ValueError):
            param = NOTSET
            param_index = 0
            if fixturedef.params is not None:
                frame = inspect.stack()[3]
                frameinfo = inspect.getframeinfo(frame[0])
                source_path = frameinfo.filename
                source_lineno = frameinfo.lineno
                source_path = py.path.local(source_path)
                if source_path.relto(funcitem.config.rootdir):
                    source_path = source_path.relto(funcitem.config.rootdir)
                msg = (
                    "The requested fixture has no parameter defined for the "
                    "current test.\n\nRequested fixture '{0}' defined in:\n{1}"
                    "\n\nRequested here:\n{2}:{3}".format(
                        fixturedef.argname,
                        getlocation(fixturedef.func, funcitem.config.rootdir),
                        source_path,
                        source_lineno,
                    )
                )
                fail(msg)
        else:
            # indices might not be set if old-style metafunc.addcall() was used
            param_index = funcitem.callspec.indices.get(argname, 0)
            # if a parametrize invocation set a scope it will override
            # the static scope defined with the fixture function
            paramscopenum = funcitem.callspec._arg2scopenum.get(argname)
            if paramscopenum is not None:
                scope = scopes[paramscopenum]
    
        subrequest = SubRequest(self, scope, param, param_index, fixturedef)
    
        # check if a higher-level scoped fixture accesses a lower level one
        subrequest._check_scope(argname, self.scope, scope)
    
        # clear sys.exc_info before invoking the fixture (python bug?)
        # if its not explicitly cleared it will leak into the call
        exc_clear()
        try:
            # call the fixture function
            val = fixturedef.execute(request=subrequest)
        finally:
            # if fixture function failed it might have registered finalizers
            self.session._setupstate.addfinalizer(functools.partial(fixturedef.finish, request=subrequest),
>                                                 subrequest.node)

../../../virtualenvs/sybil_35/lib/python3.5/site-packages/_pytest/fixtures.py:522: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <SubRequest 'session_fixture' for <SybilItem 'line:1,column:1'>>

    @property
    def node(self):
        """ underlying collection node (depends on current request scope)"""
>       return self._getscopeitem(self.scope)

../../../virtualenvs/sybil_35/lib/python3.5/site-packages/_pytest/fixtures.py:285: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <SubRequest 'session_fixture' for <SybilItem 'line:1,column:1'>>, scope = 'session'

    def _getscopeitem(self, scope):
        if scope == "function":
            # this might also be a non-function Item despite its attribute name
            return self._pyfuncitem
        node = get_scope_node(self._pyfuncitem, scope)
        if node is None and scope == "class":
            # fallback to function item itself
            node = self._pyfuncitem
>       assert node
E       AssertionError

../../../virtualenvs/sybil_35/lib/python3.5/site-packages/_pytest/fixtures.py:556: AssertionError
------------------------------------------------------------------------ Captured stdout setup -------------------------------------------------------------------------
sybil setup function_fixture setup
class_fixture setup
module_fixture setup
======================================================================= 1 error in 0.29 seconds ========================================================================
@cjw296
Copy link
Author

cjw296 commented Nov 29, 2017

Just confirmed that problem is not there in 3.2.5.

I suspect the following code may be in play:

https://github.com/cjw296/sybil/blob/master/sybil/integration/pytest.py#L41
https://github.com/cjw296/sybil/blob/master/sybil/integration/pytest.py#L61

cjw296 added a commit to simplistix/sybil that referenced this issue Nov 29, 2017
cjw296 added a commit to simplistix/testfixtures that referenced this issue Nov 29, 2017
nicoddemus added a commit to nicoddemus/sybil that referenced this issue Nov 29, 2017
@nicoddemus
Copy link
Member

Thanks @cjw296!

The problem was introduced by fixing #2127: to obtain the correct "hook proxy" to call pytest_fixture_setup and pytest_fixture_post_finalizer we need to access request.node.fpsath:

pytest/_pytest/fixtures.py

Lines 787 to 791 in 88ed1ab

self.finish(request)
assert not hasattr(self, "cached_result")
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
return hook.pytest_fixture_setup(fixturedef=self, request=request)

Previously it would always access the global hook (skipping trying to access request.node):

pytest/_pytest/fixtures.py

Lines 788 to 792 in a220a40

self.finish()
assert not hasattr(self, "cached_result")
ihook = self._fixturemanager.session.ihook
return ihook.pytest_fixture_setup(fixturedef=self, request=request)

The problem is that request.node explodes because SybilItem.getparent returns None when passed a Session class (currently it only handles Module):

https://github.com/cjw296/sybil/blob/ec1572d36ea65a1320277107ad9a583bd259ba95/sybil/integration/pytest.py#L56-L58

I opened simplistix/sybil#5 with a quick fix for this.


This demonstrates how hard is to extend the pytest internals because we don't have clear interfaces and documentation to what's needed to extend the internal classes properly.

Having said that, it seems harsh that we have an assert node here:

pytest/_pytest/fixtures.py

Lines 548 to 557 in 88ed1ab

def _getscopeitem(self, scope):
if scope == "function":
# this might also be a non-function Item despite its attribute name
return self._pyfuncitem
node = get_scope_node(self._pyfuncitem, scope)
if node is None and scope == "class":
# fallback to function item itself
node = self._pyfuncitem
assert node
return node

Perhaps it would be better to raise an exception explaining the problem?

Also, it seems to me that this code:

hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
return hook.pytest_fixture_setup(fixturedef=self, request=request)

Should use request.fspath instead of request.node.fspath, but then it gives this error:

test setup failed
self = <SubRequest 'fix' for <Function 'test_fo'>>

    def provide(self):
        if func.__name__ in scope2props[self.scope]:
            return func(self)
        raise AttributeError("%s not available in %s-scoped context" % (
>           scopename, self.scope))
E       AttributeError: fspath not available in session-scoped context

..\_pytest\fixtures.py:57: AttributeError

Shouldn't request.fspath be essentially the same as request.node.fspath? What do you think @RonnyPfannschmidt?

nicoddemus added a commit to nicoddemus/sybil that referenced this issue Nov 29, 2017
@nicoddemus nicoddemus added topic: fixtures anything involving fixtures directly or indirectly type: question general question, might be closed after 2 weeks of inactivity type: regression indicates a problem that was introduced in a release which was working previously labels Nov 29, 2017
@RonnyPfannschmidt
Copy link
Member

@nicoddemus fspath is always the owning functions fspath, node is the scope belonging node thats underlying the thing

so the api is pretty much completely broken and unfixable without a major release that will nonetheless break peoples necks

@RonnyPfannschmidt
Copy link
Member

an additional note - the assertion sits there correctly, it could be enhanced with more debugging, but it certainly shouldnt be removed

@cjw296
Copy link
Author

cjw296 commented Nov 30, 2017

@RonnyPfannschmidt - what's the "owning function" here? SybilItem's are parts of .txt files, as I think we've discussed before.

It certainly makes me sad to have to copy'n'paste bits of pytest to get fixtures to work.

@RonnyPfannschmidt
Copy link
Member

@cjw296 frim pytest pov the sybil item is the "owning function" but there is not Sybil "File/Module" around to get the details to work

i'm well aware that the fixture system is more like a bowl of pasta than a building block, but that kinda re-factoring is quite a undertaking

@cjw296
Copy link
Author

cjw296 commented Nov 30, 2017

Er, thanks github, but I think the PyTest guys weren't done discussing this ;-)

@cjw296
Copy link
Author

cjw296 commented Nov 30, 2017

@nicoddemus - thanks for the quick fix, feel free to close this issue unless you guys have further use for it.

cjw296 added a commit to simplistix/testfixtures that referenced this issue Nov 30, 2017
@nicoddemus
Copy link
Member

fspath is always the owning functions fspath, node is the scope belonging node thats underlying the thing

Thanks @RonnyPfannschmidt, I think I understand, but would you mind provide a simple example?

nicoddemus added a commit to nicoddemus/pytest that referenced this issue Nov 30, 2017
nicoddemus added a commit to nicoddemus/pytest that referenced this issue Nov 30, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: fixtures anything involving fixtures directly or indirectly type: question general question, might be closed after 2 weeks of inactivity type: regression indicates a problem that was introduced in a release which was working previously
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants