1from .. import abc
2from .. import util
3
4machinery = util.import_importlib('importlib.machinery')
5
6import errno
7import os
8import py_compile
9import stat
10import sys
11import tempfile
12from test.support import make_legacy_pyc
13import unittest
14import warnings
15
16
17class FinderTests(abc.FinderTests):
18
19    """For a top-level module, it should just be found directly in the
20    directory being searched. This is true for a directory with source
21    [top-level source], bytecode [top-level bc], or both [top-level both].
22    There is also the possibility that it is a package [top-level package], in
23    which case there will be a directory with the module name and an
24    __init__.py file. If there is a directory without an __init__.py an
25    ImportWarning is returned [empty dir].
26
27    For sub-modules and sub-packages, the same happens as above but only use
28    the tail end of the name [sub module] [sub package] [sub empty].
29
30    When there is a conflict between a package and module having the same name
31    in the same directory, the package wins out [package over module]. This is
32    so that imports of modules within the package can occur rather than trigger
33    an import error.
34
35    When there is a package and module with the same name, always pick the
36    package over the module [package over module]. This is so that imports from
37    the package have the possibility of succeeding.
38
39    """
40
41    def get_finder(self, root):
42        loader_details = [(self.machinery.SourceFileLoader,
43                            self.machinery.SOURCE_SUFFIXES),
44                          (self.machinery.SourcelessFileLoader,
45                            self.machinery.BYTECODE_SUFFIXES)]
46        return self.machinery.FileFinder(root, *loader_details)
47
48    def import_(self, root, module):
49        finder = self.get_finder(root)
50        return self._find(finder, module, loader_only=True)
51
52    def run_test(self, test, create=None, *, compile_=None, unlink=None):
53        """Test the finding of 'test' with the creation of modules listed in
54        'create'.
55
56        Any names listed in 'compile_' are byte-compiled. Modules
57        listed in 'unlink' have their source files deleted.
58
59        """
60        if create is None:
61            create = {test}
62        with util.create_modules(*create) as mapping:
63            if compile_:
64                for name in compile_:
65                    py_compile.compile(mapping[name])
66            if unlink:
67                for name in unlink:
68                    os.unlink(mapping[name])
69                    try:
70                        make_legacy_pyc(mapping[name])
71                    except OSError as error:
72                        # Some tests do not set compile_=True so the source
73                        # module will not get compiled and there will be no
74                        # PEP 3147 pyc file to rename.
75                        if error.errno != errno.ENOENT:
76                            raise
77            loader = self.import_(mapping['.root'], test)
78            self.assertTrue(hasattr(loader, 'load_module'))
79            return loader
80
81    def test_module(self):
82        # [top-level source]
83        self.run_test('top_level')
84        # [top-level bc]
85        self.run_test('top_level', compile_={'top_level'},
86                      unlink={'top_level'})
87        # [top-level both]
88        self.run_test('top_level', compile_={'top_level'})
89
90    # [top-level package]
91    def test_package(self):
92        # Source.
93        self.run_test('pkg', {'pkg.__init__'})
94        # Bytecode.
95        self.run_test('pkg', {'pkg.__init__'}, compile_={'pkg.__init__'},
96                unlink={'pkg.__init__'})
97        # Both.
98        self.run_test('pkg', {'pkg.__init__'}, compile_={'pkg.__init__'})
99
100    # [sub module]
101    def test_module_in_package(self):
102        with util.create_modules('pkg.__init__', 'pkg.sub') as mapping:
103            pkg_dir = os.path.dirname(mapping['pkg.__init__'])
104            loader = self.import_(pkg_dir, 'pkg.sub')
105            self.assertTrue(hasattr(loader, 'load_module'))
106
107    # [sub package]
108    def test_package_in_package(self):
109        context = util.create_modules('pkg.__init__', 'pkg.sub.__init__')
110        with context as mapping:
111            pkg_dir = os.path.dirname(mapping['pkg.__init__'])
112            loader = self.import_(pkg_dir, 'pkg.sub')
113            self.assertTrue(hasattr(loader, 'load_module'))
114
115    # [package over modules]
116    def test_package_over_module(self):
117        name = '_temp'
118        loader = self.run_test(name, {'{0}.__init__'.format(name), name})
119        self.assertIn('__init__', loader.get_filename(name))
120
121    def test_failure(self):
122        with util.create_modules('blah') as mapping:
123            nothing = self.import_(mapping['.root'], 'sdfsadsadf')
124            self.assertIsNone(nothing)
125
126    def test_empty_string_for_dir(self):
127        # The empty string from sys.path means to search in the cwd.
128        finder = self.machinery.FileFinder('', (self.machinery.SourceFileLoader,
129            self.machinery.SOURCE_SUFFIXES))
130        with open('mod.py', 'w') as file:
131            file.write("# test file for importlib")
132        try:
133            loader = self._find(finder, 'mod', loader_only=True)
134            self.assertTrue(hasattr(loader, 'load_module'))
135        finally:
136            os.unlink('mod.py')
137
138    def test_invalidate_caches(self):
139        # invalidate_caches() should reset the mtime.
140        finder = self.machinery.FileFinder('', (self.machinery.SourceFileLoader,
141            self.machinery.SOURCE_SUFFIXES))
142        finder._path_mtime = 42
143        finder.invalidate_caches()
144        self.assertEqual(finder._path_mtime, -1)
145
146    # Regression test for http://bugs.python.org/issue14846
147    def test_dir_removal_handling(self):
148        mod = 'mod'
149        with util.create_modules(mod) as mapping:
150            finder = self.get_finder(mapping['.root'])
151            found = self._find(finder, 'mod', loader_only=True)
152            self.assertIsNotNone(found)
153        found = self._find(finder, 'mod', loader_only=True)
154        self.assertIsNone(found)
155
156    @unittest.skipUnless(sys.platform != 'win32',
157            'os.chmod() does not support the needed arguments under Windows')
158    def test_no_read_directory(self):
159        # Issue #16730
160        tempdir = tempfile.TemporaryDirectory()
161        original_mode = os.stat(tempdir.name).st_mode
162        def cleanup(tempdir):
163            """Cleanup function for the temporary directory.
164
165            Since we muck with the permissions, we want to set them back to
166            their original values to make sure the directory can be properly
167            cleaned up.
168
169            """
170            os.chmod(tempdir.name, original_mode)
171            # If this is not explicitly called then the __del__ method is used,
172            # but since already mucking around might as well explicitly clean
173            # up.
174            tempdir.__exit__(None, None, None)
175        self.addCleanup(cleanup, tempdir)
176        os.chmod(tempdir.name, stat.S_IWUSR | stat.S_IXUSR)
177        finder = self.get_finder(tempdir.name)
178        found = self._find(finder, 'doesnotexist')
179        self.assertEqual(found, self.NOT_FOUND)
180
181    def test_ignore_file(self):
182        # If a directory got changed to a file from underneath us, then don't
183        # worry about looking for submodules.
184        with tempfile.NamedTemporaryFile() as file_obj:
185            finder = self.get_finder(file_obj.name)
186            found = self._find(finder, 'doesnotexist')
187            self.assertEqual(found, self.NOT_FOUND)
188
189
190class FinderTestsPEP451(FinderTests):
191
192    NOT_FOUND = None
193
194    def _find(self, finder, name, loader_only=False):
195        spec = finder.find_spec(name)
196        return spec.loader if spec is not None else spec
197
198
199(Frozen_FinderTestsPEP451,
200 Source_FinderTestsPEP451
201 ) = util.test_both(FinderTestsPEP451, machinery=machinery)
202
203
204class FinderTestsPEP420(FinderTests):
205
206    NOT_FOUND = (None, [])
207
208    def _find(self, finder, name, loader_only=False):
209        with warnings.catch_warnings():
210            warnings.simplefilter("ignore", DeprecationWarning)
211            loader_portions = finder.find_loader(name)
212            return loader_portions[0] if loader_only else loader_portions
213
214
215(Frozen_FinderTestsPEP420,
216 Source_FinderTestsPEP420
217 ) = util.test_both(FinderTestsPEP420, machinery=machinery)
218
219
220class FinderTestsPEP302(FinderTests):
221
222    NOT_FOUND = None
223
224    def _find(self, finder, name, loader_only=False):
225        with warnings.catch_warnings():
226            warnings.simplefilter("ignore", DeprecationWarning)
227            return finder.find_module(name)
228
229
230(Frozen_FinderTestsPEP302,
231 Source_FinderTestsPEP302
232 ) = util.test_both(FinderTestsPEP302, machinery=machinery)
233
234
235if __name__ == '__main__':
236    unittest.main()
237