← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wgrant/launchpad/bi-dependencies into lp:launchpad

 

William Grant has proposed merging lp:~wgrant/launchpad/bi-dependencies into lp:launchpad with lp:~wgrant/launchpad/bi-dependencies-1 as a prerequisite.

Commit message:
Port the remaining BuilderInteractor methods to take all variables as arguments.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/bi-dependencies/+merge/188493

Port the remaining BuilderInteractor methods to take all variables as arguments, rather than using instance attributes. BI._current_build_behavior and BI.slave die; SlaveScanner now manages those objects, and tests pass them in directly rather than patching BuilderInteractor.
-- 
https://code.launchpad.net/~wgrant/launchpad/bi-dependencies/+merge/188493
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/bi-dependencies into lp:launchpad.
=== modified file 'lib/lp/buildmaster/interactor.py'
--- lib/lp/buildmaster/interactor.py	2013-10-01 01:11:42 +0000
+++ lib/lp/buildmaster/interactor.py	2013-10-01 01:11:42 +0000
@@ -227,24 +227,6 @@
 
 class BuilderInteractor(object):
 
-    _cached_build_behavior = None
-    _cached_currentjob = None
-
-    _cached_slave = None
-    _cached_slave_attrs = None
-
-    # Tests can override _current_build_behavior and slave.
-    _override_behavior = None
-    _override_slave = None
-
-    def __init__(self, builder, override_slave=None, override_behavior=None):
-        self.builder = builder
-        self._override_slave = override_slave
-        self._override_behavior = override_behavior
-
-        # XXX wgrant: The BuilderVitals should be passed in.
-        self.vitals = extract_vitals_from_db(builder)
-
     @staticmethod
     def makeSlaveFromVitals(vitals):
         if vitals.virtualized:
@@ -254,43 +236,14 @@
         return BuilderSlave.makeBuilderSlave(
             vitals.url, vitals.vm_host, timeout)
 
-    @property
-    def slave(self):
-        """See IBuilder."""
-        if self._override_slave is not None:
-            return self._override_slave
-        # The slave cache is invalidated when the builder's URL, VM host
-        # or virtualisation change.
-        new_slave_attrs = (
-            self.vitals.url, self.vitals.vm_host, self.vitals.virtualized)
-        if self._cached_slave_attrs != new_slave_attrs:
-            self._cached_slave = self.makeSlaveFromVitals(self.vitals)
-            self._cached_slave_attrs = new_slave_attrs
-        return self._cached_slave
-
     @staticmethod
     def getBuildBehavior(queue_item, builder, slave):
+        if queue_item is None:
+            return None
         behavior = IBuildFarmJobBehavior(queue_item.specific_job)
         behavior.setBuilder(builder, slave)
         return behavior
 
-    @property
-    def _current_build_behavior(self):
-        """Return the current build behavior."""
-        if self._override_behavior is not None:
-            return self._override_behavior
-        # The _current_build_behavior cache is invalidated when
-        # builder.currentjob changes.
-        currentjob = self.builder.currentjob
-        if currentjob is None:
-            self._cached_build_behavior = None
-            self._cached_currentjob = None
-        elif currentjob != self._cached_currentjob:
-            self._cached_build_behavior = self.getBuildBehavior(
-                currentjob, self.builder, self.slave)
-            self._cached_currentjob = currentjob
-        return self._cached_build_behavior
-
     @staticmethod
     @defer.inlineCallbacks
     def slaveStatus(slave):

=== modified file 'lib/lp/buildmaster/manager.py'
--- lib/lp/buildmaster/manager.py	2013-10-01 01:11:42 +0000
+++ lib/lp/buildmaster/manager.py	2013-10-01 01:11:42 +0000
@@ -57,7 +57,7 @@
 
 
 @defer.inlineCallbacks
-def assessFailureCounts(logger, interactor, exception):
+def assessFailureCounts(logger, vitals, builder, slave, interactor, exception):
     """View builder/job failure_count and work out which needs to die.
 
     :return: A Deferred that fires either immediately or after a virtual
@@ -66,7 +66,6 @@
     # builder.currentjob hides a complicated query, don't run it twice.
     # See bug 623281 (Note that currentjob is a cachedproperty).
 
-    builder = interactor.builder
     del get_property_cache(builder).currentjob
     current_job = builder.currentjob
     if current_job is None:
@@ -105,8 +104,7 @@
             # The builder is dead, but in the virtual case it might be worth
             # resetting it.
             yield interactor.resetOrFail(
-                interactor.vitals, interactor.slave, interactor.builder,
-                logger, exception)
+                vitals, slave, builder, logger, exception)
     else:
         # The job is the culprit!  Override its status to 'failed'
         # to make sure it won't get automatically dispatched again,
@@ -144,10 +142,16 @@
     # greater than abort_timeout in launchpad-buildd's slave BuildManager.
     CANCEL_TIMEOUT = 180
 
-    def __init__(self, builder_name, builders_cache, logger, clock=None):
+    def __init__(self, builder_name, builders_cache, logger, clock=None,
+                 interactor_factory=BuilderInteractor,
+                 slave_factory=BuilderInteractor.makeSlaveFromVitals,
+                 behavior_factory=BuilderInteractor.getBuildBehavior):
         self.builder_name = builder_name
         self.builders_cache = builders_cache
         self.logger = logger
+        self.interactor_factory = interactor_factory
+        self.slave_factory = slave_factory
+        self.behavior_factory = behavior_factory
         # Use the clock if provided, so that tests can advance it.  Use the
         # reactor by default.
         if clock is None:
@@ -202,11 +206,13 @@
                 failure.getTraceback()))
 
         # Decide if we need to terminate the job or reset/fail the builder.
+        vitals = self.builders_cache.getVitals(self.builder_name)
         builder = self.builders_cache[self.builder_name]
         try:
             builder.handleFailure(self.logger)
             yield assessFailureCounts(
-                self.logger, BuilderInteractor(builder), failure.value)
+                self.logger, vitals, builder, self.slave_factory(vitals),
+                self.interactor_factory(), failure.value)
             transaction.commit()
         except Exception:
             # Catastrophic code failure! Not much we can do.
@@ -216,7 +222,7 @@
             transaction.abort()
 
     @defer.inlineCallbacks
-    def checkCancellation(self, builder, interactor):
+    def checkCancellation(self, vitals, builder, slave, interactor):
         """See if there is a pending cancellation request.
 
         If the current build is in status CANCELLING then terminate it
