~bzr-pqm/bzr/bzr.dev

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