diff -r d1c11a78b43a Doc/library/test.rst --- a/Doc/library/test.rst Tue Nov 10 19:52:20 2015 +0200 +++ b/Doc/library/test.rst Wed Nov 11 09:15:07 2015 +0100 @@ -580,6 +580,48 @@ .. versionadded:: 3.5 +.. function:: check__all__(test_case, module, name_of_module=None, extra=(), blacklist=()) + + Assert that *module*'s ``__all__`` variable contains all documented names. + + The documented *module*'s names (its public API) are detected automatically + based on whether they match the public name convention and were defined in + *module*. + + The *name_of_module* can specify (as a string or tuple thereof) what + module(s) could the API be defined in in order to be detected as the public + API. One case for this is when *module* imports part of its public API from + other modules, possibly a C backend (like ``csv`` and its ``_csv``). + + The *extra* can be a set of names that wouldn't otherwise be automatically + detected as "documented", like objects without a proper ``__module__`` + attriubute. If provided, it will be added to the automatically detected ones. + + The *blacklist* can be a set of names that must not be treated as part of + the documented API even though their names indicate otherwise. + + Example use:: + + import bar + import foo + import unittest + from test import support + + class MiscTestCase(unittest.TestCase): + def test__all__(self): + support.check__all__(self, foo) + + class OtherTestCase(unittest.TestCase): + def test__all__(self): + extra = {'BAR_CONST', 'FOO_CONST'} + blacklist = {'baz'} # Undocumented name. + # bar imports part of its API from _bar. + support.check__all__(self, bar, ('bar', '_bar'), + extra=extra, blacklist=blacklist) + + .. versionadded:: 3.6 + + The :mod:`test.support` module defines the following classes: .. class:: TransientResource(exc, **kwargs) diff -r d1c11a78b43a Lib/test/support/__init__.py --- a/Lib/test/support/__init__.py Tue Nov 10 19:52:20 2015 +0200 +++ b/Lib/test/support/__init__.py Wed Nov 11 09:15:07 2015 +0100 @@ -26,6 +26,7 @@ import sysconfig import tempfile import time +import types import unittest import urllib.error import warnings @@ -89,6 +90,7 @@ "bigmemtest", "bigaddrspacetest", "cpython_only", "get_attribute", "requires_IEEE_754", "skip_unless_xattr", "requires_zlib", "anticipate_failure", "load_package_tests", "detect_api_mismatch", + "check__all__", # sys "is_jython", "check_impl_detail", # network @@ -2199,6 +2201,65 @@ return missing_items +def check__all__(test_case, module, name_of_module=None, extra=(), + blacklist=()): + """Assert that 'module's __all__ variable contains all documented names. + + The documented 'module's names (its public API) are detected automatically + based on whether they match the public name convention and were defined in + 'module'. + + The 'name_of_module' can specify (as a string or tuple thereof) what + module(s) could the API be defined in in order to be detected as the public + API. One case for this is when 'module' imports part of its public API from + other modules, possibly a C backend (like 'csv' and its '_csv'). + + The 'extra' can be a set of names that wouldn't otherwise be automatically + detected as "documented", like objects without a proper '__module__' + attriubute. If provided, it will be added to the automatically detected + ones. + + The 'blacklist' can be a set of names that must not be treated as part of + the documented API even though their names indicate otherwise. + + Usage: + import bar + import foo + import unittest + from test import support + + class MiscTestCase(unittest.TestCase): + def test__all__(self): + support.check__all__(self, foo) + + class OtherTestCase(unittest.TestCase): + def test__all__(self): + extra = {'BAR_CONST', 'FOO_CONST'} + blacklist = {'baz'} # Undocumented name. + # bar imports part of its API from _bar. + support.check__all__(self, bar, ('bar', '_bar'), + extra=extra, blacklist=blacklist) + + """ + + if name_of_module is None: + name_of_module = (module.__name__, ) + elif isinstance(name_of_module, str): + name_of_module = (name_of_module, ) + + expected = set(extra) + + for name in dir(module): + if name.startswith('_') or name in blacklist: + continue + obj = getattr(module, name) + if (getattr(obj, '__module__', None) in name_of_module or + (not hasattr(obj, '__module__') and + not isinstance(obj, types.ModuleType))): + expected.add(name) + test_case.assertCountEqual(module.__all__, expected) + + class SuppressCrashReport: """Try to prevent a crash report from popping up. diff -r d1c11a78b43a Lib/test/test_support.py --- a/Lib/test/test_support.py Tue Nov 10 19:52:20 2015 +0200 +++ b/Lib/test/test_support.py Wed Nov 11 09:15:07 2015 +0100 @@ -312,6 +312,28 @@ self.OtherClass, self.RefClass, ignore=ignore) self.assertEqual(set(), missing_items) + def test_check__all__(self): + extra = {'tempdir'} + blacklist = {'template'} + support.check__all__(self, + tempfile, + extra=extra, + blacklist=blacklist) + + extra = {'TextTestResult', 'installHandler'} + blacklist = {'load_tests', "TestProgram", "BaseTestSuite"} + + support.check__all__(self, + unittest, + ("unittest.result", "unittest.case", + "unittest.suite", "unittest.loader", + "unittest.main", "unittest.runner", + "unittest.signals"), + extra=extra, + blacklist=blacklist) + + self.assertRaises(AssertionError, support.check__all__, self, unittest) + # XXX -follows a list of untested API # make_legacy_pyc # is_resource_enabled