~bzr-pqm/bzr/bzr.dev

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