~bzr-pqm/bzr/bzr.dev

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