~bzr/ubuntu/lucid/bzr/beta-ppa

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_lazy_import.py

  • Committer: Martin Pool
  • Date: 2010-07-02 07:29:40 UTC
  • mfrom: (129.1.7 packaging-karmic)
  • Revision ID: mbp@sourcefrog.net-20100702072940-hpzq5elg8wjve8rh
* PPA rebuild.
* PPA rebuild for Karmic.
* PPA rebuild for Jaunty.
* PPA rebuild for Hardy.
* From postinst, actually remove the example bash completion scripts.
  (LP: #249452)
* New upstream release.
* New upstream release.
* New upstream release.
* Revert change to Build-depends: Dapper does not have python-central.
  Should be python-support..
* Target ppa..
* Target ppa..
* Target ppa..
* Target ppa..
* New upstream release.
* Switch to dpkg-source 3.0 (quilt) format.
* Bump standards version to 3.8.4.
* Remove embedded copy of python-configobj. Closes: #555336
* Remove embedded copy of python-elementtree. Closes: #555343
* Change section from 'Devel' to 'Vcs'..
* Change section from 'Devel' to 'Vcs'..
* Change section from 'Devel' to 'Vcs'..
* Change section from 'Devel' to 'Vcs'..
* Change section from 'Devel' to 'Vcs'..
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* debian/control: Fix obsolete-relation-form-in-source
  lintian warning. 
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Split out docs into bzr-doc package.
* New upstream release.
* Added John Francesco Ferlito to Uploaders.
* Fix install path to quick-reference guide
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Fix FTBFS due to path changes, again.
* Fix FTBFS due to doc paths changing
* New upstream release.
* Fix FTBFS due to path changes, again.
* Fix FTBFS due to doc paths changing
* New upstream release.
* Fix FTBFS due to path changes, again.
* Fix FTBFS due to doc paths changing
* New upstream release.
* Fix FTBFS due to path changes, again, again.
* Fix FTBFS due to path changes, again.
* Fix FTBFS due to path changes.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Bump standards version to 3.8.3.
* Remove unused patch system.
* New upstream release.
* New upstream release.
* New upstream release.
* Fix copy and paste tab error in .install file
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
 + Fixes compatibility with Python 2.4. Closes: #537708
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream version.
* Bump standards version to 3.8.2.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Add python-pyrex to build-deps to ensure C extensions are always build.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Split documentation into bzr-doc package. ((LP: #385074)
* Multiple packaging changes to make us more linitan clean.
* New upstream release.
* Split documentation into bzr-doc package. ((LP: #385074)
* Multiple packaging changes to make us more linitan clean.
* New upstream release.
* Split documentation into bzr-doc package. ((LP: #385074)
* Multiple packaging changes to make us more linitan clean.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Fix API compatibility version. (Closes: #526233)
* New upstream release.
  + Fixes default format for upgrade command. (Closes: #464688)
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Add missing dependency on zlib development library. (Closes:
  #523595)
* Add zlib build-depends.
* Add zlib build-depends.
* Add zlib build-depends.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Move to section vcs.
* Bump standards version to 3.8.1.
* New upstream release.
* Remove temporary patch for missing .c files from distribution
* New upstream release.
* Remove temporary patch for missing .c files from distribution
* New upstream release.
* Remove temporary patch for missing .c files from distribution
* Add temporary patch for missing .c files from distribution
* Add temporary patch for missing .c files from distribution
* Add temporary patch for missing .c files from distribution
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Recommend ca-certificates. (Closes: #452024)
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Update watch file. bazaar now uses launchpad to host its sources.
* Remove patch for inventory root revision copy, applied upstream.
* New upstream release.
* New upstream release.
* New upstream release
* Force removal of files installed in error to /etc/bash_completion.d/
  (LP: #249452)
* New upstream release.
* New upstream release
* New upstream release.
* Bump standards version.
* Include patch for inventory root revision copy, required for bzr-svn.
* New upstream release.
* Remove unused lintian overrides.
* Correct the package version not to be native.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Final 1.5 release.
* New upstream release.
* New upstream release.
* New upstream release.
* Add myself as a co-maintainer.
* Add a Dm-Upload-Allowed: yes header.
* New upstream bugfix release.
* New upstream release.
* Final 1.3 release.
* New upstream release.
* First release candidate of the upcoming 1.3 release.
* Rebuild to fix the problem caused by a build with a broken python-central.
* New upstream release.
* Rebuild for dapper PPA.
* Apply Lamont's patches to fix build-dependencies on dapper.
  (See: https://bugs.launchpad.net/bzr/+bug/189915)

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