|
8 | 8 | import pytest |
9 | 9 |
|
10 | 10 | from pytest_cases.common import get_pytest_nodeid, get_pytest_function_scopenum, \ |
11 | | - is_function_node, get_param_names |
| 11 | + is_function_node, get_param_names, get_pytest_scopenum |
12 | 12 | from pytest_cases.main_fixtures import NOT_USED, is_fixture_union_params, UnionFixtureAlternative, apply_id_style |
13 | 13 |
|
14 | 14 | try: # python 3.3+ |
|
28 | 28 |
|
29 | 29 |
|
30 | 30 | # @hookspec(firstresult=True) |
| 31 | +# @pytest.hookimpl(tryfirst=True, hookwrapper=True) |
31 | 32 | def pytest_collection(session): |
32 | 33 | # override the fixture manager's method |
33 | 34 | session._fixturemanager.getfixtureclosure = partial(getfixtureclosure, session._fixturemanager) |
@@ -340,13 +341,41 @@ def split_and_build(self, |
340 | 341 | def has_split(self): |
341 | 342 | return self.split_fixture_name is not None |
342 | 343 |
|
| 344 | + def gather_all_required(self, include_children=True, include_parents=True): |
| 345 | + """ |
| 346 | + Returns a list of all fixtures required by the subtree at this node |
| 347 | + :param include_children: |
| 348 | + :return: |
| 349 | + """ |
| 350 | + # first the fixtures required by this node |
| 351 | + required = list(self.fixture_defs.keys()) |
| 352 | + |
| 353 | + # then the ones required by the parents |
| 354 | + if include_parents and self.parent is not None: |
| 355 | + required = required + self.parent.gather_all_required(include_children=False) |
| 356 | + |
| 357 | + # then the ones from all the children |
| 358 | + if include_children: |
| 359 | + for child in self.children.values(): |
| 360 | + required = required + child.gather_all_required(include_parents=False) |
| 361 | + |
| 362 | + return required |
| 363 | + |
| 364 | + def requires(self, fixturename): |
| 365 | + """ |
| 366 | + Returns True if the fixture with this name is required by the subtree at this node |
| 367 | + :param fixturename: |
| 368 | + :return: |
| 369 | + """ |
| 370 | + return fixturename in self.gather_all_required() |
| 371 | + |
343 | 372 | def gather_all_discarded(self): |
344 | 373 | """ |
345 | 374 | Returns a list of all fixture names discarded during splits from the parent node down to this node. |
346 | 375 | Note: this does not include the split done at this node if any, nor all of its subtree. |
347 | 376 | :return: |
348 | 377 | """ |
349 | | - discarded = self.split_fixture_discarded_names |
| 378 | + discarded = list(self.split_fixture_discarded_names) |
350 | 379 | if self.parent is not None: |
351 | 380 | discarded = discarded + self.parent.gather_all_discarded() |
352 | 381 |
|
@@ -730,12 +759,18 @@ def _cleanup_calls_list(self, calls, nodes, pending): |
730 | 759 | # For this we use a dirty hack: we add a parameter with they name in the callspec, it seems to be propagated |
731 | 760 | # in the `request`. TODO is there a better way? |
732 | 761 | # for fixture in list(fix_closure_tree): |
733 | | - for fixture in n.gather_all_discarded(): |
734 | | - if fixture not in c.params and fixture not in c.funcargs: |
735 | | - # explicitly add it as discarded by creating a parameter value for it. |
736 | | - c.params[fixture] = NOT_USED |
737 | | - c.indices[fixture] = 0 |
738 | | - c._arg2scopenum[fixture] = function_scope_num |
| 762 | + for fixture_name, fixdef in self.metafunc._arg2fixturedefs.items(): |
| 763 | + if fixture_name not in c.params and fixture_name not in c.funcargs: |
| 764 | + if not n.requires(fixture_name): |
| 765 | + # explicitly add it as discarded by creating a parameter value for it. |
| 766 | + c.params[fixture_name] = NOT_USED |
| 767 | + c.indices[fixture_name] = 0 |
| 768 | + c._arg2scopenum[fixture_name] = get_pytest_scopenum(fixdef[-1].scope) |
| 769 | + else: |
| 770 | + # explicitly add it as active |
| 771 | + c.params[fixture_name] = 'used' |
| 772 | + c.indices[fixture_name] = 1 |
| 773 | + c._arg2scopenum[fixture_name] = get_pytest_scopenum(fixdef[-1].scope) |
739 | 774 |
|
740 | 775 | def _parametrize_calls(self, init_calls, argnames, argvalues, discard_id=False, indirect=False, ids=None, |
741 | 776 | scope=None, **kwargs): |
@@ -905,3 +940,27 @@ def sort_according_to_ref_list(fixturenames, param_names): |
905 | 940 | for old_i, new_i in zip(cur_indices, target_indices): |
906 | 941 | sorted_fixturenames[new_i] = fixturenames[old_i] |
907 | 942 | return sorted_fixturenames |
| 943 | + |
| 944 | + |
| 945 | +@pytest.hookimpl(tryfirst=True, hookwrapper=True) |
| 946 | +def pytest_collection_modifyitems(session, config, items): |
| 947 | + """ |
| 948 | + An alternative to the `reorder_items` function in fixtures.py |
| 949 | + (https://github.com/pytest-dev/pytest/blob/master/src/_pytest/fixtures.py#L209) |
| 950 | +
|
| 951 | + We basically set back the previous order once the pytest ordering routine has completed. |
| 952 | +
|
| 953 | + TODO we should set back an optimal ordering, but current PR https://github.com/pytest-dev/pytest/pull/3551 |
| 954 | + will probably not be relevant to handle our "union" fixtures > need to integrate the NOT_USED markers in the method |
| 955 | +
|
| 956 | + :param session: |
| 957 | + :param config: |
| 958 | + :param items: |
| 959 | + :return: |
| 960 | + """ |
| 961 | + |
| 962 | + # remember initial order |
| 963 | + initial_order = copy(items) |
| 964 | + yield |
| 965 | + # put back the initial order |
| 966 | + items[:] = initial_order |
0 commit comments