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
sys.path.append(base_path)
456
self.addCleanup(sys.path.remove, base_path)
458
original_import = __import__
459
def instrumented_import(mod, scope1, scope2, fromlist):
460
self.actions.append(('import', mod, fromlist))
461
return original_import(mod, scope1, scope2, fromlist)
463
__builtins__['__import__'] = original_import
464
self.addCleanup(cleanup)
465
__builtins__['__import__'] = instrumented_import
467
def create_modules(self):
468
"""Create some random modules to be imported.
470
Each entry has a random suffix, and the full names are saved
472
These are setup as follows:
473
base/ <= used to ensure not in default search path
475
__init__.py <= This will contain var1, func1
476
mod-XXX.py <= This will contain var2, func2
478
__init__.py <= Contains var3, func3
479
submoda-XXX.py <= contains var4, func4
480
submodb-XXX.py <= containse var5, func5
482
rand_suffix = osutils.rand_chars(4)
483
root_name = 'root_' + rand_suffix
484
mod_name = 'mod_' + rand_suffix
485
sub_name = 'sub_' + rand_suffix
486
submoda_name = 'submoda_' + rand_suffix
487
submodb_name = 'submodb_' + rand_suffix
490
root_path = osutils.pathjoin('base', root_name)
492
root_init = osutils.pathjoin(root_path, '__init__.py')
493
f = open(osutils.pathjoin(root_path, '__init__.py'), 'wb')
495
f.write('var1 = 1\ndef func1(a):\n return a\n')
498
mod_path = osutils.pathjoin(root_path, mod_name + '.py')
499
f = open(mod_path, 'wb')
501
f.write('var2 = 2\ndef func2(a):\n return a\n')
505
sub_path = osutils.pathjoin(root_path, sub_name)
507
f = open(osutils.pathjoin(sub_path, '__init__.py'), 'wb')
509
f.write('var3 = 3\ndef func3(a):\n return a\n')
512
submoda_path = osutils.pathjoin(sub_path, submoda_name + '.py')
513
f = open(submoda_path, 'wb')
515
f.write('var4 = 4\ndef func4(a):\n return a\n')
518
submodb_path = osutils.pathjoin(sub_path, submodb_name + '.py')
519
f = open(submodb_path, 'wb')
521
f.write('var5 = 5\ndef func5(a):\n return a\n')
524
self.root_name = root_name
525
self.mod_name = mod_name
526
self.sub_name = sub_name
527
self.submoda_name = submoda_name
528
self.submodb_name = submodb_name
531
class TestImportReplacerHelper(ImportReplacerHelper):
533
def test_basic_import(self):
534
"""Test that a real import of these modules works"""
535
sub_mod_path = '.'.join([self.root_name, self.sub_name,
537
root = __import__(sub_mod_path, globals(), locals(), [])
538
self.assertEqual(1, root.var1)
539
self.assertEqual(3, getattr(root, self.sub_name).var3)
540
self.assertEqual(4, getattr(getattr(root, self.sub_name),
541
self.submoda_name).var4)
543
mod_path = '.'.join([self.root_name, self.mod_name])
544
root = __import__(mod_path, globals(), locals(), [])
545
self.assertEqual(2, getattr(root, self.mod_name).var2)
547
self.assertEqual([('import', sub_mod_path, []),
548
('import', mod_path, []),
552
class TestImportReplacer(ImportReplacerHelper):
554
def test_import_root(self):
555
"""Test 'import root-XXX as root1'"""
559
# root1 shouldn't exist yet
562
self.fail('root1 was not supposed to exist yet')
564
# This should replicate 'import root-xxyyzz as root1'
565
InstrumentedImportReplacer(scope=globals(), name='root1',
566
module_path=[self.root_name],
567
member=None, children={})
569
self.assertEqual(InstrumentedImportReplacer,
570
object.__getattribute__(root1, '__class__'))
571
self.assertEqual(1, root1.var1)
572
self.assertEqual('x', root1.func1('x'))
574
self.assertEqual([('__getattribute__', 'var1'),
576
('_import', 'root1'),
577
('import', self.root_name, []),
580
def test_import_mod(self):
581
"""Test 'import root-XXX.mod-XXX as mod2'"""
585
# mod1 shouldn't exist yet
588
self.fail('mod1 was not supposed to exist yet')
590
mod_path = self.root_name + '.' + self.mod_name
591
InstrumentedImportReplacer(scope=globals(), name='mod1',
592
module_path=[self.root_name, self.mod_name],
593
member=None, children={})
595
self.assertEqual(InstrumentedImportReplacer,
596
object.__getattribute__(mod1, '__class__'))
597
self.assertEqual(2, mod1.var2)
598
self.assertEqual('y', mod1.func2('y'))
600
self.assertEqual([('__getattribute__', 'var2'),
603
('import', mod_path, []),
606
def test_import_mod_from_root(self):
607
"""Test 'from root-XXX import mod-XXX as mod2'"""
611
# mod2 shouldn't exist yet
614
self.fail('mod2 was not supposed to exist yet')
616
InstrumentedImportReplacer(scope=globals(), name='mod2',
617
module_path=[self.root_name],
618
member=self.mod_name, children={})
620
self.assertEqual(InstrumentedImportReplacer,
621
object.__getattribute__(mod2, '__class__'))
622
self.assertEqual(2, mod2.var2)
623
self.assertEqual('y', mod2.func2('y'))
625
self.assertEqual([('__getattribute__', 'var2'),
628
('import', self.root_name, [self.mod_name]),
631
def test_import_root_and_mod(self):
632
"""Test 'import root-XXX.mod-XXX' remapping both to root3.mod3"""
636
# root3 shouldn't exist yet
639
self.fail('root3 was not supposed to exist yet')
641
InstrumentedImportReplacer(scope=globals(),
642
name='root3', module_path=[self.root_name], member=None,
643
children={'mod3':([self.root_name, self.mod_name], None, {})})
645
# So 'root3' should be a lazy import
646
# and once it is imported, mod3 should also be lazy until
648
self.assertEqual(InstrumentedImportReplacer,
649
object.__getattribute__(root3, '__class__'))
650
self.assertEqual(1, root3.var1)
652
# There is a mod3 member, but it is also lazy
653
self.assertEqual(InstrumentedImportReplacer,
654
object.__getattribute__(root3.mod3, '__class__'))
655
self.assertEqual(2, root3.mod3.var2)
657
mod_path = self.root_name + '.' + self.mod_name
658
self.assertEqual([('__getattribute__', 'var1'),
660
('_import', 'root3'),
661
('import', self.root_name, []),
662
('__getattribute__', 'var2'),
665
('import', mod_path, []),
668
def test_import_root_and_root_mod(self):
669
"""Test that 'import root, root.mod' can be done.
671
The second import should re-use the first one, and just add
672
children to be imported.
677
# root4 shouldn't exist yet
680
self.fail('root4 was not supposed to exist yet')
682
InstrumentedImportReplacer(scope=globals(),
683
name='root4', module_path=[self.root_name], member=None,
686
# So 'root4' should be a lazy import
687
self.assertEqual(InstrumentedImportReplacer,
688
object.__getattribute__(root4, '__class__'))
690
# Lets add a new child to be imported on demand
691
# This syntax of using object.__getattribute__ is the correct method
692
# for accessing the _import_replacer_children member
693
children = object.__getattribute__(root4, '_import_replacer_children')
694
children['mod4'] = ([self.root_name, self.mod_name], None, {})
696
# Accessing root4.mod4 should import root, but mod should stay lazy
697
self.assertEqual(InstrumentedImportReplacer,
698
object.__getattribute__(root4.mod4, '__class__'))
699
self.assertEqual(2, root4.mod4.var2)
701
mod_path = self.root_name + '.' + self.mod_name
702
self.assertEqual([('__getattribute__', 'mod4'),
704
('_import', 'root4'),
705
('import', self.root_name, []),
706
('__getattribute__', 'var2'),
709
('import', mod_path, []),
712
def test_import_root_sub_submod(self):
713
"""Test import root.mod, root.sub.submoda, root.sub.submodb
714
root should be a lazy import, with multiple children, who also
715
have children to be imported.
716
And when root is imported, the children should be lazy, and
717
reuse the intermediate lazy object.
722
# root5 shouldn't exist yet
725
self.fail('root5 was not supposed to exist yet')
727
InstrumentedImportReplacer(scope=globals(),
728
name='root5', module_path=[self.root_name], member=None,
729
children={'mod5':([self.root_name, self.mod_name], None, {}),
730
'sub5':([self.root_name, self.sub_name], None,
731
{'submoda5':([self.root_name, self.sub_name,
732
self.submoda_name], None, {}),
733
'submodb5':([self.root_name, self.sub_name,
734
self.submodb_name], None, {})
738
# So 'root5' should be a lazy import
739
self.assertEqual(InstrumentedImportReplacer,
740
object.__getattribute__(root5, '__class__'))
742
# Accessing root5.mod5 should import root, but mod should stay lazy
743
self.assertEqual(InstrumentedImportReplacer,
744
object.__getattribute__(root5.mod5, '__class__'))
745
# root5.sub5 should still be lazy, but not re-import root5
746
self.assertEqual(InstrumentedImportReplacer,
747
object.__getattribute__(root5.sub5, '__class__'))
749
# Accessing root5.sub5.submoda5 should import sub5, but not either
750
# of the sub objects (they should be available as lazy objects
751
self.assertEqual(InstrumentedImportReplacer,
752
object.__getattribute__(root5.sub5.submoda5, '__class__'))
753
self.assertEqual(InstrumentedImportReplacer,
754
object.__getattribute__(root5.sub5.submodb5, '__class__'))
756
# This should import mod5
757
self.assertEqual(2, root5.mod5.var2)
758
# These should import submoda5 and submodb5
759
self.assertEqual(4, root5.sub5.submoda5.var4)
760
self.assertEqual(5, root5.sub5.submodb5.var5)
762
mod_path = self.root_name + '.' + self.mod_name
763
sub_path = self.root_name + '.' + self.sub_name
764
submoda_path = sub_path + '.' + self.submoda_name
765
submodb_path = sub_path + '.' + self.submodb_name
767
self.assertEqual([('__getattribute__', 'mod5'),
769
('_import', 'root5'),
770
('import', self.root_name, []),
771
('__getattribute__', 'submoda5'),
774
('import', sub_path, []),
775
('__getattribute__', 'var2'),
778
('import', mod_path, []),
779
('__getattribute__', 'var4'),
781
('_import', 'submoda5'),
782
('import', submoda_path, []),
783
('__getattribute__', 'var5'),
785
('_import', 'submodb5'),
786
('import', submodb_path, []),
790
class TestConvertImportToMap(TestCase):
791
"""Directly test the conversion from import strings to maps"""
793
def check(self, expected, import_strings):
794
proc = lazy_import.ImportProcessor()
795
for import_str in import_strings:
796
proc._convert_import_str(import_str)
797
self.assertEqual(expected, proc.imports,
798
'Import of %r was not converted correctly'
799
' %s != %s' % (import_strings, expected,
802
def test_import_one(self):
803
self.check({'one':(['one'], None, {}),
806
def test_import_one_two(self):
807
one_two_map = {'one':(['one'], None,
808
{'two':(['one', 'two'], None, {}),
811
self.check(one_two_map, ['import one.two'])
812
self.check(one_two_map, ['import one, one.two'])
813
self.check(one_two_map, ['import one', 'import one.two'])
814
self.check(one_two_map, ['import one.two', 'import one'])
816
def test_import_one_two_three(self):
817
one_two_three_map = {
818
'one':(['one'], None,
819
{'two':(['one', 'two'], None,
820
{'three':(['one', 'two', 'three'], None, {}),
824
self.check(one_two_three_map, ['import one.two.three'])
825
self.check(one_two_three_map, ['import one, one.two.three'])
826
self.check(one_two_three_map, ['import one',
827
'import one.two.three'])
828
self.check(one_two_three_map, ['import one.two.three',
831
def test_import_one_as_x(self):
832
self.check({'x':(['one'], None, {}),
833
}, ['import one as x'])
835
def test_import_one_two_as_x(self):
836
self.check({'x':(['one', 'two'], None, {}),
837
}, ['import one.two as x'])
839
def test_import_mixed(self):
840
mixed = {'x':(['one', 'two'], None, {}),
841
'one':(['one'], None,
842
{'two':(['one', 'two'], None, {}),
845
self.check(mixed, ['import one.two as x, one.two'])
846
self.check(mixed, ['import one.two as x', 'import one.two'])
847
self.check(mixed, ['import one.two', 'import one.two as x'])
849
def test_import_with_as(self):
850
self.check({'fast':(['fast'], None, {})}, ['import fast'])
853
class TestFromToMap(TestCase):
854
"""Directly test the conversion of 'from foo import bar' syntax"""
856
def check_result(self, expected, from_strings):
857
proc = lazy_import.ImportProcessor()
858
for from_str in from_strings:
859
proc._convert_from_str(from_str)
860
self.assertEqual(expected, proc.imports,
861
'Import of %r was not converted correctly'
862
' %s != %s' % (from_strings, expected, proc.imports))
864
def test_from_one_import_two(self):
865
self.check_result({'two':(['one'], 'two', {})},
866
['from one import two'])
868
def test_from_one_import_two_as_three(self):
869
self.check_result({'three':(['one'], 'two', {})},
870
['from one import two as three'])
872
def test_from_one_import_two_three(self):
873
two_three_map = {'two':(['one'], 'two', {}),
874
'three':(['one'], 'three', {}),
876
self.check_result(two_three_map,
877
['from one import two, three'])
878
self.check_result(two_three_map,
879
['from one import two',
880
'from one import three'])
882
def test_from_one_two_import_three(self):
883
self.check_result({'three':(['one', 'two'], 'three', {})},
884
['from one.two import three'])
887
class TestCanonicalize(TestCase):
888
"""Test that we can canonicalize import texts"""
890
def check(self, expected, text):
891
proc = lazy_import.ImportProcessor()
892
parsed = proc._canonicalize_import_text(text)
893
self.assertEqual(expected, parsed,
894
'Incorrect parsing of text:\n%s\n%s\n!=\n%s'
895
% (text, expected, parsed))
897
def test_import_one(self):
898
self.check(['import one'], 'import one')
899
self.check(['import one'], '\nimport one\n\n')
901
def test_import_one_two(self):
902
self.check(['import one, two'], 'import one, two')
903
self.check(['import one, two'], '\nimport one, two\n\n')
905
def test_import_one_as_two_as(self):
906
self.check(['import one as x, two as y'], 'import one as x, two as y')
907
self.check(['import one as x, two as y'],
908
'\nimport one as x, two as y\n')
910
def test_from_one_import_two(self):
911
self.check(['from one import two'], 'from one import two')
912
self.check(['from one import two'], '\nfrom one import two\n\n')
913
self.check(['from one import two'], '\nfrom one import (two)\n')
914
self.check(['from one import two '], '\nfrom one import (\n\ttwo\n)\n')
916
def test_multiple(self):
917
self.check(['import one', 'import two, three', 'from one import four'],
918
'import one\nimport two, three\nfrom one import four')
919
self.check(['import one', 'import two, three', 'from one import four'],
920
'import one\nimport (two, three)\nfrom one import four')
921
self.check(['import one', 'import two, three', 'from one import four'],
923
'import two, three\n'
924
'from one import four')
925
self.check(['import one',
926
'import two, three', 'from one import four, '],
928
'import two, three\n'
929
'from one import (\n'
934
def test_missing_trailing(self):
935
proc = lazy_import.ImportProcessor()
936
self.assertRaises(errors.InvalidImportLine,
937
proc._canonicalize_import_text,
938
"from foo import (\n bar\n")
941
class TestImportProcessor(TestCase):
942
"""Test that ImportProcessor can turn import texts into lazy imports"""
944
def check(self, expected, text):
945
proc = lazy_import.ImportProcessor()
946
proc._build_map(text)
947
self.assertEqual(expected, proc.imports,
948
'Incorrect processing of:\n%s\n%s\n!=\n%s'
949
% (text, expected, proc.imports))
951
def test_import_one(self):
952
exp = {'one':(['one'], None, {})}
953
self.check(exp, 'import one')
954
self.check(exp, '\nimport one\n')
956
def test_import_one_two(self):
957
exp = {'one':(['one'], None,
958
{'two':(['one', 'two'], None, {}),
961
self.check(exp, 'import one.two')
962
self.check(exp, 'import one, one.two')
963
self.check(exp, 'import one\nimport one.two')
965
def test_import_as(self):
966
exp = {'two':(['one'], None, {})}
967
self.check(exp, 'import one as two')
969
def test_import_many(self):
970
exp = {'one':(['one'], None,
971
{'two':(['one', 'two'], None,
972
{'three':(['one', 'two', 'three'], None, {}),
974
'four':(['one', 'four'], None, {}),
976
'five':(['one', 'five'], None, {}),
978
self.check(exp, 'import one.two.three, one.four, one.five as five')
979
self.check(exp, 'import one.five as five\n'
981
'import one.two.three\n'
984
def test_from_one_import_two(self):
985
exp = {'two':(['one'], 'two', {})}
986
self.check(exp, 'from one import two\n')
987
self.check(exp, 'from one import (\n'
991
def test_from_one_import_two(self):
992
exp = {'two':(['one'], 'two', {})}
993
self.check(exp, 'from one import two\n')
994
self.check(exp, 'from one import (two)\n')
995
self.check(exp, 'from one import (two,)\n')
996
self.check(exp, 'from one import two as two\n')
997
self.check(exp, 'from one import (\n'
1001
def test_from_many(self):
1002
exp = {'two':(['one'], 'two', {}),
1003
'three':(['one', 'two'], 'three', {}),
1004
'five':(['one', 'two'], 'four', {}),
1006
self.check(exp, 'from one import two\n'
1007
'from one.two import three, four as five\n')
1008
self.check(exp, 'from one import two\n'
1009
'from one.two import (\n'
1014
def test_mixed(self):
1015
exp = {'two':(['one'], 'two', {}),
1016
'three':(['one', 'two'], 'three', {}),
1017
'five':(['one', 'two'], 'four', {}),
1018
'one':(['one'], None,
1019
{'two':(['one', 'two'], None, {}),
1022
self.check(exp, 'from one import two\n'
1023
'from one.two import three, four as five\n'
1025
self.check(exp, 'from one import two\n'
1026
'from one.two import (\n'
1033
def test_incorrect_line(self):
1034
proc = lazy_import.ImportProcessor()
1035
self.assertRaises(errors.InvalidImportLine,
1036
proc._build_map, 'foo bar baz')
1037
self.assertRaises(errors.InvalidImportLine,
1038
proc._build_map, 'improt foo')
1039
self.assertRaises(errors.InvalidImportLine,
1040
proc._build_map, 'importfoo')
1041
self.assertRaises(errors.InvalidImportLine,
1042
proc._build_map, 'fromimport')
1044
def test_name_collision(self):
1045
proc = lazy_import.ImportProcessor()
1046
proc._build_map('import foo')
1048
# All of these would try to create an object with the
1049
# same name as an existing object.
1050
self.assertRaises(errors.ImportNameCollision,
1051
proc._build_map, 'import bar as foo')
1052
self.assertRaises(errors.ImportNameCollision,
1053
proc._build_map, 'from foo import bar as foo')
1054
self.assertRaises(errors.ImportNameCollision,
1055
proc._build_map, 'from bar import foo')
1058
class TestLazyImportProcessor(ImportReplacerHelper):
1060
def test_root(self):
1064
pass # root6 should not be defined yet
1066
self.fail('root6 was not supposed to exist yet')
1068
text = 'import %s as root6' % (self.root_name,)
1069
proc = lazy_import.ImportProcessor(InstrumentedImportReplacer)
1070
proc.lazy_import(scope=globals(), text=text)
1072
# So 'root6' should be a lazy import
1073
self.assertEqual(InstrumentedImportReplacer,
1074
object.__getattribute__(root6, '__class__'))
1076
self.assertEqual(1, root6.var1)
1077
self.assertEqual('x', root6.func1('x'))
1079
self.assertEqual([('__getattribute__', 'var1'),
1081
('_import', 'root6'),
1082
('import', self.root_name, []),
1085
def test_import_deep(self):
1086
"""Test import root.mod, root.sub.submoda, root.sub.submodb
1087
root should be a lazy import, with multiple children, who also
1088
have children to be imported.
1089
And when root is imported, the children should be lazy, and
1090
reuse the intermediate lazy object.
1095
pass # submoda7 should not be defined yet
1097
self.fail('submoda7 was not supposed to exist yet')
1100
import %(root_name)s.%(sub_name)s.%(submoda_name)s as submoda7
1102
proc = lazy_import.ImportProcessor(InstrumentedImportReplacer)
1103
proc.lazy_import(scope=globals(), text=text)
1105
# So 'submoda7' should be a lazy import
1106
self.assertEqual(InstrumentedImportReplacer,
1107
object.__getattribute__(submoda7, '__class__'))
1109
# This should import submoda7
1110
self.assertEqual(4, submoda7.var4)
1112
sub_path = self.root_name + '.' + self.sub_name
1113
submoda_path = sub_path + '.' + self.submoda_name
1115
self.assertEqual([('__getattribute__', 'var4'),
1117
('_import', 'submoda7'),
1118
('import', submoda_path, []),
1121
def test_lazy_import(self):
1122
"""Smoke test that lazy_import() does the right thing"""
1126
pass # root8 should not be defined yet
1128
self.fail('root8 was not supposed to exist yet')
1129
lazy_import.lazy_import(globals(),
1130
'import %s as root8' % (self.root_name,),
1131
lazy_import_class=InstrumentedImportReplacer)
1133
self.assertEqual(InstrumentedImportReplacer,
1134
object.__getattribute__(root8, '__class__'))
1136
self.assertEqual(1, root8.var1)
1137
self.assertEqual(1, root8.var1)
1138
self.assertEqual(1, root8.func1(1))
1140
self.assertEqual([('__getattribute__', 'var1'),
1142
('_import', 'root8'),
1143
('import', self.root_name, []),