Jc2k/pytest-docker-tools

Failure using --fixtures flag with pytest

gudjonragnar opened this issue · 5 comments

I have a conftest.py file that is loading an image and creating a container using this plugin. It looks something like this:

image = fetch("image-name")

my_container = container(...)

@pytest.fixture
def some_other_fixture():
    ...

When I run pytest --fixtures <test-path> it fails showing this error:

INTERNALERROR> Traceback (most recent call last):
INTERNALERROR>   File "/var/as/venvs/callback/lib/python3.8/site-packages/_pytest/main.py", line 269, in wrap_session
INTERNALERROR>     session.exitstatus = doit(config, session) or 0
INTERNALERROR>   File "/var/as/venvs/callback/lib/python3.8/site-packages/_pytest/python.py", line 1497, in _showfixtures_main
INTERNALERROR>     available.sort()
INTERNALERROR> TypeError: '<' not supported between instances of 'NoneType' and 'str'

When i looked into the code and check this available list that is being sorted I see this:

(5, 'tests.conftest', 'tests/conftest.py:88', 'hostname', <FixtureDef argname='hostname' scope='module' baseid='tests'>)
(5, None, '<string>:5', 'image', <FixtureDef argname='image' scope='session' baseid='tests'>)
(5, 'tests.conftest', 'tests/conftest.py:98', 'rabbit_connections', <FixtureDef argname='rabbit_connections' scope='module' baseid='tests'>)
(5, None, '<string>:5', 'my_container', <FixtureDef argname='my_container' scope='package' baseid='tests'>)

As you can see the two lines containing image and my_container have None as their second value and the location is also weird.

The available list is being created here: https://github.com/pytest-dev/pytest/blob/master/src/_pytest/python.py#L1471

I think the issue might be related to the decorator handling in the plugin, but I haven't investigated it.

Jc2k commented

That kind of makes sense. The current method for generating fixtures relies on some grim eval to make some other part of pytest function correctly so it's location won't be a file. I guess that code needs amending to set some properties that a normal fixture would have...

Jc2k commented

My memory is a bit flakey but I think this might be related to the current setup?

#5

I guess you are referencing the build_fixture_function function in builder.py?

Jc2k commented

Yeah. So i had hoped it would be enough to make sure that that returns something with .__module__ set, but so far in my tests pytest still thinks its None regardless.

Jc2k commented

Sigh. pytest captures a copy of the fixture, so you can't mutate it after calling the pytest decorator.

So something like this fixes it for me:

diff --git a/pytest_docker_tools/builder.py b/pytest_docker_tools/builder.py
index bdb3a70..2bc959c 100644
--- a/pytest_docker_tools/builder.py
+++ b/pytest_docker_tools/builder.py
@@ -5,7 +5,7 @@ from .templates import find_fixtures_in_params, resolve_fixtures_in_params
 
 def build_fixture_function(callable, scope, wrapper_class, kwargs):
     name = callable.__name__
-    docstring = "Docker image"
+    docstring = name
     if "path" in kwargs:
         docstring = getattr(callable, "__doc__", "").format(**kwargs)
     fixtures = find_fixtures_in_params(kwargs).union({"request", "docker_client"})
@@ -15,13 +15,15 @@ def build_fixture_function(callable, scope, wrapper_class, kwargs):
         f"""
     import pytest
 
-    @pytest.fixture(scope=scope)
     def {name}({fixtures_str}):
         \'\'\'
         {docstring}
         \'\'\'
         real_kwargs = resolve_fixtures_in_params(request, kwargs)
         return _{name}(request, docker_client, wrapper_class=wrapper_class, **real_kwargs)
+
+    {name}.__module__ = callable.__module__
+    {name} = pytest.fixture(scope=scope)({name})
     """
     )
     globals = {