~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
4985.2.1 by Vincent Ladeuil
Deploy addAttrCleanup on the whole test suite.
455
        sys.path.append(base_path)
456
        self.addCleanup(sys.path.remove, base_path)
457
1996.1.3 by John Arbash Meinel
Basic single-level imports work
458
        original_import = __import__
459
        def instrumented_import(mod, scope1, scope2, fromlist):
460
            self.actions.append(('import', mod, fromlist))
461
            return original_import(mod, scope1, scope2, fromlist)
462
        def cleanup():
463
            __builtins__['__import__'] = original_import
464
        self.addCleanup(cleanup)
465
        __builtins__['__import__'] = instrumented_import
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
466
467
    def create_modules(self):
468
        """Create some random modules to be imported.
469
470
        Each entry has a random suffix, and the full names are saved
471
472
        These are setup as follows:
473
         base/ <= used to ensure not in default search path
474
            root-XXX/
475
                __init__.py <= This will contain var1, func1
476
                mod-XXX.py <= This will contain var2, func2
477
                sub-XXX/
478
                    __init__.py <= Contains var3, func3
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
479
                    submoda-XXX.py <= contains var4, func4
480
                    submodb-XXX.py <= containse var5, func5
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
481
        """
482
        rand_suffix = osutils.rand_chars(4)
483
        root_name = 'root_' + rand_suffix
484
        mod_name = 'mod_' + rand_suffix
485
        sub_name = 'sub_' + rand_suffix
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
486
        submoda_name = 'submoda_' + rand_suffix
487
        submodb_name = 'submodb_' + rand_suffix
488
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
489
        os.mkdir('base')
490
        root_path = osutils.pathjoin('base', root_name)
491
        os.mkdir(root_path)
492
        root_init = osutils.pathjoin(root_path, '__init__.py')
493
        f = open(osutils.pathjoin(root_path, '__init__.py'), 'wb')
494
        try:
495
            f.write('var1 = 1\ndef func1(a):\n  return a\n')
496
        finally:
497
            f.close()
498
        mod_path = osutils.pathjoin(root_path, mod_name + '.py')
499
        f = open(mod_path, 'wb')
500
        try:
501
            f.write('var2 = 2\ndef func2(a):\n  return a\n')
502
        finally:
503
            f.close()
504
505
        sub_path = osutils.pathjoin(root_path, sub_name)
506
        os.mkdir(sub_path)
507
        f = open(osutils.pathjoin(sub_path, '__init__.py'), 'wb')
508
        try:
509
            f.write('var3 = 3\ndef func3(a):\n  return a\n')
510
        finally:
511
            f.close()
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
512
        submoda_path = osutils.pathjoin(sub_path, submoda_name + '.py')
513
        f = open(submoda_path, 'wb')
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
514
        try:
515
            f.write('var4 = 4\ndef func4(a):\n  return a\n')
516
        finally:
517
            f.close()
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
518
        submodb_path = osutils.pathjoin(sub_path, submodb_name + '.py')
519
        f = open(submodb_path, 'wb')
520
        try:
521
            f.write('var5 = 5\ndef func5(a):\n  return a\n')
522
        finally:
523
            f.close()
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
524
        self.root_name = root_name
525
        self.mod_name = mod_name
526
        self.sub_name = sub_name
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
527
        self.submoda_name = submoda_name
528
        self.submodb_name = submodb_name
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
529
1996.1.8 by John Arbash Meinel
Split up TestImportReplacer into a helper class
530
531
class TestImportReplacerHelper(ImportReplacerHelper):
532
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
533
    def test_basic_import(self):
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
534
        """Test that a real import of these modules works"""
1996.1.3 by John Arbash Meinel
Basic single-level imports work
535
        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
536
                                  self.submoda_name])
1996.1.3 by John Arbash Meinel
Basic single-level imports work
537
        root = __import__(sub_mod_path, globals(), locals(), [])
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
538
        self.assertEqual(1, root.var1)
539
        self.assertEqual(3, getattr(root, self.sub_name).var3)
540
        self.assertEqual(4, getattr(getattr(root, self.sub_name),
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
541
                                    self.submoda_name).var4)
1996.1.3 by John Arbash Meinel
Basic single-level imports work
542
543
        mod_path = '.'.join([self.root_name, self.mod_name])
544
        root = __import__(mod_path, globals(), locals(), [])
545
        self.assertEqual(2, getattr(root, self.mod_name).var2)
546
547
        self.assertEqual([('import', sub_mod_path, []),
548
                          ('import', mod_path, []),
549
                         ], self.actions)
550
1996.1.8 by John Arbash Meinel
Split up TestImportReplacer into a helper class
551
552
class TestImportReplacer(ImportReplacerHelper):
553
1996.1.3 by John Arbash Meinel
Basic single-level imports work
554
    def test_import_root(self):
1996.1.4 by John Arbash Meinel
Change how parameters are passed to support 'import root1.mod1 as mod1'
555
        """Test 'import root-XXX as root1'"""
1996.1.3 by John Arbash Meinel
Basic single-level imports work
556
        try:
557
            root1
558
        except NameError:
559
            # root1 shouldn't exist yet
560
            pass
561
        else:
562
            self.fail('root1 was not supposed to exist yet')
563
564
        # This should replicate 'import root-xxyyzz as root1'
565
        InstrumentedImportReplacer(scope=globals(), name='root1',
1996.1.4 by John Arbash Meinel
Change how parameters are passed to support 'import root1.mod1 as mod1'
566
                                   module_path=[self.root_name],
1996.1.15 by John Arbash Meinel
Everything is now hooked up
567
                                   member=None, children={})
1996.1.3 by John Arbash Meinel
Basic single-level imports work
568
569
        self.assertEqual(InstrumentedImportReplacer,
570
                         object.__getattribute__(root1, '__class__'))
571
        self.assertEqual(1, root1.var1)
572
        self.assertEqual('x', root1.func1('x'))
573
574
        self.assertEqual([('__getattribute__', 'var1'),
575
                          '_replace',
576
                          ('_import', 'root1'),
577
                          ('import', self.root_name, []),
578
                         ], self.actions)
1996.1.4 by John Arbash Meinel
Change how parameters are passed to support 'import root1.mod1 as mod1'
579
580
    def test_import_mod(self):
581
        """Test 'import root-XXX.mod-XXX as mod2'"""
582
        try:
583
            mod1
584
        except NameError:
585
            # mod1 shouldn't exist yet
586
            pass
587
        else:
588
            self.fail('mod1 was not supposed to exist yet')
589
590
        mod_path = self.root_name + '.' + self.mod_name
591
        InstrumentedImportReplacer(scope=globals(), name='mod1',
592
                                   module_path=[self.root_name, self.mod_name],
1996.1.15 by John Arbash Meinel
Everything is now hooked up
593
                                   member=None, children={})
1996.1.4 by John Arbash Meinel
Change how parameters are passed to support 'import root1.mod1 as mod1'
594
595
        self.assertEqual(InstrumentedImportReplacer,
596
                         object.__getattribute__(mod1, '__class__'))
597
        self.assertEqual(2, mod1.var2)
598
        self.assertEqual('y', mod1.func2('y'))
599
600
        self.assertEqual([('__getattribute__', 'var2'),
601
                          '_replace',
602
                          ('_import', 'mod1'),
603
                          ('import', mod_path, []),
604
                         ], self.actions)
605
606
    def test_import_mod_from_root(self):
607
        """Test 'from root-XXX import mod-XXX as mod2'"""
608
        try:
609
            mod2
610
        except NameError:
611
            # mod2 shouldn't exist yet
612
            pass
613
        else:
614
            self.fail('mod2 was not supposed to exist yet')
615
616
        InstrumentedImportReplacer(scope=globals(), name='mod2',
617
                                   module_path=[self.root_name],
1996.1.15 by John Arbash Meinel
Everything is now hooked up
618
                                   member=self.mod_name, 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__(mod2, '__class__'))
622
        self.assertEqual(2, mod2.var2)
623
        self.assertEqual('y', mod2.func2('y'))
624
625
        self.assertEqual([('__getattribute__', 'var2'),
626
                          '_replace',
627
                          ('_import', 'mod2'),
628
                          ('import', self.root_name, [self.mod_name]),
629
                         ], self.actions)
1996.1.5 by John Arbash Meinel
Test that we can lazy import a module, and its children
630
631
    def test_import_root_and_mod(self):
632
        """Test 'import root-XXX.mod-XXX' remapping both to root3.mod3"""
633
        try:
634
            root3
635
        except NameError:
636
            # root3 shouldn't exist yet
637
            pass
638
        else:
639
            self.fail('root3 was not supposed to exist yet')
640
641
        InstrumentedImportReplacer(scope=globals(),
642
            name='root3', module_path=[self.root_name], member=None,
1996.1.15 by John Arbash Meinel
Everything is now hooked up
643
            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
644
645
        # So 'root3' should be a lazy import
646
        # and once it is imported, mod3 should also be lazy until
647
        # actually accessed.
648
        self.assertEqual(InstrumentedImportReplacer,
649
                         object.__getattribute__(root3, '__class__'))
650
        self.assertEqual(1, root3.var1)
651
652
        # There is a mod3 member, but it is also lazy
653
        self.assertEqual(InstrumentedImportReplacer,
654
                         object.__getattribute__(root3.mod3, '__class__'))
655
        self.assertEqual(2, root3.mod3.var2)
656
657
        mod_path = self.root_name + '.' + self.mod_name
658
        self.assertEqual([('__getattribute__', 'var1'),
659
                          '_replace',
660
                          ('_import', 'root3'),
661
                          ('import', self.root_name, []),
662
                          ('__getattribute__', 'var2'),
663
                          '_replace',
664
                          ('_import', 'mod3'),
665
                          ('import', mod_path, []),
666
                         ], self.actions)
1996.1.6 by John Arbash Meinel
Test that we can add more children to the existing lazy object
667
668
    def test_import_root_and_root_mod(self):
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
669
        """Test that 'import root, root.mod' can be done.
670
671
        The second import should re-use the first one, and just add
672
        children to be imported.
673
        """
1996.1.6 by John Arbash Meinel
Test that we can add more children to the existing lazy object
674
        try:
675
            root4
676
        except NameError:
677
            # root4 shouldn't exist yet
678
            pass
679
        else:
680
            self.fail('root4 was not supposed to exist yet')
681
682
        InstrumentedImportReplacer(scope=globals(),
683
            name='root4', module_path=[self.root_name], member=None,
1996.1.15 by John Arbash Meinel
Everything is now hooked up
684
            children={})
1996.1.6 by John Arbash Meinel
Test that we can add more children to the existing lazy object
685
686
        # So 'root4' should be a lazy import
687
        self.assertEqual(InstrumentedImportReplacer,
688
                         object.__getattribute__(root4, '__class__'))
689
690
        # Lets add a new child to be imported on demand
691
        # This syntax of using object.__getattribute__ is the correct method
692
        # for accessing the _import_replacer_children member
693
        children = object.__getattribute__(root4, '_import_replacer_children')
1996.1.15 by John Arbash Meinel
Everything is now hooked up
694
        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
695
696
        # Accessing root4.mod4 should import root, but mod should stay lazy
697
        self.assertEqual(InstrumentedImportReplacer,
698
                         object.__getattribute__(root4.mod4, '__class__'))
699
        self.assertEqual(2, root4.mod4.var2)
700
701
        mod_path = self.root_name + '.' + self.mod_name
702
        self.assertEqual([('__getattribute__', 'mod4'),
703
                          '_replace',
704
                          ('_import', 'root4'),
705
                          ('import', self.root_name, []),
706
                          ('__getattribute__', 'var2'),
707
                          '_replace',
708
                          ('_import', 'mod4'),
709
                          ('import', mod_path, []),
710
                         ], self.actions)
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
711
712
    def test_import_root_sub_submod(self):
713
        """Test import root.mod, root.sub.submoda, root.sub.submodb
714
        root should be a lazy import, with multiple children, who also
715
        have children to be imported.
716
        And when root is imported, the children should be lazy, and
717
        reuse the intermediate lazy object.
718
        """
719
        try:
720
            root5
721
        except NameError:
1996.1.15 by John Arbash Meinel
Everything is now hooked up
722
            # root5 shouldn't exist yet
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
723
            pass
724
        else:
725
            self.fail('root5 was not supposed to exist yet')
726
727
        InstrumentedImportReplacer(scope=globals(),
728
            name='root5', module_path=[self.root_name], member=None,
1996.1.15 by John Arbash Meinel
Everything is now hooked up
729
            children={'mod5':([self.root_name, self.mod_name], None, {}),
730
                      'sub5':([self.root_name, self.sub_name], None,
731
                            {'submoda5':([self.root_name, self.sub_name,
732
                                         self.submoda_name], None, {}),
733
                             'submodb5':([self.root_name, self.sub_name,
734
                                          self.submodb_name], None, {})
735
                            }),
736
                     })
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
737
738
        # So 'root5' should be a lazy import
739
        self.assertEqual(InstrumentedImportReplacer,
740
                         object.__getattribute__(root5, '__class__'))
741
742
        # Accessing root5.mod5 should import root, but mod should stay lazy
743
        self.assertEqual(InstrumentedImportReplacer,
744
                         object.__getattribute__(root5.mod5, '__class__'))
745
        # root5.sub5 should still be lazy, but not re-import root5
746
        self.assertEqual(InstrumentedImportReplacer,
747
                         object.__getattribute__(root5.sub5, '__class__'))
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
748
1996.1.7 by John Arbash Meinel
Test a nested import with multiple deep children
749
        # Accessing root5.sub5.submoda5 should import sub5, but not either
750
        # of the sub objects (they should be available as lazy objects
751
        self.assertEqual(InstrumentedImportReplacer,
752
                     object.__getattribute__(root5.sub5.submoda5, '__class__'))
753
        self.assertEqual(InstrumentedImportReplacer,
754
                     object.__getattribute__(root5.sub5.submodb5, '__class__'))
755
756
        # This should import mod5
757
        self.assertEqual(2, root5.mod5.var2)
758
        # These should import submoda5 and submodb5
759
        self.assertEqual(4, root5.sub5.submoda5.var4)
760
        self.assertEqual(5, root5.sub5.submodb5.var5)
761
762
        mod_path = self.root_name + '.' + self.mod_name
763
        sub_path = self.root_name + '.' + self.sub_name
764
        submoda_path = sub_path + '.' + self.submoda_name
765
        submodb_path = sub_path + '.' + self.submodb_name
766
767
        self.assertEqual([('__getattribute__', 'mod5'),
768
                          '_replace',
769
                          ('_import', 'root5'),
770
                          ('import', self.root_name, []),
771
                          ('__getattribute__', 'submoda5'),
772
                          '_replace',
773
                          ('_import', 'sub5'),
774
                          ('import', sub_path, []),
775
                          ('__getattribute__', 'var2'),
776
                          '_replace',
777
                          ('_import', 'mod5'),
778
                          ('import', mod_path, []),
779
                          ('__getattribute__', 'var4'),
780
                          '_replace',
781
                          ('_import', 'submoda5'),
782
                          ('import', submoda_path, []),
783
                          ('__getattribute__', 'var5'),
784
                          '_replace',
785
                          ('_import', 'submodb5'),
786
                          ('import', submodb_path, []),
787
                         ], self.actions)
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
788
789
1996.1.10 by John Arbash Meinel
Handle 'from foo import bar' syntax
790
class TestConvertImportToMap(TestCase):
791
    """Directly test the conversion from import strings to maps"""
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
792
1996.1.13 by John Arbash Meinel
small test updates
793
    def check(self, expected, import_strings):
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
794
        proc = lazy_import.ImportProcessor()
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
795
        for import_str in import_strings:
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
796
            proc._convert_import_str(import_str)
797
        self.assertEqual(expected, proc.imports,
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
798
                         'Import of %r was not converted correctly'
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
799
                         ' %s != %s' % (import_strings, expected,
800
                                        proc.imports))
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
801
802
    def test_import_one(self):
1996.1.13 by John Arbash Meinel
small test updates
803
        self.check({'one':(['one'], None, {}),
804
                   }, ['import one'])
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
805
806
    def test_import_one_two(self):
807
        one_two_map = {'one':(['one'], None,
808
                              {'two':(['one', 'two'], None, {}),
809
                              }),
810
                      }
1996.1.13 by John Arbash Meinel
small test updates
811
        self.check(one_two_map, ['import one.two'])
812
        self.check(one_two_map, ['import one, one.two'])
813
        self.check(one_two_map, ['import one', 'import one.two'])
814
        self.check(one_two_map, ['import one.two', 'import one'])
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
815
816
    def test_import_one_two_three(self):
817
        one_two_three_map = {
818
            'one':(['one'], None,
819
                   {'two':(['one', 'two'], None,
820
                           {'three':(['one', 'two', 'three'], None, {}),
821
                           }),
822
                   }),
823
        }
1996.1.13 by John Arbash Meinel
small test updates
824
        self.check(one_two_three_map, ['import one.two.three'])
825
        self.check(one_two_three_map, ['import one, one.two.three'])
826
        self.check(one_two_three_map, ['import one',
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
827
                                              'import one.two.three'])
1996.1.13 by John Arbash Meinel
small test updates
828
        self.check(one_two_three_map, ['import one.two.three',
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
829
                                              'import one'])
830
831
    def test_import_one_as_x(self):
1996.1.13 by John Arbash Meinel
small test updates
832
        self.check({'x':(['one'], None, {}),
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
833
                          }, ['import one as x'])
834
835
    def test_import_one_two_as_x(self):
1996.1.13 by John Arbash Meinel
small test updates
836
        self.check({'x':(['one', 'two'], None, {}),
837
                   }, ['import one.two as x'])
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
838
839
    def test_import_mixed(self):
840
        mixed = {'x':(['one', 'two'], None, {}),
841
                 'one':(['one'], None,
842
                       {'two':(['one', 'two'], None, {}),
843
                       }),
844
                }
1996.1.13 by John Arbash Meinel
small test updates
845
        self.check(mixed, ['import one.two as x, one.two'])
846
        self.check(mixed, ['import one.two as x', 'import one.two'])
847
        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.
848
849
    def test_import_with_as(self):
1996.1.13 by John Arbash Meinel
small test updates
850
        self.check({'fast':(['fast'], None, {})}, ['import fast'])
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
851
1996.1.10 by John Arbash Meinel
Handle 'from foo import bar' syntax
852
853
class TestFromToMap(TestCase):
854
    """Directly test the conversion of 'from foo import bar' syntax"""
855
856
    def check_result(self, expected, from_strings):
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
857
        proc = lazy_import.ImportProcessor()
1996.1.10 by John Arbash Meinel
Handle 'from foo import bar' syntax
858
        for from_str in from_strings:
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
859
            proc._convert_from_str(from_str)
860
        self.assertEqual(expected, proc.imports,
1996.1.10 by John Arbash Meinel
Handle 'from foo import bar' syntax
861
                         'Import of %r was not converted correctly'
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
862
                         ' %s != %s' % (from_strings, expected, proc.imports))
1996.1.10 by John Arbash Meinel
Handle 'from foo import bar' syntax
863
864
    def test_from_one_import_two(self):
865
        self.check_result({'two':(['one'], 'two', {})},
866
                          ['from one import two'])
867
868
    def test_from_one_import_two_as_three(self):
869
        self.check_result({'three':(['one'], 'two', {})},
870
                          ['from one import two as three'])
871
872
    def test_from_one_import_two_three(self):
873
        two_three_map = {'two':(['one'], 'two', {}),
874
                         'three':(['one'], 'three', {}),
875
                        }
876
        self.check_result(two_three_map,
877
                          ['from one import two, three'])
878
        self.check_result(two_three_map,
879
                          ['from one import two',
880
                           'from one import three'])
881
882
    def test_from_one_two_import_three(self):
883
        self.check_result({'three':(['one', 'two'], 'three', {})},
884
                          ['from one.two import three'])
885
1996.1.11 by John Arbash Meinel
Test the ability to take a bunch of import lines and canonicalize them
886
887
class TestCanonicalize(TestCase):
888
    """Test that we can canonicalize import texts"""
889
890
    def check(self, expected, text):
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
891
        proc = lazy_import.ImportProcessor()
1996.1.14 by John Arbash Meinel
Add tests for converting from a string to the final map
892
        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
893
        self.assertEqual(expected, parsed,
894
                         'Incorrect parsing of text:\n%s\n%s\n!=\n%s'
895
                         % (text, expected, parsed))
896
897
    def test_import_one(self):
898
        self.check(['import one'], 'import one')
899
        self.check(['import one'], '\nimport one\n\n')
900
901
    def test_import_one_two(self):
902
        self.check(['import one, two'], 'import one, two')
903
        self.check(['import one, two'], '\nimport one, two\n\n')
904
905
    def test_import_one_as_two_as(self):
906
        self.check(['import one as x, two as y'], 'import one as x, two as y')
907
        self.check(['import one as x, two as y'],
908
                   '\nimport one as x, two as y\n')
909
910
    def test_from_one_import_two(self):
911
        self.check(['from one import two'], 'from one import two')
912
        self.check(['from one import two'], '\nfrom one import two\n\n')
913
        self.check(['from one import two'], '\nfrom one import (two)\n')
914
        self.check(['from one import  two '], '\nfrom one import (\n\ttwo\n)\n')
1996.1.13 by John Arbash Meinel
small test updates
915
916
    def test_multiple(self):
917
        self.check(['import one', 'import two, three', 'from one import four'],
918
                   'import one\nimport two, three\nfrom one import four')
919
        self.check(['import one', 'import two, three', 'from one import four'],
920
                   'import one\nimport (two, three)\nfrom one import four')
1996.1.15 by John Arbash Meinel
Everything is now hooked up
921
        self.check(['import one', 'import two, three', 'from one import four'],
1996.1.13 by John Arbash Meinel
small test updates
922
                   'import one\n'
1996.1.15 by John Arbash Meinel
Everything is now hooked up
923
                   'import two, three\n'
1996.1.13 by John Arbash Meinel
small test updates
924
                   'from one import four')
1996.1.15 by John Arbash Meinel
Everything is now hooked up
925
        self.check(['import one',
926
                    'import two, three', 'from one import  four, '],
927
                   'import one\n'
928
                   'import two, three\n'
1996.1.13 by John Arbash Meinel
small test updates
929
                   'from one import (\n'
930
                   '    four,\n'
931
                   '    )\n'
932
                   )
1996.1.14 by John Arbash Meinel
Add tests for converting from a string to the final map
933
1996.1.18 by John Arbash Meinel
Add more structured error handling
934
    def test_missing_trailing(self):
935
        proc = lazy_import.ImportProcessor()
936
        self.assertRaises(errors.InvalidImportLine,
937
                          proc._canonicalize_import_text,
938
                          "from foo import (\n  bar\n")
939
1996.1.14 by John Arbash Meinel
Add tests for converting from a string to the final map
940
941
class TestImportProcessor(TestCase):
942
    """Test that ImportProcessor can turn import texts into lazy imports"""
943
944
    def check(self, expected, text):
945
        proc = lazy_import.ImportProcessor()
946
        proc._build_map(text)
947
        self.assertEqual(expected, proc.imports,
948
                         'Incorrect processing of:\n%s\n%s\n!=\n%s'
949
                         % (text, expected, proc.imports))
950
951
    def test_import_one(self):
952
        exp = {'one':(['one'], None, {})}
953
        self.check(exp, 'import one')
954
        self.check(exp, '\nimport one\n')
955
956
    def test_import_one_two(self):
957
        exp = {'one':(['one'], None,
958
                      {'two':(['one', 'two'], None, {}),
959
                      }),
960
              }
961
        self.check(exp, 'import one.two')
962
        self.check(exp, 'import one, one.two')
963
        self.check(exp, 'import one\nimport one.two')
964
965
    def test_import_as(self):
966
        exp = {'two':(['one'], None, {})}
967
        self.check(exp, 'import one as two')
968
969
    def test_import_many(self):
970
        exp = {'one':(['one'], None,
971
                      {'two':(['one', 'two'], None,
972
                              {'three':(['one', 'two', 'three'], None, {}),
973
                              }),
974
                       'four':(['one', 'four'], None, {}),
975
                      }),
976
               'five':(['one', 'five'], None, {}),
977
              }
978
        self.check(exp, 'import one.two.three, one.four, one.five as five')
979
        self.check(exp, 'import one.five as five\n'
980
                        'import one\n'
981
                        'import one.two.three\n'
982
                        'import one.four\n')
983
984
    def test_from_one_import_two(self):
985
        exp = {'two':(['one'], 'two', {})}
986
        self.check(exp, 'from one import two\n')
987
        self.check(exp, 'from one import (\n'
988
                        '    two,\n'
989
                        '    )\n')
990
991
    def test_from_one_import_two(self):
992
        exp = {'two':(['one'], 'two', {})}
993
        self.check(exp, 'from one import two\n')
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 as two\n')
997
        self.check(exp, 'from one import (\n'
998
                        '    two,\n'
999
                        '    )\n')
1000
1001
    def test_from_many(self):
1002
        exp = {'two':(['one'], 'two', {}),
1003
               'three':(['one', 'two'], 'three', {}),
1004
               'five':(['one', 'two'], 'four', {}),
1005
              }
1006
        self.check(exp, 'from one import two\n'
1007
                        'from one.two import three, four as five\n')
1008
        self.check(exp, 'from one import two\n'
1009
                        'from one.two import (\n'
1010
                        '    three,\n'
1011
                        '    four as five,\n'
1012
                        '    )\n')
1013
1014
    def test_mixed(self):
1015
        exp = {'two':(['one'], 'two', {}),
1016
               'three':(['one', 'two'], 'three', {}),
1017
               'five':(['one', 'two'], 'four', {}),
1018
               'one':(['one'], None,
1019
                      {'two':(['one', 'two'], None, {}),
1020
                      }),
1021
              }
1022
        self.check(exp, 'from one import two\n'
1023
                        'from one.two import three, four as five\n'
1024
                        'import one.two')
1025
        self.check(exp, 'from one import two\n'
1026
                        'from one.two import (\n'
1027
                        '    three,\n'
1028
                        '    four as five,\n'
1029
                        '    )\n'
1030
                        'import one\n'
1031
                        'import one.two\n')
1996.1.15 by John Arbash Meinel
Everything is now hooked up
1032
1996.1.18 by John Arbash Meinel
Add more structured error handling
1033
    def test_incorrect_line(self):
1034
        proc = lazy_import.ImportProcessor()
1035
        self.assertRaises(errors.InvalidImportLine,
1036
                          proc._build_map, 'foo bar baz')
1037
        self.assertRaises(errors.InvalidImportLine,
1038
                          proc._build_map, 'improt foo')
1039
        self.assertRaises(errors.InvalidImportLine,
1040
                          proc._build_map, 'importfoo')
1041
        self.assertRaises(errors.InvalidImportLine,
1042
                          proc._build_map, 'fromimport')
1043
1044
    def test_name_collision(self):
1045
        proc = lazy_import.ImportProcessor()
1046
        proc._build_map('import foo')
1047
1048
        # All of these would try to create an object with the
1049
        # same name as an existing object.
1050
        self.assertRaises(errors.ImportNameCollision,
1051
                          proc._build_map, 'import bar as foo')
1052
        self.assertRaises(errors.ImportNameCollision,
1053
                          proc._build_map, 'from foo import bar as foo')
1054
        self.assertRaises(errors.ImportNameCollision,
1055
                          proc._build_map, 'from bar import foo')
1056
1996.1.15 by John Arbash Meinel
Everything is now hooked up
1057
1058
class TestLazyImportProcessor(ImportReplacerHelper):
1059
1060
    def test_root(self):
1061
        try:
1062
            root6
1063
        except NameError:
1064
            pass # root6 should not be defined yet
1065
        else:
1066
            self.fail('root6 was not supposed to exist yet')
1067
1068
        text = 'import %s as root6' % (self.root_name,)
1069
        proc = lazy_import.ImportProcessor(InstrumentedImportReplacer)
1996.1.19 by John Arbash Meinel
Write a simple wrapper function to make lazy imports easy.
1070
        proc.lazy_import(scope=globals(), text=text)
1996.1.15 by John Arbash Meinel
Everything is now hooked up
1071
1072
        # So 'root6' should be a lazy import
1073
        self.assertEqual(InstrumentedImportReplacer,
1074
                         object.__getattribute__(root6, '__class__'))
1075
1076
        self.assertEqual(1, root6.var1)
1077
        self.assertEqual('x', root6.func1('x'))
1078
1079
        self.assertEqual([('__getattribute__', 'var1'),
1080
                          '_replace',
1081
                          ('_import', 'root6'),
1082
                          ('import', self.root_name, []),
1083
                         ], self.actions)
1084
1085
    def test_import_deep(self):
1086
        """Test import root.mod, root.sub.submoda, root.sub.submodb
1087
        root should be a lazy import, with multiple children, who also
1088
        have children to be imported.
1089
        And when root is imported, the children should be lazy, and
1090
        reuse the intermediate lazy object.
1091
        """
1092
        try:
1093
            submoda7
1094
        except NameError:
1095
            pass # submoda7 should not be defined yet
1096
        else:
1097
            self.fail('submoda7 was not supposed to exist yet')
1098
1099
        text = """\
1100
import %(root_name)s.%(sub_name)s.%(submoda_name)s as submoda7
1101
""" % self.__dict__
1102
        proc = lazy_import.ImportProcessor(InstrumentedImportReplacer)
1996.1.19 by John Arbash Meinel
Write a simple wrapper function to make lazy imports easy.
1103
        proc.lazy_import(scope=globals(), text=text)
1996.1.15 by John Arbash Meinel
Everything is now hooked up
1104
1105
        # So 'submoda7' should be a lazy import
1106
        self.assertEqual(InstrumentedImportReplacer,
1107
                         object.__getattribute__(submoda7, '__class__'))
1108
1109
        # This should import submoda7
1110
        self.assertEqual(4, submoda7.var4)
1111
1112
        sub_path = self.root_name + '.' + self.sub_name
1113
        submoda_path = sub_path + '.' + self.submoda_name
1114
1115
        self.assertEqual([('__getattribute__', 'var4'),
1116
                          '_replace',
1117
                          ('_import', 'submoda7'),
1118
                          ('import', submoda_path, []),
1119
                         ], self.actions)
1996.1.19 by John Arbash Meinel
Write a simple wrapper function to make lazy imports easy.
1120
1121
    def test_lazy_import(self):
1122
        """Smoke test that lazy_import() does the right thing"""
1123
        try:
1124
            root8
1125
        except NameError:
1126
            pass # root8 should not be defined yet
1127
        else:
1128
            self.fail('root8 was not supposed to exist yet')
1129
        lazy_import.lazy_import(globals(),
1130
                                'import %s as root8' % (self.root_name,),
1131
                                lazy_import_class=InstrumentedImportReplacer)
1132
1133
        self.assertEqual(InstrumentedImportReplacer,
1134
                         object.__getattribute__(root8, '__class__'))
1135
1136
        self.assertEqual(1, root8.var1)
1137
        self.assertEqual(1, root8.var1)
1138
        self.assertEqual(1, root8.func1(1))
1139
1140
        self.assertEqual([('__getattribute__', 'var1'),
1141
                          '_replace',
1142
                          ('_import', 'root8'),
1143
                          ('import', self.root_name, []),
1144
                         ], self.actions)