Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions pytest_lazyfixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,21 @@ def pytest_fixture_setup(fixturedef, request):


def pytest_runtest_call(item):
def _rec_part(val):
if is_lazy_fixture(val):
return item._request.getfixturevalue(val.name)
elif type(val) == tuple:
return tuple(item._request.getfixturevalue(v.name) if is_lazy_fixture(v) else _rec_part(v) for v in val)
elif type(val) == list:
return list(item._request.getfixturevalue(v.name) if is_lazy_fixture(v) else _rec_part(v) for v in val)
elif isinstance(val, dict):
return {key: item._request.getfixturevalue(v.name) if is_lazy_fixture(v) else _rec_part(v)
for key, v in val.items()}
return val
Comment on lines +52 to +62
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that it is a correct way to solve the problem, because it will try to find lazy fixture inside of every test' pytest.fixture(params). At least it should be disabled by default and maybe through the marks to enable this behaviour.

Copy link
Author

@artsiomkaltovich artsiomkaltovich Oct 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry is a problem with dictionaries only? Or with list too?
Actually the library checks it now, so only some more levels are added :)

But I think decrease amount of checks is a good idea. But how to do it?
Add new attr to pytest.fixture?

 @pytest.fixture(params=[. . .], check_collections_for_lazy_fixtures=True)

or add specific object:

@pytest.fixture(params=[check_collections_for_lazy_fixtures, . . .])

Will it works?

Copy link
Author

@artsiomkaltovich artsiomkaltovich Oct 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found the better option:

add an option to pytest.ini lookup for lazy_fixtures (turn off by default).
using decorator @pytest.mark.lookup_for_lazy_fixtures().

If global config is turned on we always look, if it turned off we check for decorator in item.own_markers.

BTW do you concern about slowing down performance, because of lazy_fixtures lookup?
I am not sure it will have a big impact, because usually (at least on project I worked on :) ) test doesn't use big collections with a lot of nested collections inside.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think about using marks too. I will take a look a little bit later though.

BTW do you concern about slowing down performance, because of lazy_fixtures lookup?

Yep :) Also I don't like unknown structure of the param. Maybe it will be a good idea to specify format so we will know where to lookup lazy_fixture?

Copy link
Author

@artsiomkaltovich artsiomkaltovich Oct 6, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am trying to run some benchmark, but something broke:

 C:\Users\artem\PycharmProjects\pytest-lazy-fixture\tests\test_lazyfixture.py:18: 
 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
 C:\Program Files\python\lib\site-packages\_pytest\pytester.py:993: in getitems
      modcol = self.getmodulecol(source)
 C:\Program Files\python\lib\site-packages\_pytest\pytester.py:1020: in getmodulecol
    self.config = config = self.parseconfigure(path, *configargs)
 C:\Program Files\python\lib\site-packages\_pytest\pytester.py:959: in parseconfigure
    config._do_configure()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <_pytest.config.Config object at 0x0000016F434B08C8>

def _do_configure(self):
 >       assert not self._configured
 E       AssertionError

 C:\Program Files\python\lib\site-packages\_pytest\config\__init__.py:634: AssertionError

Do you have any idea what's wrong?

Also I don't like unknown structure of the param. Maybe it will be a good idea to specify format so we will know where to lookup lazy_fixture?

I got your concern, but I think specifying format is a source of many possible issues, the users can muddle up or forget to specify it, also there can be a problem with parsing the format, and it can create bugs in lib, also that can be slower than just checking all collections. BTW we decided to keep it simple and specifying format is not very intuitive and convenient for users. :)


if hasattr(item, 'funcargs'):
for arg, val in item.funcargs.items():
if is_lazy_fixture(val):
item.funcargs[arg] = item._request.getfixturevalue(val.name)
item.funcargs[arg] = _rec_part(val)


@pytest.hookimpl(hookwrapper=True)
Expand Down Expand Up @@ -165,11 +176,19 @@ def _tree_to_list(trees, leave):
return lst


def lazy_fixture(names):
if isinstance(names, string_type):
def lazy_fixture(names=None, *args, **kwargs):
if isinstance(names, string_type) and not args and not kwargs:
return LazyFixture(names)
else:
return [LazyFixture(name) for name in names]
elif not kwargs:
names = [names] if isinstance(names, string_type) else names
names, is_tuple = (list(names), True) if isinstance(names, tuple) else (names, False)
names.extend(args)
if is_tuple:
return tuple(LazyFixture(name) for name in names)
else:
return [LazyFixture(name) for name in names]
elif kwargs and not (args or names):
return {key: LazyFixture(value) for key, value in kwargs.items()}
Comment on lines +179 to +191
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is an unnecessary to create lazy fixtures this way. I regret adding a way to pass a list of names to create multiple fixtures :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I find it pretty convenient. Yes it is not so obvious as direct dict or list creating, but at least it helps to make your lines less then 120 symbol lengths. :)
Less code is better. :)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should keep it simple. If you need dict, list or tuple of lazy fixtures just use appropriate python notation to create this objects. If you are doing it in many places just abstract this logic to your own function.



def is_lazy_fixture(val):
Expand Down
138 changes: 138 additions & 0 deletions tests/test_lazyfixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -698,3 +698,141 @@ def test_func(some_fixture2):
""")
reprec = testdir.inline_run('-s')
reprec.assertoutcome(passed=2)


def test_list_of_fixtures(testdir):
testdir.makepyfile("""
import pytest

@pytest.fixture(params=[
pytest.lazy_fixture(['one', 'two']),
pytest.lazy_fixture('one', 'two'),
[1, pytest.lazy_fixture('two')],
[pytest.lazy_fixture('one'), 2],
[pytest.lazy_fixture('one'), pytest.lazy_fixture('two')]
])
def some(request):
return request.param

@pytest.fixture
def one():
return 1

@pytest.fixture
def two():
return 2

def test_func(some):
assert some == [1, 2]
""")
reprec = testdir.inline_run('-s')
reprec.assertoutcome(passed=5)


def test_tuple_of_fixtures(testdir):
testdir.makepyfile("""
import pytest

@pytest.fixture(params=[
pytest.lazy_fixture(('one', 'two')),
(1, pytest.lazy_fixture('two')),
(pytest.lazy_fixture('one'), 2),
(pytest.lazy_fixture('one'), pytest.lazy_fixture('two'))
])
def some(request):
return request.param

@pytest.fixture
def one():
return 1

@pytest.fixture
def two():
return 2

def test_func(some):
assert some == (1, 2)
""")
reprec = testdir.inline_run('-s')
reprec.assertoutcome(passed=4)


def test_dict_of_fixtures(testdir):
testdir.makepyfile("""
import pytest

@pytest.fixture(params=[
dict(one=1, two=pytest.lazy_fixture('two')),
dict(one=pytest.lazy_fixture('one'), two=2),
dict(one=pytest.lazy_fixture('one'), two=pytest.lazy_fixture('two'))
])
def some(request):
return request.param

@pytest.fixture
def one():
return 1

@pytest.fixture
def two():
return 2

def test_func(some):
assert some == dict(one=1, two=2)
""")
reprec = testdir.inline_run('-s')
reprec.assertoutcome(passed=3)


def test_list_with_collections_of_fixtures(testdir):
testdir.makepyfile("""
import pytest

@pytest.fixture(params=[
[[pytest.lazy_fixture('one')], [(2,)]],
[[1], [(pytest.lazy_fixture('two'),)]],
[[pytest.lazy_fixture('one')], [(pytest.lazy_fixture('two'),)]],
])
def some(request):
return request.param

@pytest.fixture
def one():
return 1

@pytest.fixture
def two():
return 2

def test_func(some):
assert some == [[1], [(2,)]]
""")
reprec = testdir.inline_run('-s')
reprec.assertoutcome(passed=3)


def test_dict_with_collections_of_fixtures(testdir):
testdir.makepyfile("""
import pytest

@pytest.fixture(params=[
dict(one=[pytest.lazy_fixture('one')], two=dict(two=(2,))),
dict(one=[1], two=dict(two=(pytest.lazy_fixture('two'),))),
dict(one=[pytest.lazy_fixture('one')], two=dict(two=(pytest.lazy_fixture('two'),)))
])
def some(request):
return request.param

@pytest.fixture
def one():
return 1

@pytest.fixture
def two():
return 2

def test_func(some):
assert some == dict(one=[1], two=dict(two=(2,)))
""")
reprec = testdir.inline_run('-s')
reprec.assertoutcome(passed=3)