1
# Copyright (C) 2006 Canonical Ltd
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.
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.
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
17
"""Test the lazy_import functionality."""
27
from bzrlib.tests import TestCase, TestCaseInTempDir
30
class InstrumentedReplacer(lazy_import.ScopeReplacer):
31
"""Track what actions are done"""
34
def use_actions(actions):
35
InstrumentedReplacer.actions = actions
38
InstrumentedReplacer.actions.append('_replace')
39
return lazy_import.ScopeReplacer._replace(self)
41
def __getattribute__(self, attr):
42
InstrumentedReplacer.actions.append(('__getattribute__', attr))
43
return lazy_import.ScopeReplacer.__getattribute__(self, attr)
45
def __call__(self, *args, **kwargs):
46
InstrumentedReplacer.actions.append(('__call__', args, kwargs))
47
return lazy_import.ScopeReplacer.__call__(self, *args, **kwargs)
50
class InstrumentedImportReplacer(lazy_import.ImportReplacer):
53
def use_actions(actions):
54
InstrumentedImportReplacer.actions = actions
56
def _import(self, scope, name):
57
InstrumentedImportReplacer.actions.append(('_import', name))
58
return lazy_import.ImportReplacer._import(self, scope, name)
61
InstrumentedImportReplacer.actions.append('_replace')
62
return lazy_import.ScopeReplacer._replace(self)
64
def __getattribute__(self, attr):
65
InstrumentedImportReplacer.actions.append(('__getattribute__', attr))
66
return lazy_import.ScopeReplacer.__getattribute__(self, attr)
68
def __call__(self, *args, **kwargs):
69
InstrumentedImportReplacer.actions.append(('__call__', args, kwargs))
70
return lazy_import.ScopeReplacer.__call__(self, *args, **kwargs)
73
class TestClass(object):
74
"""Just a simple test class instrumented for the test cases"""
76
class_member = 'class_member'
79
def use_actions(actions):
80
TestClass.actions = actions
83
TestClass.actions.append('init')
86
TestClass.actions.append(('foo', x))
90
class TestScopeReplacer(TestCase):
91
"""Test the ability of the replacer to put itself into the correct scope.
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
101
# These tests assume we will not be proxying, so make sure proxying is
103
orig_proxy = lazy_import.ScopeReplacer._should_proxy
105
lazy_import.ScopeReplacer._should_proxy = orig_proxy
106
lazy_import.ScopeReplacer._should_proxy = False
108
def test_object(self):
109
"""ScopeReplacer can create an instance in local scope.
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.
115
InstrumentedReplacer.use_actions(actions)
116
TestClass.use_actions(actions)
118
def factory(replacer, scope, name):
119
actions.append('factory')
125
# test_obj1 shouldn't exist yet
128
self.fail('test_obj1 was not supposed to exist yet')
130
orig_globals = set(globals().keys())
132
InstrumentedReplacer(scope=globals(), name='test_obj1',
135
new_globals = set(globals().keys())
137
# We can't use isinstance() because that uses test_obj1.__class__
138
# and that goes through __getattribute__ which would activate
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'),
153
def test_setattr_replaces(self):
154
"""ScopeReplacer can create an instance in local scope.
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.
160
TestClass.use_actions(actions)
161
def factory(replacer, scope, name):
166
# test_obj6 shouldn't exist yet
169
self.fail('test_obj6 was not supposed to exist yet')
171
orig_globals = set(globals().keys())
173
lazy_import.ScopeReplacer(scope=globals(), name='test_obj6',
176
new_globals = set(globals().keys())
178
# We can't use isinstance() because that uses test_obj6.__class__
179
# and that goes through __getattribute__ which would activate
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)
188
def test_replace_side_effects(self):
189
"""Creating a new object should only create one entry in globals.
191
And only that entry even after replacement.
196
# test_scope1 shouldn't exist yet
199
self.fail('test_scope1 was not supposed to exist yet')
201
# ignore the logged actions
202
TestClass.use_actions([])
204
def factory(replacer, scope, name):
207
orig_globals = set(globals().keys())
209
lazy_import.ScopeReplacer(scope=globals(), name='test_scope1',
212
new_globals = set(globals().keys())
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)
219
final_globals = set(globals().keys())
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)
226
def test_class(self):
228
InstrumentedReplacer.use_actions(actions)
229
TestClass.use_actions(actions)
231
def factory(replacer, scope, name):
232
actions.append('factory')
238
# test_class2 shouldn't exist yet
241
self.fail('test_class1 was not supposed to exist yet')
243
InstrumentedReplacer(scope=globals(), name='test_class1',
246
self.assertEqual('class_member', test_class1.class_member)
247
self.assertEqual(test_class1, TestClass)
248
self.assertEqual([('__getattribute__', 'class_member'),
253
def test_call_class(self):
255
InstrumentedReplacer.use_actions(actions)
256
TestClass.use_actions(actions)
258
def factory(replacer, scope, name):
259
actions.append('factory')
265
# test_class2 shouldn't exist yet
268
self.fail('test_class2 was not supposed to exist yet')
270
InstrumentedReplacer(scope=globals(), name='test_class2',
273
self.failIf(test_class2 is TestClass)
275
self.assertIs(test_class2, TestClass)
276
self.assertIsInstance(obj, TestClass)
277
self.assertEqual('class_member', obj.class_member)
278
self.assertEqual([('__call__', (), {}),
284
def test_call_func(self):
286
InstrumentedReplacer.use_actions(actions)
288
def func(a, b, c=None):
289
actions.append('func')
292
def factory(replacer, scope, name):
293
actions.append('factory')
299
# test_func1 shouldn't exist yet
302
self.fail('test_func1 was not supposed to exist yet')
303
InstrumentedReplacer(scope=globals(), name='test_func1',
306
self.failIf(test_func1 is func)
307
val = test_func1(1, 2, c='3')
308
self.assertIs(test_func1, func)
310
self.assertEqual((1,2,'3'), val)
311
self.assertEqual([('__call__', (1,2), {'c':'3'}),
317
def test_other_variable(self):
318
"""Test when a ScopeReplacer is assigned to another variable.
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)
327
InstrumentedReplacer.use_actions(actions)
328
TestClass.use_actions(actions)
330
def factory(replacer, scope, name):
331
actions.append('factory')
337
# test_obj2 shouldn't exist yet
340
self.fail('test_obj2 was not supposed to exist yet')
342
InstrumentedReplacer(scope=globals(), name='test_obj2',
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__'))
355
# The first use of the alternate variable causes test_obj2 to
357
self.assertEqual('foo', test_obj3.foo(1))
358
# test_obj2 has been replaced, but the ScopeReplacer has no
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))
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')
373
self.assertEqual([('__getattribute__', 'foo'),
380
('__getattribute__', 'foo'),
384
def test_enable_proxying(self):
385
"""Test that we can allow ScopeReplacer to proxy."""
387
InstrumentedReplacer.use_actions(actions)
388
TestClass.use_actions(actions)
390
def factory(replacer, scope, name):
391
actions.append('factory')
397
# test_obj4 shouldn't exist yet
400
self.fail('test_obj4 was not supposed to exist yet')
402
lazy_import.ScopeReplacer._should_proxy = True
403
InstrumentedReplacer(scope=globals(), name='test_obj4',
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__'))
414
# The first use of the alternate variable causes test_obj2 to
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))
427
# However, it cannot be replaced by the ScopeReplacer
428
self.assertEqual(InstrumentedReplacer,
429
object.__getattribute__(test_obj5, '__class__'))
431
self.assertEqual([('__getattribute__', 'foo'),
437
('__getattribute__', 'foo'),
439
('__getattribute__', 'foo'),
444
class ImportReplacerHelper(TestCaseInTempDir):
445
"""Test the ability to have a lazily imported module or object"""
448
TestCaseInTempDir.setUp(self)
449
self.create_modules()
450
base_path = self.test_dir + '/base'
453
InstrumentedImportReplacer.use_actions(self.actions)
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)
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
468
def create_modules(self):
469
"""Create some random modules to be imported.
471
Each entry has a random suffix, and the full names are saved
473
These are setup as follows:
474
base/ <= used to ensure not in default search path
476
__init__.py <= This will contain var1, func1
477
mod-XXX.py <= This will contain var2, func2
479
__init__.py <= Contains var3, func3
480
submoda-XXX.py <= contains var4, func4
481
submodb-XXX.py <= containse var5, func5
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
491
root_path = osutils.pathjoin('base', root_name)
493
root_init = osutils.pathjoin(root_path, '__init__.py')
494
f = open(osutils.pathjoin(root_path, '__init__.py'), 'wb')
496
f.write('var1 = 1\ndef func1(a):\n return a\n')
499
mod_path = osutils.pathjoin(root_path, mod_name + '.py')
500
f = open(mod_path, 'wb')
502
f.write('var2 = 2\ndef func2(a):\n return a\n')
506
sub_path = osutils.pathjoin(root_path, sub_name)
508
f = open(osutils.pathjoin(sub_path, '__init__.py'), 'wb')
510
f.write('var3 = 3\ndef func3(a):\n return a\n')
513
submoda_path = osutils.pathjoin(sub_path, submoda_name + '.py')
514
f = open(submoda_path, 'wb')
516
f.write('var4 = 4\ndef func4(a):\n return a\n')
519
submodb_path = osutils.pathjoin(sub_path, submodb_name + '.py')
520
f = open(submodb_path, 'wb')
522
f.write('var5 = 5\ndef func5(a):\n return a\n')
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
532
class TestImportReplacerHelper(ImportReplacerHelper):
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,
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)
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)
548
self.assertEqual([('import', sub_mod_path, []),
549
('import', mod_path, []),
553
class TestImportReplacer(ImportReplacerHelper):
555
def test_import_root(self):
556
"""Test 'import root-XXX as root1'"""
560
# root1 shouldn't exist yet
563
self.fail('root1 was not supposed to exist yet')
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={})
570
self.assertEqual(InstrumentedImportReplacer,
571
object.__getattribute__(root1, '__class__'))
572
self.assertEqual(1, root1.var1)
573
self.assertEqual('x', root1.func1('x'))
575
self.assertEqual([('__getattribute__', 'var1'),
577
('_import', 'root1'),
578
('import', self.root_name, []),
581
def test_import_mod(self):
582
"""Test 'import root-XXX.mod-XXX as mod2'"""
586
# mod1 shouldn't exist yet
589
self.fail('mod1 was not supposed to exist yet')
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={})
596
self.assertEqual(InstrumentedImportReplacer,
597
object.__getattribute__(mod1, '__class__'))
598
self.assertEqual(2, mod1.var2)
599
self.assertEqual('y', mod1.func2('y'))
601
self.assertEqual([('__getattribute__', 'var2'),
604
('import', mod_path, []),
607
def test_import_mod_from_root(self):
608
"""Test 'from root-XXX import mod-XXX as mod2'"""
612
# mod2 shouldn't exist yet
615
self.fail('mod2 was not supposed to exist yet')
617
InstrumentedImportReplacer(scope=globals(), name='mod2',
618
module_path=[self.root_name],
619
member=self.mod_name, children={})
621
self.assertEqual(InstrumentedImportReplacer,
622
object.__getattribute__(mod2, '__class__'))
623
self.assertEqual(2, mod2.var2)
624
self.assertEqual('y', mod2.func2('y'))
626
self.assertEqual([('__getattribute__', 'var2'),
629
('import', self.root_name, [self.mod_name]),
632
def test_import_root_and_mod(self):
633
"""Test 'import root-XXX.mod-XXX' remapping both to root3.mod3"""
637
# root3 shouldn't exist yet
640
self.fail('root3 was not supposed to exist yet')
642
InstrumentedImportReplacer(scope=globals(),
643
name='root3', module_path=[self.root_name], member=None,
644
children={'mod3':([self.root_name, self.mod_name], None, {})})
646
# So 'root3' should be a lazy import
647
# and once it is imported, mod3 should also be lazy until
649
self.assertEqual(InstrumentedImportReplacer,
650
object.__getattribute__(root3, '__class__'))
651
self.assertEqual(1, root3.var1)
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)
658
mod_path = self.root_name + '.' + self.mod_name
659
self.assertEqual([('__getattribute__', 'var1'),
661
('_import', 'root3'),
662
('import', self.root_name, []),
663
('__getattribute__', 'var2'),
666
('import', mod_path, []),
669
def test_import_root_and_root_mod(self):
670
"""Test that 'import root, root.mod' can be done.
672
The second import should re-use the first one, and just add
673
children to be imported.
678
# root4 shouldn't exist yet
681
self.fail('root4 was not supposed to exist yet')
683
InstrumentedImportReplacer(scope=globals(),
684
name='root4', module_path=[self.root_name], member=None,
687
# So 'root4' should be a lazy import
688
self.assertEqual(InstrumentedImportReplacer,
689
object.__getattribute__(root4, '__class__'))
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, {})
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)
702
mod_path = self.root_name + '.' + self.mod_name
703
self.assertEqual([('__getattribute__', 'mod4'),
705
('_import', 'root4'),
706
('import', self.root_name, []),
707
('__getattribute__', 'var2'),
710
('import', mod_path, []),
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.
723
# root5 shouldn't exist yet
726
self.fail('root5 was not supposed to exist yet')
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, {})
739
# So 'root5' should be a lazy import
740
self.assertEqual(InstrumentedImportReplacer,
741
object.__getattribute__(root5, '__class__'))
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__'))
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__'))
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)
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
768
self.assertEqual([('__getattribute__', 'mod5'),
770
('_import', 'root5'),
771
('import', self.root_name, []),
772
('__getattribute__', 'submoda5'),
775
('import', sub_path, []),
776
('__getattribute__', 'var2'),
779
('import', mod_path, []),
780
('__getattribute__', 'var4'),
782
('_import', 'submoda5'),
783
('import', submoda_path, []),
784
('__getattribute__', 'var5'),
786
('_import', 'submodb5'),
787
('import', submodb_path, []),
791
class TestConvertImportToMap(TestCase):
792
"""Directly test the conversion from import strings to maps"""
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,
803
def test_import_one(self):
804
self.check({'one':(['one'], None, {}),
807
def test_import_one_two(self):
808
one_two_map = {'one':(['one'], None,
809
{'two':(['one', 'two'], None, {}),
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'])
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, {}),
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',
832
def test_import_one_as_x(self):
833
self.check({'x':(['one'], None, {}),
834
}, ['import one as x'])
836
def test_import_one_two_as_x(self):
837
self.check({'x':(['one', 'two'], None, {}),
838
}, ['import one.two as x'])
840
def test_import_mixed(self):
841
mixed = {'x':(['one', 'two'], None, {}),
842
'one':(['one'], None,
843
{'two':(['one', 'two'], None, {}),
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'])
850
def test_import_with_as(self):
851
self.check({'fast':(['fast'], None, {})}, ['import fast'])
854
class TestFromToMap(TestCase):
855
"""Directly test the conversion of 'from foo import bar' syntax"""
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))
865
def test_from_one_import_two(self):
866
self.check_result({'two':(['one'], 'two', {})},
867
['from one import two'])
869
def test_from_one_import_two_as_three(self):
870
self.check_result({'three':(['one'], 'two', {})},
871
['from one import two as three'])
873
def test_from_one_import_two_three(self):
874
two_three_map = {'two':(['one'], 'two', {}),
875
'three':(['one'], 'three', {}),
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'])
883
def test_from_one_two_import_three(self):
884
self.check_result({'three':(['one', 'two'], 'three', {})},
885
['from one.two import three'])
888
class TestCanonicalize(TestCase):
889
"""Test that we can canonicalize import texts"""
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))
898
def test_import_one(self):
899
self.check(['import one'], 'import one')
900
self.check(['import one'], '\nimport one\n\n')
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')
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')
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')
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'],
924
'import two, three\n'
925
'from one import four')
926
self.check(['import one',
927
'import two, three', 'from one import four, '],
929
'import two, three\n'
930
'from one import (\n'
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")
942
class TestImportProcessor(TestCase):
943
"""Test that ImportProcessor can turn import texts into lazy imports"""
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))
952
def test_import_one(self):
953
exp = {'one':(['one'], None, {})}
954
self.check(exp, 'import one')
955
self.check(exp, '\nimport one\n')
957
def test_import_one_two(self):
958
exp = {'one':(['one'], None,
959
{'two':(['one', 'two'], None, {}),
962
self.check(exp, 'import one.two')
963
self.check(exp, 'import one, one.two')
964
self.check(exp, 'import one\nimport one.two')
966
def test_import_as(self):
967
exp = {'two':(['one'], None, {})}
968
self.check(exp, 'import one as two')
970
def test_import_many(self):
971
exp = {'one':(['one'], None,
972
{'two':(['one', 'two'], None,
973
{'three':(['one', 'two', 'three'], None, {}),
975
'four':(['one', 'four'], None, {}),
977
'five':(['one', 'five'], None, {}),
979
self.check(exp, 'import one.two.three, one.four, one.five as five')
980
self.check(exp, 'import one.five as five\n'
982
'import one.two.three\n'
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'
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'
1002
def test_from_many(self):
1003
exp = {'two':(['one'], 'two', {}),
1004
'three':(['one', 'two'], 'three', {}),
1005
'five':(['one', 'two'], 'four', {}),
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'
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, {}),
1023
self.check(exp, 'from one import two\n'
1024
'from one.two import three, four as five\n'
1026
self.check(exp, 'from one import two\n'
1027
'from one.two import (\n'
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')
1045
def test_name_collision(self):
1046
proc = lazy_import.ImportProcessor()
1047
proc._build_map('import foo')
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')
1059
class TestLazyImportProcessor(ImportReplacerHelper):
1061
def test_root(self):
1065
pass # root6 should not be defined yet
1067
self.fail('root6 was not supposed to exist yet')
1069
text = 'import %s as root6' % (self.root_name,)
1070
proc = lazy_import.ImportProcessor(InstrumentedImportReplacer)
1071
proc.lazy_import(scope=globals(), text=text)
1073
# So 'root6' should be a lazy import
1074
self.assertEqual(InstrumentedImportReplacer,
1075
object.__getattribute__(root6, '__class__'))
1077
self.assertEqual(1, root6.var1)
1078
self.assertEqual('x', root6.func1('x'))
1080
self.assertEqual([('__getattribute__', 'var1'),
1082
('_import', 'root6'),
1083
('import', self.root_name, []),
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.
1096
pass # submoda7 should not be defined yet
1098
self.fail('submoda7 was not supposed to exist yet')
1101
import %(root_name)s.%(sub_name)s.%(submoda_name)s as submoda7
1103
proc = lazy_import.ImportProcessor(InstrumentedImportReplacer)
1104
proc.lazy_import(scope=globals(), text=text)
1106
# So 'submoda7' should be a lazy import
1107
self.assertEqual(InstrumentedImportReplacer,
1108
object.__getattribute__(submoda7, '__class__'))
1110
# This should import submoda7
1111
self.assertEqual(4, submoda7.var4)
1113
sub_path = self.root_name + '.' + self.sub_name
1114
submoda_path = sub_path + '.' + self.submoda_name
1116
self.assertEqual([('__getattribute__', 'var4'),
1118
('_import', 'submoda7'),
1119
('import', submoda_path, []),
1122
def test_lazy_import(self):
1123
"""Smoke test that lazy_import() does the right thing"""
1127
pass # root8 should not be defined yet
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)
1134
self.assertEqual(InstrumentedImportReplacer,
1135
object.__getattribute__(root8, '__class__'))
1137
self.assertEqual(1, root8.var1)
1138
self.assertEqual(1, root8.var1)
1139
self.assertEqual(1, root8.func1(1))
1141
self.assertEqual([('__getattribute__', 'var1'),
1143
('_import', 'root8'),
1144
('import', self.root_name, []),