1from .. import util
2
3importlib = util.import_importlib('importlib')
4machinery = util.import_importlib('importlib.machinery')
5
6import os
7import sys
8import tempfile
9from types import ModuleType
10import unittest
11import warnings
12import zipimport
13
14
15class FinderTests:
16
17    """Tests for PathFinder."""
18
19    find = None
20    check_found = None
21
22    def test_failure(self):
23        # Test None returned upon not finding a suitable loader.
24        module = '<test module>'
25        with util.import_state():
26            self.assertIsNone(self.find(module))
27
28    def test_sys_path(self):
29        # Test that sys.path is used when 'path' is None.
30        # Implicitly tests that sys.path_importer_cache is used.
31        module = '<test module>'
32        path = '<test path>'
33        importer = util.mock_spec(module)
34        with util.import_state(path_importer_cache={path: importer},
35                               path=[path]):
36            found = self.find(module)
37            self.check_found(found, importer)
38
39    def test_path(self):
40        # Test that 'path' is used when set.
41        # Implicitly tests that sys.path_importer_cache is used.
42        module = '<test module>'
43        path = '<test path>'
44        importer = util.mock_spec(module)
45        with util.import_state(path_importer_cache={path: importer}):
46            found = self.find(module, [path])
47            self.check_found(found, importer)
48
49    def test_empty_list(self):
50        # An empty list should not count as asking for sys.path.
51        module = 'module'
52        path = '<test path>'
53        importer = util.mock_spec(module)
54        with util.import_state(path_importer_cache={path: importer},
55                               path=[path]):
56            self.assertIsNone(self.find('module', []))
57
58    def test_path_hooks(self):
59        # Test that sys.path_hooks is used.
60        # Test that sys.path_importer_cache is set.
61        module = '<test module>'
62        path = '<test path>'
63        importer = util.mock_spec(module)
64        hook = util.mock_path_hook(path, importer=importer)
65        with util.import_state(path_hooks=[hook]):
66            found = self.find(module, [path])
67            self.check_found(found, importer)
68            self.assertIn(path, sys.path_importer_cache)
69            self.assertIs(sys.path_importer_cache[path], importer)
70
71    def test_empty_path_hooks(self):
72        # Test that if sys.path_hooks is empty a warning is raised,
73        # sys.path_importer_cache gets None set, and PathFinder returns None.
74        path_entry = 'bogus_path'
75        with util.import_state(path_importer_cache={}, path_hooks=[],
76                               path=[path_entry]):
77            with warnings.catch_warnings(record=True) as w:
78                warnings.simplefilter('always', ImportWarning)
79                warnings.simplefilter('ignore', DeprecationWarning)
80                self.assertIsNone(self.find('os'))
81                self.assertIsNone(sys.path_importer_cache[path_entry])
82                self.assertEqual(len(w), 1)
83                self.assertTrue(issubclass(w[-1].category, ImportWarning))
84
85    def test_path_importer_cache_empty_string(self):
86        # The empty string should create a finder using the cwd.
87        path = ''
88        module = '<test module>'
89        importer = util.mock_spec(module)
90        hook = util.mock_path_hook(os.getcwd(), importer=importer)
91        with util.import_state(path=[path], path_hooks=[hook]):
92            found = self.find(module)
93            self.check_found(found, importer)
94            self.assertIn(os.getcwd(), sys.path_importer_cache)
95
96    def test_None_on_sys_path(self):
97        # Putting None in sys.path[0] caused an import regression from Python
98        # 3.2: http://bugs.python.org/issue16514
99        new_path = sys.path[:]
100        new_path.insert(0, None)
101        new_path_importer_cache = sys.path_importer_cache.copy()
102        new_path_importer_cache.pop(None, None)
103        new_path_hooks = [zipimport.zipimporter,
104                          self.machinery.FileFinder.path_hook(
105                              *self.importlib._bootstrap_external._get_supported_file_loaders())]
106        missing = object()
107        email = sys.modules.pop('email', missing)
108        try:
109            with util.import_state(meta_path=sys.meta_path[:],
110                                   path=new_path,
111                                   path_importer_cache=new_path_importer_cache,
112                                   path_hooks=new_path_hooks):
113                module = self.importlib.import_module('email')
114                self.assertIsInstance(module, ModuleType)
115        finally:
116            if email is not missing:
117                sys.modules['email'] = email
118
119    def test_finder_with_find_module(self):
120        class TestFinder:
121            def find_module(self, fullname):
122                return self.to_return
123        failing_finder = TestFinder()
124        failing_finder.to_return = None
125        path = 'testing path'
126        with util.import_state(path_importer_cache={path: failing_finder}):
127            with warnings.catch_warnings():
128                warnings.simplefilter("ignore", ImportWarning)
129                self.assertIsNone(
130                    self.machinery.PathFinder.find_spec('whatever', [path]))
131        success_finder = TestFinder()
132        success_finder.to_return = __loader__
133        with util.import_state(path_importer_cache={path: success_finder}):
134            with warnings.catch_warnings():
135                warnings.simplefilter("ignore", ImportWarning)
136                spec = self.machinery.PathFinder.find_spec('whatever', [path])
137        self.assertEqual(spec.loader, __loader__)
138
139    def test_finder_with_find_loader(self):
140        class TestFinder:
141            loader = None
142            portions = []
143            def find_loader(self, fullname):
144                return self.loader, self.portions
145        path = 'testing path'
146        with util.import_state(path_importer_cache={path: TestFinder()}):
147            with warnings.catch_warnings():
148                warnings.simplefilter("ignore", ImportWarning)
149                self.assertIsNone(
150                    self.machinery.PathFinder.find_spec('whatever', [path]))
151        success_finder = TestFinder()
152        success_finder.loader = __loader__
153        with util.import_state(path_importer_cache={path: success_finder}):
154            with warnings.catch_warnings():
155                warnings.simplefilter("ignore", ImportWarning)
156                spec = self.machinery.PathFinder.find_spec('whatever', [path])
157        self.assertEqual(spec.loader, __loader__)
158
159    def test_finder_with_find_spec(self):
160        class TestFinder:
161            spec = None
162            def find_spec(self, fullname, target=None):
163                return self.spec
164        path = 'testing path'
165        with util.import_state(path_importer_cache={path: TestFinder()}):
166            self.assertIsNone(
167                    self.machinery.PathFinder.find_spec('whatever', [path]))
168        success_finder = TestFinder()
169        success_finder.spec = self.machinery.ModuleSpec('whatever', __loader__)
170        with util.import_state(path_importer_cache={path: success_finder}):
171            got = self.machinery.PathFinder.find_spec('whatever', [path])
172        self.assertEqual(got, success_finder.spec)
173
174    def test_deleted_cwd(self):
175        # Issue #22834
176        old_dir = os.getcwd()
177        self.addCleanup(os.chdir, old_dir)
178        new_dir = tempfile.mkdtemp()
179        try:
180            os.chdir(new_dir)
181            try:
182                os.rmdir(new_dir)
183            except OSError:
184                # EINVAL on Solaris, EBUSY on AIX, ENOTEMPTY on Windows
185                self.skipTest("platform does not allow "
186                              "the deletion of the cwd")
187        except:
188            os.chdir(old_dir)
189            os.rmdir(new_dir)
190            raise
191
192        with util.import_state(path=['']):
193            # Do not want FileNotFoundError raised.
194            self.assertIsNone(self.machinery.PathFinder.find_spec('whatever'))
195
196    def test_invalidate_caches_finders(self):
197        # Finders with an invalidate_caches() method have it called.
198        class FakeFinder:
199            def __init__(self):
200                self.called = False
201
202            def invalidate_caches(self):
203                self.called = True
204
205        cache = {'leave_alone': object(), 'finder_to_invalidate': FakeFinder()}
206        with util.import_state(path_importer_cache=cache):
207            self.machinery.PathFinder.invalidate_caches()
208        self.assertTrue(cache['finder_to_invalidate'].called)
209
210    def test_invalidate_caches_clear_out_None(self):
211        # Clear out None in sys.path_importer_cache() when invalidating caches.
212        cache = {'clear_out': None}
213        with util.import_state(path_importer_cache=cache):
214            self.machinery.PathFinder.invalidate_caches()
215        self.assertEqual(len(cache), 0)
216
217
218class FindModuleTests(FinderTests):
219    def find(self, *args, **kwargs):
220        with warnings.catch_warnings():
221            warnings.simplefilter("ignore", DeprecationWarning)
222            return self.machinery.PathFinder.find_module(*args, **kwargs)
223    def check_found(self, found, importer):
224        self.assertIs(found, importer)
225
226
227(Frozen_FindModuleTests,
228 Source_FindModuleTests
229) = util.test_both(FindModuleTests, importlib=importlib, machinery=machinery)
230
231
232class FindSpecTests(FinderTests):
233    def find(self, *args, **kwargs):
234        return self.machinery.PathFinder.find_spec(*args, **kwargs)
235    def check_found(self, found, importer):
236        self.assertIs(found.loader, importer)
237
238
239(Frozen_FindSpecTests,
240 Source_FindSpecTests
241 ) = util.test_both(FindSpecTests, importlib=importlib, machinery=machinery)
242
243
244class PathEntryFinderTests:
245
246    def test_finder_with_failing_find_spec(self):
247        # PathEntryFinder with find_module() defined should work.
248        # Issue #20763.
249        class Finder:
250            path_location = 'test_finder_with_find_module'
251            def __init__(self, path):
252                if path != self.path_location:
253                    raise ImportError
254
255            @staticmethod
256            def find_module(fullname):
257                return None
258
259
260        with util.import_state(path=[Finder.path_location]+sys.path[:],
261                               path_hooks=[Finder]):
262            with warnings.catch_warnings():
263                warnings.simplefilter("ignore", ImportWarning)
264                self.machinery.PathFinder.find_spec('importlib')
265
266    def test_finder_with_failing_find_module(self):
267        # PathEntryFinder with find_module() defined should work.
268        # Issue #20763.
269        class Finder:
270            path_location = 'test_finder_with_find_module'
271            def __init__(self, path):
272                if path != self.path_location:
273                    raise ImportError
274
275            @staticmethod
276            def find_module(fullname):
277                return None
278
279
280        with util.import_state(path=[Finder.path_location]+sys.path[:],
281                               path_hooks=[Finder]):
282            with warnings.catch_warnings():
283                warnings.simplefilter("ignore", ImportWarning)
284                warnings.simplefilter("ignore", DeprecationWarning)
285                self.machinery.PathFinder.find_module('importlib')
286
287
288(Frozen_PEFTests,
289 Source_PEFTests
290 ) = util.test_both(PathEntryFinderTests, machinery=machinery)
291
292
293if __name__ == '__main__':
294    unittest.main()
295