test_abc.py revision 7e70fa53142564bed7415b3798c3e77f0574f816
1import contextlib
2import inspect
3import io
4import marshal
5import os
6import sys
7from test import support
8import types
9import unittest
10from unittest import mock
11
12from . import util
13
14frozen_init, source_init = util.import_importlib('importlib')
15frozen_abc, source_abc = util.import_importlib('importlib.abc')
16frozen_util, source_util = util.import_importlib('importlib.util')
17
18##### Inheritance ##############################################################
19class InheritanceTests:
20
21    """Test that the specified class is a subclass/superclass of the expected
22    classes."""
23
24    subclasses = []
25    superclasses = []
26
27    def __init__(self, *args, **kwargs):
28        super().__init__(*args, **kwargs)
29        self.superclasses = [getattr(self.abc, class_name)
30                             for class_name in self.superclass_names]
31        if hasattr(self, 'subclass_names'):
32            # Because test.support.import_fresh_module() creates a new
33            # importlib._bootstrap per module, inheritance checks fail when
34            # checking across module boundaries (i.e. the _bootstrap in abc is
35            # not the same as the one in machinery). That means stealing one of
36            # the modules from the other to make sure the same instance is used.
37            self.subclasses = [getattr(self.abc.machinery, class_name)
38                                for class_name in self.subclass_names]
39        assert self.subclasses or self.superclasses, self.__class__
40        testing = self.__class__.__name__.partition('_')[2]
41        self.__test = getattr(self.abc, testing)
42
43    def test_subclasses(self):
44        # Test that the expected subclasses inherit.
45        for subclass in self.subclasses:
46            self.assertTrue(issubclass(subclass, self.__test),
47                "{0} is not a subclass of {1}".format(subclass, self.__test))
48
49    def test_superclasses(self):
50        # Test that the class inherits from the expected superclasses.
51        for superclass in self.superclasses:
52            self.assertTrue(issubclass(self.__test, superclass),
53               "{0} is not a superclass of {1}".format(superclass, self.__test))
54
55def create_inheritance_tests(base_class):
56    def set_frozen(ns):
57        ns['abc'] = frozen_abc
58    def set_source(ns):
59        ns['abc'] = source_abc
60
61    classes = []
62    for prefix, ns_set in [('Frozen', set_frozen), ('Source', set_source)]:
63        classes.append(types.new_class('_'.join([prefix, base_class.__name__]),
64                                       (base_class, unittest.TestCase),
65                                       exec_body=ns_set))
66    return classes
67
68
69class MetaPathFinder(InheritanceTests):
70    superclass_names = ['Finder']
71    subclass_names = ['BuiltinImporter', 'FrozenImporter', 'PathFinder',
72                      'WindowsRegistryFinder']
73
74tests = create_inheritance_tests(MetaPathFinder)
75Frozen_MetaPathFinderInheritanceTests, Source_MetaPathFinderInheritanceTests = tests
76
77
78class PathEntryFinder(InheritanceTests):
79    superclass_names = ['Finder']
80    subclass_names = ['FileFinder']
81
82tests = create_inheritance_tests(PathEntryFinder)
83Frozen_PathEntryFinderInheritanceTests, Source_PathEntryFinderInheritanceTests = tests
84
85
86class ResourceLoader(InheritanceTests):
87    superclass_names = ['Loader']
88
89tests = create_inheritance_tests(ResourceLoader)
90Frozen_ResourceLoaderInheritanceTests, Source_ResourceLoaderInheritanceTests = tests
91
92
93class InspectLoader(InheritanceTests):
94    superclass_names = ['Loader']
95    subclass_names = ['BuiltinImporter', 'FrozenImporter', 'ExtensionFileLoader']
96
97tests = create_inheritance_tests(InspectLoader)
98Frozen_InspectLoaderInheritanceTests, Source_InspectLoaderInheritanceTests = tests
99
100
101class ExecutionLoader(InheritanceTests):
102    superclass_names = ['InspectLoader']
103    subclass_names = ['ExtensionFileLoader']
104
105tests = create_inheritance_tests(ExecutionLoader)
106Frozen_ExecutionLoaderInheritanceTests, Source_ExecutionLoaderInheritanceTests = tests
107
108
109class FileLoader(InheritanceTests):
110    superclass_names = ['ResourceLoader', 'ExecutionLoader']
111    subclass_names = ['SourceFileLoader', 'SourcelessFileLoader']
112
113tests = create_inheritance_tests(FileLoader)
114Frozen_FileLoaderInheritanceTests, Source_FileLoaderInheritanceTests = tests
115
116
117class SourceLoader(InheritanceTests):
118    superclass_names = ['ResourceLoader', 'ExecutionLoader']
119    subclass_names = ['SourceFileLoader']
120
121tests = create_inheritance_tests(SourceLoader)
122Frozen_SourceLoaderInheritanceTests, Source_SourceLoaderInheritanceTests = tests
123
124##### Default return values ####################################################
125def make_abc_subclasses(base_class):
126    classes = []
127    for kind, abc in [('Frozen', frozen_abc), ('Source', source_abc)]:
128        name = '_'.join([kind, base_class.__name__])
129        base_classes = base_class, getattr(abc, base_class.__name__)
130        classes.append(types.new_class(name, base_classes))
131    return classes
132
133def make_return_value_tests(base_class, test_class):
134    frozen_class, source_class = make_abc_subclasses(base_class)
135    tests = []
136    for prefix, class_in_test in [('Frozen', frozen_class), ('Source', source_class)]:
137        def set_ns(ns):
138            ns['ins'] = class_in_test()
139        tests.append(types.new_class('_'.join([prefix, test_class.__name__]),
140                                     (test_class, unittest.TestCase),
141                                     exec_body=set_ns))
142    return tests
143
144
145class MetaPathFinder:
146
147    def find_module(self, fullname, path):
148        return super().find_module(fullname, path)
149
150Frozen_MPF, Source_MPF = make_abc_subclasses(MetaPathFinder)
151
152
153class MetaPathFinderDefaultsTests:
154
155    def test_find_module(self):
156        # Default should return None.
157        self.assertIsNone(self.ins.find_module('something', None))
158
159    def test_invalidate_caches(self):
160        # Calling the method is a no-op.
161        self.ins.invalidate_caches()
162
163
164tests = make_return_value_tests(MetaPathFinder, MetaPathFinderDefaultsTests)
165Frozen_MPFDefaultTests, Source_MPFDefaultTests = tests
166
167
168class PathEntryFinder:
169
170    def find_loader(self, fullname):
171        return super().find_loader(fullname)
172
173Frozen_PEF, Source_PEF = make_abc_subclasses(PathEntryFinder)
174
175
176class PathEntryFinderDefaultsTests:
177
178    def test_find_loader(self):
179        self.assertEqual((None, []), self.ins.find_loader('something'))
180
181    def find_module(self):
182        self.assertEqual(None, self.ins.find_module('something'))
183
184    def test_invalidate_caches(self):
185        # Should be a no-op.
186        self.ins.invalidate_caches()
187
188
189tests = make_return_value_tests(PathEntryFinder, PathEntryFinderDefaultsTests)
190Frozen_PEFDefaultTests, Source_PEFDefaultTests = tests
191
192
193class Loader:
194
195    def load_module(self, fullname):
196        return super().load_module(fullname)
197
198
199Frozen_L, Source_L = make_abc_subclasses(Loader)
200
201
202class LoaderDefaultsTests:
203
204    def test_load_module(self):
205        with self.assertRaises(ImportError):
206            self.ins.load_module('something')
207
208    def test_module_repr(self):
209        mod = types.ModuleType('blah')
210        with self.assertRaises(NotImplementedError):
211            self.ins.module_repr(mod)
212        original_repr = repr(mod)
213        mod.__loader__ = self.ins
214        # Should still return a proper repr.
215        self.assertTrue(repr(mod))
216
217
218tests = make_return_value_tests(Loader, LoaderDefaultsTests)
219Frozen_LDefaultTests, SourceLDefaultTests = tests
220
221
222class ResourceLoader(Loader):
223
224    def get_data(self, path):
225        return super().get_data(path)
226
227
228Frozen_RL, Source_RL = make_abc_subclasses(ResourceLoader)
229
230
231class ResourceLoaderDefaultsTests:
232
233    def test_get_data(self):
234        with self.assertRaises(IOError):
235            self.ins.get_data('/some/path')
236
237
238tests = make_return_value_tests(ResourceLoader, ResourceLoaderDefaultsTests)
239Frozen_RLDefaultTests, Source_RLDefaultTests = tests
240
241
242class InspectLoader(Loader):
243
244    def is_package(self, fullname):
245        return super().is_package(fullname)
246
247    def get_source(self, fullname):
248        return super().get_source(fullname)
249
250
251Frozen_IL, Source_IL = make_abc_subclasses(InspectLoader)
252
253
254class InspectLoaderDefaultsTests:
255
256    def test_is_package(self):
257        with self.assertRaises(ImportError):
258            self.ins.is_package('blah')
259
260    def test_get_source(self):
261        with self.assertRaises(ImportError):
262            self.ins.get_source('blah')
263
264
265tests = make_return_value_tests(InspectLoader, InspectLoaderDefaultsTests)
266Frozen_ILDefaultTests, Source_ILDefaultTests = tests
267
268
269class ExecutionLoader(InspectLoader):
270
271    def get_filename(self, fullname):
272        return super().get_filename(fullname)
273
274Frozen_EL, Source_EL = make_abc_subclasses(ExecutionLoader)
275
276
277class ExecutionLoaderDefaultsTests:
278
279    def test_get_filename(self):
280        with self.assertRaises(ImportError):
281            self.ins.get_filename('blah')
282
283
284tests = make_return_value_tests(ExecutionLoader, InspectLoaderDefaultsTests)
285Frozen_ELDefaultTests, Source_ELDefaultsTests = tests
286
287##### Loader concrete methods ##################################################
288class LoaderConcreteMethodTests:
289
290    def test_init_module_attrs(self):
291        loader = self.LoaderSubclass()
292        module = types.ModuleType('blah')
293        loader.init_module_attrs(module)
294        self.assertEqual(module.__loader__, loader)
295
296
297class Frozen_LoaderConcreateMethodTests(LoaderConcreteMethodTests, unittest.TestCase):
298    LoaderSubclass = Frozen_L
299
300class Source_LoaderConcreateMethodTests(LoaderConcreteMethodTests, unittest.TestCase):
301    LoaderSubclass = Source_L
302
303
304##### InspectLoader concrete methods ###########################################
305class InspectLoaderSourceToCodeTests:
306
307    def source_to_module(self, data, path=None):
308        """Help with source_to_code() tests."""
309        module = types.ModuleType('blah')
310        loader = self.InspectLoaderSubclass()
311        if path is None:
312            code = loader.source_to_code(data)
313        else:
314            code = loader.source_to_code(data, path)
315        exec(code, module.__dict__)
316        return module
317
318    def test_source_to_code_source(self):
319        # Since compile() can handle strings, so should source_to_code().
320        source = 'attr = 42'
321        module = self.source_to_module(source)
322        self.assertTrue(hasattr(module, 'attr'))
323        self.assertEqual(module.attr, 42)
324
325    def test_source_to_code_bytes(self):
326        # Since compile() can handle bytes, so should source_to_code().
327        source = b'attr = 42'
328        module = self.source_to_module(source)
329        self.assertTrue(hasattr(module, 'attr'))
330        self.assertEqual(module.attr, 42)
331
332    def test_source_to_code_path(self):
333        # Specifying a path should set it for the code object.
334        path = 'path/to/somewhere'
335        loader = self.InspectLoaderSubclass()
336        code = loader.source_to_code('', path)
337        self.assertEqual(code.co_filename, path)
338
339    def test_source_to_code_no_path(self):
340        # Not setting a path should still work and be set to <string> since that
341        # is a pre-existing practice as a default to compile().
342        loader = self.InspectLoaderSubclass()
343        code = loader.source_to_code('')
344        self.assertEqual(code.co_filename, '<string>')
345
346
347class Frozen_ILSourceToCodeTests(InspectLoaderSourceToCodeTests, unittest.TestCase):
348    InspectLoaderSubclass = Frozen_IL
349
350class Source_ILSourceToCodeTests(InspectLoaderSourceToCodeTests, unittest.TestCase):
351    InspectLoaderSubclass = Source_IL
352
353
354class InspectLoaderGetCodeTests:
355
356    def test_get_code(self):
357        # Test success.
358        module = types.ModuleType('blah')
359        with mock.patch.object(self.InspectLoaderSubclass, 'get_source') as mocked:
360            mocked.return_value = 'attr = 42'
361            loader = self.InspectLoaderSubclass()
362            code = loader.get_code('blah')
363        exec(code, module.__dict__)
364        self.assertEqual(module.attr, 42)
365
366    def test_get_code_source_is_None(self):
367        # If get_source() is None then this should be None.
368        with mock.patch.object(self.InspectLoaderSubclass, 'get_source') as mocked:
369            mocked.return_value = None
370            loader = self.InspectLoaderSubclass()
371            code = loader.get_code('blah')
372        self.assertIsNone(code)
373
374    def test_get_code_source_not_found(self):
375        # If there is no source then there is no code object.
376        loader = self.InspectLoaderSubclass()
377        with self.assertRaises(ImportError):
378            loader.get_code('blah')
379
380
381class Frozen_ILGetCodeTests(InspectLoaderGetCodeTests, unittest.TestCase):
382    InspectLoaderSubclass = Frozen_IL
383
384class Source_ILGetCodeTests(InspectLoaderGetCodeTests, unittest.TestCase):
385    InspectLoaderSubclass = Source_IL
386
387
388class InspectLoaderInitModuleTests:
389
390    def mock_is_package(self, return_value):
391        return mock.patch.object(self.InspectLoaderSubclass, 'is_package',
392                                 return_value=return_value)
393
394    def init_module_attrs(self, name):
395        loader = self.InspectLoaderSubclass()
396        module = types.ModuleType(name)
397        loader.init_module_attrs(module)
398        self.assertEqual(module.__loader__, loader)
399        return module
400
401    def test_package(self):
402        # If a package, then __package__ == __name__, __path__ == []
403        with self.mock_is_package(True):
404            name = 'blah'
405            module = self.init_module_attrs(name)
406            self.assertEqual(module.__package__, name)
407            self.assertEqual(module.__path__, [])
408
409    def test_toplevel(self):
410        # If a module is top-level, __package__ == ''
411        with self.mock_is_package(False):
412            name = 'blah'
413            module = self.init_module_attrs(name)
414            self.assertEqual(module.__package__, '')
415
416    def test_submodule(self):
417        # If a module is contained within a package then set __package__ to the
418        # package name.
419        with self.mock_is_package(False):
420            name = 'pkg.mod'
421            module = self.init_module_attrs(name)
422            self.assertEqual(module.__package__, 'pkg')
423
424    def test_is_package_ImportError(self):
425        # If is_package() raises ImportError, __package__ should be None and
426        # __path__ should not be set.
427        with self.mock_is_package(False) as mocked_method:
428            mocked_method.side_effect = ImportError
429            name = 'mod'
430            module = self.init_module_attrs(name)
431            self.assertIsNone(module.__package__)
432            self.assertFalse(hasattr(module, '__path__'))
433
434
435class Frozen_ILInitModuleTests(InspectLoaderInitModuleTests, unittest.TestCase):
436    InspectLoaderSubclass = Frozen_IL
437
438class Source_ILInitModuleTests(InspectLoaderInitModuleTests, unittest.TestCase):
439    InspectLoaderSubclass = Source_IL
440
441
442class InspectLoaderLoadModuleTests:
443
444    """Test InspectLoader.load_module()."""
445
446    module_name = 'blah'
447
448    def setUp(self):
449        support.unload(self.module_name)
450        self.addCleanup(support.unload, self.module_name)
451
452    def mock_get_code(self):
453        return mock.patch.object(self.InspectLoaderSubclass, 'get_code')
454
455    def test_get_code_ImportError(self):
456        # If get_code() raises ImportError, it should propagate.
457        with self.mock_get_code() as mocked_get_code:
458            mocked_get_code.side_effect = ImportError
459            with self.assertRaises(ImportError):
460                loader = self.InspectLoaderSubclass()
461                loader.load_module(self.module_name)
462
463    def test_get_code_None(self):
464        # If get_code() returns None, raise ImportError.
465        with self.mock_get_code() as mocked_get_code:
466            mocked_get_code.return_value = None
467            with self.assertRaises(ImportError):
468                loader = self.InspectLoaderSubclass()
469                loader.load_module(self.module_name)
470
471    def test_module_returned(self):
472        # The loaded module should be returned.
473        code = compile('attr = 42', '<string>', 'exec')
474        with self.mock_get_code() as mocked_get_code:
475            mocked_get_code.return_value = code
476            loader = self.InspectLoaderSubclass()
477            module = loader.load_module(self.module_name)
478            self.assertEqual(module, sys.modules[self.module_name])
479
480
481class Frozen_ILLoadModuleTests(InspectLoaderLoadModuleTests, unittest.TestCase):
482    InspectLoaderSubclass = Frozen_IL
483
484class Source_ILLoadModuleTests(InspectLoaderLoadModuleTests, unittest.TestCase):
485    InspectLoaderSubclass = Source_IL
486
487
488##### ExecutionLoader concrete methods #########################################
489class ExecutionLoaderGetCodeTests:
490
491    def mock_methods(self, *, get_source=False, get_filename=False):
492        source_mock_context, filename_mock_context = None, None
493        if get_source:
494            source_mock_context = mock.patch.object(self.ExecutionLoaderSubclass,
495                                                    'get_source')
496        if get_filename:
497            filename_mock_context = mock.patch.object(self.ExecutionLoaderSubclass,
498                                                      'get_filename')
499        return source_mock_context, filename_mock_context
500
501    def test_get_code(self):
502        path = 'blah.py'
503        source_mock_context, filename_mock_context = self.mock_methods(
504                get_source=True, get_filename=True)
505        with source_mock_context as source_mock, filename_mock_context as name_mock:
506            source_mock.return_value = 'attr = 42'
507            name_mock.return_value = path
508            loader = self.ExecutionLoaderSubclass()
509            code = loader.get_code('blah')
510        self.assertEqual(code.co_filename, path)
511        module = types.ModuleType('blah')
512        exec(code, module.__dict__)
513        self.assertEqual(module.attr, 42)
514
515    def test_get_code_source_is_None(self):
516        # If get_source() is None then this should be None.
517        source_mock_context, _ = self.mock_methods(get_source=True)
518        with source_mock_context as mocked:
519            mocked.return_value = None
520            loader = self.ExecutionLoaderSubclass()
521            code = loader.get_code('blah')
522        self.assertIsNone(code)
523
524    def test_get_code_source_not_found(self):
525        # If there is no source then there is no code object.
526        loader = self.ExecutionLoaderSubclass()
527        with self.assertRaises(ImportError):
528            loader.get_code('blah')
529
530    def test_get_code_no_path(self):
531        # If get_filename() raises ImportError then simply skip setting the path
532        # on the code object.
533        source_mock_context, filename_mock_context = self.mock_methods(
534                get_source=True, get_filename=True)
535        with source_mock_context as source_mock, filename_mock_context as name_mock:
536            source_mock.return_value = 'attr = 42'
537            name_mock.side_effect = ImportError
538            loader = self.ExecutionLoaderSubclass()
539            code = loader.get_code('blah')
540        self.assertEqual(code.co_filename, '<string>')
541        module = types.ModuleType('blah')
542        exec(code, module.__dict__)
543        self.assertEqual(module.attr, 42)
544
545
546class Frozen_ELGetCodeTests(ExecutionLoaderGetCodeTests, unittest.TestCase):
547    ExecutionLoaderSubclass = Frozen_EL
548
549class Source_ELGetCodeTests(ExecutionLoaderGetCodeTests, unittest.TestCase):
550    ExecutionLoaderSubclass = Source_EL
551
552
553class ExecutionLoaderInitModuleTests:
554
555    def mock_is_package(self, return_value):
556        return mock.patch.object(self.ExecutionLoaderSubclass, 'is_package',
557                                 return_value=return_value)
558
559    @contextlib.contextmanager
560    def mock_methods(self, is_package, filename):
561        is_package_manager = self.mock_is_package(is_package)
562        get_filename_manager = mock.patch.object(self.ExecutionLoaderSubclass,
563                'get_filename', return_value=filename)
564        with is_package_manager as mock_is_package:
565            with get_filename_manager as mock_get_filename:
566                yield {'is_package': mock_is_package,
567                       'get_filename': mock_get_filename}
568
569    def test_toplevel(self):
570        # Verify __loader__, __file__, and __package__; no __path__.
571        name = 'blah'
572        path = os.path.join('some', 'path', '{}.py'.format(name))
573        with self.mock_methods(False, path):
574            loader = self.ExecutionLoaderSubclass()
575            module = types.ModuleType(name)
576            loader.init_module_attrs(module)
577        self.assertIs(module.__loader__, loader)
578        self.assertEqual(module.__file__, path)
579        self.assertEqual(module.__package__, '')
580        self.assertFalse(hasattr(module, '__path__'))
581
582    def test_package(self):
583        # Verify __loader__, __file__, __package__, and __path__.
584        name = 'pkg'
585        path = os.path.join('some', 'pkg', '__init__.py')
586        with self.mock_methods(True, path):
587            loader = self.ExecutionLoaderSubclass()
588            module = types.ModuleType(name)
589            loader.init_module_attrs(module)
590        self.assertIs(module.__loader__, loader)
591        self.assertEqual(module.__file__, path)
592        self.assertEqual(module.__package__, 'pkg')
593        self.assertEqual(module.__path__, [os.path.dirname(path)])
594
595    def test_submodule(self):
596        # Verify __package__ and not __path__; test_toplevel() takes care of
597        # other attributes.
598        name = 'pkg.submodule'
599        path = os.path.join('some', 'pkg', 'submodule.py')
600        with self.mock_methods(False, path):
601            loader = self.ExecutionLoaderSubclass()
602            module = types.ModuleType(name)
603            loader.init_module_attrs(module)
604        self.assertEqual(module.__package__, 'pkg')
605        self.assertEqual(module.__file__, path)
606        self.assertFalse(hasattr(module, '__path__'))
607
608    def test_get_filename_ImportError(self):
609        # If get_filename() raises ImportError, don't set __file__.
610        name = 'blah'
611        path = 'blah.py'
612        with self.mock_methods(False, path) as mocked_methods:
613            mocked_methods['get_filename'].side_effect = ImportError
614            loader = self.ExecutionLoaderSubclass()
615            module = types.ModuleType(name)
616            loader.init_module_attrs(module)
617        self.assertFalse(hasattr(module, '__file__'))
618
619
620class Frozen_ELInitModuleTests(ExecutionLoaderInitModuleTests, unittest.TestCase):
621    ExecutionLoaderSubclass = Frozen_EL
622
623class Source_ELInitModuleTests(ExecutionLoaderInitModuleTests, unittest.TestCase):
624    ExecutionLoaderSubclass = Source_EL
625
626
627##### SourceLoader concrete methods ############################################
628class SourceLoader:
629
630    # Globals that should be defined for all modules.
631    source = (b"_ = '::'.join([__name__, __file__, __cached__, __package__, "
632              b"repr(__loader__)])")
633
634    def __init__(self, path):
635        self.path = path
636
637    def get_data(self, path):
638        if path != self.path:
639            raise IOError
640        return self.source
641
642    def get_filename(self, fullname):
643        return self.path
644
645    def module_repr(self, module):
646        return '<module>'
647
648
649Frozen_SourceOnlyL, Source_SourceOnlyL = make_abc_subclasses(SourceLoader)
650
651
652class SourceLoader(SourceLoader):
653
654    source_mtime = 1
655
656    def __init__(self, path, magic=None):
657        super().__init__(path)
658        self.bytecode_path = self.util.cache_from_source(self.path)
659        self.source_size = len(self.source)
660        if magic is None:
661            magic = self.util.MAGIC_NUMBER
662        data = bytearray(magic)
663        data.extend(self.init._w_long(self.source_mtime))
664        data.extend(self.init._w_long(self.source_size))
665        code_object = compile(self.source, self.path, 'exec',
666                                dont_inherit=True)
667        data.extend(marshal.dumps(code_object))
668        self.bytecode = bytes(data)
669        self.written = {}
670
671    def get_data(self, path):
672        if path == self.path:
673            return super().get_data(path)
674        elif path == self.bytecode_path:
675            return self.bytecode
676        else:
677            raise OSError
678
679    def path_stats(self, path):
680        if path != self.path:
681            raise IOError
682        return {'mtime': self.source_mtime, 'size': self.source_size}
683
684    def set_data(self, path, data):
685        self.written[path] = bytes(data)
686        return path == self.bytecode_path
687
688
689Frozen_SL, Source_SL = make_abc_subclasses(SourceLoader)
690Frozen_SL.util = frozen_util
691Source_SL.util = source_util
692Frozen_SL.init = frozen_init
693Source_SL.init = source_init
694
695
696class SourceLoaderTestHarness:
697
698    def setUp(self, *, is_package=True, **kwargs):
699        self.package = 'pkg'
700        if is_package:
701            self.path = os.path.join(self.package, '__init__.py')
702            self.name = self.package
703        else:
704            module_name = 'mod'
705            self.path = os.path.join(self.package, '.'.join(['mod', 'py']))
706            self.name = '.'.join([self.package, module_name])
707        self.cached = self.util.cache_from_source(self.path)
708        self.loader = self.loader_mock(self.path, **kwargs)
709
710    def verify_module(self, module):
711        self.assertEqual(module.__name__, self.name)
712        self.assertEqual(module.__file__, self.path)
713        self.assertEqual(module.__cached__, self.cached)
714        self.assertEqual(module.__package__, self.package)
715        self.assertEqual(module.__loader__, self.loader)
716        values = module._.split('::')
717        self.assertEqual(values[0], self.name)
718        self.assertEqual(values[1], self.path)
719        self.assertEqual(values[2], self.cached)
720        self.assertEqual(values[3], self.package)
721        self.assertEqual(values[4], repr(self.loader))
722
723    def verify_code(self, code_object):
724        module = types.ModuleType(self.name)
725        module.__file__ = self.path
726        module.__cached__ = self.cached
727        module.__package__ = self.package
728        module.__loader__ = self.loader
729        module.__path__ = []
730        exec(code_object, module.__dict__)
731        self.verify_module(module)
732
733
734class SourceOnlyLoaderTests(SourceLoaderTestHarness):
735
736    """Test importlib.abc.SourceLoader for source-only loading.
737
738    Reload testing is subsumed by the tests for
739    importlib.util.module_for_loader.
740
741    """
742
743    def test_get_source(self):
744        # Verify the source code is returned as a string.
745        # If an OSError is raised by get_data then raise ImportError.
746        expected_source = self.loader.source.decode('utf-8')
747        self.assertEqual(self.loader.get_source(self.name), expected_source)
748        def raise_OSError(path):
749            raise OSError
750        self.loader.get_data = raise_OSError
751        with self.assertRaises(ImportError) as cm:
752            self.loader.get_source(self.name)
753        self.assertEqual(cm.exception.name, self.name)
754
755    def test_is_package(self):
756        # Properly detect when loading a package.
757        self.setUp(is_package=False)
758        self.assertFalse(self.loader.is_package(self.name))
759        self.setUp(is_package=True)
760        self.assertTrue(self.loader.is_package(self.name))
761        self.assertFalse(self.loader.is_package(self.name + '.__init__'))
762
763    def test_get_code(self):
764        # Verify the code object is created.
765        code_object = self.loader.get_code(self.name)
766        self.verify_code(code_object)
767
768    def test_source_to_code(self):
769        # Verify the compiled code object.
770        code = self.loader.source_to_code(self.loader.source, self.path)
771        self.verify_code(code)
772
773    def test_load_module(self):
774        # Loading a module should set __name__, __loader__, __package__,
775        # __path__ (for packages), __file__, and __cached__.
776        # The module should also be put into sys.modules.
777        with util.uncache(self.name):
778            module = self.loader.load_module(self.name)
779            self.verify_module(module)
780            self.assertEqual(module.__path__, [os.path.dirname(self.path)])
781            self.assertIn(self.name, sys.modules)
782
783    def test_package_settings(self):
784        # __package__ needs to be set, while __path__ is set on if the module
785        # is a package.
786        # Testing the values for a package are covered by test_load_module.
787        self.setUp(is_package=False)
788        with util.uncache(self.name):
789            module = self.loader.load_module(self.name)
790            self.verify_module(module)
791            self.assertTrue(not hasattr(module, '__path__'))
792
793    def test_get_source_encoding(self):
794        # Source is considered encoded in UTF-8 by default unless otherwise
795        # specified by an encoding line.
796        source = "_ = 'ü'"
797        self.loader.source = source.encode('utf-8')
798        returned_source = self.loader.get_source(self.name)
799        self.assertEqual(returned_source, source)
800        source = "# coding: latin-1\n_ = ü"
801        self.loader.source = source.encode('latin-1')
802        returned_source = self.loader.get_source(self.name)
803        self.assertEqual(returned_source, source)
804
805
806class Frozen_SourceOnlyLTests(SourceOnlyLoaderTests, unittest.TestCase):
807    loader_mock = Frozen_SourceOnlyL
808    util = frozen_util
809
810class Source_SourceOnlyLTests(SourceOnlyLoaderTests, unittest.TestCase):
811    loader_mock = Source_SourceOnlyL
812    util = source_util
813
814
815@unittest.skipIf(sys.dont_write_bytecode, "sys.dont_write_bytecode is true")
816class SourceLoaderBytecodeTests(SourceLoaderTestHarness):
817
818    """Test importlib.abc.SourceLoader's use of bytecode.
819
820    Source-only testing handled by SourceOnlyLoaderTests.
821
822    """
823
824    def verify_code(self, code_object, *, bytecode_written=False):
825        super().verify_code(code_object)
826        if bytecode_written:
827            self.assertIn(self.cached, self.loader.written)
828            data = bytearray(self.util.MAGIC_NUMBER)
829            data.extend(self.init._w_long(self.loader.source_mtime))
830            data.extend(self.init._w_long(self.loader.source_size))
831            data.extend(marshal.dumps(code_object))
832            self.assertEqual(self.loader.written[self.cached], bytes(data))
833
834    def test_code_with_everything(self):
835        # When everything should work.
836        code_object = self.loader.get_code(self.name)
837        self.verify_code(code_object)
838
839    def test_no_bytecode(self):
840        # If no bytecode exists then move on to the source.
841        self.loader.bytecode_path = "<does not exist>"
842        # Sanity check
843        with self.assertRaises(OSError):
844            bytecode_path = self.util.cache_from_source(self.path)
845            self.loader.get_data(bytecode_path)
846        code_object = self.loader.get_code(self.name)
847        self.verify_code(code_object, bytecode_written=True)
848
849    def test_code_bad_timestamp(self):
850        # Bytecode is only used when the timestamp matches the source EXACTLY.
851        for source_mtime in (0, 2):
852            assert source_mtime != self.loader.source_mtime
853            original = self.loader.source_mtime
854            self.loader.source_mtime = source_mtime
855            # If bytecode is used then EOFError would be raised by marshal.
856            self.loader.bytecode = self.loader.bytecode[8:]
857            code_object = self.loader.get_code(self.name)
858            self.verify_code(code_object, bytecode_written=True)
859            self.loader.source_mtime = original
860
861    def test_code_bad_magic(self):
862        # Skip over bytecode with a bad magic number.
863        self.setUp(magic=b'0000')
864        # If bytecode is used then EOFError would be raised by marshal.
865        self.loader.bytecode = self.loader.bytecode[8:]
866        code_object = self.loader.get_code(self.name)
867        self.verify_code(code_object, bytecode_written=True)
868
869    def test_dont_write_bytecode(self):
870        # Bytecode is not written if sys.dont_write_bytecode is true.
871        # Can assume it is false already thanks to the skipIf class decorator.
872        try:
873            sys.dont_write_bytecode = True
874            self.loader.bytecode_path = "<does not exist>"
875            code_object = self.loader.get_code(self.name)
876            self.assertNotIn(self.cached, self.loader.written)
877        finally:
878            sys.dont_write_bytecode = False
879
880    def test_no_set_data(self):
881        # If set_data is not defined, one can still read bytecode.
882        self.setUp(magic=b'0000')
883        original_set_data = self.loader.__class__.mro()[1].set_data
884        try:
885            del self.loader.__class__.mro()[1].set_data
886            code_object = self.loader.get_code(self.name)
887            self.verify_code(code_object)
888        finally:
889            self.loader.__class__.mro()[1].set_data = original_set_data
890
891    def test_set_data_raises_exceptions(self):
892        # Raising NotImplementedError or OSError is okay for set_data.
893        def raise_exception(exc):
894            def closure(*args, **kwargs):
895                raise exc
896            return closure
897
898        self.setUp(magic=b'0000')
899        self.loader.set_data = raise_exception(NotImplementedError)
900        code_object = self.loader.get_code(self.name)
901        self.verify_code(code_object)
902
903
904class Frozen_SLBytecodeTests(SourceLoaderBytecodeTests, unittest.TestCase):
905    loader_mock = Frozen_SL
906    init = frozen_init
907    util = frozen_util
908
909class SourceSLBytecodeTests(SourceLoaderBytecodeTests, unittest.TestCase):
910    loader_mock = Source_SL
911    init = source_init
912    util = source_util
913
914
915class SourceLoaderGetSourceTests:
916
917    """Tests for importlib.abc.SourceLoader.get_source()."""
918
919    def test_default_encoding(self):
920        # Should have no problems with UTF-8 text.
921        name = 'mod'
922        mock = self.SourceOnlyLoaderMock('mod.file')
923        source = 'x = "ü"'
924        mock.source = source.encode('utf-8')
925        returned_source = mock.get_source(name)
926        self.assertEqual(returned_source, source)
927
928    def test_decoded_source(self):
929        # Decoding should work.
930        name = 'mod'
931        mock = self.SourceOnlyLoaderMock("mod.file")
932        source = "# coding: Latin-1\nx='ü'"
933        assert source.encode('latin-1') != source.encode('utf-8')
934        mock.source = source.encode('latin-1')
935        returned_source = mock.get_source(name)
936        self.assertEqual(returned_source, source)
937
938    def test_universal_newlines(self):
939        # PEP 302 says universal newlines should be used.
940        name = 'mod'
941        mock = self.SourceOnlyLoaderMock('mod.file')
942        source = "x = 42\r\ny = -13\r\n"
943        mock.source = source.encode('utf-8')
944        expect = io.IncrementalNewlineDecoder(None, True).decode(source)
945        self.assertEqual(mock.get_source(name), expect)
946
947
948class Frozen_SourceOnlyLGetSourceTests(SourceLoaderGetSourceTests, unittest.TestCase):
949    SourceOnlyLoaderMock = Frozen_SourceOnlyL
950
951class Source_SourceOnlyLGetSourceTests(SourceLoaderGetSourceTests, unittest.TestCase):
952    SourceOnlyLoaderMock = Source_SourceOnlyL
953
954
955class SourceLoaderInitModuleAttrTests:
956
957    """Tests for importlib.abc.SourceLoader.init_module_attrs()."""
958
959    def test_init_module_attrs(self):
960        # If __file__ set, __cached__ == importlib.util.cached_from_source(__file__).
961        name = 'blah'
962        path = 'blah.py'
963        loader = self.SourceOnlyLoaderMock(path)
964        module = types.ModuleType(name)
965        loader.init_module_attrs(module)
966        self.assertEqual(module.__loader__, loader)
967        self.assertEqual(module.__package__, '')
968        self.assertEqual(module.__file__, path)
969        self.assertEqual(module.__cached__, self.util.cache_from_source(path))
970
971    def test_no_get_filename(self):
972        # No __file__, no __cached__.
973        with mock.patch.object(self.SourceOnlyLoaderMock, 'get_filename') as mocked:
974            mocked.side_effect = ImportError
975            name = 'blah'
976            loader = self.SourceOnlyLoaderMock('blah.py')
977            module = types.ModuleType(name)
978            loader.init_module_attrs(module)
979        self.assertFalse(hasattr(module, '__file__'))
980        self.assertFalse(hasattr(module, '__cached__'))
981
982
983class Frozen_SLInitModuleAttrTests(SourceLoaderInitModuleAttrTests, unittest.TestCase):
984    SourceOnlyLoaderMock = Frozen_SourceOnlyL
985    util = frozen_util
986
987    # Difficult to test under source thanks to cross-module mocking needs.
988    @mock.patch('importlib._bootstrap.cache_from_source')
989    def test_cache_from_source_NotImplementedError(self, mock_cache_from_source):
990        # If importlib.util.cache_from_source() raises NotImplementedError don't set
991        # __cached__.
992        mock_cache_from_source.side_effect = NotImplementedError
993        name = 'blah'
994        path = 'blah.py'
995        loader = self.SourceOnlyLoaderMock(path)
996        module = types.ModuleType(name)
997        loader.init_module_attrs(module)
998        self.assertEqual(module.__file__, path)
999        self.assertFalse(hasattr(module, '__cached__'))
1000
1001
1002class Source_SLInitModuleAttrTests(SourceLoaderInitModuleAttrTests, unittest.TestCase):
1003    SourceOnlyLoaderMock = Source_SourceOnlyL
1004    util = source_util
1005
1006
1007
1008if __name__ == '__main__':
1009    unittest.main()
1010