mirror of
https://github.com/python/cpython.git
synced 2025-08-30 05:35:08 +00:00
bpo-23882: unittest: Drop PEP 420 support from discovery. (GH-29745)
This commit is contained in:
parent
1bee9a4625
commit
0b2b9d2513
5 changed files with 44 additions and 78 deletions
|
@ -266,8 +266,7 @@ Test Discovery
|
||||||
|
|
||||||
Unittest supports simple test discovery. In order to be compatible with test
|
Unittest supports simple test discovery. In order to be compatible with test
|
||||||
discovery, all of the test files must be :ref:`modules <tut-modules>` or
|
discovery, all of the test files must be :ref:`modules <tut-modules>` or
|
||||||
:ref:`packages <tut-packages>` (including :term:`namespace packages
|
:ref:`packages <tut-packages>` importable from the top-level directory of
|
||||||
<namespace package>`) importable from the top-level directory of
|
|
||||||
the project (this means that their filenames must be valid :ref:`identifiers
|
the project (this means that their filenames must be valid :ref:`identifiers
|
||||||
<identifiers>`).
|
<identifiers>`).
|
||||||
|
|
||||||
|
@ -340,6 +339,24 @@ the `load_tests protocol`_.
|
||||||
directory too (e.g.
|
directory too (e.g.
|
||||||
``python -m unittest discover -s root/namespace -t root``).
|
``python -m unittest discover -s root/namespace -t root``).
|
||||||
|
|
||||||
|
.. versionchanged:: 3.11
|
||||||
|
Python 3.11 dropped the :term:`namespace packages <namespace package>`
|
||||||
|
support. It has been broken since Python 3.7. Start directory and
|
||||||
|
subdirectories containing tests must be regular package that have
|
||||||
|
``__init__.py`` file.
|
||||||
|
|
||||||
|
Directories containing start directory still can be a namespace package.
|
||||||
|
In this case, you need to specify start directory as dotted package name,
|
||||||
|
and target directory explicitly. For example::
|
||||||
|
|
||||||
|
# proj/ <-- current directory
|
||||||
|
# namespace/
|
||||||
|
# mypkg/
|
||||||
|
# __init__.py
|
||||||
|
# test_mypkg.py
|
||||||
|
|
||||||
|
python -m unittest discover -s namespace.mypkg -t .
|
||||||
|
|
||||||
|
|
||||||
.. _organizing-tests:
|
.. _organizing-tests:
|
||||||
|
|
||||||
|
@ -1858,6 +1875,10 @@ Loading and running tests
|
||||||
whether their path matches *pattern*, because it is impossible for
|
whether their path matches *pattern*, because it is impossible for
|
||||||
a package name to match the default pattern.
|
a package name to match the default pattern.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.11
|
||||||
|
*start_dir* can not be a :term:`namespace packages <namespace package>`.
|
||||||
|
It has been broken since Python 3.7 and Python 3.11 officially remove it.
|
||||||
|
|
||||||
|
|
||||||
The following attributes of a :class:`TestLoader` can be configured either by
|
The following attributes of a :class:`TestLoader` can be configured either by
|
||||||
subclassing or assignment on an instance:
|
subclassing or assignment on an instance:
|
||||||
|
|
|
@ -542,6 +542,10 @@ Removed
|
||||||
|
|
||||||
(Contributed by Hugo van Kemenade in :issue:`45320`.)
|
(Contributed by Hugo van Kemenade in :issue:`45320`.)
|
||||||
|
|
||||||
|
* Remove namespace package support from unittest discovery. It was introduced in
|
||||||
|
Python 3.4 but has been broken since Python 3.7.
|
||||||
|
(Contributed by Inada Naoki in :issue:`23882`.)
|
||||||
|
|
||||||
|
|
||||||
Porting to Python 3.11
|
Porting to Python 3.11
|
||||||
======================
|
======================
|
||||||
|
|
|
@ -264,8 +264,6 @@ class TestLoader(object):
|
||||||
self._top_level_dir = top_level_dir
|
self._top_level_dir = top_level_dir
|
||||||
|
|
||||||
is_not_importable = False
|
is_not_importable = False
|
||||||
is_namespace = False
|
|
||||||
tests = []
|
|
||||||
if os.path.isdir(os.path.abspath(start_dir)):
|
if os.path.isdir(os.path.abspath(start_dir)):
|
||||||
start_dir = os.path.abspath(start_dir)
|
start_dir = os.path.abspath(start_dir)
|
||||||
if start_dir != top_level_dir:
|
if start_dir != top_level_dir:
|
||||||
|
@ -281,50 +279,25 @@ class TestLoader(object):
|
||||||
top_part = start_dir.split('.')[0]
|
top_part = start_dir.split('.')[0]
|
||||||
try:
|
try:
|
||||||
start_dir = os.path.abspath(
|
start_dir = os.path.abspath(
|
||||||
os.path.dirname((the_module.__file__)))
|
os.path.dirname((the_module.__file__)))
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# look for namespace packages
|
if the_module.__name__ in sys.builtin_module_names:
|
||||||
try:
|
|
||||||
spec = the_module.__spec__
|
|
||||||
except AttributeError:
|
|
||||||
spec = None
|
|
||||||
|
|
||||||
if spec and spec.loader is None:
|
|
||||||
if spec.submodule_search_locations is not None:
|
|
||||||
is_namespace = True
|
|
||||||
|
|
||||||
for path in the_module.__path__:
|
|
||||||
if (not set_implicit_top and
|
|
||||||
not path.startswith(top_level_dir)):
|
|
||||||
continue
|
|
||||||
self._top_level_dir = \
|
|
||||||
(path.split(the_module.__name__
|
|
||||||
.replace(".", os.path.sep))[0])
|
|
||||||
tests.extend(self._find_tests(path,
|
|
||||||
pattern,
|
|
||||||
namespace=True))
|
|
||||||
elif the_module.__name__ in sys.builtin_module_names:
|
|
||||||
# builtin module
|
# builtin module
|
||||||
raise TypeError('Can not use builtin modules '
|
raise TypeError('Can not use builtin modules '
|
||||||
'as dotted module names') from None
|
'as dotted module names') from None
|
||||||
else:
|
else:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
'don\'t know how to discover from {!r}'
|
f"don't know how to discover from {the_module!r}"
|
||||||
.format(the_module)) from None
|
) from None
|
||||||
|
|
||||||
if set_implicit_top:
|
if set_implicit_top:
|
||||||
if not is_namespace:
|
self._top_level_dir = self._get_directory_containing_module(top_part)
|
||||||
self._top_level_dir = \
|
sys.path.remove(top_level_dir)
|
||||||
self._get_directory_containing_module(top_part)
|
|
||||||
sys.path.remove(top_level_dir)
|
|
||||||
else:
|
|
||||||
sys.path.remove(top_level_dir)
|
|
||||||
|
|
||||||
if is_not_importable:
|
if is_not_importable:
|
||||||
raise ImportError('Start directory is not importable: %r' % start_dir)
|
raise ImportError('Start directory is not importable: %r' % start_dir)
|
||||||
|
|
||||||
if not is_namespace:
|
tests = list(self._find_tests(start_dir, pattern))
|
||||||
tests = list(self._find_tests(start_dir, pattern))
|
|
||||||
return self.suiteClass(tests)
|
return self.suiteClass(tests)
|
||||||
|
|
||||||
def _get_directory_containing_module(self, module_name):
|
def _get_directory_containing_module(self, module_name):
|
||||||
|
@ -359,7 +332,7 @@ class TestLoader(object):
|
||||||
# override this method to use alternative matching strategy
|
# override this method to use alternative matching strategy
|
||||||
return fnmatch(path, pattern)
|
return fnmatch(path, pattern)
|
||||||
|
|
||||||
def _find_tests(self, start_dir, pattern, namespace=False):
|
def _find_tests(self, start_dir, pattern):
|
||||||
"""Used by discovery. Yields test suites it loads."""
|
"""Used by discovery. Yields test suites it loads."""
|
||||||
# Handle the __init__ in this package
|
# Handle the __init__ in this package
|
||||||
name = self._get_name_from_path(start_dir)
|
name = self._get_name_from_path(start_dir)
|
||||||
|
@ -368,8 +341,7 @@ class TestLoader(object):
|
||||||
if name != '.' and name not in self._loading_packages:
|
if name != '.' and name not in self._loading_packages:
|
||||||
# name is in self._loading_packages while we have called into
|
# name is in self._loading_packages while we have called into
|
||||||
# loadTestsFromModule with name.
|
# loadTestsFromModule with name.
|
||||||
tests, should_recurse = self._find_test_path(
|
tests, should_recurse = self._find_test_path(start_dir, pattern)
|
||||||
start_dir, pattern, namespace)
|
|
||||||
if tests is not None:
|
if tests is not None:
|
||||||
yield tests
|
yield tests
|
||||||
if not should_recurse:
|
if not should_recurse:
|
||||||
|
@ -380,8 +352,7 @@ class TestLoader(object):
|
||||||
paths = sorted(os.listdir(start_dir))
|
paths = sorted(os.listdir(start_dir))
|
||||||
for path in paths:
|
for path in paths:
|
||||||
full_path = os.path.join(start_dir, path)
|
full_path = os.path.join(start_dir, path)
|
||||||
tests, should_recurse = self._find_test_path(
|
tests, should_recurse = self._find_test_path(full_path, pattern)
|
||||||
full_path, pattern, namespace)
|
|
||||||
if tests is not None:
|
if tests is not None:
|
||||||
yield tests
|
yield tests
|
||||||
if should_recurse:
|
if should_recurse:
|
||||||
|
@ -389,11 +360,11 @@ class TestLoader(object):
|
||||||
name = self._get_name_from_path(full_path)
|
name = self._get_name_from_path(full_path)
|
||||||
self._loading_packages.add(name)
|
self._loading_packages.add(name)
|
||||||
try:
|
try:
|
||||||
yield from self._find_tests(full_path, pattern, namespace)
|
yield from self._find_tests(full_path, pattern)
|
||||||
finally:
|
finally:
|
||||||
self._loading_packages.discard(name)
|
self._loading_packages.discard(name)
|
||||||
|
|
||||||
def _find_test_path(self, full_path, pattern, namespace=False):
|
def _find_test_path(self, full_path, pattern):
|
||||||
"""Used by discovery.
|
"""Used by discovery.
|
||||||
|
|
||||||
Loads tests from a single file, or a directories' __init__.py when
|
Loads tests from a single file, or a directories' __init__.py when
|
||||||
|
@ -437,8 +408,7 @@ class TestLoader(object):
|
||||||
msg % (mod_name, module_dir, expected_dir))
|
msg % (mod_name, module_dir, expected_dir))
|
||||||
return self.loadTestsFromModule(module, pattern=pattern), False
|
return self.loadTestsFromModule(module, pattern=pattern), False
|
||||||
elif os.path.isdir(full_path):
|
elif os.path.isdir(full_path):
|
||||||
if (not namespace and
|
if not os.path.isfile(os.path.join(full_path, '__init__.py')):
|
||||||
not os.path.isfile(os.path.join(full_path, '__init__.py'))):
|
|
||||||
return None, False
|
return None, False
|
||||||
|
|
||||||
load_tests = None
|
load_tests = None
|
||||||
|
|
|
@ -396,7 +396,7 @@ class TestDiscovery(unittest.TestCase):
|
||||||
self.addCleanup(restore_isdir)
|
self.addCleanup(restore_isdir)
|
||||||
|
|
||||||
_find_tests_args = []
|
_find_tests_args = []
|
||||||
def _find_tests(start_dir, pattern, namespace=None):
|
def _find_tests(start_dir, pattern):
|
||||||
_find_tests_args.append((start_dir, pattern))
|
_find_tests_args.append((start_dir, pattern))
|
||||||
return ['tests']
|
return ['tests']
|
||||||
loader._find_tests = _find_tests
|
loader._find_tests = _find_tests
|
||||||
|
@ -792,7 +792,7 @@ class TestDiscovery(unittest.TestCase):
|
||||||
expectedPath = os.path.abspath(os.path.dirname(unittest.test.__file__))
|
expectedPath = os.path.abspath(os.path.dirname(unittest.test.__file__))
|
||||||
|
|
||||||
self.wasRun = False
|
self.wasRun = False
|
||||||
def _find_tests(start_dir, pattern, namespace=None):
|
def _find_tests(start_dir, pattern):
|
||||||
self.wasRun = True
|
self.wasRun = True
|
||||||
self.assertEqual(start_dir, expectedPath)
|
self.assertEqual(start_dir, expectedPath)
|
||||||
return tests
|
return tests
|
||||||
|
@ -825,37 +825,6 @@ class TestDiscovery(unittest.TestCase):
|
||||||
'Can not use builtin modules '
|
'Can not use builtin modules '
|
||||||
'as dotted module names')
|
'as dotted module names')
|
||||||
|
|
||||||
def test_discovery_from_dotted_namespace_packages(self):
|
|
||||||
loader = unittest.TestLoader()
|
|
||||||
|
|
||||||
package = types.ModuleType('package')
|
|
||||||
package.__path__ = ['/a', '/b']
|
|
||||||
package.__spec__ = types.SimpleNamespace(
|
|
||||||
loader=None,
|
|
||||||
submodule_search_locations=['/a', '/b']
|
|
||||||
)
|
|
||||||
|
|
||||||
def _import(packagename, *args, **kwargs):
|
|
||||||
sys.modules[packagename] = package
|
|
||||||
return package
|
|
||||||
|
|
||||||
_find_tests_args = []
|
|
||||||
def _find_tests(start_dir, pattern, namespace=None):
|
|
||||||
_find_tests_args.append((start_dir, pattern))
|
|
||||||
return ['%s/tests' % start_dir]
|
|
||||||
|
|
||||||
loader._find_tests = _find_tests
|
|
||||||
loader.suiteClass = list
|
|
||||||
|
|
||||||
with unittest.mock.patch('builtins.__import__', _import):
|
|
||||||
# Since loader.discover() can modify sys.path, restore it when done.
|
|
||||||
with import_helper.DirsOnSysPath():
|
|
||||||
# Make sure to remove 'package' from sys.modules when done.
|
|
||||||
with test.test_importlib.util.uncache('package'):
|
|
||||||
suite = loader.discover('package')
|
|
||||||
|
|
||||||
self.assertEqual(suite, ['/a/tests', '/b/tests'])
|
|
||||||
|
|
||||||
def test_discovery_failed_discovery(self):
|
def test_discovery_failed_discovery(self):
|
||||||
loader = unittest.TestLoader()
|
loader = unittest.TestLoader()
|
||||||
package = types.ModuleType('package')
|
package = types.ModuleType('package')
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Remove namespace package (PEP 420) support from unittest discovery. It was
|
||||||
|
introduced in Python 3.4 but has been broken since Python 3.7.
|
Loading…
Add table
Add a link
Reference in a new issue