Skip to content

Commit f012ba4

Browse files
committed
Issue #22002: Make full use of test discovery in test sub-packages.
Adds `load_package_tests` function to test.support, uses it in test_asyncio, test_email, test_json, test_tools, test_importlib and all test_importlib sub-packages to implement test discovery.
1 parent c4c4649 commit f012ba4

File tree

21 files changed

+104
-169
lines changed

21 files changed

+104
-169
lines changed

Doc/library/test.rst

+16-1
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ The :mod:`test.support` module defines the following functions:
461461
.. function:: make_bad_fd()
462462

463463
Create an invalid file descriptor by opening and closing a temporary file,
464-
and returning its descripor.
464+
and returning its descriptor.
465465

466466

467467
.. function:: import_module(name, deprecated=False)
@@ -554,6 +554,21 @@ The :mod:`test.support` module defines the following functions:
554554
run simultaneously, which is a problem for buildbots.
555555

556556

557+
.. function:: load_package_tests(pkg_dir, loader, standard_tests, pattern)
558+
559+
Generic implementation of the :mod:`unittest` ``load_tests`` protocol for
560+
use in test packages. *pkg_dir* is the root directory of the package;
561+
*loader*, *standard_tests*, and *pattern* are the arguments expected by
562+
``load_tests``. In simple cases, the test package's ``__init__.py``
563+
can be the following::
564+
565+
import os
566+
from test.support import load_package_tests
567+
568+
def load_tests(*args):
569+
return load_package_tests(os.path.dirname(__file__), *args)
570+
571+
557572
The :mod:`test.support` module defines the following classes:
558573

559574
.. class:: TransientResource(exc, **kwargs)

Lib/test/support/__init__.py

+20-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
"skip_unless_symlink", "requires_gzip", "requires_bz2", "requires_lzma",
8686
"bigmemtest", "bigaddrspacetest", "cpython_only", "get_attribute",
8787
"requires_IEEE_754", "skip_unless_xattr", "requires_zlib",
88-
"anticipate_failure",
88+
"anticipate_failure", "load_package_tests",
8989
# sys
9090
"is_jython", "check_impl_detail",
9191
# network
@@ -188,6 +188,25 @@ def anticipate_failure(condition):
188188
return unittest.expectedFailure
189189
return lambda f: f
190190

191+
def load_package_tests(pkg_dir, loader, standard_tests, pattern):
192+
"""Generic load_tests implementation for simple test packages.
193+
194+
Most packages can implement load_tests using this function as follows:
195+
196+
def load_tests(*args):
197+
return load_package_tests(os.path.dirname(__file__), *args)
198+
"""
199+
if pattern is None:
200+
pattern = "test*"
201+
top_dir = os.path.dirname( # Lib
202+
os.path.dirname( # test
203+
os.path.dirname(__file__))) # support
204+
package_tests = loader.discover(start_dir=pkg_dir,
205+
top_level_dir=top_dir,
206+
pattern=pattern)
207+
standard_tests.addTests(package_tests)
208+
return standard_tests
209+
191210

192211
def import_fresh_module(name, fresh=(), blocked=(), deprecated=False):
193212
"""Import and return a module, deliberately bypassing sys.modules.

Lib/test/test_asyncio/__init__.py

+3-22
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,10 @@
11
import os
2-
import sys
3-
import unittest
4-
from test.support import run_unittest, import_module
2+
from test.support import load_package_tests, import_module
53

64
# Skip tests if we don't have threading.
75
import_module('threading')
86
# Skip tests if we don't have concurrent.futures.
97
import_module('concurrent.futures')
108

11-
12-
def suite():
13-
tests = unittest.TestSuite()
14-
loader = unittest.TestLoader()
15-
for fn in os.listdir(os.path.dirname(__file__)):
16-
if fn.startswith("test") and fn.endswith(".py"):
17-
mod_name = 'test.test_asyncio.' + fn[:-3]
18-
try:
19-
__import__(mod_name)
20-
except unittest.SkipTest:
21-
pass
22-
else:
23-
mod = sys.modules[mod_name]
24-
tests.addTests(loader.loadTestsFromModule(mod))
25-
return tests
26-
27-
28-
def test_main():
29-
run_unittest(suite())
9+
def load_tests(*args):
10+
return load_package_tests(os.path.dirname(__file__), *args)

Lib/test/test_asyncio/__main__.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
from . import test_main
1+
from . import load_tests
2+
import unittest
23

3-
4-
if __name__ == '__main__':
5-
test_main()
4+
unittest.main()

Lib/test/test_email/__init__.py

+4-19
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,16 @@
11
import os
22
import sys
33
import unittest
4-
import test.support
54
import collections
65
import email
76
from email.message import Message
87
from email._policybase import compat32
8+
from test.support import load_package_tests
99
from test.test_email import __file__ as landmark
1010

11-
# Run all tests in package for '-m unittest test.test_email'
12-
def load_tests(loader, standard_tests, pattern):
13-
this_dir = os.path.dirname(__file__)
14-
if pattern is None:
15-
pattern = "test*"
16-
package_tests = loader.discover(start_dir=this_dir, pattern=pattern)
17-
standard_tests.addTests(package_tests)
18-
return standard_tests
19-
20-
21-
# used by regrtest and __main__.
22-
def test_main():
23-
here = os.path.dirname(__file__)
24-
# Unittest mucks with the path, so we have to save and restore
25-
# it to keep regrtest happy.
26-
savepath = sys.path[:]
27-
test.support._run_suite(unittest.defaultTestLoader.discover(here))
28-
sys.path[:] = savepath
11+
# Load all tests in package
12+
def load_tests(*args):
13+
return load_package_tests(os.path.dirname(__file__), *args)
2914

3015

3116
# helper code used by a number of test modules.

Lib/test/test_email/__main__.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
from test.test_email import test_main
1+
from test.test_email import load_tests
2+
import unittest
23

3-
test_main()
4+
unittest.main()

Lib/test/test_importlib/__init__.py

+3-31
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,5 @@
11
import os
2-
import sys
3-
from test import support
4-
import unittest
2+
from test.support import load_package_tests
53

6-
def test_suite(package=__package__, directory=os.path.dirname(__file__)):
7-
suite = unittest.TestSuite()
8-
for name in os.listdir(directory):
9-
if name.startswith(('.', '__')):
10-
continue
11-
path = os.path.join(directory, name)
12-
if (os.path.isfile(path) and name.startswith('test_') and
13-
name.endswith('.py')):
14-
submodule_name = os.path.splitext(name)[0]
15-
module_name = "{0}.{1}".format(package, submodule_name)
16-
__import__(module_name, level=0)
17-
module_tests = unittest.findTestCases(sys.modules[module_name])
18-
suite.addTest(module_tests)
19-
elif os.path.isdir(path):
20-
package_name = "{0}.{1}".format(package, name)
21-
__import__(package_name, level=0)
22-
package_tests = getattr(sys.modules[package_name], 'test_suite')()
23-
suite.addTest(package_tests)
24-
else:
25-
continue
26-
return suite
27-
28-
29-
def test_main():
30-
start_dir = os.path.dirname(__file__)
31-
top_dir = os.path.dirname(os.path.dirname(start_dir))
32-
test_loader = unittest.TestLoader()
33-
support.run_unittest(test_loader.discover(start_dir, top_level_dir=top_dir))
4+
def load_tests(*args):
5+
return load_package_tests(os.path.dirname(__file__), *args)

Lib/test/test_importlib/__main__.py

+3-8
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
"""Run importlib's test suite.
1+
from . import load_tests
2+
import unittest
23