@@ -226,11 +232,10 @@
             by resuming a slave host, so that there is no need to update its
             status.
         """
-        buildqueue = builder.currentjob
-        if not buildqueue:
+        if not vitals.build_queue:
             self.date_cancel = None
             defer.returnValue(False)
-        build = buildqueue.specific_job.build
+        build = vitals.build_queue.specific_job.build
         if build.status != BuildStatus.CANCELLING:
             self.date_cancel = None
             defer.returnValue(False)
@@ -238,7 +243,7 @@
         try:
             if self.date_cancel is None:
                 self.logger.info("Cancelling build '%s'" % build.title)
-                yield interactor.slave.abort()
+                yield slave.abort()
                 self.date_cancel = self._clock.seconds() + self.CANCEL_TIMEOUT
                 defer.returnValue(False)
             else:
@@ -255,18 +260,17 @@
         except Exception as e:
             self.logger.info(
                 "Build '%s' on %s failed to cancel" %
-                (build.title, builder.name))
+                (build.title, vitals.name))
             self.date_cancel = None
-            buildqueue.cancel()
+            vitals.build_queue.cancel()
             transaction.commit()
             value = yield interactor.resetOrFail(
-                interactor.vitals, interactor.slave, interactor.builder,
-                self.logger, e)
+                vitals, slave, builder, self.logger, e)
             # value is not None if we resumed a slave host.
             defer.returnValue(value is not None)
 
     @defer.inlineCallbacks
-    def scan(self, interactor=None):
+    def scan(self):
         """Probe the builder and update/dispatch/collect as appropriate.
 
         :return: A Deferred that fires when the scan is complete.
@@ -276,8 +280,8 @@
         # latest data from the DB.
         transaction.commit()
         vitals = self.builders_cache.getVitals(self.builder_name)
-        interactor = interactor or BuilderInteractor(
-            self.builders_cache[self.builder_name])
+        interactor = self.interactor_factory()
+        slave = self.slave_factory(vitals)
 
         # Confirm that the DB and slave sides are in a valid, mutually
         # agreeable state.
@@ -285,13 +289,15 @@
         if not vitals.builderok:
             lost_reason = '%s is disabled' % vitals.name
         else:
+            builder = self.builders_cache[self.builder_name]
             cancelled = yield self.checkCancellation(
-                interactor.builder, interactor)
+                vitals, builder, slave, interactor)
             if cancelled:
                 return
+            behavior = self.behavior_factory(
+                vitals.build_queue, builder, slave)
             lost = yield interactor.rescueIfLost(
-                vitals, interactor.slave, interactor._current_build_behavior,
-                self.logger)
+                vitals, slave, behavior, self.logger)
             if lost:
                 lost_reason = '%s is lost' % vitals.name
 
@@ -314,9 +320,9 @@
         if vitals.build_queue is not None:
             # Scan the slave and get the logtail, or collect the build
             # if it's ready.  Yes, "updateBuild" is a bad name.
-            yield interactor.updateBuild(
-                vitals.build_queue, interactor.slave,
-                interactor._current_build_behavior)
+            behavior = behavior or self.behavior_factory(
+                vitals.build_queue, builder, slave)
+            yield interactor.updateBuild(vitals.build_queue, slave, behavior)
         elif vitals.manual:
             # If the builder is in manual mode, don't dispatch anything.
             self.logger.debug(
@@ -324,7 +330,7 @@
         else:
             # See if there is a job we can dispatch to the builder slave.
             builder = self.builders_cache[self.builder_name]
-            yield interactor.findAndStartJob(vitals, builder, interactor.slave)
+            yield interactor.findAndStartJob(vitals, builder, slave)
             if builder.currentjob is not None:
                 # After a successful dispatch we can reset the
                 # failure_count.

=== modified file 'lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py'
--- lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py	2013-09-23 02:04:18 +0000
+++ lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py	2013-10-01 01:11:42 +0000
@@ -135,9 +135,9 @@
         self.build.buildqueue_record.markAsBuilding(self.builder)
         self.slave = WaitingSlave('BuildStatus.OK')
         self.slave.valid_file_hashes.append('test_file_hash')
-        self.interactor = BuilderInteractor(self.builder, self.slave)
-        self.behavior = removeSecurityProxy(
-            self.interactor._current_build_behavior)
+        self.interactor = BuilderInteractor()
+        self.behavior = self.interactor.getBuildBehavior(
+            self.build.buildqueue_record, self.builder, self.slave)
 
         # We overwrite the buildmaster root to use a temp directory.
         tempdir = tempfile.mkdtemp()
@@ -253,9 +253,8 @@
 
     def test_handleStatus_ABORTED_recovers_building(self):
         self.builder.vm_host = "fake_vm_host"
-        self.interactor = BuilderInteractor(self.builder, self.slave)
-        self.behavior = removeSecurityProxy(
-            self.interactor._current_build_behavior)
+        self.behavior = self.interactor.getBuildBehavior(
+            self.build.buildqueue_record, self.builder, self.slave)
         self.build.updateStatus(BuildStatus.BUILDING)
 
         def got_status(ignored):

=== modified file 'lib/lp/buildmaster/tests/test_interactor.py'
--- lib/lp/buildmaster/tests/test_interactor.py	2013-10-01 01:11:42 +0000
+++ lib/lp/buildmaster/tests/test_interactor.py	2013-10-01 01:11:42 +0000
@@ -68,36 +68,32 @@
         # extractBuildStatus picks the name of the build status out of a
         # dict describing the slave's status.
         slave_status = {'build_status': 'BuildStatus.BUILDING'}
-        interactor = BuilderInteractor(MockBuilder())
         self.assertEqual(
-            'BUILDING', interactor.extractBuildStatus(slave_status))
+            'BUILDING', BuilderInteractor.extractBuildStatus(slave_status))
 
     def test_extractBuildStatus_malformed(self):
         # extractBuildStatus errors out when the status string is not
         # of the form it expects.
         slave_status = {'build_status': 'BUILDING'}
-        interactor = BuilderInteractor(MockBuilder())
         self.assertRaises(
-            AssertionError, interactor.extractBuildStatus, slave_status)
+            AssertionError, BuilderInteractor.extractBuildStatus, slave_status)
 
     def test_verifySlaveBuildCookie_building_match(self):
-        interactor = BuilderInteractor(MockBuilder())
-        interactor.verifySlaveBuildCookie(TrivialBehavior(), 'trivial')
+        BuilderInteractor.verifySlaveBuildCookie(TrivialBehavior(), 'trivial')
 
     def test_verifySlaveBuildCookie_building_mismatch(self):
-        interactor = BuilderInteractor(MockBuilder())
         self.assertRaises(
             CorruptBuildCookie,
-            interactor.verifySlaveBuildCookie, TrivialBehavior(), 'difficult')
+            BuilderInteractor.verifySlaveBuildCookie,
+            TrivialBehavior(), 'difficult')
 
     def test_verifySlaveBuildCookie_idle_match(self):
-        interactor = BuilderInteractor(MockBuilder())
-        interactor.verifySlaveBuildCookie(None, None)
+        BuilderInteractor.verifySlaveBuildCookie(None, None)
 
     def test_verifySlaveBuildCookie_idle_mismatch(self):
-        interactor = BuilderInteractor(MockBuilder())
         self.assertRaises(
-            CorruptBuildCookie, interactor.verifySlaveBuildCookie, None, 'foo')
+            CorruptBuildCookie,
+            BuilderInteractor.verifySlaveBuildCookie, None, 'foo')
 
     def test_rescueIfLost_aborts_lost_and_broken_slave(self):
         # A slave that's 'lost' should be aborted; when the slave is
@@ -168,22 +164,24 @@
     def test_resetOrFail_nonvirtual(self):
         builder = MockBuilder(virtualized=False, builderok=True)
         vitals = extract_vitals_from_db(builder)
-        yield BuilderInteractor(builder).resetOrFail(
+        yield BuilderInteractor().resetOrFail(
             vitals, None, builder, DevNullLogger(), Exception())
         self.assertFalse(builder.builderok)
 
-    def test_slave(self):
+    def test_makeSlaveFromVitals(self):
         # Builder.slave is a BuilderSlave that points at the actual Builder.
         # The Builder is only ever used in scripts that run outside of the
         # security context.
         builder = MockBuilder(virtualized=False)
-        interactor = BuilderInteractor(builder)
-        self.assertEqual(builder.url, interactor.slave.url)
-        self.assertEqual(10, interactor.slave.timeout)
+        vitals = extract_vitals_from_db(builder)
+        slave = BuilderInteractor.makeSlaveFromVitals(vitals)
+        self.assertEqual(builder.url, slave.url)
+        self.assertEqual(10, slave.timeout)
 
         builder = MockBuilder(virtualized=True)
-        interactor = BuilderInteractor(builder)
-        self.assertEqual(5, interactor.slave.timeout)
+        vitals = extract_vitals_from_db(builder)
+        slave = BuilderInteractor.makeSlaveFromVitals(vitals)
+        self.assertEqual(5, slave.timeout)
 
     @defer.inlineCallbacks
     def test_recover_idle_slave(self):
@@ -307,23 +305,25 @@
     layer = ZopelessDatabaseLayer
     run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=10)
 
-    def test_current_build_behavior_idle(self):
+    def test_getBuildBehavior_idle(self):
         """An idle builder has no build behavior."""
         self.assertIs(
-            None, BuilderInteractor(MockBuilder())._current_build_behavior)
+            None,
+            BuilderInteractor.getBuildBehavior(None, MockBuilder(), None))
 
-    def test_current_build_behavior_building(self):
+    def test_getBuildBehavior_building(self):
         """The current behavior is set automatically from the current job."""
         # Set the builder attribute on the buildqueue record so that our
         # builder will think it has a current build.
         builder = self.factory.makeBuilder(name='builder')
-        interactor = BuilderInteractor(builder)
+        slave = BuildingSlave()
         build = self.factory.makeBinaryPackageBuild()
-        build.queueBuild().markAsBuilding(builder)
-        behavior = interactor._current_build_behavior
+        bq = build.queueBuild()
+        bq.markAsBuilding(builder)
+        behavior = BuilderInteractor.getBuildBehavior(bq, builder, slave)
         self.assertIsInstance(behavior, BinaryPackageBuildBehavior)
         self.assertEqual(behavior._builder, builder)
-        self.assertEqual(behavior._slave, interactor.slave)
+        self.assertEqual(behavior._slave, slave)
 
     def _setupBuilder(self):
         processor = self.factory.makeProcessor(name="i386")
@@ -401,30 +401,6 @@
 
         return d.addCallback(check_build_started)
 
-    @defer.inlineCallbacks
-    def test_recover_building_slave_with_job_that_finished_elsewhere(self):
-        # See bug 671242
-        # When a job is destroyed, the builder's behaviour should be reset
-        # too so that we don't traceback when the wrong behaviour tries
-        # to access a non-existent job.
-        builder, build = self._setupBinaryBuildAndBuilder()
-        candidate = build.queueBuild()
-        building_slave = BuildingSlave()
-        interactor = BuilderInteractor(builder, building_slave)
-        candidate.markAsBuilding(builder)
-
-        # At this point we should see a valid behaviour on the builder:
-        self.assertIsNot(None, interactor._current_build_behavior)
-        yield BuilderInteractor.rescueIfLost(
-            interactor.vitals, building_slave,
-            interactor._current_build_behavior)
-        self.assertIsNot(None, interactor._current_build_behavior)
-        candidate.destroySelf()
-        yield BuilderInteractor.rescueIfLost(
-            interactor.vitals, building_slave,
-            interactor._current_build_behavior)
-        self.assertIs(None, interactor._current_build_behavior)
-
 
 class TestSlave(TestCase):
     """

=== modified file 'lib/lp/buildmaster/tests/test_manager.py'
--- lib/lp/buildmaster/tests/test_manager.py	2013-09-17 04:31:16 +0000
+++ lib/lp/buildmaster/tests/test_manager.py	2013-10-01 01:11:42 +0000
@@ -470,19 +470,23 @@
     @defer.inlineCallbacks
     def test_scan_with_job(self):
         # SlaveScanner.scan calls updateBuild() when a job is building.
-        interactor = BuilderInteractor(
-            MockBuilder(), BuildingSlave('trivial'), TrivialBehavior())
+        slave = BuildingSlave('trivial')
         bq = FakeBuildQueue()
+
+        # Instrument updateBuild.
+        interactor = BuilderInteractor()
+        interactor.updateBuild = FakeMethod()
+
         scanner = SlaveScanner(
-            'mock', MockBuildersCache(interactor.builder, bq), BufferLogger())
-
-        # Instrument updateBuild and currentjob.reset
-        interactor.updateBuild = FakeMethod()
+            'mock', MockBuildersCache(MockBuilder(), bq), BufferLogger(),
+            interactor_factory=FakeMethod(interactor),
+            slave_factory=FakeMethod(slave),
+            behavior_factory=FakeMethod(TrivialBehavior()))
         # XXX: checkCancellation needs more than a FakeBuildQueue.
         scanner.checkCancellation = FakeMethod(defer.succeed(False))
 
-        yield scanner.scan(interactor=interactor)
-        self.assertEqual(['status'], interactor.slave.call_log)
+        yield scanner.scan()
+        self.assertEqual(['status'], slave.call_log)
         self.assertEqual(1, interactor.updateBuild.call_count)
         self.assertEqual(0, bq.reset.call_count)
 
@@ -490,22 +494,26 @@
     def test_scan_aborts_lost_slave_with_job(self):
         # SlaveScanner.scan uses BuilderInteractor.rescueIfLost to abort
         # slaves that don't have the expected job.
-        interactor = BuilderInteractor(
-            MockBuilder(), BuildingSlave('nontrivial'), TrivialBehavior())
+        slave = BuildingSlave('nontrivial')
         bq = FakeBuildQueue()
+
+        # Instrument updateBuild.
+        interactor = BuilderInteractor()
+        interactor.updateBuild = FakeMethod()
+
         scanner = SlaveScanner(
-            'mock', MockBuildersCache(interactor.builder, bq), BufferLogger())
-
-        # Instrument updateBuild and currentjob.reset
-        interactor.updateBuild = FakeMethod()
+            'mock', MockBuildersCache(MockBuilder(), bq), BufferLogger(),
+            interactor_factory=FakeMethod(interactor),
+            slave_factory=FakeMethod(slave),
+            behavior_factory=FakeMethod(TrivialBehavior()))
         # XXX: checkCancellation needs more than a FakeBuildQueue.
         scanner.checkCancellation = FakeMethod(defer.succeed(False))
 
         # A single scan will call status(), notice that the slave is
         # lost, abort() the slave, then reset() the job without calling
         # updateBuild().
-        yield scanner.scan(interactor=interactor)
-        self.assertEqual(['status', 'abort'], interactor.slave.call_log)
+        yield scanner.scan()
+        self.assertEqual(['status', 'abort'], slave.call_log)
         self.assertEqual(0, interactor.updateBuild.call_count)
         self.assertEqual(1, bq.reset.call_count)
 
@@ -513,19 +521,23 @@
     def test_scan_aborts_lost_slave_when_idle(self):
         # SlaveScanner.scan uses BuilderInteractor.rescueIfLost to abort
         # slaves that aren't meant to have a job.
-        interactor = BuilderInteractor(MockBuilder(), BuildingSlave(), None)
-        scanner = SlaveScanner(
-            'mock', MockBuildersCache(interactor.builder, None),
-            BufferLogger())
+        slave = BuildingSlave()
 
         # Instrument updateBuild.
+        interactor = BuilderInteractor()
         interactor.updateBuild = FakeMethod()
 
+        scanner = SlaveScanner(
+            'mock', MockBuildersCache(MockBuilder(), None), BufferLogger(),
+            interactor_factory=FakeMethod(interactor),
+            slave_factory=FakeMethod(slave),
+            behavior_factory=FakeMethod(None))
+
         # A single scan will call status(), notice that the slave is
         # lost, abort() the slave, then reset() the job without calling
         # updateBuild().
-        yield scanner.scan(interactor=interactor)
-        self.assertEqual(['status', 'abort'], interactor.slave.call_log)
+        yield scanner.scan()
+        self.assertEqual(['status', 'abort'], slave.call_log)
         self.assertEqual(0, interactor.updateBuild.call_count)
 
 
@@ -540,7 +552,11 @@
         builder_name = BOB_THE_BUILDER_NAME
         self.builder = getUtility(IBuilderSet)[builder_name]
         self.builder.virtualized = True
-        self.interactor = BuilderInteractor(self.builder)
+        self.interactor = BuilderInteractor()
+
+    @property
+    def vitals(self):
+        return extract_vitals_from_db(self.builder)
 
     def _getScanner(self, clock=None):
         scanner = SlaveScanner(
@@ -551,7 +567,8 @@
     def test_ignores_nonvirtual(self):
         # If the builder is nonvirtual make sure we return False.
         self.builder.virtualized = False
-        d = self._getScanner().checkCancellation(self.builder, self.interactor)
+        d = self._getScanner().checkCancellation(
+            self.vitals, self.builder, None, self.interactor)
         return d.addCallback(self.assertFalse)
 
     def test_ignores_no_buildqueue(self):
@@ -559,12 +576,14 @@
         # make sure we return False.
         buildqueue = self.builder.currentjob
         buildqueue.reset()
-        d = self._getScanner().checkCancellation(self.builder, self.interactor)
+        d = self._getScanner().checkCancellation(
+            self.vitals, self.builder, None, self.interactor)
         return d.addCallback(self.assertFalse)
 
     def test_ignores_build_not_cancelling(self):
         # If the active build is not in a CANCELLING state, ignore it.
-        d = self._getScanner().checkCancellation(self.builder, self.interactor)
+        d = self._getScanner().checkCancellation(
+            self.vitals, self.builder, None, self.interactor)
         return d.addCallback(self.assertFalse)
 
     @defer.inlineCallbacks
@@ -573,21 +592,21 @@
         # True is returned and the slave was resumed.
         slave = OkSlave()
         self.builder.vm_host = "fake_vm_host"
-        self.patch(BuilderSlave, 'makeBuilderSlave', FakeMethod(slave))
-        self.interactor = BuilderInteractor(self.builder)
         buildqueue = self.builder.currentjob
         build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(buildqueue)
         build.updateStatus(BuildStatus.CANCELLING)
         clock = task.Clock()
         scanner = self._getScanner(clock=clock)
 
-        result = yield scanner.checkCancellation(self.builder, self.interactor)
+        result = yield scanner.checkCancellation(
+            self.vitals, self.builder, slave, self.interactor)
         self.assertNotIn("resume", slave.call_log)
         self.assertFalse(result)
         self.assertEqual(BuildStatus.CANCELLING, build.status)
 
         clock.advance(SlaveScanner.CANCEL_TIMEOUT)
-        result = yield scanner.checkCancellation(self.builder, self.interactor)
+        result = yield scanner.checkCancellation(
+            self.vitals, self.builder, slave, self.interactor)
         self.assertEqual(1, slave.call_log.count("resume"))
         self.assertTrue(result)
         self.assertEqual(BuildStatus.CANCELLED, build.status)
@@ -598,13 +617,11 @@
         # immediately resume the slave as if the cancel timeout had expired.
         slave = LostBuildingBrokenSlave()
         self.builder.vm_host = "fake_vm_host"
-        self.patch(BuilderSlave, 'makeBuilderSlave', FakeMethod(slave))
-        self.interactor = BuilderInteractor(self.builder)
         buildqueue = self.builder.currentjob
         build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(buildqueue)
         build.updateStatus(BuildStatus.CANCELLING)
         result = yield self._getScanner().checkCancellation(
-            self.builder, self.interactor)
+            self.vitals, self.builder, slave, self.interactor)
         self.assertEqual(1, slave.call_log.count("resume"))
         self.assertTrue(result)
         self.assertEqual(BuildStatus.CANCELLED, build.status)
@@ -649,6 +666,7 @@
 class TestFailureAssessments(TestCaseWithFactory):
 
     layer = ZopelessDatabaseLayer
+    run_tests_with = AsynchronousDeferredRunTest
 
     def setUp(self):
         TestCaseWithFactory.setUp(self)
@@ -656,12 +674,13 @@
         self.build = self.factory.makeSourcePackageRecipeBuild()
         self.buildqueue = self.build.queueBuild()
         self.buildqueue.markAsBuilding(self.builder)
-        self.interactor = BuilderInteractor(self.builder)
+        self.slave = OkSlave()
 
     def _assessFailureCounts(self, fail_notes):
         # Helper for assessFailureCounts boilerplate.
         return assessFailureCounts(
-            BufferLogger(), self.interactor, Exception(fail_notes))
+            BufferLogger(), extract_vitals_from_db(self.builder), self.builder,
+            self.slave, BuilderInteractor(), Exception(fail_notes))
 
     @defer.inlineCallbacks
     def test_equal_failures_reset_job(self):
@@ -686,7 +705,7 @@
     @defer.inlineCallbacks
     def test_virtual_builder_reset_thresholds(self):
         self.builder.virtualized = True
-        self.patch(self.interactor, "resumeSlaveHost", FakeMethod())
+        self.builder.vm_host = 'foo'
 
         for failure_count in range(
             Builder.RESET_THRESHOLD - 1,
@@ -697,7 +716,7 @@
             self.assertEqual(self.build.status, BuildStatus.NEEDSBUILD)
             self.assertEqual(
                 failure_count // Builder.RESET_THRESHOLD,
-                self.interactor.resumeSlaveHost.call_count)
+                self.slave.call_log.count('resume'))
             self.assertTrue(self.builder.builderok)
 
         self.builder.failure_count = (
@@ -707,27 +726,25 @@
         self.assertEqual(self.build.status, BuildStatus.NEEDSBUILD)
         self.assertEqual(
             Builder.RESET_FAILURE_THRESHOLD - 1,
-            self.interactor.resumeSlaveHost.call_count)
+            self.slave.call_log.count('resume'))
         self.assertFalse(self.builder.builderok)
         self.assertEqual("failnotes", self.builder.failnotes)
 
     @defer.inlineCallbacks
     def test_non_virtual_builder_reset_thresholds(self):
         self.builder.virtualized = False
-        self.patch(self.interactor, "resumeSlaveHost", FakeMethod())
-
         self.builder.failure_count = Builder.RESET_THRESHOLD - 1
         yield self._assessFailureCounts("failnotes")
         self.assertIs(None, self.builder.currentjob)
         self.assertEqual(self.build.status, BuildStatus.NEEDSBUILD)
-        self.assertEqual(0, self.interactor.resumeSlaveHost.call_count)
+        self.assertEqual(0, self.slave.call_log.count('resume'))
         self.assertTrue(self.builder.builderok)
 
         self.builder.failure_count = Builder.RESET_THRESHOLD
         yield self._assessFailureCounts("failnotes")
         self.assertIs(None, self.builder.currentjob)
         self.assertEqual(self.build.status, BuildStatus.NEEDSBUILD)
-        self.assertEqual(0, self.interactor.resumeSlaveHost.call_count)
+        self.assertEqual(0, self.slave.call_log.count('resume'))
         self.assertFalse(self.builder.builderok)
         self.assertEqual("failnotes", self.builder.failnotes)
 

=== modified file 'lib/lp/code/model/tests/test_recipebuilder.py'
--- lib/lp/code/model/tests/test_recipebuilder.py	2013-09-23 02:04:18 +0000
+++ lib/lp/code/model/tests/test_recipebuilder.py	2013-10-01 01:11:42 +0000
@@ -369,8 +369,8 @@
             self.addCleanup(config.pop, 'tmp_builddmaster_root')
         self.queue_record.builder = self.factory.makeBuilder()
         slave = WaitingSlave('BuildStatus.OK')
-        interactor = BuilderInteractor(self.queue_record.builder, slave)
-        return removeSecurityProxy(interactor._current_build_behavior)
+        return BuilderInteractor.getBuildBehavior(
+            self.queue_record, self.queue_record.builder, slave)
 
     def assertDeferredNotifyCount(self, status, behavior, expected_count):
         d = behavior.handleStatus(self.queue_record, status, {'filemap': {}})

=== modified file 'lib/lp/soyuz/tests/test_binarypackagebuildbehavior.py'
--- lib/lp/soyuz/tests/test_binarypackagebuildbehavior.py	2013-10-01 01:11:42 +0000
+++ lib/lp/soyuz/tests/test_binarypackagebuildbehavior.py	2013-10-01 01:11:42 +0000
@@ -20,7 +20,7 @@
 from lp.buildmaster.enums import BuildStatus
 from lp.buildmaster.interactor import (
     BuilderInteractor,
-    BuilderSlave,
+    extract_vitals_from_db,
     )
 from lp.buildmaster.interfaces.builder import CannotBuild
 from lp.buildmaster.interfaces.buildfarmjobbehavior import (
@@ -51,7 +51,6 @@
 from lp.soyuz.enums import ArchivePurpose
 from lp.testing import TestCaseWithFactory
 from lp.testing.dbuser import switch_dbuser
-from lp.testing.fakemethod import FakeMethod
 from lp.testing.layers import LaunchpadZopelessLayer
 
 
@@ -72,16 +71,16 @@
         super(TestBinaryBuildPackageBehavior, self).setUp()
         switch_dbuser('testadmin')
 
-    def assertExpectedInteraction(self, ignored, call_log, interactor, build,
+    def assertExpectedInteraction(self, ignored, call_log, builder, build,
                                   chroot, archive, archive_purpose,
                                   component=None, extra_urls=None,
                                   filemap_names=None):
         expected = self.makeExpectedInteraction(
-            interactor, build, chroot, archive, archive_purpose, component,
+            builder, build, chroot, archive, archive_purpose, component,
             extra_urls, filemap_names)
         self.assertEqual(call_log, expected)
 
-    def makeExpectedInteraction(self, interactor, build, chroot, archive,
+    def makeExpectedInteraction(self, builder, build, chroot, archive,
                                 archive_purpose, component=None,
                                 extra_urls=None, filemap_names=None):
         """Build the log of calls that we expect to be made to the slave.
@@ -97,7 +96,8 @@
             in order to trick the slave into building correctly.
         :return: A list of the calls we expect to be made.
         """
-        cookie = interactor._current_build_behavior.getBuildCookie()
+        cookie = IBuildFarmJobBehavior(
+            build.buildqueue_record.specific_job).getBuildCookie()
         ds_name = build.distro_arch_series.distroseries.name
         suite = ds_name + pocketsuffix[build.pocket]
         archives = get_sources_list_for_building(
@@ -128,7 +128,7 @@
         build_log = [
             ('build', cookie, 'binarypackage', chroot.content.sha1,
              filemap_names, extra_args)]
-        if interactor.builder.virtualized:
+        if builder.virtualized:
             result = [('echo', 'ping')] + upload_logs + build_log
         else:
             result = upload_logs + build_log
@@ -143,6 +143,7 @@
         archive = self.factory.makeArchive(virtualized=False)
         slave = OkSlave()
         builder = self.factory.makeBuilder(virtualized=False)
+        vitals = extract_vitals_from_db(builder)
         build = self.factory.makeBinaryPackageBuild(
             builder=builder, archive=archive)
         lf = self.factory.makeLibraryFileAlias()
@@ -150,12 +151,12 @@
         build.distro_arch_series.addOrUpdateChroot(lf)
         bq = build.queueBuild()
         bq.markAsBuilding(builder)
-        interactor = BuilderInteractor(builder, slave)
+        interactor = BuilderInteractor()
         d = interactor._startBuild(
-            bq, interactor.vitals, interactor.builder, interactor.slave,
-            interactor._current_build_behavior, BufferLogger())
+            bq, vitals, builder, slave,
+            interactor.getBuildBehavior(bq, builder, slave), BufferLogger())
         d.addCallback(
-            self.assertExpectedInteraction, slave.call_log, interactor, build,
+            self.assertExpectedInteraction, slave.call_log, builder, build,
             lf, archive, ArchivePurpose.PRIMARY, 'universe')
         return d
 
@@ -166,6 +167,7 @@
         slave = OkSlave()
         builder = self.factory.makeBuilder(
             virtualized=True, vm_host="foohost")
+        vitals = extract_vitals_from_db(builder)
         build = self.factory.makeBinaryPackageBuild(
             builder=builder, archive=archive)
         lf = self.factory.makeLibraryFileAlias()
@@ -173,10 +175,10 @@
         build.distro_arch_series.addOrUpdateChroot(lf)
         bq = build.queueBuild()
         bq.markAsBuilding(builder)
-        interactor = BuilderInteractor(builder, slave)
+        interactor = BuilderInteractor()
         d = interactor._startBuild(
-            bq, interactor.vitals, interactor.builder, interactor.slave,
-            interactor._current_build_behavior, BufferLogger())
+            bq, vitals, builder, slave,
+            interactor.getBuildBehavior(bq, builder, slave), BufferLogger())
 
         def check_build(ignored):
             # We expect the first call to the slave to be a resume call,
@@ -184,7 +186,7 @@
             expected_resume_call = slave.call_log.pop(0)
             self.assertEqual('resume', expected_resume_call)
             self.assertExpectedInteraction(
-                ignored, slave.call_log, interactor, build, lf, archive,
+                ignored, slave.call_log, builder, build, lf, archive,
                 ArchivePurpose.PPA)
         return d.addCallback(check_build)
 
@@ -193,6 +195,7 @@
             virtualized=False, purpose=ArchivePurpose.PARTNER)
         slave = OkSlave()
         builder = self.factory.makeBuilder(virtualized=False)
+        vitals = extract_vitals_from_db(builder)
         build = self.factory.makeBinaryPackageBuild(
             builder=builder, archive=archive)
         lf = self.factory.makeLibraryFileAlias()
@@ -200,12 +203,12 @@
         build.distro_arch_series.addOrUpdateChroot(lf)
         bq = build.queueBuild()
         bq.markAsBuilding(builder)
-        interactor = BuilderInteractor(builder, slave)
+        interactor = BuilderInteractor()
         d = interactor._startBuild(
-            bq, interactor.vitals, interactor.builder, interactor.slave,
-            interactor._current_build_behavior, BufferLogger())
+            bq, vitals, builder, slave,
+            interactor.getBuildBehavior(bq, builder, slave), BufferLogger())
         d.addCallback(
-            self.assertExpectedInteraction, slave.call_log, interactor, build,
+            self.assertExpectedInteraction, slave.call_log, builder, build,
             lf, archive, ArchivePurpose.PARTNER)
         return d
 
@@ -322,7 +325,7 @@
         switch_dbuser('testadmin')
 
         self.builder = self.factory.makeBuilder()
-        self.interactor = BuilderInteractor(self.builder)
+        self.interactor = BuilderInteractor()
         self.build = self.factory.makeBinaryPackageBuild(
             builder=self.builder, pocket=PackagePublishingPocket.RELEASE)
         lf = self.factory.makeLibraryFileAlias()
@@ -334,10 +337,10 @@
         # hang around between test runs.
         self.addCleanup(self._cleanup)
 
-    def updateBuild(self, candidate):
+    def updateBuild(self, candidate, slave):
         return self.interactor.updateBuild(
-            candidate, self.interactor.slave,
-            self.interactor._current_build_behavior)
+            candidate, slave,
+            self.interactor.getBuildBehavior(candidate, self.builder, slave))
 
     def assertBuildProperties(self, build):
         """Check that a build happened by making sure some of its properties
@@ -350,51 +353,39 @@
     def test_packagefail_collection(self):
         # When a package fails to build, make sure the builder notes are
         # stored and the build status is set as failed.
-        waiting_slave = WaitingSlave('BuildStatus.PACKAGEFAIL')
-        self.patch(BuilderSlave, 'makeBuilderSlave',
-                   FakeMethod(waiting_slave))
-
         def got_update(ignored):
             self.assertBuildProperties(self.build)
             self.assertEqual(BuildStatus.FAILEDTOBUILD, self.build.status)
 
-        d = self.updateBuild(self.candidate)
+        d = self.updateBuild(
+            self.candidate, WaitingSlave('BuildStatus.PACKAGEFAIL'))
         return d.addCallback(got_update)
 
     def test_depwait_collection(self):
         # Package build was left in dependency wait.
         DEPENDENCIES = 'baz (>= 1.0.1)'
-        waiting_slave = WaitingSlave('BuildStatus.DEPFAIL', DEPENDENCIES)
-        self.patch(BuilderSlave, 'makeBuilderSlave',
-                   FakeMethod(waiting_slave))
 
         def got_update(ignored):
             self.assertBuildProperties(self.build)
             self.assertEqual(BuildStatus.MANUALDEPWAIT, self.build.status)
             self.assertEqual(DEPENDENCIES, self.build.dependencies)
 
