~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_lazy_import.py

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2011 Canonical Ltd
 
1
# Copyright (C) 2006 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
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
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
"""Test the lazy_import functionality."""
18
18
 
19
 
import linecache
20
19
import os
21
 
import re
22
20
import sys
23
21
 
24
22
from bzrlib import (
26
24
    lazy_import,
27
25
    osutils,
28
26
    )
29
 
from bzrlib.tests import (
30
 
    TestCase,
31
 
    TestCaseInTempDir,
32
 
    )
 
27
from bzrlib.tests import TestCase, TestCaseInTempDir
33
28
 
34
29
 
35
30
class InstrumentedReplacer(lazy_import.ScopeReplacer):
39
34
    def use_actions(actions):
40
35
        InstrumentedReplacer.actions = actions
41
36
 
 
37
    def _replace(self):
 
38
        InstrumentedReplacer.actions.append('_replace')
 
39
        return lazy_import.ScopeReplacer._replace(self)
 
40
 
42
41
    def __getattribute__(self, attr):
43
42
        InstrumentedReplacer.actions.append(('__getattribute__', attr))
44
43
        return lazy_import.ScopeReplacer.__getattribute__(self, attr)
58
57
        InstrumentedImportReplacer.actions.append(('_import', name))
59
58
        return lazy_import.ImportReplacer._import(self, scope, name)
60
59
 
 
60
    def _replace(self):
 
61
        InstrumentedImportReplacer.actions.append('_replace')
 
62
        return lazy_import.ScopeReplacer._replace(self)
 
63
 
61
64
    def __getattribute__(self, attr):
62
65
        InstrumentedImportReplacer.actions.append(('__getattribute__', attr))
63
66
        return lazy_import.ScopeReplacer.__getattribute__(self, attr)
94
97
    """
95
98
 
96
99
    def setUp(self):
97
 
        super(TestScopeReplacer, self).setUp()
 
100
        TestCase.setUp(self)
98
101
        # These tests assume we will not be proxying, so make sure proxying is
99
102
        # disabled.
100
103
        orig_proxy = lazy_import.ScopeReplacer._should_proxy
104
107
 
105
108
    def test_object(self):
106
109
        """ScopeReplacer can create an instance in local scope.
107
 
 
 
110
        
108
111
        An object should appear in globals() by constructing a ScopeReplacer,
109
112
        and it will be replaced with the real object upon the first request.
110
113
        """
124
127
        else:
125
128
            self.fail('test_obj1 was not supposed to exist yet')
126
129
 
 
130
        orig_globals = set(globals().keys())
 
131
 
127
132
        InstrumentedReplacer(scope=globals(), name='test_obj1',
128
133
                             factory=factory)
129
134
 
 
135
        new_globals = set(globals().keys())
 
136
 
130
137
        # We can't use isinstance() because that uses test_obj1.__class__
131
138
        # and that goes through __getattribute__ which would activate
132
139
        # the replacement
136
143
        self.assertIsInstance(test_obj1, TestClass)
137
144
        self.assertEqual('foo', test_obj1.foo(2))
138
145
        self.assertEqual([('__getattribute__', 'foo'),
 
146
                          '_replace',
139
147
                          'factory',
140
148
                          'init',
141
149
                          ('foo', 1),
160
168
        else:
161
169
            self.fail('test_obj6 was not supposed to exist yet')
162
170
 
 
171
        orig_globals = set(globals().keys())
 
172
 
163
173
        lazy_import.ScopeReplacer(scope=globals(), name='test_obj6',
164
174
                                  factory=factory)
165
175
 
 
176
        new_globals = set(globals().keys())
 
177
 
166
178
        # We can't use isinstance() because that uses test_obj6.__class__
167
179
        # and that goes through __getattribute__ which would activate
168
180
        # the replacement
234
246
        self.assertEqual('class_member', test_class1.class_member)
235
247
        self.assertEqual(test_class1, TestClass)
236
248
        self.assertEqual([('__getattribute__', 'class_member'),
 
249
                          '_replace',
237
250
                          'factory',
238
251
                         ], actions)
239
252
 
257
270
        InstrumentedReplacer(scope=globals(), name='test_class2',
258
271
                             factory=factory)
259
272
 
260
 
        self.assertFalse(test_class2 is TestClass)
 
273
        self.failIf(test_class2 is TestClass)
261
274
        obj = test_class2()
262
275
        self.assertIs(test_class2, TestClass)
263
276
        self.assertIsInstance(obj, TestClass)
264
277
        self.assertEqual('class_member', obj.class_member)
265
278
        self.assertEqual([('__call__', (), {}),
 
279
                          '_replace',
266
280
                          'factory',
267
281
                          'init',
268
282
                         ], actions)
289
303
        InstrumentedReplacer(scope=globals(), name='test_func1',
290
304
                             factory=factory)
291
305
 
292
 
        self.assertFalse(test_func1 is func)
 
306
        self.failIf(test_func1 is func)
293
307
        val = test_func1(1, 2, c='3')
294
308
        self.assertIs(test_func1, func)
295
309
 
296
310
        self.assertEqual((1,2,'3'), val)
297
311
        self.assertEqual([('__call__', (1,2), {'c':'3'}),
 
312
                          '_replace',
298
313
                          'factory',
299
314
                          'func',
300
315
                         ], actions)
336
351
                         object.__getattribute__(test_obj2, '__class__'))
337
352
        self.assertEqual(InstrumentedReplacer,
338
353
                         object.__getattribute__(test_obj3, '__class__'))
339
 
 
 
354
        
340
355
        # The first use of the alternate variable causes test_obj2 to
341
356
        # be replaced.
342
357
        self.assertEqual('foo', test_obj3.foo(1))
354
369
        # because only now are we able to detect the problem.
355
370
        self.assertRaises(errors.IllegalUseOfScopeReplacer,
356
371
                          getattr, test_obj3, 'foo')
357
 
 
 
372
        
358
373
        self.assertEqual([('__getattribute__', 'foo'),
 
374
                          '_replace',
359
375
                          'factory',
360
376
                          'init',
361
377
                          ('foo', 1),
362
378
                          ('foo', 2),
363
379
                          ('foo', 3),
364
380
                          ('__getattribute__', 'foo'),
 
381
                          '_replace',
365
382
                         ], actions)
366
383
 
367
384
    def test_enable_proxying(self):
412
429
                         object.__getattribute__(test_obj5, '__class__'))
413
430
 
414
431
        self.assertEqual([('__getattribute__', 'foo'),
 
432
                          '_replace',
415
433
                          'factory',
416
434
                          'init',
417
435
                          ('foo', 1),
422
440
                          ('foo', 4),
423
441
                         ], actions)
424
442
 
425
 
    def test_replacing_from_own_scope_fails(self):
426
 
        """If a ScopeReplacer tries to replace itself a nice error is given"""
427
 
        actions = []
428
 
        InstrumentedReplacer.use_actions(actions)
429
 
        TestClass.use_actions(actions)
430
 
 
431
 
        def factory(replacer, scope, name):
432
 
            actions.append('factory')
433
 
            # return the name in given scope, which is currently the replacer
434
 
            return scope[name]
435
 
 
436
 
        try:
437
 
            test_obj7
438
 
        except NameError:
439
 
            # test_obj7 shouldn't exist yet
440
 
            pass
441
 
        else:
442
 
            self.fail('test_obj7 was not supposed to exist yet')
443
 
 
444
 
        InstrumentedReplacer(scope=globals(), name='test_obj7',
445
 
                             factory=factory)
446
 
 
447
 
        self.assertEqual(InstrumentedReplacer,
448
 
                         object.__getattribute__(test_obj7, '__class__'))
449
 
        e = self.assertRaises(errors.IllegalUseOfScopeReplacer, test_obj7)
450
 
        self.assertIn("replace itself", e.msg)
451
 
        self.assertEqual([('__call__', (), {}),
452
 
                          'factory'], actions)
453
 
 
454
443
 
455
444
class ImportReplacerHelper(TestCaseInTempDir):
456
445
    """Test the ability to have a lazily imported module or object"""
457
446
 
458
447
    def setUp(self):
459
 
        super(ImportReplacerHelper, self).setUp()
 
448
        TestCaseInTempDir.setUp(self)
460
449
        self.create_modules()
461
450
        base_path = self.test_dir + '/base'
462
451
 
463
452
        self.actions = []
464
453
        InstrumentedImportReplacer.use_actions(self.actions)
465
454
 
466
 
        sys.path.append(base_path)
467
 
        self.addCleanup(sys.path.remove, base_path)
468
 
 
469
455
        original_import = __import__
470
 
        def instrumented_import(mod, scope1, scope2, fromlist, level):
471
 
            self.actions.append(('import', mod, fromlist, level))
472
 
            return original_import(mod, scope1, scope2, fromlist, level)
 
456
        def instrumented_import(mod, scope1, scope2, fromlist):
 
457
            self.actions.append(('import', mod, fromlist))
 
458
            return original_import(mod, scope1, scope2, fromlist)
 
459
 
473
460
        def cleanup():
 
461
            if base_path in sys.path:
 
462
                sys.path.remove(base_path)
474
463
            __builtins__['__import__'] = original_import
475
464
        self.addCleanup(cleanup)
 
465
        sys.path.append(base_path)
476
466
        __builtins__['__import__'] = instrumented_import
477
467
 
478
468
    def create_modules(self):
545
535
        """Test that a real import of these modules works"""
546
536
        sub_mod_path = '.'.join([self.root_name, self.sub_name,
547
537
                                  self.submoda_name])
548
 
        root = __import__(sub_mod_path, globals(), locals(), [], 0)
 
538
        root = __import__(sub_mod_path, globals(), locals(), [])
549
539
        self.assertEqual(1, root.var1)
550
540
        self.assertEqual(3, getattr(root, self.sub_name).var3)
551
541
        self.assertEqual(4, getattr(getattr(root, self.sub_name),
552
542
                                    self.submoda_name).var4)
553
543
 
554
544
        mod_path = '.'.join([self.root_name, self.mod_name])
555
 
        root = __import__(mod_path, globals(), locals(), [], 0)
 
545
        root = __import__(mod_path, globals(), locals(), [])
556
546
        self.assertEqual(2, getattr(root, self.mod_name).var2)
557
547
 
558
 
        self.assertEqual([('import', sub_mod_path, [], 0),
559
 
                          ('import', mod_path, [], 0),
 
548
        self.assertEqual([('import', sub_mod_path, []),
 
549
                          ('import', mod_path, []),
560
550
                         ], self.actions)
561
551
 
562
552
 
583
573
        self.assertEqual('x', root1.func1('x'))
584
574
 
585
575
        self.assertEqual([('__getattribute__', 'var1'),
 
576
                          '_replace',
586
577
                          ('_import', 'root1'),
587
 
                          ('import', self.root_name, [], 0),
 
578
                          ('import', self.root_name, []),
588
579
                         ], self.actions)
589
580
 
590
581
    def test_import_mod(self):
608
599
        self.assertEqual('y', mod1.func2('y'))
609
600
 
610
601
        self.assertEqual([('__getattribute__', 'var2'),
 
602
                          '_replace',
611
603
                          ('_import', 'mod1'),
612
 
                          ('import', mod_path, [], 0),
 
604
                          ('import', mod_path, []),
613
605
                         ], self.actions)
614
606
 
615
607
    def test_import_mod_from_root(self):
632
624
        self.assertEqual('y', mod2.func2('y'))
633
625
 
634
626
        self.assertEqual([('__getattribute__', 'var2'),
 
627
                          '_replace',
635
628
                          ('_import', 'mod2'),
636
 
                          ('import', self.root_name, [self.mod_name], 0),
 
629
                          ('import', self.root_name, [self.mod_name]),
637
630
                         ], self.actions)
638
631
 
639
632
    def test_import_root_and_mod(self):
664
657
 
665
658
        mod_path = self.root_name + '.' + self.mod_name
666
659
        self.assertEqual([('__getattribute__', 'var1'),
 
660
                          '_replace',
667
661
                          ('_import', 'root3'),
668
 
                          ('import', self.root_name, [], 0),
 
662
                          ('import', self.root_name, []),
669
663
                          ('__getattribute__', 'var2'),
 
664
                          '_replace',
670
665
                          ('_import', 'mod3'),
671
 
                          ('import', mod_path, [], 0),
 
666
                          ('import', mod_path, []),
672
667
                         ], self.actions)
673
668
 
674
669
    def test_import_root_and_root_mod(self):
706
701
 
707
702
        mod_path = self.root_name + '.' + self.mod_name
708
703
        self.assertEqual([('__getattribute__', 'mod4'),
 
704
                          '_replace',
709
705
                          ('_import', 'root4'),
710
 
                          ('import', self.root_name, [], 0),
 
706
                          ('import', self.root_name, []),
711
707
                          ('__getattribute__', 'var2'),
 
708
                          '_replace',
712
709
                          ('_import', 'mod4'),
713
 
                          ('import', mod_path, [], 0),
 
710
                          ('import', mod_path, []),
714
711
                         ], self.actions)
715
712
 
716
713
    def test_import_root_sub_submod(self):
769
766
        submodb_path = sub_path + '.' + self.submodb_name
770
767
 
771
768
        self.assertEqual([('__getattribute__', 'mod5'),
 
769
                          '_replace',
772
770
                          ('_import', 'root5'),
773
 
                          ('import', self.root_name, [], 0),
 
771
                          ('import', self.root_name, []),
774
772
                          ('__getattribute__', 'submoda5'),
 
773
                          '_replace',
775
774
                          ('_import', 'sub5'),
776
 
                          ('import', sub_path, [], 0),
 
775
                          ('import', sub_path, []),
777
776
                          ('__getattribute__', 'var2'),
 
777
                          '_replace',
778
778
                          ('_import', 'mod5'),
779
 
                          ('import', mod_path, [], 0),
 
779
                          ('import', mod_path, []),
780
780
                          ('__getattribute__', 'var4'),
 
781
                          '_replace',
781
782
                          ('_import', 'submoda5'),
782
 
                          ('import', submoda_path, [], 0),
 
783
                          ('import', submoda_path, []),
783
784
                          ('__getattribute__', 'var5'),
 
785
                          '_replace',
784
786
                          ('_import', 'submodb5'),
785
 
                          ('import', submodb_path, [], 0),
 
787
                          ('import', submodb_path, []),
786
788
                         ], self.actions)
787
789
 
788
790
 
1076
1078
        self.assertEqual('x', root6.func1('x'))
1077
1079
 
1078
1080
        self.assertEqual([('__getattribute__', 'var1'),
 
1081
                          '_replace',
1079
1082
                          ('_import', 'root6'),
1080
 
                          ('import', self.root_name, [], 0),
 
1083
                          ('import', self.root_name, []),
1081
1084
                         ], self.actions)
1082
1085
 
1083
1086
    def test_import_deep(self):
1111
1114
        submoda_path = sub_path + '.' + self.submoda_name
1112
1115
 
1113
1116
        self.assertEqual([('__getattribute__', 'var4'),
 
1117
                          '_replace',
1114
1118
                          ('_import', 'submoda7'),
1115
 
                          ('import', submoda_path, [], 0),
 
1119
                          ('import', submoda_path, []),
1116
1120
                         ], self.actions)
1117
1121
 
1118
1122
    def test_lazy_import(self):
1135
1139
        self.assertEqual(1, root8.func1(1))
1136
1140
 
1137
1141
        self.assertEqual([('__getattribute__', 'var1'),
 
1142
                          '_replace',
1138
1143
                          ('_import', 'root8'),
1139
 
                          ('import', self.root_name, [], 0),
 
1144
                          ('import', self.root_name, []),
1140
1145
                         ], self.actions)
1141
 
 
1142
 
 
1143
 
class TestScopeReplacerReentrance(TestCase):
1144
 
    """The ScopeReplacer should be reentrant.
