Merge pull request #3010 from cryvate/fix-issue-2985
Improve handling of pyargs
This commit is contained in:
		
						commit
						476d4df1b7
					
				
							
								
								
									
										1
									
								
								AUTHORS
								
								
								
								
							
							
						
						
									
										1
									
								
								AUTHORS
								
								
								
								
							|  | @ -74,6 +74,7 @@ Grig Gheorghiu | |||
| Grigorii Eremeev (budulianin) | ||||
| Guido Wesdorp | ||||
| Harald Armin Massa | ||||
| Henk-Jaap Wagenaar | ||||
| Hugo van Kemenade | ||||
| Hui Wang (coldnight) | ||||
| Ian Bicking | ||||
|  |  | |||
|  | @ -1,8 +1,10 @@ | |||
| """ core implementation of testing process: init, session, runtest loop. """ | ||||
| from __future__ import absolute_import, division, print_function | ||||
| 
 | ||||
| import contextlib | ||||
| import functools | ||||
| import os | ||||
| import pkgutil | ||||
| import six | ||||
| import sys | ||||
| 
 | ||||
|  | @ -206,6 +208,46 @@ def pytest_ignore_collect(path, config): | |||
|     return False | ||||
| 
 | ||||
| 
 | ||||
| @contextlib.contextmanager | ||||
| def _patched_find_module(): | ||||
|     """Patch bug in pkgutil.ImpImporter.find_module | ||||
| 
 | ||||
|     When using pkgutil.find_loader on python<3.4 it removes symlinks | ||||
|     from the path due to a call to os.path.realpath. This is not consistent | ||||
|     with actually doing the import (in these versions, pkgutil and __import__ | ||||
|     did not share the same underlying code). This can break conftest | ||||
|     discovery for pytest where symlinks are involved. | ||||
| 
 | ||||
|     The only supported python<3.4 by pytest is python 2.7. | ||||
|     """ | ||||
|     if six.PY2:  # python 3.4+ uses importlib instead | ||||
|         def find_module_patched(self, fullname, path=None): | ||||
|             # Note: we ignore 'path' argument since it is only used via meta_path | ||||
|             subname = fullname.split(".")[-1] | ||||
|             if subname != fullname and self.path is None: | ||||
|                 return None | ||||
|             if self.path is None: | ||||
|                 path = None | ||||
|             else: | ||||
|                 # original: path = [os.path.realpath(self.path)] | ||||
|                 path = [self.path] | ||||
|             try: | ||||
|                 file, filename, etc = pkgutil.imp.find_module(subname, | ||||
|                                                               path) | ||||
|             except ImportError: | ||||
|                 return None | ||||
|             return pkgutil.ImpLoader(fullname, file, filename, etc) | ||||
| 
 | ||||
|         old_find_module = pkgutil.ImpImporter.find_module | ||||
|         pkgutil.ImpImporter.find_module = find_module_patched | ||||
|         try: | ||||
|             yield | ||||
|         finally: | ||||
|             pkgutil.ImpImporter.find_module = old_find_module | ||||
|     else: | ||||
|         yield | ||||
| 
 | ||||
| 
 | ||||
| class FSHookProxy: | ||||
|     def __init__(self, fspath, pm, remove_mods): | ||||
|         self.fspath = fspath | ||||
|  | @ -728,9 +770,10 @@ class Session(FSCollector): | |||
|         """Convert a dotted module name to path. | ||||
| 
 | ||||
|         """ | ||||
|         import pkgutil | ||||
| 
 | ||||
|         try: | ||||
|             loader = pkgutil.find_loader(x) | ||||
|             with _patched_find_module(): | ||||
|                 loader = pkgutil.find_loader(x) | ||||
|         except ImportError: | ||||
|             return x | ||||
|         if loader is None: | ||||
|  | @ -738,7 +781,8 @@ class Session(FSCollector): | |||
|         # This method is sometimes invoked when AssertionRewritingHook, which | ||||
|         # does not define a get_filename method, is already in place: | ||||
|         try: | ||||
|             path = loader.get_filename(x) | ||||
|             with _patched_find_module(): | ||||
|                 path = loader.get_filename(x) | ||||
|         except AttributeError: | ||||
|             # Retrieve path from AssertionRewritingHook: | ||||
|             path = loader.modules[x][0].co_filename | ||||
|  |  | |||
|  | @ -0,0 +1 @@ | |||
| Fix conversion of pyargs to filename to not convert symlinks and not use deprecated features on Python 3. | ||||
|  | @ -3,6 +3,8 @@ from __future__ import absolute_import, division, print_function | |||
| import os | ||||
| import sys | ||||
| 
 | ||||
