python: change `pytest pkg/__init__.py` to only collect the `__init__.py` Module
Previously it would collect the entire package, but this is not what users expect. Refs #3749 Fixes #8976 Fixes #9263 Fixes #9313
This commit is contained in:
		
							parent
							
								
									2870157234
								
							
						
					
					
						commit
						c8b1790ee7
					
				|  | @ -0,0 +1,5 @@ | ||||||
|  | Running `pytest pkg/__init__.py` now collects the `pkg/__init__.py` file (module) only. | ||||||
|  | Previously, it collected the entire `pkg` package, including other test files in the directory, but excluding tests in the `__init__.py` file itself | ||||||
|  | (unless :confval:`python_files` was changed to allow `__init__.py` file). | ||||||
|  | 
 | ||||||
|  | To collect the entire package, specify just the directory: `pytest pkg`. | ||||||
|  | @ -467,12 +467,26 @@ The ``yield_fixture`` function/decorator | ||||||
| It has been so for a very long time, so can be search/replaced safely. | It has been so for a very long time, so can be search/replaced safely. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Removed Features | Removed Features and Breaking Changes | ||||||
| ---------------- | ------------------------------------- | ||||||
| 
 | 
 | ||||||
| As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after | As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after | ||||||
| an appropriate period of deprecation has passed. | an appropriate period of deprecation has passed. | ||||||
| 
 | 
 | ||||||
|  | Some breaking changes which could not be deprecated are also listed. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Collecting ``__init__.py`` files no longer collects package | ||||||
|  | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||||
|  | 
 | ||||||
|  | .. versionremoved:: 8.0 | ||||||
|  | 
 | ||||||
|  | Running `pytest pkg/__init__.py` now collects the `pkg/__init__.py` file (module) only. | ||||||
|  | Previously, it collected the entire `pkg` package, including other test files in the directory, but excluding tests in the `__init__.py` file itself | ||||||
|  | (unless :confval:`python_files` was changed to allow `__init__.py` file). | ||||||
|  | 
 | ||||||
|  | To collect the entire package, specify just the directory: `pytest pkg`. | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| The ``pytest.collect`` module | The ``pytest.collect`` module | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||||
|  |  | ||||||
|  | @ -736,7 +736,9 @@ class Package(Module): | ||||||
|         this_path = self.path.parent |         this_path = self.path.parent | ||||||
| 
 | 
 | ||||||
|         # Always collect the __init__ first. |         # Always collect the __init__ first. | ||||||
|         if path_matches_patterns(self.path, self.config.getini("python_files")): |         if self.session.isinitpath(self.path) or path_matches_patterns( | ||||||
|  |             self.path, self.config.getini("python_files") | ||||||
|  |         ): | ||||||
|             yield Module.from_parent(self, path=self.path) |             yield Module.from_parent(self, path=self.path) | ||||||
| 
 | 
 | ||||||
|         pkg_prefixes: Set[Path] = set() |         pkg_prefixes: Set[Path] = set() | ||||||
|  |  | ||||||
|  | @ -0,0 +1,2 @@ | ||||||
|  | def test_init(): | ||||||
|  |     pass | ||||||
|  | @ -1,2 +1,2 @@ | ||||||
| def test(): | def test_foo(): | ||||||
|     pass |     pass | ||||||
|  |  | ||||||
|  | @ -1420,10 +1420,15 @@ def test_package_collection_infinite_recursion(pytester: Pytester) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_package_collection_init_given_as_argument(pytester: Pytester) -> None: | def test_package_collection_init_given_as_argument(pytester: Pytester) -> None: | ||||||
|     """Regression test for #3749""" |     """Regression test for #3749, #8976, #9263, #9313. | ||||||
|  | 
 | ||||||
|  |     Specifying an __init__.py file directly should collect only the __init__.py | ||||||
|  |     Module, not the entire package. | ||||||
|  |     """ | ||||||
|     p = pytester.copy_example("collect/package_init_given_as_arg") |     p = pytester.copy_example("collect/package_init_given_as_arg") | ||||||
|     result = pytester.runpytest(p / "pkg" / "__init__.py") |     items, hookrecorder = pytester.inline_genitems(p / "pkg" / "__init__.py") | ||||||
|     result.stdout.fnmatch_lines(["*1 passed*"]) |     assert len(items) == 1 | ||||||
|  |     assert items[0].name == "test_init" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_package_with_modules(pytester: Pytester) -> None: | def test_package_with_modules(pytester: Pytester) -> None: | ||||||
|  |  | ||||||
|  | @ -1392,19 +1392,27 @@ def test_collect_pkg_init_and_file_in_args(pytester: Pytester) -> None: | ||||||
|     p = subdir.joinpath("test_file.py") |     p = subdir.joinpath("test_file.py") | ||||||
|     p.write_text("def test_file(): pass", encoding="utf-8") |     p.write_text("def test_file(): pass", encoding="utf-8") | ||||||
| 
 | 
 | ||||||
