~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_lazy_import.py

  • Committer: Alexander Belchenko
  • Date: 2006-07-30 16:43:12 UTC
  • mto: (1711.2.111 jam-integration)
  • mto: This revision was merged to the branch mainline in revision 1906.
  • Revision ID: bialix@ukr.net-20060730164312-b025fd3ff0cee59e
rename  gpl.txt => COPYING.txt

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 Canonical Ltd
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
"""Test the lazy_import functionality."""
18
 
 
19
 
import os
20
 
import sys
21
 
 
22
 
from bzrlib import (
23
 
    errors,
24
 
    lazy_import,
25
 
    osutils,
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
 
 
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
 
 
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 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
 
 
108
 
    def test_object(self):
109
 
        """ScopeReplacer can create an instance in local scope.
110
 
 
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
 
        """
114
 
        actions = []
115
 
        InstrumentedReplacer.use_actions(actions)
116
 
        TestClass.use_actions(actions)
117
 
 
118
 
        def factory(replacer, scope, name):
119
 
            actions.append('factory')
120
 
            return TestClass()
121
 
 
122
 
        try:
123
 
            test_obj1
124
 
        except NameError:
125
 
            # test_obj1 shouldn't exist yet
126
 
            pass
127
 
        else:
128
 
            self.fail('test_obj1 was not supposed to exist yet')
129
 
 
130
 
        orig_globals = set(globals().keys())
131
 
 
132
 
        InstrumentedReplacer(scope=globals(), name='test_obj1',
133
 
                             factory=factory)
134
 
 
135
 
        new_globals = set(globals().keys())
136
 
 
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
 
 
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
 
        """
159
 
        actions = []
160
 
        TestClass.use_actions(actions)
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__'))
186
 
        self.assertEqual('test', test_obj6.bar)
187
 
 
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
 
 
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
 
 
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__'))
354
 
 
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')
372
 
 
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),
441
 
                         ], actions)
442
 
 
443
 
 
444
 
class ImportReplacerHelper(TestCaseInTempDir):
445
 
    """Test the ability to have a lazily imported module or object"""
446
 
 
447
 
    def setUp(self):
448
 
        TestCaseInTempDir.setUp(self)
449
 
        self.create_modules()
450
 
        base_path = self.test_dir + '/base'
451
 
 
452
 
        self.actions = []
453
 
        InstrumentedImportReplacer.use_actions(self.actions)
454
 
 
455
 
        sys.path.append(base_path)
456
 
        self.addCleanup(sys.path.remove, base_path)
457
 
 
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
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
479
 
                    submoda-XXX.py <= contains var4, func4
480
 
                    submodb-XXX.py <= containse var5, func5
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
486
 
        submoda_name = 'submoda_' + rand_suffix
487
 
        submodb_name = 'submodb_' + rand_suffix
488
 
 
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()
512
 
        submoda_path = osutils.pathjoin(sub_path, submoda_name + '.py')
513
 
        f = open(submoda_path, 'wb')
514
 
        try:
515
 
            f.write('var4 = 4\ndef func4(a):\n  return a\n')
516
 
        finally:
517
 
            f.close()
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()
524
 
        self.root_name = root_name
525
 
        self.mod_name = mod_name
526
 
        self.sub_name = sub_name
527
 
        self.submoda_name = submoda_name
528
 
        self.submodb_name = submodb_name
529
 
 
530
 
 
531
 
class TestImportReplacerHelper(ImportReplacerHelper):
532
 
 
533
 
    def test_basic_import(self):
534
 
        """Test that a real import of these modules works"""
535
 
        sub_mod_path = '.'.join([self.root_name, self.sub_name,
536
 
                                  self.submoda_name])
537
 
        root = __import__(sub_mod_path, globals(), locals(), [])
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),
541
 
                                    self.submoda_name).var4)
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
 
 
551
 
 
552
 
class TestImportReplacer(ImportReplacerHelper):
553
 
 
554
 
    def test_import_root(self):
555
 
        """Test 'import root-XXX as root1'"""
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',
566
 
                                   module_path=[self.root_name],
567
 
                                   member=None, children={})
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)
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],
593
 
                                   member=None, children={})
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],
618
 
                                   member=self.mod_name, children={})
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)
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,
643
 
            children={'mod3':([self.root_name, self.mod_name], None, {})})
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)
667
 
 
668
 
    def test_import_root_and_root_mod(self):
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
 
        """
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,
684
 
            children={})
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')
694
 
        children['mod4'] = ([self.root_name, self.mod_name], None, {})
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)
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:
722
 
            # root5 shouldn't exist yet
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,
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
 
                     })
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__'))
748
 
 
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)
788
 
 
789
 
 
790
 
class TestConvertImportToMap(TestCase):
791
 
    """Directly test the conversion from import strings to maps"""
792
 
 
793
 
    def check(self, expected, import_strings):
794
 
        proc = lazy_import.ImportProcessor()
795
 
        for import_str in import_strings:
796
 
            proc._convert_import_str(import_str)
797
 
        self.assertEqual(expected, proc.imports,
798
 
                         'Import of %r was not converted correctly'
799
 
                         ' %s != %s' % (import_strings, expected,
800
 
                                        proc.imports))
801
 
 
802
 
    def test_import_one(self):
803
 
        self.check({'one':(['one'], None, {}),
804
 
                   }, ['import one'])
805
 
 
806
 
    def test_import_one_two(self):
807
 
        one_two_map = {'one':(['one'], None,
808
 
                              {'two':(['one', 'two'], None, {}),
809
 
                              }),
810
 
                      }
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'])
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
 
        }
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',
827
 
                                              'import one.two.three'])
828
 
        self.check(one_two_three_map, ['import one.two.three',
829
 
                                              'import one'])
830
 
 
831
 
    def test_import_one_as_x(self):
832
 
        self.check({'x':(['one'], None, {}),
833
 
                          }, ['import one as x'])
834
 
 
835
 
    def test_import_one_two_as_x(self):
836
 
        self.check({'x':(['one', 'two'], None, {}),
837
 
                   }, ['import one.two as x'])
838
 
 
839
 
    def test_import_mixed(self):
840
 
        mixed = {'x':(['one', 'two'], None, {}),
841
 
                 'one':(['one'], None,
842
 
                       {'two':(['one', 'two'], None, {}),
843
 
                       }),
844
 
                }
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'])
848
 
 
849
 
    def test_import_with_as(self):
850
 
        self.check({'fast':(['fast'], None, {})}, ['import fast'])
851
 
 
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):
857
 
        proc = lazy_import.ImportProcessor()
858
 
        for from_str in from_strings:
859
 
            proc._convert_from_str(from_str)
860
 
        self.assertEqual(expected, proc.imports,
861
 
                         'Import of %r was not converted correctly'
862
 
                         ' %s != %s' % (from_strings, expected, proc.imports))
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
 
 
886
 
 
887
 
class TestCanonicalize(TestCase):
888
 
    """Test that we can canonicalize import texts"""
889
 
 
890
 
    def check(self, expected, text):
891
 
        proc = lazy_import.ImportProcessor()
892
 
        parsed = proc._canonicalize_import_text(text)
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')
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')
921
 
        self.check(['import one', 'import two, three', 'from one import four'],
922
 
                   'import one\n'
923
 
                   'import two, three\n'
924
 
                   'from one import four')
925
 
        self.check(['import one',
926
 
                    'import two, three', 'from one import  four, '],
927
 
                   'import one\n'
928
 
                   'import two, three\n'
929
 
                   'from one import (\n'
930
 
                   '    four,\n'
931
 
                   '    )\n'
932
 
                   )
933
 
 
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
 
 
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')
1032
 
 
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
 
 
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)
1070
 
        proc.lazy_import(scope=globals(), text=text)
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)
1103
 
        proc.lazy_import(scope=globals(), text=text)
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)
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)