mirror of
https://github.com/python/cpython.git
synced 2025-11-03 03:22:27 +00:00
- Issue #16662: load_tests() is now unconditionally run when it is present in
a package's __init__.py. TestLoader.loadTestsFromModule() still accepts use_load_tests, but it is deprecated and ignored. A new keyword-only attribute `pattern` is added and documented. Patch given by Robert Collins, tweaked by Barry Warsaw.
This commit is contained in:
parent
238f5aa6a5
commit
d78742a260
5 changed files with 472 additions and 61 deletions
|
|
@ -68,7 +68,13 @@ class TestDiscovery(unittest.TestCase):
|
|||
self.addCleanup(restore_isfile)
|
||||
|
||||
loader._get_module_from_name = lambda path: path + ' module'
|
||||
loader.loadTestsFromModule = lambda module: module + ' tests'
|
||||
orig_load_tests = loader.loadTestsFromModule
|
||||
def loadTestsFromModule(module, pattern=None):
|
||||
# This is where load_tests is called.
|
||||
base = orig_load_tests(module, pattern=pattern)
|
||||
return base + [module + ' tests']
|
||||
loader.loadTestsFromModule = loadTestsFromModule
|
||||
loader.suiteClass = lambda thing: thing
|
||||
|
||||
top_level = os.path.abspath('/foo')
|
||||
loader._top_level_dir = top_level
|
||||
|
|
@ -76,9 +82,9 @@ class TestDiscovery(unittest.TestCase):
|
|||
|
||||
# The test suites found should be sorted alphabetically for reliable
|
||||
# execution order.
|
||||
expected = [name + ' module tests' for name in
|
||||
('test1', 'test2')]
|
||||
expected.extend([('test_dir.%s' % name) + ' module tests' for name in
|
||||
expected = [[name + ' module tests'] for name in
|
||||
('test1', 'test2', 'test_dir')]
|
||||
expected.extend([[('test_dir.%s' % name) + ' module tests'] for name in
|
||||
('test3', 'test4')])
|
||||
self.assertEqual(suite, expected)
|
||||
|
||||
|
|
@ -116,34 +122,204 @@ class TestDiscovery(unittest.TestCase):
|
|||
if os.path.basename(path) == 'test_directory':
|
||||
def load_tests(loader, tests, pattern):
|
||||
self.load_tests_args.append((loader, tests, pattern))
|
||||
return 'load_tests'
|
||||
return [self.path + ' load_tests']
|
||||
self.load_tests = load_tests
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.path == other.path
|
||||
|
||||
loader._get_module_from_name = lambda name: Module(name)
|
||||
def loadTestsFromModule(module, use_load_tests):
|
||||
if use_load_tests:
|
||||
raise self.failureException('use_load_tests should be False for packages')
|
||||
return module.path + ' module tests'
|
||||
orig_load_tests = loader.loadTestsFromModule
|
||||
def loadTestsFromModule(module, pattern=None):
|
||||
# This is where load_tests is called.
|
||||
base = orig_load_tests(module, pattern=pattern)
|
||||
return base + [module.path + ' module tests']
|
||||
loader.loadTestsFromModule = loadTestsFromModule
|
||||
loader.suiteClass = lambda thing: thing
|
||||
|
||||
loader._top_level_dir = '/foo'
|
||||
# this time no '.py' on the pattern so that it can match
|
||||
# a test package
|
||||
suite = list(loader._find_tests('/foo', 'test*'))
|
||||
|
||||
# We should have loaded tests from the test_directory package by calling load_tests
|
||||
# and directly from the test_directory2 package
|
||||
# We should have loaded tests from the a_directory and test_directory2
|
||||
# directly and via load_tests for the test_directory package, which
|
||||
# still calls the baseline module loader.
|
||||
self.assertEqual(suite,
|
||||
['load_tests', 'test_directory2' + ' module tests'])
|
||||
[['a_directory module tests'],
|
||||
['test_directory load_tests',
|
||||
'test_directory module tests'],
|
||||
['test_directory2 module tests']])
|
||||
|
||||
|
||||
# The test module paths should be sorted for reliable execution order
|
||||
self.assertEqual(Module.paths, ['test_directory', 'test_directory2'])
|
||||
self.assertEqual(Module.paths,
|
||||
['a_directory', 'test_directory', 'test_directory2'])
|
||||
|
||||
# load_tests should have been called once with loader, tests and pattern
|
||||
# (but there are no tests in our stub module itself, so thats [] at the
|
||||
# time of call.
|
||||
self.assertEqual(Module.load_tests_args,
|
||||
[(loader, [], 'test*')])
|
||||
|
||||
def test_find_tests_default_calls_package_load_tests(self):
|
||||
loader = unittest.TestLoader()
|
||||
|
||||
original_listdir = os.listdir
|
||||
def restore_listdir():
|
||||
os.listdir = original_listdir
|
||||
original_isfile = os.path.isfile
|
||||
def restore_isfile():
|
||||
os.path.isfile = original_isfile
|
||||
original_isdir = os.path.isdir
|
||||
def restore_isdir():
|
||||
os.path.isdir = original_isdir
|
||||
|
||||
directories = ['a_directory', 'test_directory', 'test_directory2']
|
||||
path_lists = [directories, [], [], []]
|
||||
os.listdir = lambda path: path_lists.pop(0)
|
||||
self.addCleanup(restore_listdir)
|
||||
|
||||
os.path.isdir = lambda path: True
|
||||
self.addCleanup(restore_isdir)
|
||||
|
||||
os.path.isfile = lambda path: os.path.basename(path) not in directories
|
||||
self.addCleanup(restore_isfile)
|
||||
|
||||
class Module(object):
|
||||
paths = []
|
||||
load_tests_args = []
|
||||
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
self.paths.append(path)
|
||||
if os.path.basename(path) == 'test_directory':
|
||||
def load_tests(loader, tests, pattern):
|
||||
self.load_tests_args.append((loader, tests, pattern))
|
||||
return [self.path + ' load_tests']
|
||||
self.load_tests = load_tests
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.path == other.path
|
||||
|
||||
loader._get_module_from_name = lambda name: Module(name)
|
||||
orig_load_tests = loader.loadTestsFromModule
|
||||
def loadTestsFromModule(module, pattern=None):
|
||||
# This is where load_tests is called.
|
||||
base = orig_load_tests(module, pattern=pattern)
|
||||
return base + [module.path + ' module tests']
|
||||
loader.loadTestsFromModule = loadTestsFromModule
|
||||
loader.suiteClass = lambda thing: thing
|
||||
|
||||
loader._top_level_dir = '/foo'
|
||||
# this time no '.py' on the pattern so that it can match
|
||||
# a test package
|
||||
suite = list(loader._find_tests('/foo', 'test*.py'))
|
||||
|
||||
# We should have loaded tests from the a_directory and test_directory2
|
||||
# directly and via load_tests for the test_directory package, which
|
||||
# still calls the baseline module loader.
|
||||
self.assertEqual(suite,
|
||||
[['a_directory module tests'],
|
||||
['test_directory load_tests',
|
||||
'test_directory module tests'],
|
||||
['test_directory2 module tests']])
|
||||
# The test module paths should be sorted for reliable execution order
|
||||
self.assertEqual(Module.paths,
|
||||
['a_directory', 'test_directory', 'test_directory2'])
|
||||
|
||||
|
||||
# load_tests should have been called once with loader, tests and pattern
|
||||
self.assertEqual(Module.load_tests_args,
|
||||
[(loader, 'test_directory' + ' module tests', 'test*')])
|
||||
[(loader, [], 'test*.py')])
|
||||
|
||||
def test_find_tests_customise_via_package_pattern(self):
|
||||
# This test uses the example 'do-nothing' load_tests from
|
||||
# https://docs.python.org/3/library/unittest.html#load-tests-protocol
|
||||
# to make sure that that actually works.
|
||||
# Housekeeping
|
||||
original_listdir = os.listdir
|
||||
def restore_listdir():
|
||||
os.listdir = original_listdir
|
||||
self.addCleanup(restore_listdir)
|
||||
original_isfile = os.path.isfile
|
||||
def restore_isfile():
|
||||
os.path.isfile = original_isfile
|
||||
self.addCleanup(restore_isfile)
|
||||
original_isdir = os.path.isdir
|
||||
def restore_isdir():
|
||||
os.path.isdir = original_isdir
|
||||
self.addCleanup(restore_isdir)
|
||||
self.addCleanup(sys.path.remove, '/foo')
|
||||
|
||||
# Test data: we expect the following:
|
||||
# a listdir to find our package, and a isfile and isdir check on it.
|
||||
# a module-from-name call to turn that into a module
|
||||
# followed by load_tests.
|
||||
# then our load_tests will call discover() which is messy
|
||||
# but that finally chains into find_tests again for the child dir -
|
||||
# which is why we don't have a infinite loop.
|
||||
# We expect to see:
|
||||
# the module load tests for both package and plain module called,
|
||||
# and the plain module result nested by the package module load_tests
|
||||
# indicating that it was processed and could have been mutated.
|
||||
vfs = {'/foo': ['my_package'],
|
||||
'/foo/my_package': ['__init__.py', 'test_module.py']}
|
||||
def list_dir(path):
|
||||
return list(vfs[path])
|
||||
os.listdir = list_dir
|
||||
os.path.isdir = lambda path: not path.endswith('.py')
|
||||
os.path.isfile = lambda path: path.endswith('.py')
|
||||
|
||||
class Module(object):
|
||||
paths = []
|
||||
load_tests_args = []
|
||||
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
self.paths.append(path)
|
||||
if path.endswith('test_module'):
|
||||
def load_tests(loader, tests, pattern):
|
||||
self.load_tests_args.append((loader, tests, pattern))
|
||||
return [self.path + ' load_tests']
|
||||
else:
|
||||
def load_tests(loader, tests, pattern):
|
||||
self.load_tests_args.append((loader, tests, pattern))
|
||||
# top level directory cached on loader instance
|
||||
__file__ = '/foo/my_package/__init__.py'
|
||||
this_dir = os.path.dirname(__file__)
|
||||
pkg_tests = loader.discover(
|
||||
start_dir=this_dir, pattern=pattern)
|
||||
return [self.path + ' load_tests', tests
|
||||
] + pkg_tests
|
||||
self.load_tests = load_tests
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.path == other.path
|
||||
|
||||
loader = unittest.TestLoader()
|
||||
loader._get_module_from_name = lambda name: Module(name)
|
||||
loader.suiteClass = lambda thing: thing
|
||||
|
||||
loader._top_level_dir = '/foo'
|
||||
# this time no '.py' on the pattern so that it can match
|
||||
# a test package
|
||||
suite = list(loader._find_tests('/foo', 'test*.py'))
|
||||
|
||||
# We should have loaded tests from both my_package and
|
||||
# my_pacakge.test_module, and also run the load_tests hook in both.
|
||||
# (normally this would be nested TestSuites.)
|
||||
self.assertEqual(suite,
|
||||
[['my_package load_tests', [],
|
||||
['my_package.test_module load_tests']]])
|
||||
# Parents before children.
|
||||
self.assertEqual(Module.paths,
|
||||
['my_package', 'my_package.test_module'])
|
||||
|
||||
# load_tests should have been called twice with loader, tests and pattern
|
||||
self.assertEqual(Module.load_tests_args,
|
||||
[(loader, [], 'test*.py'),
|
||||
(loader, [], 'test*.py')])
|
||||
|
||||
def test_discover(self):
|
||||
loader = unittest.TestLoader()
|
||||
|
|
@ -203,6 +379,17 @@ class TestDiscovery(unittest.TestCase):
|
|||
sys.path[:] = orig_sys_path
|
||||
self.addCleanup(restore)
|
||||
|
||||
def setup_import_issue_package_tests(self, vfs):
|
||||
self.addCleanup(setattr, os, 'listdir', os.listdir)
|
||||
self.addCleanup(setattr, os.path, 'isfile', os.path.isfile)
|
||||
self.addCleanup(setattr, os.path, 'isdir', os.path.isdir)
|
||||
self.addCleanup(sys.path.__setitem__, slice(None), list(sys.path))
|
||||
def list_dir(path):
|
||||
return list(vfs[path])
|
||||
os.listdir = list_dir
|
||||
os.path.isdir = lambda path: not path.endswith('.py')
|
||||
os.path.isfile = lambda path: path.endswith('.py')
|
||||
|
||||
def test_discover_with_modules_that_fail_to_import(self):
|
||||
loader = unittest.TestLoader()
|
||||
|
||||
|
|
@ -216,6 +403,25 @@ class TestDiscovery(unittest.TestCase):
|
|||
with self.assertRaises(ImportError):
|
||||
test.test_this_does_not_exist()
|
||||
|
||||
def test_discover_with_init_modules_that_fail_to_import(self):
|
||||
vfs = {'/foo': ['my_package'],
|
||||
'/foo/my_package': ['__init__.py', 'test_module.py']}
|
||||
self.setup_import_issue_package_tests(vfs)
|
||||
import_calls = []
|
||||
def _get_module_from_name(name):
|
||||
import_calls.append(name)
|
||||
raise ImportError("Cannot import Name")
|
||||
loader = unittest.TestLoader()
|
||||
loader._get_module_from_name = _get_module_from_name
|
||||
suite = loader.discover('/foo')
|
||||
|
||||
self.assertIn('/foo', sys.path)
|
||||
self.assertEqual(suite.countTestCases(), 1)
|
||||
test = list(list(suite)[0])[0] # extract test from suite
|
||||
with self.assertRaises(ImportError):
|
||||
test.my_package()
|
||||
self.assertEqual(import_calls, ['my_package'])
|
||||
|
||||
def test_discover_with_module_that_raises_SkipTest_on_import(self):
|
||||
loader = unittest.TestLoader()
|
||||
|
||||
|
|
@ -232,6 +438,26 @@ class TestDiscovery(unittest.TestCase):
|
|||
suite.run(result)
|
||||
self.assertEqual(len(result.skipped), 1)
|
||||
|
||||
def test_discover_with_init_module_that_raises_SkipTest_on_import(self):
|
||||
vfs = {'/foo': ['my_package'],
|
||||
'/foo/my_package': ['__init__.py', 'test_module.py']}
|
||||
self.setup_import_issue_package_tests(vfs)
|
||||
import_calls = []
|
||||
def _get_module_from_name(name):
|
||||
import_calls.append(name)
|
||||
raise unittest.SkipTest('skipperoo')
|
||||
loader = unittest.TestLoader()
|
||||
loader._get_module_from_name = _get_module_from_name
|
||||
suite = loader.discover('/foo')
|
||||
|
||||
self.assertIn('/foo', sys.path)
|
||||
self.assertEqual(suite.countTestCases(), 1)
|
||||
result = unittest.TestResult()
|
||||
suite.run(result)
|
||||
self.assertEqual(len(result.skipped), 1)
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
self.assertEqual(import_calls, ['my_package'])
|
||||
|
||||
def test_command_line_handling_parseArgs(self):
|
||||
program = TestableTestProgram()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue