diff --git a/changelog/3796.bugfix.rst b/changelog/3796.bugfix.rst new file mode 100644 index 000000000..7a115301a --- /dev/null +++ b/changelog/3796.bugfix.rst @@ -0,0 +1,2 @@ +Fixed an issue where teardown of fixtures of consecutive sub-packages were executed once, at the end of the outer +package. diff --git a/changelog/3854.bugfix.rst b/changelog/3854.bugfix.rst new file mode 100644 index 000000000..6184f03b6 --- /dev/null +++ b/changelog/3854.bugfix.rst @@ -0,0 +1 @@ +Fixes double collection of tests within packages when the filename starts with a capital letter. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index c12691caa..bfbf7bb54 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -2,7 +2,6 @@ from __future__ import absolute_import, division, print_function import functools import inspect -import os import sys import warnings from collections import OrderedDict, deque, defaultdict @@ -93,7 +92,7 @@ def get_scope_package(node, fixturedef): cls = pytest.Package current = node - fixture_package_name = os.path.join(fixturedef.baseid, "__init__.py") + fixture_package_name = "%s/%s" % (fixturedef.baseid, "__init__.py") while current and ( type(current) is not cls or fixture_package_name != current.nodeid ): diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 45ef3be61..7fb7ff9ef 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -590,17 +590,28 @@ class Package(Module): self.session.config.pluginmanager._duplicatepaths.remove(path) this_path = self.fspath.dirpath() - pkg_prefix = None + pkg_prefixes = set() for path in this_path.visit(rec=self._recurse, bf=True, sort=True): # we will visit our own __init__.py file, in which case we skip it + skip = False if path.basename == "__init__.py" and path.dirpath() == this_path: continue - if pkg_prefix and pkg_prefix in path.parts(): + + for pkg_prefix in pkg_prefixes: + if ( + pkg_prefix in path.parts() + and pkg_prefix.join("__init__.py") != path + ): + skip = True + + if skip: continue + + if path.isdir() and path.join("__init__.py").check(file=1): + pkg_prefixes.add(path) + for x in self._collectfile(path): yield x - if isinstance(x, Package): - pkg_prefix = path.dirpath() def _get_xunit_setup_teardown(holder, attr_name, param_obj=None): diff --git a/testing/python/collect.py b/testing/python/collect.py index b3b19802a..8f4283e40 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -1624,3 +1624,38 @@ def test_package_with_modules(testdir): root.chdir() result = testdir.runpytest("-v", "-s") result.assert_outcomes(passed=2) + + +def test_package_ordering(testdir): + """ + . + └── root + ├── Test_root.py + ├── __init__.py + ├── sub1 + │ ├── Test_sub1.py + │ └── __init__.py + └── sub2 + └── test + └── test_sub2.py + + """ + testdir.makeini( + """ + [pytest] + python_files=*.py + """ + ) + root = testdir.mkpydir("root") + sub1 = root.mkdir("sub1") + sub1.ensure("__init__.py") + sub2 = root.mkdir("sub2") + sub2_test = sub2.mkdir("sub2") + + root.join("Test_root.py").write("def test_1(): pass") + sub1.join("Test_sub1.py").write("def test_2(): pass") + sub2_test.join("test_sub2.py").write("def test_3(): pass") + + # Execute from . + result = testdir.runpytest("-v", "-s") + result.assert_outcomes(passed=3) diff --git a/testing/python/fixture.py b/testing/python/fixture.py index 47be7800c..f8f5eb54e 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import textwrap import pytest @@ -3977,3 +3978,71 @@ class TestScopeOrdering(object): items, _ = testdir.inline_genitems() request = FixtureRequest(items[0]) assert request.fixturenames == "s1 p1 m1 m2 c1 f2 f1".split() + + def test_multiple_packages(self, testdir): + """Complex test involving multiple package fixtures. Make sure teardowns + are executed in order. + . + └── root + ├── __init__.py + ├── sub1 + │ ├── __init__.py + │ ├── conftest.py + │ └── test_1.py + └── sub2 + ├── __init__.py + ├── conftest.py + └── test_2.py + """ + root = testdir.mkdir("root") + root.join("__init__.py").write("values = []") + sub1 = root.mkdir("sub1") + sub1.ensure("__init__.py") + sub1.join("conftest.py").write( + textwrap.dedent( + """\ + import pytest + from .. import values + @pytest.fixture(scope="package") + def fix(): + values.append("pre-sub1") + yield values + assert values.pop() == "pre-sub1" + """ + ) + ) + sub1.join("test_1.py").write( + textwrap.dedent( + """\ + from .. import values + def test_1(fix): + assert values == ["pre-sub1"] + """ + ) + ) + sub2 = root.mkdir("sub2") + sub2.ensure("__init__.py") + sub2.join("conftest.py").write( + textwrap.dedent( + """\ + import pytest + from .. import values + @pytest.fixture(scope="package") + def fix(): + values.append("pre-sub2") + yield values + assert values.pop() == "pre-sub2" + """ + ) + ) + sub2.join("test_2.py").write( + textwrap.dedent( + """\ + from .. import values + def test_2(fix): + assert values == ["pre-sub2"] + """ + ) + ) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=2)