| import six | ||||
| 
 | ||||
| import _pytest._code | ||||
| import py | ||||
| import pytest | ||||
|  | @ -645,6 +647,69 @@ class TestInvocationVariants(object): | |||
|             "*1 passed*" | ||||
|         ]) | ||||
| 
 | ||||
|     @pytest.mark.skipif(not hasattr(os, "symlink"), reason="requires symlinks") | ||||
|     def test_cmdline_python_package_symlink(self, testdir, monkeypatch): | ||||
|         """ | ||||
|         test --pyargs option with packages with path containing symlink can | ||||
|         have conftest.py in their package (#2985) | ||||
|         """ | ||||
|         monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', raising=False) | ||||
| 
 | ||||
|         search_path = ["lib", os.path.join("local", "lib")] | ||||
| 
 | ||||
|         dirname = "lib" | ||||
|         d = testdir.mkdir(dirname) | ||||
|         foo = d.mkdir("foo") | ||||
|         foo.ensure("__init__.py") | ||||
|         lib = foo.mkdir('bar') | ||||
|         lib.ensure("__init__.py") | ||||
|         lib.join("test_bar.py"). \ | ||||
|             write("def test_bar(): pass\n" | ||||
|                   "def test_other(a_fixture):pass") | ||||
|         lib.join("conftest.py"). \ | ||||
|             write("import pytest\n" | ||||
|                   "@pytest.fixture\n" | ||||
|                   "def a_fixture():pass") | ||||
| 
 | ||||
|         d_local = testdir.mkdir("local") | ||||
|         symlink_location = os.path.join(str(d_local), "lib") | ||||
|         if six.PY2: | ||||
|             os.symlink(str(d), symlink_location) | ||||
|         else: | ||||
|             os.symlink(str(d), symlink_location, target_is_directory=True) | ||||
| 
 | ||||
|         # The structure of the test directory is now: | ||||
|         # . | ||||
|         # ├── local | ||||
|         # │   └── lib -> ../lib | ||||
|         # └── lib | ||||
|         #     └── foo | ||||
|         #         ├── __init__.py | ||||
|         #         └── bar | ||||
|         #             ├── __init__.py | ||||
|         #             ├── conftest.py | ||||
|         #             └── test_bar.py | ||||
| 
 | ||||
|         def join_pythonpath(*dirs): | ||||
|             cur = os.getenv('PYTHONPATH') | ||||
|             if cur: | ||||
|                 dirs += (cur,) | ||||
|             return os.pathsep.join(str(p) for p in dirs) | ||||
| 
 | ||||
|         monkeypatch.setenv('PYTHONPATH', join_pythonpath(*search_path)) | ||||
|         for p in search_path: | ||||
|             monkeypatch.syspath_prepend(p) | ||||
| 
 | ||||
|         # module picked up in symlink-ed directory: | ||||
|         result = testdir.runpytest("--pyargs", "-v", "foo.bar") | ||||
|         testdir.chdir() | ||||
|         assert result.ret == 0 | ||||
|         result.stdout.fnmatch_lines([ | ||||
|             "*lib/foo/bar/test_bar.py::test_bar*PASSED*", | ||||
|             "*lib/foo/bar/test_bar.py::test_other*PASSED*", | ||||
|             "*2 passed*" | ||||
|         ]) | ||||
| 
 | ||||
|     def test_cmdline_python_package_not_exists(self, testdir): | ||||
|         result = testdir.runpytest("--pyargs", "tpkgwhatv") | ||||
|         assert result.ret | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue