Skip to content

Commit efa9ecc

Browse files
committed
Fix test discovery for directories with non-identifiers
* The unittest test discovery was incorrectly looking for tests in directories which had non-identifiers in the directory name. This change makes the discovery only enter directories which can be valid python identifiers * Add a changelog entry for the unittest test discovery fix * Remove python2-straddling code * Merge test_find_tests_with_unicode with test_find_tests as they are really just variants on the data provided. * Simplify a few sections of the code as suggested by ezio melotti
1 parent e2da328 commit efa9ecc

3 files changed

Lines changed: 48 additions & 87 deletions

File tree

Lib/test/test_unittest/test_discovery.py

Lines changed: 37 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -42,105 +42,69 @@ def test_get_name_from_path(self):
4242
loader._get_name_from_path('/bar/baz.py')
4343

4444
def test_find_tests(self):
45-
loader = unittest.TestLoader()
46-
47-
original_listdir = os.listdir
48-
def restore_listdir():
49-
os.listdir = original_listdir
50-
original_isfile = os.path.isfile
51-
def restore_isfile():
52-
os.path.isfile = original_isfile
53-
original_isdir = os.path.isdir
54-
def restore_isdir():
55-
os.path.isdir = original_isdir
56-
57-
path_lists = [['test2.py', 'test1.py', 'not_a_test.py', 'test_dir',
58-
'test.foo', 'test-not-a-module.py', 'another_dir'],
59-
['test4.py', 'test3.py', ]]
60-
os.listdir = lambda path: path_lists.pop(0)
61-
self.addCleanup(restore_listdir)
62-
63-
def isdir(path):
64-
return path.endswith('dir')
65-
os.path.isdir = isdir
66-
self.addCleanup(restore_isdir)
67-
68-
def isfile(path):
69-
# another_dir is not a package and so shouldn't be recursed into
70-
return not path.endswith('dir') and not 'another_dir' in path
71-
os.path.isfile = isfile
72-
self.addCleanup(restore_isfile)
73-
74-
loader._get_module_from_name = lambda path: path + ' module'
75-
orig_load_tests = loader.loadTestsFromModule
76-
def loadTestsFromModule(module, pattern=None):
77-
# This is where load_tests is called.
78-
base = orig_load_tests(module, pattern=pattern)
79-
return base + [module + ' tests']
80-
loader.loadTestsFromModule = loadTestsFromModule
81-
loader.suiteClass = lambda thing: thing
82-
83-
top_level = os.path.abspath('/foo')
84-
loader._top_level_dir = top_level
85-
suite = list(loader._find_tests(top_level, 'test*.py'))
86-
87-
# The test suites found should be sorted alphabetically for reliable
88-
# execution order.
89-
expected = [[name + ' module tests'] for name in
90-
('test1', 'test2', 'test_dir')]
91-
expected.extend([[('test_dir.%s' % name) + ' module tests'] for name in
92-
('test3', 'test4')])
93-
self.assertEqual(suite, expected)
45+
path_lists = [[
46+
# Valid module and package names
47+
'test2.py', 'test1.py', 'not_a_test.py', 'test_dir',
48+
'測試2.py', '測試1.py', '不是測試.py', '測試_資料夾',
49+
# Invalid names
50+
'test.foo', 'test-not-a-module.py', 'not-a-package_dir',
51+
'another_dir',
52+
'測試.付歐歐', '測試-不是-一個-模組.py',
53+
'測試-不是-一個-模組_資料夾', '另外的_資料夾'],
54+
# Valid names; test case tests that these work from inside
55+
# of a package directory
56+
['test4.py', 'test3.py'],
57+
['測試4.py', '測試3.py', ]]
9458

95-
def test_find_tests_with_unicode(self):
9659
loader = unittest.TestLoader()
9760

9861
original_listdir = os.listdir
99-
def restore_listdir():
100-
os.listdir = original_listdir
101-
original_isfile = os.path.isfile
102-
def restore_isfile():
103-
os.path.isfile = original_isfile
10462
original_isdir = os.path.isdir
105-
def restore_isdir():
106-
os.path.isdir = original_isdir
63+
original_isfile = os.path.isfile
10764

108-
path_lists = [['測試2.py', '測試1.py', '不是測試.py', '測試_資料夾',
109-
'測試.付歐歐', '測試-不是-一個-模組.py', '另外的_資料夾'],
110-
['測試4.py', '測試3.py', ]]
11165
os.listdir = lambda path: path_lists.pop(0)
112-
self.addCleanup(restore_listdir)
113-
114-
def isdir(path):
115-
return path.endswith('資料夾')
116-
os.path.isdir = isdir
117-
self.addCleanup(restore_isdir)
66+
os.path.isdir = lambda path: path.endswith(('dir', '資料夾'))
11867

