-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Provide a way to nicely control fixture execution order #1216
Comments
the idea of declaring indirect dependency orders is tricky to solve, |
I was thinking of was something similar to what was done w/ plugin order via tryfirst/trylast ... but no idea how quick that would be. Perhaps a sorting system similar to how logging levels work, where fixture order within a level would be indeterminate? |
i guess there could be another parameter to the "pytest.fixture" decorator which influences ordering like "after='a''" but not sure how easy it would be to implement. It would also need to provide proper error messages when ordering can not be established according to fixture configuration. And i somewhat agree to the point Ronny is making that we might need to do some internal refactoring first. That being said, it's always good to have a guiding use case that should be enabled by a refactoring (apart from the need for full backward compat of course). FWIW myself i probably won't try to implement this on unpaid time. |
For a more concrete use case: I'm trying to implement shared DB state fixtures based on layering subtransactions here: pytest-dev/pytest-django#258, and I need to make sure For this case something like inherited |
I also have a concrete use case. A top level conftest.py has a couple of fixtures def user():
return new_user
def application(user):
return new_application(user) A subdirectory with some special tests needs to create some special state for user before loading the application. Ideally we could do. @pytest.usefixtures(before='application')
def special_user_setup(user):
prepare_some_special_stuff_for(user) We could override the user fixture, but that would be unfortunate if the user fixture was doing a lot of work on its own (we'd have to copy paste). Is there a way to do this today, or will it require such a |
@nipunn1313 I wonder if you could just do something like: @pytest.fixture
def user(user):
pass Kind of a hack, it might or might not work. |
I had the same problem (wanted @pytest.yield_fixture
def foo():
...
@pytest.mark.early
@pytest.yield_fixture
def bar():
...
def reorder_early_fixtures(metafunc):
for fixturedef in metafunc._arg2fixturedefs.values():
fixturedef = fixturedef[0]
if getattr(fixturedef.func, 'early', None):
order = metafunc.fixturenames
order.insert(0, order.pop(order.index(fixturedef.argname)))
def pytest_generate_tests(metafunc):
reorder_early_fixtures(metafunc)
def test_baz(foo, bar): Whereas I suppose marking fixtures - while not intended - is innocuous, I have doubts as to how foolish assumptions of
How long would these assumptions remain to be true (I'm using pytest 3.0.3)? |
@aurzenligl looks like a good idea, question, can we implement the marker with the before param mentioned above, sounds like a great way to enforce the order |
While I do suggest caution in ordering fixtures, I've written up a flexible way to do so when the situation calls for it: def order_fixtures(metafunc):
metafunc.fixturenames[:] = []
orders = {name: getattr(definition[0].func, "order", None)
for name, definition in metafunc._arg2fixturedefs.items()}
ordered = {name: getattr(order, "args")[0] for name, order in orders.items() if order}
unordered = [name for name, order in orders.items() if not order]
first = {name: order for name, order in ordered.items() if order and order < 0}
last = {name: order for name, order in ordered.items() if order and order > 0}
merged = sorted(first, key=first.get) + unordered + sorted(last, key=last.get)
metafunc.fixturenames.extend(merged)
def pytest_generate_tests(metafunc):
order_fixtures(metafunc) Fixtures can then be ordered so that those with a negative order are run before unordered fixtures in ascending order, then unordered fixtures are run, then those with a positive order are run in ascending order, as in the following example: @pytest.mark.order(-2)
@pytest.fixture
def first():
print("I go first.")
@pytest.mark.order(-1)
@pytest.fixture
def second():
print("I go second.")
@pytest.fixture
def third():
print("I go third.")
@pytest.mark.order(1)
@pytest.fixture
def fourth():
print("I go fourth.")
@pytest.mark.order(2)
@pytest.fixture
def fifth():
print("I go fifth.")
def test_something(fifth, fourth, third, second, first):
print("Running your test.")
"""
Output:
I go first.
I go second.
I go third.
I go fourth.
I go fifth.
Running your test.
""" |
in general marks are not supported on fixtures, and its not clear how marks of fixtures shouzld map to tests, |
Hey @RonnyPfannschmidt, |
What about using something like z-index from CSS? @pytest.fixture(teardown_index=-9999)
def first():
pass
@pytest.fixture(teardown_index=9999)
def last:
pass just go in order of teardown index, if 2 items have the same value, no guarantees. The default index can be 0. If teardown should be early, use a negative, if they should be later, use a positive number. |
that doesn't take scopes into account ^^ |
Let's say scope takes priority, it doesn't make sense to teardown class fixtures after module fixtures. The kwarg could even by |
@claweyenuk that still just imposes a random artificial order - which in turn is very likely to bite someone |
To complete @aurzenligl solution (that broke with pytest 4.1), here is an updated example: def reorder_early_fixtures(metafunc):
"""
Put fixtures with `pytest.mark.early` first during execution
This allows patch of configurations before the application is initialized
"""
for fixturedef in metafunc._arg2fixturedefs.values():
fixturedef = fixturedef[0]
for mark in getattr(fixturedef.func, 'pytestmark', []):
if mark.name == 'early':
order = metafunc.fixturenames
order.insert(0, order.pop(order.index(fixturedef.argname)))
break
def pytest_generate_tests(metafunc):
reorder_early_fixtures(metafunc)
@pytest.fixture
@pytest.mark.early
def early_stuff(self, another_fixture):
# ... |
Is there any approved solution to be working and safely implemented? |
@GabrieleCalarota For simple cases, using the other fixture as an argument - for more complex cases (or what the OP described), no, otherwise this would be closed. |
@The-Compiler Yep, the problem I had was related to the order but I import multiple fixture in pytest. Example:
Is this achievable in some way to get a newly fetched fixture |
To complicate matters even more, Now it seem the order for the teardown is a reverse of the order of the setup. Assuming this is true, |
Tear down as reverse of the setup it's the most solid pattern for preventing loads of foot guns If you need a different resources cleanup pattern outlining the use case in a discussion would be a good start |
It seems changing the order of item.fixturenames in pytest_runtest_teardown hook has no effect? |
An obvious workaround would be to split a single fixture with teardown into two fixtures, |
I'm not going to keep discussing foot guns in a XY problem Style,im out |
This comment was marked as abuse.
This comment was marked as abuse.
This comment was marked as abuse.
This comment was marked as abuse.
That probably was me -- and ronny has been one of the key collaborators on pytest from the start FYI. I am overall not sure that this issue should be kept open, btw. Several people pointed out workarounds/ways to implement some sort of fixture ordering and putting support into pytest itself would be a lot of work as far as i can still tell. |
i believe best to close this one, and revisit the idea at a later point when the fixture system is in better shape recently a new person started to do great work towards cleaning things up and eventually enabling more |
Agree, closing. |
Hello, are we planning to revisit this? |
We sometimes have fixtures that need to execute in a specific order, without having them include one another as an argument. This is to provide flexibility with fixtures, so they aren't always linked to one another.
Quick example:
I know there's ways to do this w/ using fixture arguments, like having intermediary fixtures that exist purely to order the other fixtures, but it's not very pretty.
The text was updated successfully, but these errors were encountered: