1import unittest
2from test import support
3from test.support import warnings_helper
4import os
5import sys
6
7
8class NoAll(RuntimeError):
9    pass
10
11class FailedImport(RuntimeError):
12    pass
13
14
15class AllTest(unittest.TestCase):
16
17    def check_all(self, modname):
18        names = {}
19        with warnings_helper.check_warnings(
20            (".* (module|package)", DeprecationWarning),
21            (".* (module|package)", PendingDeprecationWarning),
22            ("", ResourceWarning),
23            quiet=True):
24            try:
25                exec("import %s" % modname, names)
26            except:
27                # Silent fail here seems the best route since some modules
28                # may not be available or not initialize properly in all
29                # environments.
30                raise FailedImport(modname)
31        if not hasattr(sys.modules[modname], "__all__"):
32            raise NoAll(modname)
33        names = {}
34        with self.subTest(module=modname):
35            with warnings_helper.check_warnings(
36                ("", DeprecationWarning),
37                ("", ResourceWarning),
38                quiet=True):
39                try:
40                    exec("from %s import *" % modname, names)
41                except Exception as e:
42                    # Include the module name in the exception string
43                    self.fail("__all__ failure in {}: {}: {}".format(
44                              modname, e.__class__.__name__, e))
45                if "__builtins__" in names:
46                    del names["__builtins__"]
47                if '__annotations__' in names:
48                    del names['__annotations__']
49                if "__warningregistry__" in names:
50                    del names["__warningregistry__"]
51                keys = set(names)
52                all_list = sys.modules[modname].__all__
53                all_set = set(all_list)
54                self.assertCountEqual(all_set, all_list, "in module {}".format(modname))
55                self.assertEqual(keys, all_set, "in module {}".format(modname))
56
57    def walk_modules(self, basedir, modpath):
58        for fn in sorted(os.listdir(basedir)):
59            path = os.path.join(basedir, fn)
60            if os.path.isdir(path):
61                pkg_init = os.path.join(path, '__init__.py')
62                if os.path.exists(pkg_init):
63                    yield pkg_init, modpath + fn
64                    for p, m in self.walk_modules(path, modpath + fn + "."):
65                        yield p, m
66                continue
67            if not fn.endswith('.py') or fn == '__init__.py':
68                continue
69            yield path, modpath + fn[:-3]
70
71    def test_all(self):
72        # List of denied modules and packages
73        denylist = set([
74            # Will raise a SyntaxError when compiling the exec statement
75            '__future__',
76        ])
77
78        if not sys.platform.startswith('java'):
79            # In case _socket fails to build, make this test fail more gracefully
80            # than an AttributeError somewhere deep in CGIHTTPServer.
81            import _socket
82
83        ignored = []
84        failed_imports = []
85        lib_dir = os.path.dirname(os.path.dirname(__file__))
86        for path, modname in self.walk_modules(lib_dir, ""):
87            m = modname
88            denied = False
89            while m:
90                if m in denylist:
91                    denied = True
92                    break
93                m = m.rpartition('.')[0]
94            if denied:
95                continue
96            if support.verbose:
97                print(modname)
98            try:
99                # This heuristic speeds up the process by removing, de facto,
100                # most test modules (and avoiding the auto-executing ones).
101                with open(path, "rb") as f:
102                    if b"__all__" not in f.read():
103                        raise NoAll(modname)
104                    self.check_all(modname)
105            except NoAll:
106                ignored.append(modname)
107            except FailedImport:
108                failed_imports.append(modname)
109
110        if support.verbose:
111            print('Following modules have no __all__ and have been ignored:',
112                  ignored)
113            print('Following modules failed to be imported:', failed_imports)
114
115
116if __name__ == "__main__":
117    unittest.main()
118