~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_lazy_import.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-08-25 14:04:03 UTC
  • mfrom: (6082.4.2 bug-702914)
  • Revision ID: pqm@pqm.ubuntu.com-20110825140403-9xf3jcvxzxrazjuv
(jameinel) Handle bug #702914,
 avoid race conditions removing attributes of ImportReplacer. (Benji York)

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
"""Test the lazy_import functionality."""
18
18
 
 
19
import linecache
19
20
import os
 
21
import re
20
22
import sys
21
23
 
22
24
from bzrlib import (
1167
1169
                          ('_import', 'root8'),
1168
1170
                          ('import', self.root_name, []),
1169
1171
                         ], self.actions)
 
1172
 
 
1173
 
 
1174
class TestScopeReplacerReentrance(TestCase):
 
1175
    """The ScopeReplacer should be reentrant.
 
1176
 
 
1177
    Invoking a replacer while an invocation was already on-going leads to a
 
1178
    race to see which invocation will be the first to delete the _factory and
 
1179
    _scope attributes.  The loosing caller used to see AttributeErrors (bug
 
1180
    702914).
 
1181
 
 
1182
    These tests set up a tracer that stops at the moment just before one of
 
1183
    the attributes is being deleted and starts another call to the
 
1184
    functionality in question (__call__, __getattribute__, __setattr_) in
 
1185
    order win the race, setting up the originall caller to loose.
 
1186
    """
 
1187
 
 
1188
    def tracer(self, frame, event, arg):
 
1189
        # Grab the name of the file that contains the code being executed.
 
1190
        filename = frame.f_globals["__file__"]
 
1191
        # Convert ".pyc" and ".pyo" file names to their ".py" equivalent.
 
1192
        filename = re.sub(r'\.py[co]$', '.py', filename)
 
1193
        # If we're executing a line of code from the right module...
 
1194
        if event == 'line' and 'lazy_import.py' in filename:
 
1195
            line = linecache.getline(filename, frame.f_lineno)
 
1196
            # ...and the line of code is the one we're looking for...
 
1197
            if 'del self._factory' in line:
 
1198
                # We don't need to trace any more.
 
1199
                sys.settrace(None)
 
1200
                # Run another racer.  This one will "win" the race, deleting
 
1201
                # the attributes.  When the first racer resumes it will loose
 
1202
                # the race, generating an AttributeError.
 
1203
                self.racer()
 
1204
        return self.tracer
 
1205
 
 
1206
    def run_race(self, racer):
 
1207
        self.racer = racer
 
1208
        sys.settrace(self.tracer)
 
1209
        self.racer() # Should not raise an AttributeError
 
1210
        # Make sure the tracer actually found the code it was looking for.  If
 
1211
        # not, maybe the code was refactored in such a way that these tests
 
1212
        # aren't needed any more.
 
1213
        self.assertEqual(None, sys.gettrace())
 
1214
 
 
1215
    def test_call(self):
 
1216
        def factory(*args):
 
1217
            return factory
 
1218
        replacer = lazy_import.ScopeReplacer({}, factory, 'name')
 
1219
        self.run_race(replacer)
 
1220
 
 
1221
    def test_setattr(self):
 
1222
        class Replaced:
 
1223
            pass
 
1224
 
 
1225
        def factory(*args):
 
1226
            return Replaced()
 
1227
 
 
1228
        replacer = lazy_import.ScopeReplacer({}, factory, 'name')
 
1229
 
 
1230
        def racer():
 
1231
            replacer.foo = 42
 
1232
 
 
1233
        self.run_race(racer)
 
1234
 
 
1235
    def test_getattribute(self):
 
1236
        class Replaced:
 
1237
            foo = 'bar'
 
1238
 
 
1239
        def factory(*args):
 
1240
            return Replaced()
 
1241
 
 
1242
        replacer = lazy_import.ScopeReplacer({}, factory, 'name')
 
1243
 
 
1244
        def racer():
 
1245
            replacer.foo
 
1246
 
 
1247
        self.run_race(racer)