/brz/remove-bazaar

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