~bzr-pqm/bzr/bzr.dev

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