~bzr-pqm/bzr/bzr.dev

2052.3.2 by John Arbash Meinel
Change Copyright .. by Canonical to Copyright ... Canonical
1
# Copyright (C) 2006 Canonical Ltd
1996.1.1 by John Arbash Meinel
Adding a ScopeReplacer class, which can replace itself on demand
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
"""Test the lazy_import functionality."""
18
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
19
import os
20
import sys
1996.1.1 by John Arbash Meinel
Adding a ScopeReplacer class, which can replace itself on demand
21
22
from bzrlib import (
1996.1.16 by John Arbash Meinel
Raise an exception when ScopeReplacer has been misused
23
    errors,
1996.1.1 by John Arbash Meinel
Adding a ScopeReplacer class, which can replace itself on demand
24
    lazy_import,
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
25
    osutils,
1996.1.1 by John Arbash Meinel
Adding a ScopeReplacer class, which can replace itself on demand
26
    )
27
from bzrlib.tests import TestCase, TestCaseInTempDir
28
29
30
class InstrumentedReplacer(lazy_import.ScopeReplacer):
31
    """Track what actions are done"""
32
33
    @staticmethod
34
    def use_actions(actions):
35
        InstrumentedReplacer.actions = actions
36
37
    def _replace(self):
38
        InstrumentedReplacer.actions.append('_replace')
39
        return lazy_import.ScopeReplacer._replace(self)
40
41
    def __getattribute__(self, attr):
42
        InstrumentedReplacer.actions.append(('__getattribute__', attr))
43
        return lazy_import.ScopeReplacer.__getattribute__(self, attr)
44
45
    def __call__(self, *args, **kwargs):
46
        InstrumentedReplacer.actions.append(('__call__', args, kwargs))
47
        return lazy_import.ScopeReplacer.__call__(self, *args, **kwargs)
48
49
1996.1.3 by John Arbash Meinel
Basic single-level imports work
50
class InstrumentedImportReplacer(lazy_import.ImportReplacer):
51
52
    @staticmethod
53
    def use_actions(actions):
54
        InstrumentedImportReplacer.actions = actions
55
56
    def _import(self, scope, name):
57
        InstrumentedImportReplacer.actions.append(('_import', name))
58
        return lazy_import.ImportReplacer._import(self, scope, name)
59
60
    def _replace(self):
61
        InstrumentedImportReplacer.actions.append('_replace')
62
        return lazy_import.ScopeReplacer._replace(self)
63
64
    def __getattribute__(self, attr):
65
        InstrumentedImportReplacer.actions.append(('__getattribute__', attr))
66
        return lazy_import.ScopeReplacer.__getattribute__(self, attr)
67
68
    def __call__(self, *args, **kwargs):
69
        InstrumentedImportReplacer.actions.append(('__call__', args, kwargs))
70
        return lazy_import.ScopeReplacer.__call__(self, *args, **kwargs)
71
72
1996.1.1 by John Arbash Meinel
Adding a ScopeReplacer class, which can replace itself on demand
73
class TestClass(object):
74
    """Just a simple test class instrumented for the test cases"""
75
76
    class_member = 'class_member'
77
78
    @staticmethod
79
    def use_actions(actions):
80
        TestClass.actions = actions
81
82
    def __init__(self):
83
        TestClass.actions.append('init')
84
85
    def foo(self, x):
86
        TestClass.actions.append(('foo', x))
87
        return 'foo'
88
89
90
class TestScopeReplacer(TestCase):
91
    """Test the ability of the replacer to put itself into the correct scope.
92
93
    In these tests we use the global scope, because we cannot replace
94
    variables in the local scope. This means that we need to be careful
95
    and not have the replacing objects use the same name, or we would
96
    get collisions.
97
    """
98
2399.1.11 by John Arbash Meinel
Update lazy_import with tests for the new '_should_proxy' variable.
99
    def setUp(self):
100
        TestCase.setUp(self)
101
        # These tests assume we will not be proxying, so make sure proxying is
102
        # disabled.
103
        orig_proxy = lazy_import.ScopeReplacer._should_proxy
104
        def restore():
105
            lazy_import.ScopeReplacer._should_proxy = orig_proxy
106
        lazy_import.ScopeReplacer._should_proxy = False
107
1996.1.1 by John Arbash Meinel
Adding a ScopeReplacer class, which can replace itself on demand
108
    def test_object(self):
1996.1.27 by John Arbash Meinel
Add a test for side-effects from using ScopeReplacer
109
        """ScopeReplacer can create an instance in local scope.
110
        
111
        An object should appear in globals() by constructing a ScopeReplacer,
112
        and it will be replaced with the real object upon the first request.
113
        """
1996.1.1 by John Arbash Meinel
Adding a ScopeReplacer class, which can replace itself on demand
114
        actions = []
115
        InstrumentedReplacer.use_actions(actions)
116
        TestClass.use_actions(actions)
117
118
        def factory(replacer, scope, name):
119
            actions.append('factory')
120
            return TestClass()
121
122
        try:
123
            test_obj1
124
        except NameError:
125
            # test_obj1 shouldn't exist yet
126
            pass
127
        else:
128
            self.fail('test_obj1 was not supposed to exist yet')
129
1996.1.27 by John Arbash Meinel
Add a test for side-effects from using ScopeReplacer
130
        orig_globals = set(globals().keys())
131
1996.1.1 by John Arbash Meinel
Adding a ScopeReplacer class, which can replace itself on demand
132
        InstrumentedReplacer(scope=globals(), name='test_obj1',
133
                             factory=factory)
134
1996.1.27 by John Arbash Meinel
Add a test for side-effects from using ScopeReplacer
135
        new_globals = set(globals().keys())
136
1996.1.1 by John Arbash Meinel
Adding a ScopeReplacer class, which can replace itself on demand
137
        # We can't use isinstance() because that uses test_obj1.__class__
138
        # and that goes through __getattribute__ which would activate
139
        # the replacement
140
        self.assertEqual(InstrumentedReplacer,
141
                         object.__getattribute__(test_obj1, '__class__'))
142
        self.assertEqual('foo', test_obj1.foo(1))
143
        self.assertIsInstance(test_obj1, TestClass)
144
        self.assertEqual('foo', test_obj1.foo(2))
145
        self.assertEqual([('__getattribute__', 'foo'),
146
                          '_replace',
147
                          'factory',
148
                          'init',
149
                          ('foo', 1),
150
                          ('foo', 2),
151
                         ], actions)
152
1996.1.27 by John Arbash Meinel
Add a test for side-effects from using ScopeReplacer
153
    def test_replace_side_effects(self):
154
        """Creating a new object should only create one entry in globals.
155
156
        And only that entry even after replacement.
157
        """
158
        try:
159
            test_scope1
160
        except NameError:
161
            # test_scope1 shouldn't exist yet
162
            pass
163
        else:
164
            self.fail('test_scope1 was not supposed to exist yet')
165
166
        # ignore the logged actions
167
        TestClass.use_actions([])
168
169
        def factory(replacer, scope, name):
170
            return TestClass()
171
172
        orig_globals = set(globals().keys())
173
174
        lazy_import.ScopeReplacer(scope=globals(), name='test_scope1',
175
                                  factory=factory)
176
177
        new_globals = set(globals().keys())
178
179
        self.assertEqual(lazy_import.ScopeReplacer,
180
                         object.__getattribute__(test_scope1, '__class__'))
181
        self.assertEqual('foo', test_scope1.foo(1))
182
        self.assertIsInstance(test_scope1, TestClass)
183
184
        final_globals = set(globals().keys())
185
186
        self.assertEqual(set(['test_scope1']), new_globals - orig_globals)
187
        self.assertEqual(set(), orig_globals - new_globals)
188
        self.assertEqual(set(), final_globals - new_globals)
189
        self.assertEqual(set(), new_globals - final_globals)
190
1996.1.1 by John Arbash Meinel
Adding a ScopeReplacer class, which can replace itself on demand
191
    def test_class(self):
192
        actions = []
193
        InstrumentedReplacer.use_actions(actions)
194
        TestClass.use_actions(actions)
195
196
        def factory(replacer, scope, name):
197
            actions.append('factory')
198
            return TestClass
199
200
        try:
201
            test_class1
202
        except NameError:
203
            # test_class2 shouldn't exist yet
204
            pass
205
        else:
206
            self.fail('test_class1 was not supposed to exist yet')
207
208
        InstrumentedReplacer(scope=globals(), name='test_class1',
209
                             factory=factory)
210
211
        self.assertEqual('class_member', test_class1.class_member)
212
        self.assertEqual(test_class1, TestClass)
213
        self.assertEqual([('__getattribute__', 'class_member'),
214
                          '_replace',
215
                          'factory',
216
                         ], actions)
217
218
    def test_call_class(self):
219
        actions = []
220
        InstrumentedReplacer.use_actions(actions)
221
        TestClass.use_actions(actions)
222
223
        def factory(replacer, scope, name):
224
            actions.append('factory')
225
            return TestClass
226
227
        try:
228
            test_class2
229
        except NameError:
230
            # test_class2 shouldn't exist yet
231
            pass
232
        else:
233
            self.fail('test_class2 was not supposed to exist yet')
234
235
        InstrumentedReplacer(scope=globals(), name='test_class2',
236
                             factory=factory)
237
238
        self.failIf(test_class2 is TestClass)
239
        obj = test_class2()
240
        self.assertIs(test_class2, TestClass)
241
        self.assertIsInstance(obj, TestClass)
242
        self.assertEqual('class_member', obj.class_member)
243
        self.assertEqual([('__call__', (), {}),
244
                          '_replace',
245
                          'factory',
246
                          'init',
247
                         ], actions)
248
249
    def test_call_func(self):
250
        actions = []
251
        InstrumentedReplacer.use_actions(actions)
252
253
        def func(a, b, c=None):
254
            actions.append('func')
255
            return (a, b, c)
256
257
        def factory(replacer, scope, name):
258
            actions.append('factory')
259
            return func
260
261
        try:
262
            test_func1
263
        except NameError:
264
            # test_func1 shouldn't exist yet
265
            pass
266
        else:
267
            self.fail('test_func1 was not supposed to exist yet')
268
        InstrumentedReplacer(scope=globals(), name='test_func1',
269
                             factory=factory)
270
271
        self.failIf(test_func1 is func)
272
        val = test_func1(1, 2, c='3')
273
        self.assertIs(test_func1, func)
274
275
        self.assertEqual((1,2,'3'), val)
276
        self.assertEqual([('__call__', (1,2), {'c':'3'}),
277
                          '_replace',
278
                          'factory',
279
                          'func',
280
                         ], actions)
281
1996.1.16 by John Arbash Meinel
Raise an exception when ScopeReplacer has been misused
282
    def test_other_variable(self):
283
        """Test when a ScopeReplacer is assigned to another variable.
284
285
        This test could be updated if we find a way to trap '=' rather
286
        than just giving a belated exception.
287
        ScopeReplacer only knows about the variable it was created as,
288
        so until the object is replaced, it is illegal to pass it to
289
        another variable. (Though discovering this may take a while)
290
        """
291
        actions = []
292
        InstrumentedReplacer.use_actions(actions)
293
        TestClass.use_actions(actions)
294
295
        def factory(replacer, scope, name):
296
            actions.append('factory')
297
            return TestClass()
298
299
        try:
300
            test_obj2
301
        except NameError:
302
            # test_obj2 shouldn't exist yet
303
            pass
304
        else:
305
            self.fail('test_obj2 was not supposed to exist yet')
306
307
        InstrumentedReplacer(scope=globals(), name='test_obj2',
308
                             factory=factory)
309
310
        self.assertEqual(InstrumentedReplacer,
311
                         object.__getattribute__(test_obj2, '__class__'))
312
        # This is technically not allowed, but we don't have a way to
313
        # test it until later.
314
        test_obj3 = test_obj2
315
        self.assertEqual(InstrumentedReplacer,
316
                         object.__getattribute__(test_obj2, '__class__'))
317
        self.assertEqual(InstrumentedReplacer,
318
                         object.__getattribute__(test_obj3, '__class__'))
319
        
320
        # The first use of the alternate variable causes test_obj2 to
321
        # be replaced.
322
        self.assertEqual('foo', test_obj3.foo(1))
323
        # test_obj2 has been replaced, but the ScopeReplacer has no
324
        # idea of test_obj3
325
        self.assertEqual(TestClass,
326
                         object.__getattribute__(test_obj2, '__class__'))
327
        self.assertEqual(InstrumentedReplacer,
328
                         object.__getattribute__(test_obj3, '__class__'))
329
        # We should be able to access test_obj2 attributes normally
330
        self.assertEqual('foo', test_obj2.foo(2))
331
        self.assertEqual('foo', test_obj2.foo(3))
332
333
        # However, the next access on test_obj3 should raise an error
334
        # because only now are we able to detect the problem.
335
        self.assertRaises(errors.IllegalUseOfScopeReplacer,
336
                          getattr, test_obj3, 'foo')
337
        
2399.1.11 by John Arbash Meinel
Update lazy_import with tests for the new '_should_proxy' variable.
338
        self.assertEqual([('__getattribute__', 'foo'),
339
                          '_replace',
340
                          'factory',
341
                          'init',
342
                          ('foo', 1),
343
                          ('foo', 2),
344
                          ('foo', 3),
345
                          ('__getattribute__', 'foo'),
346
                          '_replace',
347
                         ], actions)
348
349
    def test_enable_proxying(self):
350
        """Test that we can allow ScopeReplacer to proxy."""
351
        actions = []
352
        InstrumentedReplacer.use_actions(actions)
353
        TestClass.use_actions(actions)
354
355
        def factory(replacer, scope, name):
356
            actions.append('factory')
357
            return TestClass()
358
359
        try:
360
            test_obj4
361
        except NameError:
362
            # test_obj4 shouldn't exist yet
363
            pass
364
        else:
365
            self.fail('test_obj4 was not supposed to exist yet')
366
367
        lazy_import.ScopeReplacer._should_proxy = True
368
        InstrumentedReplacer(scope=globals(), name='test_obj4',
369
                             factory=factory)
370
371
        self.assertEqual(InstrumentedReplacer,
372
                         object.__getattribute__(test_obj4, '__class__'))
373
        test_obj5 = test_obj4
374
        self.assertEqual(InstrumentedReplacer,
375
                         object.__getattribute__(test_obj4, '__class__'))
376
        self.assertEqual(InstrumentedReplacer,
377
                         object.__getattribute__(test_obj5, '__class__'))
378
379
        # The first use of the alternate variable causes test_obj2 to
380
        # be replaced.
381
        self.assertEqual('foo', test_obj4.foo(1))
382
        self.assertEqual(TestClass,
383
                         object.__getattribute__(test_obj4, '__class__'))
384
        self.assertEqual(InstrumentedReplacer,
385
                         object.__getattribute__(test_obj5, '__class__'))
386
        # We should be able to access test_obj4 attributes normally
387
        self.assertEqual('foo', test_obj4.foo(2))
388
        # because we enabled proxying, test_obj5 can access its members as well
389
        self.assertEqual('foo', test_obj5.foo(3))
390
        self.assertEqual('foo', test_obj5.foo(4))
391
392
        # However, it cannot be replaced by the ScopeReplacer
393
        self.assertEqual(InstrumentedReplacer,
394
                         object.__getattribute__(test_obj5, '__class__'))
395
396
        self.assertEqual([('__getattribute__', 'foo'),
397
                          '_replace',
398
                          'factory',
399
                          'init',
400
                          ('foo', 1),
401
                          ('foo', 2),
402
                          ('__getattribute__', 'foo'),
403
                          ('foo', 3),
404
                          ('__getattribute__', 'foo'),
405
                          ('foo', 4),
1996.1.16 by John Arbash Meinel
Raise an exception when ScopeReplacer has been misused
406
                         ], actions)
407
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
408
1996.1.8 by John Arbash Meinel
Split up TestImportReplacer into a helper class
409
class ImportReplacerHelper(TestCaseInTempDir):
1996.1.1 by John Arbash Meinel
Adding a ScopeReplacer class, which can replace itself on demand
410
    """Test the ability to have a lazily imported module or object"""
411
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
412
    def setUp(self):
413
        TestCaseInTempDir.setUp(self)
414
        self.create_modules()
415
        base_path = self.test_dir + '/base'
416
1996.1.3 by John Arbash Meinel
Basic single-level imports work
417
        self.actions = []
418
        InstrumentedImportReplacer.use_actions(self.actions)
419
420
        original_import = __import__
421
        def instrumented_import(mod, scope1, scope2, fromlist):
422
            self.actions.append(('import', mod, fromlist))
423
            return original_import(mod, scope1, scope2, fromlist)
424
425
        def cleanup():
426
            if base_path in sys.path:
427
                sys.path.remove(base_path)
428
            __builtins__['__import__'] = original_import
429
        self.addCleanup(cleanup)
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
430
        sys.path.append(base_path)
1996.1.3 by John Arbash Meinel
Basic single-level imports work
431
        __builtins__['__import__'] = instrumented_import
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
432
433
    def create_modules(self):
434
        """Create some random modules to be imported.
435
436
        Each entry has a random suffix, and the full names are saved
437
438
        These are setup as follows:
439
         base/ <= used to ensure not in default search path
440
            root-XXX/
441
                __init__.py <= This will contain var1, func1
442
                mod-XXX.py <= This will contain var2, func2
443
                sub-XXX/
444
                    __init__.py <= Contains var3, func3
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
445
                    submoda-XXX.py <= contains var4, func4
446
                    submodb-XXX.py <= containse var5, func5
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
447
        """
448
        rand_suffix = osutils.rand_chars(4)
449
        root_name = 'root_' + rand_suffix
450
        mod_name = 'mod_' + rand_suffix
451
        sub_name = 'sub_' + rand_suffix
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
452
        submoda_name = 'submoda_' + rand_suffix
453
        submodb_name = 'submodb_' + rand_suffix
454
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
455
        os.mkdir('base')
456
        root_path = osutils.pathjoin('base', root_name)
457
        os.mkdir(root_path)
458
        root_init = osutils.pathjoin(root_path, '__init__.py')
459
        f = open(osutils.pathjoin(root_path, '__init__.py'), 'wb')
460
        try:
461
            f.write('var1 = 1\ndef func1(a):\n  return a\n')
462
        finally:
463
            f.close()
464
        mod_path = osutils.pathjoin(root_path, mod_name + '.py')
465
        f = open(mod_path, 'wb')
466
        try:
467
            f.write('var2 = 2\ndef func2(a):\n  return a\n')
468
        finally:
469
            f.close()
470
471
        sub_path = osutils.pathjoin(root_path, sub_name)
472
        os.mkdir(sub_path)
473
        f = open(osutils.pathjoin(sub_path, '__init__.py'), 'wb')
474
        try:
475
            f.write('var3 = 3\ndef func3(a):\n  return a\n')
476
        finally:
477
            f.close()
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
478
        submoda_path = osutils.pathjoin(sub_path, submoda_name + '.py')
479
        f = open(submoda_path, 'wb')
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
480
        try:
481
            f.write('var4 = 4\ndef func4(a):\n  return a\n')
482
        finally:
483
            f.close()
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
484
        submodb_path = osutils.pathjoin(sub_path, submodb_name + '.py')
485
        f = open(submodb_path, 'wb')
486
        try:
487
            f.write('var5 = 5\ndef func5(a):\n  return a\n')
488
        finally:
489
            f.close()
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
490
        self.root_name = root_name
491
        self.mod_name = mod_name
492
        self.sub_name = sub_name
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
493
        self.submoda_name = submoda_name
494
        self.submodb_name = submodb_name
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
495
1996.1.8 by John Arbash Meinel
Split up TestImportReplacer into a helper class
496
497
class TestImportReplacerHelper(ImportReplacerHelper):
498
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
499
    def test_basic_import(self):
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
500
        """Test that a real import of these modules works"""
1996.1.3 by John Arbash Meinel
Basic single-level imports work
501
        sub_mod_path = '.'.join([self.root_name, self.sub_name,
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
502
                                  self.submoda_name])
1996.1.3 by John Arbash Meinel
Basic single-level imports work
503
        root = __import__(sub_mod_path, globals(), locals(), [])
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
504
        self.assertEqual(1, root.var1)
505
        self.assertEqual(3, getattr(root, self.sub_name).var3)
506
        self.assertEqual(4, getattr(getattr(root, self.sub_name),
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
507
                                    self.submoda_name).var4)
1996.1.3 by John Arbash Meinel
Basic single-level imports work
508
509
        mod_path = '.'.join([self.root_name, self.mod_name])
510
        root = __import__(mod_path, globals(), locals(), [])
511
        self.assertEqual(2, getattr(root, self.mod_name).var2)
512
513
        self.assertEqual([('import', sub_mod_path, []),
514
                          ('import', mod_path, []),
515
                         ], self.actions)
516
1996.1.8 by John Arbash Meinel
Split up TestImportReplacer into a helper class
517
518
class TestImportReplacer(ImportReplacerHelper):
519
1996.1.3 by John Arbash Meinel
Basic single-level imports work
520
    def test_import_root(self):
1996.1.4 by John Arbash Meinel
Change how parameters are passed to support 'import root1.mod1 as mod1'
521
        """Test 'import root-XXX as root1'"""
1996.1.3 by John Arbash Meinel
Basic single-level imports work
522
        try:
523
            root1
524
        except NameError:
525
            # root1 shouldn't exist yet
526
            pass
527
        else:
528
            self.fail('root1 was not supposed to exist yet')
529
530
        # This should replicate 'import root-xxyyzz as root1'
531
        InstrumentedImportReplacer(scope=globals(), name='root1',
1996.1.4 by John Arbash Meinel
Change how parameters are passed to support 'import root1.mod1 as mod1'
532
                                   module_path=[self.root_name],
1996.1.15 by John Arbash Meinel
Everything is now hooked up
533
                                   member=None, children={})
1996.1.3 by John Arbash Meinel
Basic single-level imports work
534
535
        self.assertEqual(InstrumentedImportReplacer,
536
                         object.__getattribute__(root1, '__class__'))
537
        self.assertEqual(1, root1.var1)
538
        self.assertEqual('x', root1.func1('x'))
539
540
        self.assertEqual([('__getattribute__', 'var1'),
541
                          '_replace',
542
                          ('_import', 'root1'),
543
                          ('import', self.root_name, []),
544
                         ], self.actions)
1996.1.4 by John Arbash Meinel
Change how parameters are passed to support 'import root1.mod1 as mod1'
545
546
    def test_import_mod(self):
547
        """Test 'import root-XXX.mod-XXX as mod2'"""
548
        try:
549
            mod1
550
        except NameError:
551
            # mod1 shouldn't exist yet
552
            pass
553
        else:
554
            self.fail('mod1 was not supposed to exist yet')
555
556
        mod_path = self.root_name + '.' + self.mod_name
557
        InstrumentedImportReplacer(scope=globals(), name='mod1',
558
                                   module_path=[self.root_name, self.mod_name],
1996.1.15 by John Arbash Meinel
Everything is now hooked up
559
                                   member=None, children={})
1996.1.4 by John Arbash Meinel
Change how parameters are passed to support 'import root1.mod1 as mod1'
560
561
        self.assertEqual(InstrumentedImportReplacer,
562
                         object.__getattribute__(mod1, '__class__'))
563
        self.assertEqual(2, mod1.var2)
564
        self.assertEqual('y', mod1.func2('y'))
565
566
        self.assertEqual([('__getattribute__', 'var2'),
567
                          '_replace',
568
                          ('_import', 'mod1'),
569
                          ('import', mod_path, []),
570
                         ], self.actions)
571
572
    def test_import_mod_from_root(self):
573
        """Test 'from root-XXX import mod-XXX as mod2'"""
574
        try:
575
            mod2
576
        except NameError:
577
            # mod2 shouldn't exist yet
578
            pass
579
        else:
580
            self.fail('mod2 was not supposed to exist yet')
581
582
        InstrumentedImportReplacer(scope=globals(), name='mod2',
583
                                   module_path=[self.root_name],
1996.1.15 by John Arbash Meinel
Everything is now hooked up
584
                                   member=self.mod_name, children={})
1996.1.4 by John Arbash Meinel
Change how parameters are passed to support 'import root1.mod1 as mod1'
585
586
        self.assertEqual(InstrumentedImportReplacer,
587
                         object.__getattribute__(mod2, '__class__'))
588
        self.assertEqual(2, mod2.var2)
589
        self.assertEqual('y', mod2.func2('y'))
590
591
        self.assertEqual([('__getattribute__', 'var2'),
592
                          '_replace',
593
                          ('_import', 'mod2'),
594
                          ('import', self.root_name, [self.mod_name]),
595
                         ], self.actions)
1996.1.5 by John Arbash Meinel
Test that we can lazy import a module, and its children
596
597
    def test_import_root_and_mod(self):
598
        """Test 'import root-XXX.mod-XXX' remapping both to root3.mod3"""
599
        try:
600
            root3
601
        except NameError:
602
            # root3 shouldn't exist yet
603
            pass
604
        else:
605
            self.fail('root3 was not supposed to exist yet')
606
607
        InstrumentedImportReplacer(scope=globals(),
608
            name='root3', module_path=[self.root_name], member=None,
1996.1.15 by John Arbash Meinel
Everything is now hooked up
609
            children={'mod3':([self.root_name, self.mod_name], None, {})})
1996.1.5 by John Arbash Meinel
Test that we can lazy import a module, and its children
610
611
        # So 'root3' should be a lazy import
612
        # and once it is imported, mod3 should also be lazy until
613
        # actually accessed.
614
        self.assertEqual(InstrumentedImportReplacer,
615
                         object.__getattribute__(root3, '__class__'))
616
        self.assertEqual(1, root3.var1)
617
618
        # There is a mod3 member, but it is also lazy
619
        self.assertEqual(InstrumentedImportReplacer,
620
                         object.__getattribute__(root3.mod3, '__class__'))
621
        self.assertEqual(2, root3.mod3.var2)
622
623
        mod_path = self.root_name + '.' + self.mod_name
624
        self.assertEqual([('__getattribute__', 'var1'),
625
                          '_replace',
626
                          ('_import', 'root3'),
627
                          ('import', self.root_name, []),
628
                          ('__getattribute__', 'var2'),
629
                          '_replace',
630
                          ('_import', 'mod3'),
631
                          ('import', mod_path, []),
632
                         ], self.actions)
1996.1.6 by John Arbash Meinel
Test that we can add more children to the existing lazy object
633
634
    def test_import_root_and_root_mod(self):
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
635
        """Test that 'import root, root.mod' can be done.
636
637
        The second import should re-use the first one, and just add
638
        children to be imported.
639
        """
1996.1.6 by John Arbash Meinel
Test that we can add more children to the existing lazy object
640
        try:
641
            root4
642
        except NameError:
643
            # root4 shouldn't exist yet
644
            pass
645
        else:
646
            self.fail('root4 was not supposed to exist yet')
647
648
        InstrumentedImportReplacer(scope=globals(),
649
            name='root4', module_path=[self.root_name], member=None,
1996.1.15 by John Arbash Meinel
Everything is now hooked up
650
            children={})
1996.1.6 by John Arbash Meinel
Test that we can add more children to the existing lazy object
651
652
        # So 'root4' should be a lazy import
653
        self.assertEqual(InstrumentedImportReplacer,
654
                         object.__getattribute__(root4, '__class__'))
655
656
        # Lets add a new child to be imported on demand
657
        # This syntax of using object.__getattribute__ is the correct method
658
        # for accessing the _import_replacer_children member
659
        children = object.__getattribute__(root4, '_import_replacer_children')
1996.1.15 by John Arbash Meinel
Everything is now hooked up
660
        children['mod4'] = ([self.root_name, self.mod_name], None, {})
1996.1.6 by John Arbash Meinel
Test that we can add more children to the existing lazy object
661
662
        # Accessing root4.mod4 should import root, but mod should stay lazy
663
        self.assertEqual(InstrumentedImportReplacer,
664
                         object.__getattribute__(root4.mod4, '__class__'))
665
        self.assertEqual(2, root4.mod4.var2)
666
667
        mod_path = self.root_name + '.' + self.mod_name
668
        self.assertEqual([('__getattribute__', 'mod4'),
669
                          '_replace',
670
                          ('_import', 'root4'),
671
                          ('import', self.root_name, []),
672
                          ('__getattribute__', 'var2'),
673
                          '_replace',
674
                          ('_import', 'mod4'),
675
                          ('import', mod_path, []),
676
                         ], self.actions)
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
677
678
    def test_import_root_sub_submod(self):
679
        """Test import root.mod, root.sub.submoda, root.sub.submodb
680
        root should be a lazy import, with multiple children, who also
681
        have children to be imported.
682
        And when root is imported, the children should be lazy, and
683
        reuse the intermediate lazy object.
684
        """
685
        try:
686
            root5
687
        except NameError:
1996.1.15 by John Arbash Meinel
Everything is now hooked up
688
            # root5 shouldn't exist yet
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
689
            pass
690
        else:
691
            self.fail('root5 was not supposed to exist yet')
692
693
        InstrumentedImportReplacer(scope=globals(),
694
            name='root5', module_path=[self.root_name], member=None,
1996.1.15 by John Arbash Meinel
Everything is now hooked up
695
            children={'mod5':([self.root_name, self.mod_name], None, {}),
696
                      'sub5':([self.root_name, self.sub_name], None,
697
                            {'submoda5':([self.root_name, self.sub_name,
698
                                         self.submoda_name], None, {}),
699
                             'submodb5':([self.root_name, self.sub_name,
700
                                          self.submodb_name], None, {})
701
                            }),
702
                     })
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
703
704
        # So 'root5' should be a lazy import
705
        self.assertEqual(InstrumentedImportReplacer,
706
                         object.__getattribute__(root5, '__class__'))
707
708
        # Accessing root5.mod5 should import root, but mod should stay lazy
709
        self.assertEqual(InstrumentedImportReplacer,
710
                         object.__getattribute__(root5.mod5, '__class__'))
711
        # root5.sub5 should still be lazy, but not re-import root5
712
        self.assertEqual(InstrumentedImportReplacer,
713
                         object.__getattribute__(root5.sub5, '__class__'))
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
714
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
715
        # Accessing root5.sub5.submoda5 should import sub5, but not either
716
        # of the sub objects (they should be available as lazy objects
717
        self.assertEqual(InstrumentedImportReplacer,
718
                     object.__getattribute__(root5.sub5.submoda5, '__class__'))
719
        self.assertEqual(InstrumentedImportReplacer,
720
                     object.__getattribute__(root5.sub5.submodb5, '__class__'))
721
722
        # This should import mod5
723
        self.assertEqual(2, root5.mod5.var2)
724
        # These should import submoda5 and submodb5
725
        self.assertEqual(4, root5.sub5.submoda5.var4)
726
        self.assertEqual(5, root5.sub5.submodb5.var5)
727
728
        mod_path = self.root_name + '.' + self.mod_name
729
        sub_path = self.root_name + '.' + self.sub_name
730
        submoda_path = sub_path + '.' + self.submoda_name
731
        submodb_path = sub_path + '.' + self.submodb_name
732
733
        self.assertEqual([('__getattribute__', 'mod5'),
734
                          '_replace',
735
                          ('_import', 'root5'),
736
                          ('import', self.root_name, []),
737
                          ('__getattribute__', 'submoda5'),
738
                          '_replace',
739
                          ('_import', 'sub5'),
740
                          ('import', sub_path, []),
741
                          ('__getattribute__', 'var2'),
742
                          '_replace',
743
                          ('_import', 'mod5'),
744
                          ('import', mod_path, []),
745
                          ('__getattribute__', 'var4'),
746
                          '_replace',
747
                          ('_import', 'submoda5'),
748
                          ('import', submoda_path, []),
749
                          ('__getattribute__', 'var5'),
750
                          '_replace',
751
                          ('_import', 'submodb5'),
752
                          ('import', submodb_path, []),
753
                         ], self.actions)
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
754
755
1996.1.10 by John Arbash Meinel
Handle 'from foo import bar' syntax
756
class TestConvertImportToMap(TestCase):
757
    """Directly test the conversion from import strings to maps"""
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
758
1996.1.13 by John Arbash Meinel
small test updates
759
    def check(self, expected, import_strings):
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
760
        proc = lazy_import.ImportProcessor()
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
761
        for import_str in import_strings:
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
762
            proc._convert_import_str(import_str)
763
        self.assertEqual(expected, proc.imports,
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
764
                         'Import of %r was not converted correctly'
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
765
                         ' %s != %s' % (import_strings, expected,
766
                                        proc.imports))
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
767
768
    def test_import_one(self):
1996.1.13 by John Arbash Meinel
small test updates
769
        self.check({'one':(['one'], None, {}),
770
                   }, ['import one'])
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
771
772
    def test_import_one_two(self):
773
        one_two_map = {'one':(['one'], None,
774
                              {'two':(['one', 'two'], None, {}),
775
                              }),
776
                      }
1996.1.13 by John Arbash Meinel
small test updates
777
        self.check(one_two_map, ['import one.two'])
778
        self.check(one_two_map, ['import one, one.two'])
779
        self.check(one_two_map, ['import one', 'import one.two'])
780
        self.check(one_two_map, ['import one.two', 'import one'])
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
781
782
    def test_import_one_two_three(self):
783
        one_two_three_map = {
784
            'one':(['one'], None,
785
                   {'two':(['one', 'two'], None,
786
                           {'three':(['one', 'two', 'three'], None, {}),
787
                           }),
788
                   }),
789
        }
1996.1.13 by John Arbash Meinel
small test updates
790
        self.check(one_two_three_map, ['import one.two.three'])
791
        self.check(one_two_three_map, ['import one, one.two.three'])
792
        self.check(one_two_three_map, ['import one',
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
793
                                              'import one.two.three'])
1996.1.13 by John Arbash Meinel
small test updates
794
        self.check(one_two_three_map, ['import one.two.three',
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
795
                                              'import one'])
796
797
    def test_import_one_as_x(self):
1996.1.13 by John Arbash Meinel
small test updates
798
        self.check({'x':(['one'], None, {}),
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
799
                          }, ['import one as x'])
800
801
    def test_import_one_two_as_x(self):
1996.1.13 by John Arbash Meinel
small test updates
802
        self.check({'x':(['one', 'two'], None, {}),
803
                   }, ['import one.two as x'])
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
804
805
    def test_import_mixed(self):
806
        mixed = {'x':(['one', 'two'], None, {}),
807
                 'one':(['one'], None,
808
                       {'two':(['one', 'two'], None, {}),
809
                       }),
810
                }
1996.1.13 by John Arbash Meinel
small test updates
811
        self.check(mixed, ['import one.two as x, one.two'])
812
        self.check(mixed, ['import one.two as x', 'import one.two'])
813
        self.check(mixed, ['import one.two', 'import one.two as x'])
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
814
815
    def test_import_with_as(self):
1996.1.13 by John Arbash Meinel
small test updates
816
        self.check({'fast':(['fast'], None, {})}, ['import fast'])
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
817
1996.1.10 by John Arbash Meinel
Handle 'from foo import bar' syntax
818
819
class TestFromToMap(TestCase):
820
    """Directly test the conversion of 'from foo import bar' syntax"""
821
822
    def check_result(self, expected, from_strings):
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
823
        proc = lazy_import.ImportProcessor()
1996.1.10 by John Arbash Meinel
Handle 'from foo import bar' syntax
824
        for from_str in from_strings:
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
825
            proc._convert_from_str(from_str)
826
        self.assertEqual(expected, proc.imports,
1996.1.10 by John Arbash Meinel
Handle 'from foo import bar' syntax
827
                         'Import of %r was not converted correctly'
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
828
                         ' %s != %s' % (from_strings, expected, proc.imports))
1996.1.10 by John Arbash Meinel
Handle 'from foo import bar' syntax
829
830
    def test_from_one_import_two(self):
831
        self.check_result({'two':(['one'], 'two', {})},
832
                          ['from one import two'])
833
834
    def test_from_one_import_two_as_three(self):
835
        self.check_result({'three':(['one'], 'two', {})},
836
                          ['from one import two as three'])
837
838
    def test_from_one_import_two_three(self):
839
        two_three_map = {'two':(['one'], 'two', {}),
840
                         'three':(['one'], 'three', {}),
841
                        }
842
        self.check_result(two_three_map,
843
                          ['from one import two, three'])
844
        self.check_result(two_three_map,
845
                          ['from one import two',
846
                           'from one import three'])
847
848
    def test_from_one_two_import_three(self):
849
        self.check_result({'three':(['one', 'two'], 'three', {})},
850
                          ['from one.two import three'])
851
1996.1.11 by John Arbash Meinel
Test the ability to take a bunch of import lines and canonicalize them
852
853
class TestCanonicalize(TestCase):
854
    """Test that we can canonicalize import texts"""
855
856
    def check(self, expected, text):
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
857
        proc = lazy_import.ImportProcessor()
1996.1.14 by John Arbash Meinel
Add tests for converting from a string to the final map
858
        parsed = proc._canonicalize_import_text(text)
1996.1.11 by John Arbash Meinel
Test the ability to take a bunch of import lines and canonicalize them
859
        self.assertEqual(expected, parsed,
860
                         'Incorrect parsing of text:\n%s\n%s\n!=\n%s'
861
                         % (text, expected, parsed))
862
863
    def test_import_one(self):
864
        self.check(['import one'], 'import one')
865
        self.check(['import one'], '\nimport one\n\n')
866
867
    def test_import_one_two(self):
868
        self.check(['import one, two'], 'import one, two')
869
        self.check(['import one, two'], '\nimport one, two\n\n')
870
871
    def test_import_one_as_two_as(self):
872
        self.check(['import one as x, two as y'], 'import one as x, two as y')
873
        self.check(['import one as x, two as y'],
874
                   '\nimport one as x, two as y\n')
875
876
    def test_from_one_import_two(self):
877
        self.check(['from one import two'], 'from one import two')
878
        self.check(['from one import two'], '\nfrom one import two\n\n')
879
        self.check(['from one import two'], '\nfrom one import (two)\n')
880
        self.check(['from one import  two '], '\nfrom one import (\n\ttwo\n)\n')
1996.1.13 by John Arbash Meinel
small test updates
881
882
    def test_multiple(self):
883
        self.check(['import one', 'import two, three', 'from one import four'],
884
                   'import one\nimport two, three\nfrom one import four')
885
        self.check(['import one', 'import two, three', 'from one import four'],
886
                   'import one\nimport (two, three)\nfrom one import four')
1996.1.15 by John Arbash Meinel
Everything is now hooked up
887
        self.check(['import one', 'import two, three', 'from one import four'],
1996.1.13 by John Arbash Meinel
small test updates
888
                   'import one\n'
1996.1.15 by John Arbash Meinel
Everything is now hooked up
889
                   'import two, three\n'
1996.1.13 by John Arbash Meinel
small test updates
890
                   'from one import four')
1996.1.15 by John Arbash Meinel
Everything is now hooked up
891
        self.check(['import one',
892
                    'import two, three', 'from one import  four, '],
893
                   'import one\n'
894
                   'import two, three\n'
1996.1.13 by John Arbash Meinel
small test updates
895
                   'from one import (\n'
896
                   '    four,\n'
897
                   '    )\n'
898
                   )
1996.1.14 by John Arbash Meinel
Add tests for converting from a string to the final map
899
1996.1.18 by John Arbash Meinel
Add more structured error handling
900
    def test_missing_trailing(self):
901
        proc = lazy_import.ImportProcessor()
902
        self.assertRaises(errors.InvalidImportLine,
903
                          proc._canonicalize_import_text,
904
                          "from foo import (\n  bar\n")
905
1996.1.14 by John Arbash Meinel
Add tests for converting from a string to the final map
906
907
class TestImportProcessor(TestCase):
908
    """Test that ImportProcessor can turn import texts into lazy imports"""
909
910
    def check(self, expected, text):
911
        proc = lazy_import.ImportProcessor()
912
        proc._build_map(text)
913
        self.assertEqual(expected, proc.imports,
914
                         'Incorrect processing of:\n%s\n%s\n!=\n%s'
915
                         % (text, expected, proc.imports))
916
917
    def test_import_one(self):
918
        exp = {'one':(['one'], None, {})}
919
        self.check(exp, 'import one')
920
        self.check(exp, '\nimport one\n')
921
922
    def test_import_one_two(self):
923
        exp = {'one':(['one'], None,
924
                      {'two':(['one', 'two'], None, {}),
925
                      }),
926
              }
927
        self.check(exp, 'import one.two')
928
        self.check(exp, 'import one, one.two')
929
        self.check(exp, 'import one\nimport one.two')
930
931
    def test_import_as(self):
932
        exp = {'two':(['one'], None, {})}
933
        self.check(exp, 'import one as two')
934
935
    def test_import_many(self):
936
        exp = {'one':(['one'], None,
937
                      {'two':(['one', 'two'], None,
938
                              {'three':(['one', 'two', 'three'], None, {}),
939
                              }),
940
                       'four':(['one', 'four'], None, {}),
941
                      }),
942
               'five':(['one', 'five'], None, {}),
943
              }
944
        self.check(exp, 'import one.two.three, one.four, one.five as five')
945
        self.check(exp, 'import one.five as five\n'
946
                        'import one\n'
947
                        'import one.two.three\n'
948
                        'import one.four\n')
949
950
    def test_from_one_import_two(self):
951
        exp = {'two':(['one'], 'two', {})}
952
        self.check(exp, 'from one import two\n')
953
        self.check(exp, 'from one import (\n'
954
                        '    two,\n'
955
                        '    )\n')
956
957
    def test_from_one_import_two(self):
958
        exp = {'two':(['one'], 'two', {})}
959
        self.check(exp, 'from one import two\n')
960
        self.check(exp, 'from one import (two)\n')
961
        self.check(exp, 'from one import (two,)\n')
962
        self.check(exp, 'from one import two as two\n')
963
        self.check(exp, 'from one import (\n'
964
                        '    two,\n'
965
                        '    )\n')
966
967
    def test_from_many(self):
968
        exp = {'two':(['one'], 'two', {}),
969
               'three':(['one', 'two'], 'three', {}),
970
               'five':(['one', 'two'], 'four', {}),
971
              }
972
        self.check(exp, 'from one import two\n'
973
                        'from one.two import three, four as five\n')
974
        self.check(exp, 'from one import two\n'
975
                        'from one.two import (\n'
976
                        '    three,\n'
977
                        '    four as five,\n'
978
                        '    )\n')
979
980
    def test_mixed(self):
981
        exp = {'two':(['one'], 'two', {}),
982
               'three':(['one', 'two'], 'three', {}),
983
               'five':(['one', 'two'], 'four', {}),
984
               'one':(['one'], None,
985
                      {'two':(['one', 'two'], None, {}),
986
                      }),
987
              }
988
        self.check(exp, 'from one import two\n'
989
                        'from one.two import three, four as five\n'
990
                        'import one.two')
991
        self.check(exp, 'from one import two\n'
992
                        'from one.two import (\n'
993
                        '    three,\n'
994
                        '    four as five,\n'
995
                        '    )\n'
996
                        'import one\n'
997
                        'import one.two\n')
1996.1.15 by John Arbash Meinel
Everything is now hooked up
998
1996.1.18 by John Arbash Meinel
Add more structured error handling
999
    def test_incorrect_line(self):
1000
        proc = lazy_import.ImportProcessor()
1001
        self.assertRaises(errors.InvalidImportLine,
1002
                          proc._build_map, 'foo bar baz')
1003
        self.assertRaises(errors.InvalidImportLine,
1004
                          proc._build_map, 'improt foo')
1005
        self.assertRaises(errors.InvalidImportLine,
1006
                          proc._build_map, 'importfoo')
1007
        self.assertRaises(errors.InvalidImportLine,
1008
                          proc._build_map, 'fromimport')
1009
1010
    def test_name_collision(self):
1011
        proc = lazy_import.ImportProcessor()
1012
        proc._build_map('import foo')
1013
1014
        # All of these would try to create an object with the
1015
        # same name as an existing object.
1016
        self.assertRaises(errors.ImportNameCollision,
1017
                          proc._build_map, 'import bar as foo')
1018
        self.assertRaises(errors.ImportNameCollision,
1019
                          proc._build_map, 'from foo import bar as foo')
1020
        self.assertRaises(errors.ImportNameCollision,
1021
                          proc._build_map, 'from bar import foo')
1022
1996.1.15 by John Arbash Meinel
Everything is now hooked up
1023
1024
class TestLazyImportProcessor(ImportReplacerHelper):
1025
1026
    def test_root(self):
1027
        try:
1028
            root6
1029
        except NameError:
1030
            pass # root6 should not be defined yet
1031
        else:
1032
            self.fail('root6 was not supposed to exist yet')
1033
1034
        text = 'import %s as root6' % (self.root_name,)
1035
        proc = lazy_import.ImportProcessor(InstrumentedImportReplacer)
1996.1.19 by John Arbash Meinel
Write a simple wrapper function to make lazy imports easy.
1036
        proc.lazy_import(scope=globals(), text=text)
1996.1.15 by John Arbash Meinel
Everything is now hooked up
1037
1038
        # So 'root6' should be a lazy import
1039
        self.assertEqual(InstrumentedImportReplacer,
1040
                         object.__getattribute__(root6, '__class__'))
1041
1042
        self.assertEqual(1, root6.var1)
1043
        self.assertEqual('x', root6.func1('x'))
1044
1045
        self.assertEqual([('__getattribute__', 'var1'),
1046
                          '_replace',
1047
                          ('_import', 'root6'),
1048
                          ('import', self.root_name, []),
1049
                         ], self.actions)
1050
1051
    def test_import_deep(self):
1052
        """Test import root.mod, root.sub.submoda, root.sub.submodb
1053
        root should be a lazy import, with multiple children, who also
1054
        have children to be imported.
1055
        And when root is imported, the children should be lazy, and
1056
        reuse the intermediate lazy object.
1057
        """
1058
        try:
1059
            submoda7
1060
        except NameError:
1061
            pass # submoda7 should not be defined yet
1062
        else:
1063
            self.fail('submoda7 was not supposed to exist yet')
1064
1065
        text = """\
1066
import %(root_name)s.%(sub_name)s.%(submoda_name)s as submoda7
1067
""" % self.__dict__
1068
        proc = lazy_import.ImportProcessor(InstrumentedImportReplacer)
1996.1.19 by John Arbash Meinel
Write a simple wrapper function to make lazy imports easy.
1069
        proc.lazy_import(scope=globals(), text=text)
1996.1.15 by John Arbash Meinel
Everything is now hooked up
1070
1071
        # So 'submoda7' should be a lazy import
1072
        self.assertEqual(InstrumentedImportReplacer,
1073
                         object.__getattribute__(submoda7, '__class__'))
1074
1075
        # This should import submoda7
1076
        self.assertEqual(4, submoda7.var4)
1077
1078
        sub_path = self.root_name + '.' + self.sub_name
1079
        submoda_path = sub_path + '.' + self.submoda_name
1080
1081
        self.assertEqual([('__getattribute__', 'var4'),
1082
                          '_replace',
1083
                          ('_import', 'submoda7'),
1084
                          ('import', submoda_path, []),
1085
                         ], self.actions)
1996.1.19 by John Arbash Meinel
Write a simple wrapper function to make lazy imports easy.
1086
1087
    def test_lazy_import(self):
1088
        """Smoke test that lazy_import() does the right thing"""
1089
        try:
1090
            root8
1091
        except NameError:
1092
            pass # root8 should not be defined yet
1093
        else:
1094
            self.fail('root8 was not supposed to exist yet')
1095
        lazy_import.lazy_import(globals(),
1096
                                'import %s as root8' % (self.root_name,),
1097
                                lazy_import_class=InstrumentedImportReplacer)
1098
1099
        self.assertEqual(InstrumentedImportReplacer,
1100
                         object.__getattribute__(root8, '__class__'))
1101
1102
        self.assertEqual(1, root8.var1)
1103
        self.assertEqual(1, root8.var1)
1104
        self.assertEqual(1, root8.func1(1))
1105
1106
        self.assertEqual([('__getattribute__', 'var1'),
1107
                          '_replace',
1108
                          ('_import', 'root8'),
1109
                          ('import', self.root_name, []),
1110
                         ], self.actions)