11968
def isfile(path):
120-
# another_dir is not a package and so shouldn't be recursed into
121-
return not path.endswith('資料夾') and not '另外的_資料夾' in path
69+
# Mocking isfile to pretend that path names that end in dir or
70+
# 資料夾 are directories rather than files.
71+
# Additionally, another_dir and 另外的_資料夾 are supposed to be
72+
# directories that are not python packages. We simulate that by
73+
# returning False here when unittest asks us if __init__.py is
74+
# present in those directories.
75+
return (not (path.endswith(('dir', '資料夾')))
76+
and 'another_dir' not in path
77+
and '另外的_資料夾' not in path)
12278
os.path.isfile = isfile
123-
self.addCleanup(restore_isfile)
79+
80+
self.addCleanup(setattr, os, 'listdir', original_listdir)
81+
self.addCleanup(setattr, os.path, 'isdir', original_isdir)
82+
self.addCleanup(setattr, os.path, 'isfile', original_isfile)
12483

12584
loader._get_module_from_name = lambda path: path + ' module'
12685
orig_load_tests = loader.loadTestsFromModule
86+
12787
def loadTestsFromModule(module, pattern=None):
12888
# This is where load_tests is called.
12989
base = orig_load_tests(module, pattern=pattern)
13090
return base + [module + ' tests']
91+
13192
loader.loadTestsFromModule = loadTestsFromModule
13293
loader.suiteClass = lambda thing: thing
13394

13495
top_level = os.path.abspath('/foo')
13596
loader._top_level_dir = top_level
136-
suite = list(loader._find_tests(top_level, '測試*.py'))
97+
suite = list(loader._find_tests(top_level, '[t測]*.py'))
13798

13899
# The test suites found should be sorted alphabetically for reliable
139100
# execution order.
140-
expected = [[name + ' module tests'] for name in
141-
('測試1', '測試2', '測試_資料夾')]
142-
expected.extend([[('測試_資料夾.%s' % name) + ' module tests'] for name in
101+
expected = [['%s module tests' % name] for name in
102+
('test1', 'test2', 'test_dir', '測試1', '測試2', '測試_資料夾')]
103+
expected.extend([['test_dir.%s module tests' % name] for name in
104+
('test3', 'test4')])
105+
expected.extend([['測試_資料夾.%s module tests' % name] for name in
143106
('測試3', '測試4')])
107+
expected.sort()
144108
self.assertEqual(suite, expected)
145109

146110
def test_find_tests_socket(self):

Lib/unittest/loader.py

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,6 @@
1313

1414
__unittest = True
1515

16-
# what about .pyc (etc)
17-
# we would need to avoid loading the same tests multiple times
18-
# from '.py', *and* '.pyc'
19-
VALID_MODULE_NAME = re.compile(r'[_a-z]\w*\.py$', re.IGNORECASE)
20-
2116

2217
class _FailedTest(case.TestCase):
2318
_testMethodName = None
@@ -372,23 +367,19 @@ def _find_test_path(self, full_path, pattern):
372367
basename = os.path.basename(full_path)
373368
if os.path.isfile(full_path):
374369
root, ext = os.path.splitext(basename)
375-
try:
376-
if not (ext == '.py' and root.isidentifier()):
377-
# valid Python identifiers only
378-
return None, False
379-
except AttributeError:
380-
if not VALID_MODULE_NAME.match(basename):
381-
# valid Python identifiers only
382-
return None, False
370+
if not (ext == '.py' and root.isidentifier()):
371+
# valid Python identifiers only
372+
return None, False
383373
if not self._match_path(basename, full_path, pattern):
384374
return None, False
375+
385376
# if the test file matches, load it
386377
name = self._get_name_from_path(full_path)
387378
try:
388379
module = self._get_module_from_name(name)
389380
except case.SkipTest as e:
390381
return _make_skipped_test(name, e, self.suiteClass), False
391-
except:
382+
except Exception:
392383
error_case, error_message = \
393384
_make_failed_import_test(name, self.suiteClass)
394385
self.errors.append(error_message)
@@ -417,6 +408,10 @@ def _find_test_path(self, full_path, pattern):
417408
load_tests = None
418409
tests = None
419410
name = self._get_name_from_path(full_path)
411+
412+
if not name.isidentifier():
413+
return None, False
414+
420415
try:
421416
package = self._get_module_from_name(name)
422417
except case.SkipTest as e:
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix unittest test discovery to find tests in files and directories with
2+
non-ascii characters in their filename

0 commit comments

Comments
 (0)