1145
 
 
1146
 
    Invoking a replacer while an invocation was already on-going leads to a
1147
 
    race to see which invocation will be the first to call _replace.
1148
 
    The losing caller used to see an exception (bugs 396819 and 702914).
1149
 
 
1150
 
    These tests set up a tracer that stops at a suitable moment (upon
1151
 
    entry of a specified method) and starts another call to the
1152
 
    functionality in question (__call__, __getattribute__, __setattr_)
1153
 
    in order to win the race, setting up the original caller to lose.
1154
 
    """
1155
 
 
1156
 
    def tracer(self, frame, event, arg):
1157
 
        if event != 'call':
1158
 
            return self.tracer
1159
 
        # Grab the name of the file that contains the code being executed.
1160
 
        code = frame.f_code
1161
 
        filename = code.co_filename
1162
 
        # Convert ".pyc" and ".pyo" file names to their ".py" equivalent.
1163
 
        filename = re.sub(r'\.py[co]$', '.py', filename)
1164
 
        function_name = code.co_name
1165
 
        # If we're executing a line of code from the right module...
1166
 
        if (filename.endswith('lazy_import.py') and
1167
 
            function_name == self.method_to_trace):
1168
 
            # We don't need to trace any more.
1169
 
            sys.settrace(None)
1170
 
            # Run another racer.  This one will "win" the race.
1171
 
            self.racer()
1172
 
        return self.tracer
1173
 
 
1174
 
    def run_race(self, racer, method_to_trace='_resolve'):
1175
 
        self.overrideAttr(lazy_import.ScopeReplacer, '_should_proxy', True)
1176
 
        self.racer = racer
1177
 
        self.method_to_trace = method_to_trace
1178
 
        sys.settrace(self.tracer)
1179
 
        self.racer() # Should not raise any exception
1180
 
        # Make sure the tracer actually found the code it was
1181
 
        # looking for.  If not, maybe the code was refactored in
1182
 
        # such a way that these tests aren't needed any more.
1183
 
        self.assertEqual(None, sys.gettrace())
1184
 
 
1185
 
    def test_call(self):
1186
 
        def factory(*args):
1187
 
            return factory
1188
 
        replacer = lazy_import.ScopeReplacer({}, factory, 'name')
1189
 
        self.run_race(replacer)
1190
 
 
1191
 
    def test_setattr(self):
1192
 
        class Replaced:
1193
 
            pass
1194
 
 
1195
 
        def factory(*args):
1196
 
            return Replaced()
1197
 
 
1198
 
        replacer = lazy_import.ScopeReplacer({}, factory, 'name')
1199
 
 
1200
 
        def racer():
1201
 
            replacer.foo = 42
1202
 
 
1203
 
        self.run_race(racer)
1204
 
 
1205
 
    def test_getattribute(self):
1206
 
        class Replaced:
1207
 
            foo = 'bar'
1208
 
 
1209
 
        def factory(*args):
1210
 
            return Replaced()
1211
 
 
1212
 
        replacer = lazy_import.ScopeReplacer({}, factory, 'name')
1213
 
 
1214
 
        def racer():
1215
 
            replacer.foo
1216
 
 
1217
 
        self.run_race(racer)