3-
Specifying the ``--builtin`` flag will run tests, where applicable, with
4-
builtins.__import__ instead of importlib.__import__.
5-
6-
"""
7-
if __name__ == '__main__':
8-
from . import test_main
9-
test_main()
4+
unittest.main()
+3-10
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
1-
from .. import test_suite
21
import os
2+
from test.support import load_package_tests
33

4-
5-
def test_suite():
6-
directory = os.path.dirname(__file__)
7-
return test_suite('importlib.test.builtin', directory)
8-
9-
10-
if __name__ == '__main__':
11-
from test.support import run_unittest
12-
run_unittest(test_suite())
4+
def load_tests(*args):
5+
return load_package_tests(os.path.dirname(__file__), *args)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from . import load_tests
2+
import unittest
3+
4+
unittest.main()
+4-12
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
1-
from .. import test_suite
2-
import os.path
3-
import unittest
1+
import os
2+
from test.support import load_package_tests
43

5-
6-
def test_suite():
7-
directory = os.path.dirname(__file__)
8-
return test_suite('importlib.test.extension', directory)
9-
10-
11-
if __name__ == '__main__':
12-
from test.support import run_unittest
13-
run_unittest(test_suite())
4+
def load_tests(*args):
5+
return load_package_tests(os.path.dirname(__file__), *args)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from . import load_tests
2+
import unittest
3+
4+
unittest.main()
+4-12
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
1-
from .. import test_suite
2-
import os.path
3-
import unittest
1+
import os
2+
from test.support import load_package_tests
43

5-
6-
def test_suite():
7-
directory = os.path.dirname(__file__)
8-
return test_suite('importlib.test.frozen', directory)
9-
10-
11-
if __name__ == '__main__':
12-
from test.support import run_unittest
13-
run_unittest(test_suite())
4+
def load_tests(*args):
5+
return load_package_tests(os.path.dirname(__file__), *args)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from . import load_tests
2+
import unittest
3+
4+
unittest.main()
+4-12
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
1-
from .. import test_suite
2-
import os.path
3-
import unittest
1+
import os
2+
from test.support import load_package_tests
43

5-
6-
def test_suite():
7-
directory = os.path.dirname(__file__)
8-
return test_suite('importlib.test.import_', directory)
9-
10-
11-
if __name__ == '__main__':
12-
from test.support import run_unittest
13-
run_unittest(test_suite())
4+
def load_tests(*args):
5+
return load_package_tests(os.path.dirname(__file__), *args)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from . import load_tests
2+
import unittest
3+
4+
unittest.main()
+4-12
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
1-
from .. import test_suite
2-
import os.path
3-
import unittest
1+
import os
2+
from test.support import load_package_tests
43

5-
6-
def test_suite():
7-
directory = os.path.dirname(__file__)
8-
return test.test_suite('importlib.test.source', directory)
9-
10-
11-
if __name__ == '__main__':
12-
from test.support import run_unittest
13-
run_unittest(test_suite())
4+
def load_tests(*args):
5+
return load_package_tests(os.path.dirname(__file__), *args)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from . import load_tests
2+
import unittest
3+
4+
unittest.main()

Lib/test/test_json/__init__.py

+4-15
Original file line numberDiff line numberDiff line change
@@ -42,23 +42,12 @@ def test_cjson(self):
4242
'_json')
4343

4444

45-
here = os.path.dirname(__file__)
46-
47-
def load_tests(*args):
48-
suite = additional_tests()
49-
loader = unittest.TestLoader()
50-
for fn in os.listdir(here):
51-
if fn.startswith("test") and fn.endswith(".py"):
52-
modname = "test.test_json." + fn[:-3]
53-
__import__(modname)
54-
module = sys.modules[modname]
55-
suite.addTests(loader.loadTestsFromModule(module))
56-
return suite
57-
58-
def additional_tests():
45+
def load_tests(loader, _, pattern):
5946
suite = unittest.TestSuite()
6047
for mod in (json, json.encoder, json.decoder):
6148
suite.addTest(doctest.DocTestSuite(mod))
6249
suite.addTest(TestPyTest('test_pyjson'))
6350
suite.addTest(TestCTest('test_cjson'))
64-
return suite
51+
52+
pkg_dir = os.path.dirname(__file__)
53+
return support.load_package_tests(pkg_dir, loader, suite, pattern)

Lib/test/test_tools/__init__.py

+2-8
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,5 @@ def import_tool(toolname):
2121
with support.DirsOnSysPath(scriptsdir):
2222
return importlib.import_module(toolname)
2323

24-
def load_tests(loader, standard_tests, pattern):
25-
this_dir = os.path.dirname(__file__)
26-
if pattern is None:
27-
pattern = "test*"
28-
with support.DirsOnSysPath():
29-
package_tests = loader.discover(start_dir=this_dir, pattern=pattern)
30-
standard_tests.addTests(package_tests)
31-
return standard_tests
24+
def load_tests(*args):
25+
return support.load_package_tests(os.path.dirname(__file__), *args)

Misc/NEWS

+4
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,10 @@ IDLE
209209
Tests
210210
-----
211211

212+
- Issue #22002: Added ``load_package_tests`` function to test.support and used
213+
it to implement/augment test discovery in test_asyncio, test_email,
214+
test_importlib, test_json, and test_tools.
215+
212216
- Issue #21976: Fix test_ssl to accept LibreSSL version strings. Thanks
213217
to William Orr.
214218

0 commit comments

Comments
 (0)