|     # NOTE: without "-o python_files=*.py" this collects test_file.py twice. |     # Just the package directory, the __init__.py module is filtered out. | ||||||
|     # This changed/broke with "Add package scoped fixtures #2283" (2b1410895) |     result = pytester.runpytest("-v", subdir) | ||||||
|     # initially (causing a RecursionError). |  | ||||||
|     result = pytester.runpytest("-v", str(init), str(p)) |  | ||||||
|     result.stdout.fnmatch_lines( |     result.stdout.fnmatch_lines( | ||||||
|         [ |         [ | ||||||
|             "sub/test_file.py::test_file PASSED*", |             "sub/test_file.py::test_file PASSED*", | ||||||
|  |             "*1 passed in*", | ||||||
|  |         ] | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     # But it's included if specified directly. | ||||||
|  |     result = pytester.runpytest("-v", init, p) | ||||||
|  |     result.stdout.fnmatch_lines( | ||||||
|  |         [ | ||||||
|  |             "sub/__init__.py::test_init PASSED*", | ||||||
|             "sub/test_file.py::test_file PASSED*", |             "sub/test_file.py::test_file PASSED*", | ||||||
|             "*2 passed in*", |             "*2 passed in*", | ||||||
|         ] |         ] | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     result = pytester.runpytest("-v", "-o", "python_files=*.py", str(init), str(p)) |     # Or if the pattern allows it. | ||||||
|  |     result = pytester.runpytest("-v", "-o", "python_files=*.py", subdir) | ||||||
|     result.stdout.fnmatch_lines( |     result.stdout.fnmatch_lines( | ||||||
|         [ |         [ | ||||||
|             "sub/__init__.py::test_init PASSED*", |             "sub/__init__.py::test_init PASSED*", | ||||||
|  | @ -1419,10 +1427,13 @@ def test_collect_pkg_init_only(pytester: Pytester) -> None: | ||||||
|     init = subdir.joinpath("__init__.py") |     init = subdir.joinpath("__init__.py") | ||||||
|     init.write_text("def test_init(): pass", encoding="utf-8") |     init.write_text("def test_init(): pass", encoding="utf-8") | ||||||
| 
 | 
 | ||||||
|     result = pytester.runpytest(str(init)) |     result = pytester.runpytest(subdir) | ||||||
|     result.stdout.fnmatch_lines(["*no tests ran in*"]) |     result.stdout.fnmatch_lines(["*no tests ran in*"]) | ||||||
| 
 | 
 | ||||||
|     result = pytester.runpytest("-v", "-o", "python_files=*.py", str(init)) |     result = pytester.runpytest("-v", init) | ||||||
|  |     result.stdout.fnmatch_lines(["sub/__init__.py::test_init PASSED*", "*1 passed in*"]) | ||||||
|  | 
 | ||||||
|  |     result = pytester.runpytest("-v", "-o", "python_files=*.py", subdir) | ||||||
|     result.stdout.fnmatch_lines(["sub/__init__.py::test_init PASSED*", "*1 passed in*"]) |     result.stdout.fnmatch_lines(["sub/__init__.py::test_init PASSED*", "*1 passed in*"]) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -114,7 +114,7 @@ class TestDoctests: | ||||||
|         reprec.assertoutcome(failed=1) |         reprec.assertoutcome(failed=1) | ||||||
| 
 | 
 | ||||||
|     def test_importmode(self, pytester: Pytester): |     def test_importmode(self, pytester: Pytester): | ||||||
|         p = pytester.makepyfile( |         pytester.makepyfile( | ||||||
|             **{ |             **{ | ||||||
|                 "namespacepkg/innerpkg/__init__.py": "", |                 "namespacepkg/innerpkg/__init__.py": "", | ||||||
|                 "namespacepkg/innerpkg/a.py": """ |                 "namespacepkg/innerpkg/a.py": """ | ||||||
|  | @ -132,7 +132,7 @@ class TestDoctests: | ||||||
|                 """, |                 """, | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|         reprec = pytester.inline_run(p, "--doctest-modules", "--import-mode=importlib") |         reprec = pytester.inline_run("--doctest-modules", "--import-mode=importlib") | ||||||
|         reprec.assertoutcome(passed=1) |         reprec.assertoutcome(passed=1) | ||||||
| 
 | 
 | ||||||
|     def test_new_pattern(self, pytester: Pytester): |     def test_new_pattern(self, pytester: Pytester): | ||||||
|  |  | ||||||
|  | @ -504,7 +504,7 @@ def test_nose_setup_skipped_if_non_callable(pytester: Pytester) -> None: | ||||||
|             pass |             pass | ||||||
|         """, |         """, | ||||||
|     ) |     ) | ||||||
|     result = pytester.runpytest(p, "-p", "nose") |     result = pytester.runpytest(p.parent, "-p", "nose") | ||||||
|     assert result.ret == 0 |     assert result.ret == 0 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue