Brett Cannon | 23cbd8a | 2009-01-18 00:24:28 +0000 | [diff] [blame] | 1 | import importlib |
Brett Cannon | 30b047d | 2009-02-01 02:05:11 +0000 | [diff] [blame] | 2 | from .. import abc |
Brett Cannon | 4ee2cda | 2009-02-01 03:08:31 +0000 | [diff] [blame] | 3 | from . import util as source_util |
Brett Cannon | 23cbd8a | 2009-01-18 00:24:28 +0000 | [diff] [blame] | 4 | |
| 5 | import imp |
| 6 | import os |
| 7 | import py_compile |
| 8 | import sys |
| 9 | import unittest |
| 10 | |
| 11 | |
| 12 | class SimpleTest(unittest.TestCase): |
| 13 | |
| 14 | """Should have no issue importing a source module [basic]. And if there is |
| 15 | a syntax error, it should raise a SyntaxError [syntax error]. |
| 16 | |
| 17 | """ |
| 18 | |
| 19 | # [basic] |
Brett Cannon | 30b047d | 2009-02-01 02:05:11 +0000 | [diff] [blame] | 20 | def test_module(self): |
Brett Cannon | 4ee2cda | 2009-02-01 03:08:31 +0000 | [diff] [blame] | 21 | with source_util.create_modules('_temp') as mapping: |
Brett Cannon | 23cbd8a | 2009-01-18 00:24:28 +0000 | [diff] [blame] | 22 | loader = importlib._PyFileLoader('_temp', mapping['_temp'], False) |
Brett Cannon | 30b047d | 2009-02-01 02:05:11 +0000 | [diff] [blame] | 23 | module = loader.load_module('_temp') |
Brett Cannon | 23cbd8a | 2009-01-18 00:24:28 +0000 | [diff] [blame] | 24 | self.assert_('_temp' in sys.modules) |
Brett Cannon | 30b047d | 2009-02-01 02:05:11 +0000 | [diff] [blame] | 25 | check = {'__name__': '_temp', '__file__': mapping['_temp'], |
Brett Cannon | 06c9d96 | 2009-02-07 01:52:25 +0000 | [diff] [blame^] | 26 | '__package__': ''} |
Brett Cannon | 30b047d | 2009-02-01 02:05:11 +0000 | [diff] [blame] | 27 | for attr, value in check.items(): |
| 28 | self.assertEqual(getattr(module, attr), value) |
| 29 | |
| 30 | def test_package(self): |
Brett Cannon | 4ee2cda | 2009-02-01 03:08:31 +0000 | [diff] [blame] | 31 | with source_util.create_modules('_pkg.__init__') as mapping: |
Brett Cannon | 30b047d | 2009-02-01 02:05:11 +0000 | [diff] [blame] | 32 | loader = importlib._PyFileLoader('_pkg', mapping['_pkg.__init__'], |
| 33 | True) |
| 34 | module = loader.load_module('_pkg') |
| 35 | self.assert_('_pkg' in sys.modules) |
| 36 | check = {'__name__': '_pkg', '__file__': mapping['_pkg.__init__'], |
| 37 | '__path__': [os.path.dirname(mapping['_pkg.__init__'])], |
| 38 | '__package__': '_pkg'} |
| 39 | for attr, value in check.items(): |
| 40 | self.assertEqual(getattr(module, attr), value) |
| 41 | |
| 42 | |
| 43 | def test_lacking_parent(self): |
Brett Cannon | 4ee2cda | 2009-02-01 03:08:31 +0000 | [diff] [blame] | 44 | with source_util.create_modules('_pkg.__init__', '_pkg.mod')as mapping: |
Brett Cannon | 30b047d | 2009-02-01 02:05:11 +0000 | [diff] [blame] | 45 | loader = importlib._PyFileLoader('_pkg.mod', mapping['_pkg.mod'], |
| 46 | False) |
| 47 | module = loader.load_module('_pkg.mod') |
| 48 | self.assert_('_pkg.mod' in sys.modules) |
| 49 | check = {'__name__': '_pkg.mod', '__file__': mapping['_pkg.mod'], |
| 50 | '__package__': '_pkg'} |
| 51 | for attr, value in check.items(): |
| 52 | self.assertEqual(getattr(module, attr), value) |
| 53 | |
| 54 | def fake_mtime(self, fxn): |
| 55 | """Fake mtime to always be higher than expected.""" |
| 56 | return lambda name: fxn(name) + 1 |
| 57 | |
| 58 | def test_module_reuse(self): |
Brett Cannon | 4ee2cda | 2009-02-01 03:08:31 +0000 | [diff] [blame] | 59 | with source_util.create_modules('_temp') as mapping: |
Brett Cannon | 30b047d | 2009-02-01 02:05:11 +0000 | [diff] [blame] | 60 | loader = importlib._PyFileLoader('_temp', mapping['_temp'], False) |
| 61 | module = loader.load_module('_temp') |
| 62 | module_id = id(module) |
| 63 | module_dict_id = id(module.__dict__) |
| 64 | with open(mapping['_temp'], 'w') as file: |
| 65 | file.write("testing_var = 42\n") |
| 66 | # For filesystems where the mtime is only to a second granularity, |
| 67 | # everything that has happened above can be too fast; |
| 68 | # force an mtime on the source that is guaranteed to be different |
| 69 | # than the original mtime. |
| 70 | loader.source_mtime = self.fake_mtime(loader.source_mtime) |
| 71 | module = loader.load_module('_temp') |
| 72 | self.assert_('testing_var' in module.__dict__, |
| 73 | "'testing_var' not in " |
| 74 | "{0}".format(list(module.__dict__.keys()))) |
| 75 | self.assertEqual(module, sys.modules['_temp']) |
| 76 | self.assertEqual(id(module), module_id) |
| 77 | self.assertEqual(id(module.__dict__), module_dict_id) |
| 78 | |
| 79 | def test_state_after_failure(self): |
| 80 | # A failed reload should leave the original module intact. |
| 81 | attributes = ('__file__', '__path__', '__package__') |
| 82 | value = '<test>' |
| 83 | name = '_temp' |
Brett Cannon | 4ee2cda | 2009-02-01 03:08:31 +0000 | [diff] [blame] | 84 | with source_util.create_modules(name) as mapping: |
Brett Cannon | 30b047d | 2009-02-01 02:05:11 +0000 | [diff] [blame] | 85 | orig_module = imp.new_module(name) |
| 86 | for attr in attributes: |
| 87 | setattr(orig_module, attr, value) |
| 88 | with open(mapping[name], 'w') as file: |
| 89 | file.write('+++ bad syntax +++') |
| 90 | loader = importlib._PyFileLoader('_temp', mapping['_temp'], False) |
| 91 | self.assertRaises(SyntaxError, loader.load_module, name) |
| 92 | for attr in attributes: |
| 93 | self.assertEqual(getattr(orig_module, attr), value) |
Brett Cannon | 23cbd8a | 2009-01-18 00:24:28 +0000 | [diff] [blame] | 94 | |
| 95 | # [syntax error] |
| 96 | def test_bad_syntax(self): |
Brett Cannon | 4ee2cda | 2009-02-01 03:08:31 +0000 | [diff] [blame] | 97 | with source_util.create_modules('_temp') as mapping: |
Brett Cannon | 23cbd8a | 2009-01-18 00:24:28 +0000 | [diff] [blame] | 98 | with open(mapping['_temp'], 'w') as file: |
| 99 | file.write('=') |
| 100 | loader = importlib._PyFileLoader('_temp', mapping['_temp'], False) |
| 101 | self.assertRaises(SyntaxError, loader.load_module, '_temp') |
| 102 | self.assert_('_temp' not in sys.modules) |
| 103 | |
| 104 | |
| 105 | class DontWriteBytecodeTest(unittest.TestCase): |
| 106 | |
| 107 | """If sys.dont_write_bytcode is true then no bytecode should be created.""" |
| 108 | |
| 109 | def tearDown(self): |
| 110 | sys.dont_write_bytecode = False |
| 111 | |
Brett Cannon | 4ee2cda | 2009-02-01 03:08:31 +0000 | [diff] [blame] | 112 | @source_util.writes_bytecode |
Brett Cannon | 23cbd8a | 2009-01-18 00:24:28 +0000 | [diff] [blame] | 113 | def run_test(self, assertion): |
Brett Cannon | 4ee2cda | 2009-02-01 03:08:31 +0000 | [diff] [blame] | 114 | with source_util.create_modules('_temp') as mapping: |
Brett Cannon | 23cbd8a | 2009-01-18 00:24:28 +0000 | [diff] [blame] | 115 | loader = importlib._PyFileLoader('_temp', mapping['_temp'], False) |
| 116 | loader.load_module('_temp') |
Brett Cannon | 4ee2cda | 2009-02-01 03:08:31 +0000 | [diff] [blame] | 117 | bytecode_path = source_util.bytecode_path(mapping['_temp']) |
Brett Cannon | 23cbd8a | 2009-01-18 00:24:28 +0000 | [diff] [blame] | 118 | assertion(bytecode_path) |
| 119 | |
| 120 | def test_bytecode_written(self): |
| 121 | fxn = lambda bc_path: self.assert_(os.path.exists(bc_path)) |
| 122 | self.run_test(fxn) |
| 123 | |
| 124 | def test_bytecode_not_written(self): |
| 125 | sys.dont_write_bytecode = True |
| 126 | fxn = lambda bc_path: self.assert_(not os.path.exists(bc_path)) |
| 127 | self.run_test(fxn) |
| 128 | |
| 129 | |
| 130 | class BadDataTest(unittest.TestCase): |
| 131 | |
| 132 | """If the bytecode has a magic number that does not match the |
| 133 | interpreters', ImportError is raised [bad magic]. The timestamp can have |
| 134 | any value. And bad marshal data raises ValueError. |
| 135 | |
| 136 | """ |
| 137 | |
| 138 | # [bad magic] |
| 139 | def test_bad_magic(self): |
Brett Cannon | 4ee2cda | 2009-02-01 03:08:31 +0000 | [diff] [blame] | 140 | with source_util.create_modules('_temp') as mapping: |
Brett Cannon | 23cbd8a | 2009-01-18 00:24:28 +0000 | [diff] [blame] | 141 | py_compile.compile(mapping['_temp']) |
| 142 | os.unlink(mapping['_temp']) |
Brett Cannon | 4ee2cda | 2009-02-01 03:08:31 +0000 | [diff] [blame] | 143 | bytecode_path = source_util.bytecode_path(mapping['_temp']) |
Brett Cannon | 23cbd8a | 2009-01-18 00:24:28 +0000 | [diff] [blame] | 144 | with open(bytecode_path, 'r+b') as file: |
| 145 | file.seek(0) |
| 146 | file.write(b'\x00\x00\x00\x00') |
| 147 | loader = importlib._PyFileLoader('_temp', mapping['_temp'], False) |
| 148 | self.assertRaises(ImportError, loader.load_module, '_temp') |
| 149 | self.assert_('_temp' not in sys.modules) |
| 150 | |
| 151 | |
| 152 | class SourceBytecodeInteraction(unittest.TestCase): |
| 153 | |
| 154 | """When both source and bytecode are present, certain rules dictate which |
| 155 | version of the code takes precedent. All things being equal, the bytecode |
| 156 | is used with the value of __file__ set to the source [basic top-level], |
| 157 | [basic package], [basic sub-module], [basic sub-package]. |
| 158 | |
| 159 | """ |
| 160 | |
| 161 | def import_(self, file, module, *, pkg=False): |
| 162 | loader = importlib._PyFileLoader(module, file, pkg) |
| 163 | return loader.load_module(module) |
| 164 | |
| 165 | def run_test(self, test, *create, pkg=False): |
| 166 | create += (test,) |
Brett Cannon | 4ee2cda | 2009-02-01 03:08:31 +0000 | [diff] [blame] | 167 | with source_util.create_modules(*create) as mapping: |
Brett Cannon | 23cbd8a | 2009-01-18 00:24:28 +0000 | [diff] [blame] | 168 | for name in create: |
| 169 | py_compile.compile(mapping[name]) |
| 170 | if pkg: |
| 171 | import_name = test.rsplit('.', 1)[0] |
| 172 | else: |
| 173 | import_name = test |
| 174 | loader = importlib._PyFileLoader(import_name, mapping[test], pkg) |
| 175 | # Because some platforms only have a granularity to the second for |
| 176 | # atime you can't check the physical files. Instead just make it an |
| 177 | # exception trigger if source was read. |
| 178 | loader.get_source = lambda self, x: 42 |
| 179 | module = loader.load_module(import_name) |
| 180 | self.assertEqual(module.__file__, mapping[name]) |
| 181 | self.assert_(import_name in sys.modules) |
| 182 | self.assertEqual(id(module), id(sys.modules[import_name])) |
| 183 | |
| 184 | # [basic top-level] |
| 185 | def test_basic_top_level(self): |
| 186 | self.run_test('top_level') |
| 187 | |
| 188 | # [basic package] |
| 189 | def test_basic_package(self): |
| 190 | self.run_test('pkg.__init__', pkg=True) |
| 191 | |
| 192 | # [basic sub-module] |
| 193 | def test_basic_sub_module(self): |
| 194 | self.run_test('pkg.sub', 'pkg.__init__') |
| 195 | |
| 196 | # [basic sub-package] |
| 197 | def test_basic_sub_package(self): |
| 198 | self.run_test('pkg.sub.__init__', 'pkg.__init__', pkg=True) |
| 199 | |
| 200 | |
| 201 | class BadBytecodeTest(unittest.TestCase): |
| 202 | |
| 203 | """But there are several things about the bytecode which might lead to the |
| 204 | source being preferred. If the magic number differs from what the |
| 205 | interpreter uses, then the source is used with the bytecode regenerated. |
| 206 | If the timestamp is older than the modification time for the source then |
| 207 | the bytecode is not used [bad timestamp]. |
| 208 | |
| 209 | But if the marshal data is bad, even if the magic number and timestamp |
| 210 | work, a ValueError is raised and the source is not used [bad marshal]. |
| 211 | |
| 212 | """ |
| 213 | |
| 214 | def import_(self, file, module_name): |
| 215 | loader = importlib._PyFileLoader(module_name, file, False) |
| 216 | module = loader.load_module(module_name) |
| 217 | self.assert_(module_name in sys.modules) |
| 218 | |
| 219 | # [bad magic] |
Brett Cannon | 4ee2cda | 2009-02-01 03:08:31 +0000 | [diff] [blame] | 220 | @source_util.writes_bytecode |
Brett Cannon | 23cbd8a | 2009-01-18 00:24:28 +0000 | [diff] [blame] | 221 | def test_bad_magic(self): |
Brett Cannon | 4ee2cda | 2009-02-01 03:08:31 +0000 | [diff] [blame] | 222 | with source_util.create_modules('_temp') as mapping: |
Brett Cannon | 23cbd8a | 2009-01-18 00:24:28 +0000 | [diff] [blame] | 223 | py_compile.compile(mapping['_temp']) |
Brett Cannon | 4ee2cda | 2009-02-01 03:08:31 +0000 | [diff] [blame] | 224 | bytecode_path = source_util.bytecode_path(mapping['_temp']) |
Brett Cannon | 23cbd8a | 2009-01-18 00:24:28 +0000 | [diff] [blame] | 225 | with open(bytecode_path, 'r+b') as bytecode_file: |
| 226 | bytecode_file.seek(0) |
| 227 | bytecode_file.write(b'\x00\x00\x00\x00') |
| 228 | self.import_(mapping['_temp'], '_temp') |
| 229 | with open(bytecode_path, 'rb') as bytecode_file: |
| 230 | self.assertEqual(bytecode_file.read(4), imp.get_magic()) |
| 231 | |
| 232 | # [bad timestamp] |
Brett Cannon | 4ee2cda | 2009-02-01 03:08:31 +0000 | [diff] [blame] | 233 | @source_util.writes_bytecode |
Brett Cannon | 23cbd8a | 2009-01-18 00:24:28 +0000 | [diff] [blame] | 234 | def test_bad_bytecode(self): |
| 235 | zeros = b'\x00\x00\x00\x00' |
Brett Cannon | 4ee2cda | 2009-02-01 03:08:31 +0000 | [diff] [blame] | 236 | with source_util.create_modules('_temp') as mapping: |
Brett Cannon | 23cbd8a | 2009-01-18 00:24:28 +0000 | [diff] [blame] | 237 | py_compile.compile(mapping['_temp']) |
Brett Cannon | 4ee2cda | 2009-02-01 03:08:31 +0000 | [diff] [blame] | 238 | bytecode_path = source_util.bytecode_path(mapping['_temp']) |
Brett Cannon | 23cbd8a | 2009-01-18 00:24:28 +0000 | [diff] [blame] | 239 | with open(bytecode_path, 'r+b') as bytecode_file: |
| 240 | bytecode_file.seek(4) |
| 241 | bytecode_file.write(zeros) |
| 242 | self.import_(mapping['_temp'], '_temp') |
| 243 | source_mtime = os.path.getmtime(mapping['_temp']) |
| 244 | source_timestamp = importlib._w_long(source_mtime) |
| 245 | with open(bytecode_path, 'rb') as bytecode_file: |
| 246 | bytecode_file.seek(4) |
| 247 | self.assertEqual(bytecode_file.read(4), source_timestamp) |
| 248 | |
| 249 | # [bad marshal] |
| 250 | def test_bad_marshal(self): |
Brett Cannon | 4ee2cda | 2009-02-01 03:08:31 +0000 | [diff] [blame] | 251 | with source_util.create_modules('_temp') as mapping: |
| 252 | bytecode_path = source_util.bytecode_path(mapping['_temp']) |
Brett Cannon | 23cbd8a | 2009-01-18 00:24:28 +0000 | [diff] [blame] | 253 | source_mtime = os.path.getmtime(mapping['_temp']) |
| 254 | source_timestamp = importlib._w_long(source_mtime) |
| 255 | with open(bytecode_path, 'wb') as bytecode_file: |
| 256 | bytecode_file.write(imp.get_magic()) |
| 257 | bytecode_file.write(source_timestamp) |
| 258 | bytecode_file.write(b'AAAA') |
| 259 | self.assertRaises(ValueError, self.import_, mapping['_temp'], |
| 260 | '_temp') |
| 261 | self.assert_('_temp' not in sys.modules) |
| 262 | |
| 263 | |
| 264 | def test_main(): |
| 265 | from test.support import run_unittest |
| 266 | run_unittest(SimpleTest, DontWriteBytecodeTest, BadDataTest, |
| 267 | SourceBytecodeInteraction, BadBytecodeTest) |
| 268 | |
| 269 | |
| 270 | if __name__ == '__main__': |
| 271 | test_main() |