-        d = self.updateBuild(self.candidate)
+        d = self.updateBuild(
+            self.candidate, WaitingSlave('BuildStatus.DEPFAIL', DEPENDENCIES))
         return d.addCallback(got_update)
 
     def test_chrootfail_collection(self):
         # There was a chroot problem for this build.
-        waiting_slave = WaitingSlave('BuildStatus.CHROOTFAIL')
-        self.patch(BuilderSlave, 'makeBuilderSlave',
-                   FakeMethod(waiting_slave))
-
         def got_update(ignored):
             self.assertBuildProperties(self.build)
             self.assertEqual(BuildStatus.CHROOTWAIT, self.build.status)
 
-        d = self.updateBuild(self.candidate)
+        d = self.updateBuild(
+            self.candidate, WaitingSlave('BuildStatus.CHROOTFAIL'))
         return d.addCallback(got_update)
 
     def test_builderfail_collection(self):
         # The builder failed after we dispatched the build.
-        waiting_slave = WaitingSlave('BuildStatus.BUILDERFAIL')
-        self.patch(BuilderSlave, 'makeBuilderSlave',
-                   FakeMethod(waiting_slave))
-
         def got_update(ignored):
             self.assertEqual(
                 "Builder returned BUILDERFAIL when asked for its status",
@@ -404,40 +395,33 @@
             job = self.candidate.specific_job.job
             self.assertEqual(JobStatus.WAITING, job.status)
 
-        d = self.updateBuild(self.candidate)
+        d = self.updateBuild(
+            self.candidate, WaitingSlave('BuildStatus.BUILDERFAIL'))
         return d.addCallback(got_update)
 
     def test_building_collection(self):
         # The builder is still building the package.
-        self.patch(BuilderSlave, 'makeBuilderSlave',
-                   FakeMethod(BuildingSlave()))
-
         def got_update(ignored):
             # The fake log is returned from the BuildingSlave() mock.
             self.assertEqual("This is a build log", self.candidate.logtail)
 
-        d = self.updateBuild(self.candidate)
+        d = self.updateBuild(self.candidate, BuildingSlave())
         return d.addCallback(got_update)
 
     def test_aborting_collection(self):
         # The builder is in the process of aborting.
-        self.patch(BuilderSlave, 'makeBuilderSlave',
-                   FakeMethod(AbortingSlave()))
-
         def got_update(ignored):
             self.assertEqual(
                 "Waiting for slave process to be terminated",
                 self.candidate.logtail)
 
-        d = self.updateBuild(self.candidate)
+        d = self.updateBuild(self.candidate, AbortingSlave())
         return d.addCallback(got_update)
 
     def test_collection_for_deleted_source(self):
         # If we collected a build for a superseded/deleted source then
         # the build should get marked superseded as the build results
         # get discarded.
-        self.patch(BuilderSlave, 'makeBuilderSlave',
-                   FakeMethod(WaitingSlave('BuildStatus.OK')))
         spr = removeSecurityProxy(self.build.source_package_release)
         pub = self.build.current_source_publication
         pub.requestDeletion(spr.creator)
@@ -446,27 +430,21 @@
             self.assertEqual(
                 BuildStatus.SUPERSEDED, self.build.status)
 
-        d = self.updateBuild(self.candidate)
+        d = self.updateBuild(self.candidate, WaitingSlave('BuildStatus.OK'))
         return d.addCallback(got_update)
 
     def test_uploading_collection(self):
         # After a successful build, the status should be UPLOADING.
-        self.patch(BuilderSlave, 'makeBuilderSlave',
-                   FakeMethod(WaitingSlave('BuildStatus.OK')))
-
         def got_update(ignored):
             self.assertEqual(self.build.status, BuildStatus.UPLOADING)
             # We do not store any upload log information when the binary
             # upload processing succeeded.
             self.assertIs(None, self.build.upload_log)
 
-        d = self.updateBuild(self.candidate)
+        d = self.updateBuild(self.candidate, WaitingSlave('BuildStatus.OK'))
         return d.addCallback(got_update)
 
     def test_givenback_collection(self):
-        waiting_slave = WaitingSlave('BuildStatus.GIVENBACK')
-        self.patch(BuilderSlave, 'makeBuilderSlave',
-                   FakeMethod(waiting_slave))
         score = self.candidate.lastscore
 
         def got_update(ignored):
@@ -477,7 +455,8 @@
             job = self.candidate.specific_job.job
             self.assertEqual(JobStatus.WAITING, job.status)
 
-        d = self.updateBuild(self.candidate)
+        d = self.updateBuild(
+            self.candidate, WaitingSlave('BuildStatus.GIVENBACK'))
         return d.addCallback(got_update)
 
     def test_log_file_collection(self):
@@ -532,8 +511,6 @@
     def test_private_build_log_storage(self):
         # Builds in private archives should have their log uploaded to
         # the restricted librarian.
-        self.patch(BuilderSlave, 'makeBuilderSlave',
-                   FakeMethod(WaitingSlave('BuildStatus.OK')))
 
         # Go behind Storm's back since the field validator on
         # Archive.private prevents us from setting it to True with
@@ -548,7 +525,7 @@
             self.layer.txn.commit()
             self.assertTrue(self.build.log.restricted)
 
-        d = self.updateBuild(self.candidate)
+        d = self.updateBuild(self.candidate, WaitingSlave('BuildStatus.OK'))
         return d.addCallback(got_update)
 
 


Follow ups