← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wgrant/launchpad/builderinteractor-c-b-b into lp:launchpad

 

William Grant has proposed merging lp:~wgrant/launchpad/builderinteractor-c-b-b into lp:launchpad.

Commit message:
Move Builder.current_build_behavior to BuilderInteractor, as it's only used by buildd-manager.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/builderinteractor-c-b-b/+merge/182290

This branch eliminates Builder.current_build_behavior. The property now lives on BuilderInteractor, in buildd-manager's exclusive domain, with a cache (possibly unnecessary, but I didn't want to remove the caching yet) predicated on builder.currentjob. It's no longer able to be set directly, though it can be overridden by tests.

Builder._getCurrentBuildBehavior used to always clear the Builder.currentjob cache, which doesn't really fly after the move to BuilderInteractor. Since Builder.currentjob just finds the builder's assigned BuildQueue, I adjusted various BuildQueue methods to invalidate the cache themselves.
-- 
https://code.launchpad.net/~wgrant/launchpad/builderinteractor-c-b-b/+merge/182290
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/builderinteractor-c-b-b into lp:launchpad.
=== modified file 'lib/lp/buildmaster/doc/builder.txt'
--- lib/lp/buildmaster/doc/builder.txt	2012-12-26 01:32:19 +0000
+++ lib/lp/buildmaster/doc/builder.txt	2013-08-27 07:55:38 +0000
@@ -27,19 +27,6 @@
     >>> print builder.failnotes
     None
 
-Builders can take on different behaviors depending on the type of build
-they are currently processing. Each builder provides an attribute
-(current_build_behavior) to which all the build-type specific behavior
-for the current build is delegated. In the sample data, bob's current
-behavior is dealing with binary packages.
-
-    >>> from zope.security.proxy import isinstance
-    >>> from lp.soyuz.model.binarypackagebuildbehavior import (
-    ...     BinaryPackageBuildBehavior)
-    >>> isinstance(
-    ...     builder.current_build_behavior, BinaryPackageBuildBehavior)
-    True
-
 
 BuilderSet
 ==========

=== modified file 'lib/lp/buildmaster/doc/buildfarmjobbehavior.txt'
--- lib/lp/buildmaster/doc/buildfarmjobbehavior.txt	2013-06-20 05:50:00 +0000
+++ lib/lp/buildmaster/doc/buildfarmjobbehavior.txt	2013-08-27 07:55:38 +0000
@@ -90,14 +90,15 @@
 
 First, we'll reset the current build behavior and destroy the current job.
 
-    >>> bob.current_build_behavior = None
     >>> bob.currentjob.destroySelf()
 
 Attempting to use any other build-related functionality when a builder is
 idle, such as making a call to log the start of a build, will raise an
 appropriate exception.
 
-    >>> bob.current_build_behavior.logStartBuild(None)
+    >>> from lp.buildmaster.model.builder import BuilderInteractor
+    >>> interactor = BuilderInteractor(bob)
+    >>> interactor.current_build_behavior.logStartBuild(None)
     Traceback (most recent call last):
      ...
     BuildBehaviorMismatch: Builder was idle when asked to log the start of a
@@ -106,7 +107,7 @@
 If a slave is working on a job while we think it is idle, it will always be
 aborted.
 
-    >>> bob.current_build_behavior.verifySlaveBuildCookie('foo')
+    >>> interactor.current_build_behavior.verifySlaveBuildCookie('foo')
     Traceback (most recent call last):
     ...
     CorruptBuildCookie: No job assigned to builder

=== modified file 'lib/lp/buildmaster/interfaces/builder.py'
--- lib/lp/buildmaster/interfaces/builder.py	2013-08-19 12:01:51 +0000
+++ lib/lp/buildmaster/interfaces/builder.py	2013-08-27 07:55:38 +0000
@@ -169,10 +169,6 @@
         title=_('Failure Count'), required=False, default=0,
        description=_("Number of consecutive failures for this builder.")))
 
-    current_build_behavior = Field(
-        title=u"The current behavior of the builder for the current job.",
-        required=False)
-
     def gotFailure():
         """Increment failure_count on the builder."""
 

=== modified file 'lib/lp/buildmaster/model/builder.py'
--- lib/lp/buildmaster/model/builder.py	2013-08-21 12:16:34 +0000
+++ lib/lp/buildmaster/model/builder.py	2013-08-27 07:55:38 +0000
@@ -20,7 +20,6 @@
 from urlparse import urlparse
 import xmlrpclib
 
-from lazr.restful.utils import safe_hasattr
 from sqlobject import (
     BoolCol,
     ForeignKey,
@@ -42,7 +41,10 @@
 from twisted.web.client import downloadPage
 from zope.component import getUtility
 from zope.interface import implements
-from zope.security.proxy import removeSecurityProxy
+from zope.security.proxy import (
+    isinstance as zope_isinstance,
+    removeSecurityProxy,
+    )
 
 from lp.app.errors import NotFoundError
 from lp.buildmaster.interfaces.builder import (
@@ -76,10 +78,7 @@
 from lp.services.job.model.job import Job
 from lp.services.librarian.interfaces import ILibraryFileAliasSet
 from lp.services.librarian.utils import copy_and_close
-from lp.services.propertycache import (
-    cachedproperty,
-    get_property_cache,
-    )
+from lp.services.propertycache import cachedproperty
 from lp.services.twistedsupport import cancel_on_timeout
 from lp.services.twistedsupport.processmonitor import ProcessWithTimeout
 from lp.services.webapp import urlappend
@@ -393,25 +392,57 @@
 
 class BuilderInteractor(object):
 
-    def __init__(self, builder, override_slave=None):
+    _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._slave = override_slave
+        self._override_slave = override_slave
+        self._override_behavior = override_behavior
 
-    @cachedproperty
+    @property
     def slave(self):
         """See IBuilder."""
-        if self._slave is not None:
-            return self._slave
-        if self.builder.virtualized:
-            timeout = config.builddmaster.virtualized_socket_timeout
-        else:
-            timeout = config.builddmaster.socket_timeout
-        return BuilderSlave.makeBuilderSlave(
-            self.builder.url, self.builder.vm_host, timeout)
+        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.builder.url, self.builder.vm_host, self.builder.virtualized)
+        if self._cached_slave_attrs != new_slave_attrs:
+            if self.builder.virtualized:
+                timeout = config.builddmaster.virtualized_socket_timeout
+            else:
+                timeout = config.builddmaster.socket_timeout
+            self._cached_slave = BuilderSlave.makeBuilderSlave(
+                self.builder.url, self.builder.vm_host, timeout)
+            self._cached_slave_attrs = new_slave_attrs
+        return self._cached_slave
 
     @property
     def current_build_behavior(self):
-        return removeSecurityProxy(self.builder.current_build_behavior)
+        """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:
+            if not isinstance(
+                    self._cached_build_behavior, IdleBuildBehavior):
+                self._cached_build_behavior = IdleBuildBehavior()
+        elif currentjob != self._cached_currentjob:
+            self._cached_build_behavior = currentjob.required_build_behavior
+            self._cached_build_behavior.setBuilderInteractor(self)
+            self._cached_currentjob = currentjob
+        return self._cached_build_behavior
 
     def slaveStatus(self):
         """Get the slave status for this builder.
@@ -553,7 +584,7 @@
 
         return d.addCallback(got_resume_ok).addErrback(got_resume_bad)
 
-    def startBuild(self, build_queue_item, logger):
+    def _startBuild(self, build_queue_item, logger):
         """Start a build on this builder.
 
         :param build_queue_item: A BuildQueueItem to build.
@@ -563,8 +594,12 @@
             value is None, or a Failure that contains an exception
             explaining what went wrong.
         """
-        removeSecurityProxy(self.builder).current_build_behavior = (
-            build_queue_item.required_build_behavior)
+        needed_bfjb = type(removeSecurityProxy(
+            build_queue_item.required_build_behavior))
+        if not zope_isinstance(self.current_build_behavior, needed_bfjb):
+            raise AssertionError(
+                "Inappropriate IBuildFarmJobBehavior: %r is not a %r" %
+                (self.current_build_behavior, needed_bfjb))
         self.current_build_behavior.logStartBuild(logger)
 
         # Make sure the request is valid; an exception is raised if it's not.
@@ -615,7 +650,7 @@
         logger = self._getSlaveScannerLogger()
         # Using maybeDeferred ensures that any exceptions are also
         # wrapped up and caught later.
-        d = defer.maybeDeferred(self.startBuild, candidate, logger)
+        d = defer.maybeDeferred(self._startBuild, candidate, logger)
         return d
 
     def resetOrFail(self, logger, exception):
@@ -771,51 +806,6 @@
     # give up and mark it builderok=False.
     FAILURE_THRESHOLD = 5
 
-    def __storm_invalidated__(self):
-        """Clear cached properties."""
-        super(Builder, self).__storm_invalidated__()
-        self._current_build_behavior = None
-
-    def _getCurrentBuildBehavior(self):
-        """Return the current build behavior."""
-        self._clean_currentjob_cache()
-        if not safe_hasattr(self, '_current_build_behavior'):
-            self._current_build_behavior = None
-
-        if (self._current_build_behavior is None or
-            isinstance(self._current_build_behavior, IdleBuildBehavior)):
-            # If we don't currently have a current build behavior set,
-            # or we are currently idle, then...
-            currentjob = self.currentjob
-            if currentjob is not None:
-                # ...we'll set it based on our current job.
-                self._current_build_behavior = (
-                    currentjob.required_build_behavior)
-                self._current_build_behavior.setBuilderInteractor(
-                    BuilderInteractor(self))
-                return self._current_build_behavior
-            elif self._current_build_behavior is None:
-                # If we don't have a current job or an idle behavior
-                # already set, then we just set the idle behavior
-                # before returning.
-                self._current_build_behavior = IdleBuildBehavior()
-            return self._current_build_behavior
-
-        else:
-            # We did have a current non-idle build behavior set, so
-            # we just return it.
-            return self._current_build_behavior
-
-    def _setCurrentBuildBehavior(self, new_behavior):
-        """Set the current build behavior."""
-        self._current_build_behavior = new_behavior
-        if self._current_build_behavior is not None:
-            self._current_build_behavior.setBuilderInteractor(
-                BuilderInteractor(self))
-
-    current_build_behavior = property(
-        _getCurrentBuildBehavior, _setCurrentBuildBehavior)
-
     def _getBuilderok(self):
         return self._builderok
 
@@ -829,21 +819,16 @@
     def gotFailure(self):
         """See `IBuilder`."""
         self.failure_count += 1
-        self._clean_currentjob_cache()
 
     def resetFailureCount(self):
         """See `IBuilder`."""
         self.failure_count = 0
-        self._clean_currentjob_cache()
 
     @cachedproperty
     def currentjob(self):
         """See IBuilder"""
         return getUtility(IBuildQueueSet).getByBuilder(self)
 
-    def _clean_currentjob_cache(self):
-        del get_property_cache(self).currentjob
-
     def failBuilder(self, reason):
         """See IBuilder"""
         # XXX cprov 2007-04-17: ideally we should be able to notify the

=== modified file 'lib/lp/buildmaster/model/buildqueue.py'
--- lib/lp/buildmaster/model/buildqueue.py	2013-06-20 05:50:00 +0000
+++ lib/lp/buildmaster/model/buildqueue.py	2013-08-27 07:55:38 +0000
@@ -182,9 +182,12 @@
         """Remove this record and associated job/specific_job."""
         job = self.job
         specific_job = self.specific_job
+        builder = self.builder
         SQLBase.destroySelf(self)
         specific_job.cleanUp()
         job.destroySelf()
+        if builder is not None:
+            del get_property_cache(builder).currentjob
         self._clear_specific_job_cache()
 
     def manualScore(self, value):
@@ -219,9 +222,12 @@
         if self.job.status != JobStatus.RUNNING:
             self.job.start()
         self.specific_job.jobStarted()
+        if builder is not None:
+            del get_property_cache(builder).currentjob
 
     def reset(self):
         """See `IBuildQueue`."""
+        builder = self.builder
         self.builder = None
         if self.job.status != JobStatus.WAITING:
             self.job.queue()
@@ -229,6 +235,8 @@
         self.job.date_finished = None
         self.logtail = None
         self.specific_job.jobReset()
+        if builder is not None:
+            del get_property_cache(builder).currentjob
 
     def cancel(self):
         """See `IBuildQueue`."""

=== modified file 'lib/lp/buildmaster/tests/mock_slaves.py'
--- lib/lp/buildmaster/tests/mock_slaves.py	2013-08-21 07:41:34 +0000
+++ lib/lp/buildmaster/tests/mock_slaves.py	2013-08-27 07:55:38 +0000
@@ -37,7 +37,6 @@
     CorruptBuildCookie,
     )
 from lp.buildmaster.model.builder import BuilderSlave
-from lp.buildmaster.model.buildfarmjobbehavior import IdleBuildBehavior
 from lp.services.config import config
 from lp.testing.sampledata import I386_ARCHITECTURE_NAME
 
@@ -52,9 +51,8 @@
 class MockBuilder:
     """Emulates a IBuilder class."""
 
-    def __init__(self, name='mock-builder', behavior=None, builderok=True,
-                 manual=False, virtualized=True, vm_host=None):
-        self.current_build_behavior = behavior or IdleBuildBehavior()
+    def __init__(self, name='mock-builder', builderok=True, manual=False,
+                 virtualized=True, vm_host=None):
         self.currentjob = None
         self.builderok = builderok
         self.manual = manual

=== modified file 'lib/lp/buildmaster/tests/test_builder.py'
--- lib/lp/buildmaster/tests/test_builder.py	2013-08-21 07:41:34 +0000
+++ lib/lp/buildmaster/tests/test_builder.py	2013-08-27 07:55:38 +0000
@@ -229,24 +229,23 @@
         builder, build = self._setupBinaryBuildAndBuilder()
         candidate = build.queueBuild()
         building_slave = BuildingSlave()
-        self.patch(
-            BuilderSlave, 'makeBuilderSlave', FakeMethod(building_slave))
+        interactor = BuilderInteractor(builder, building_slave)
         candidate.markAsBuilding(builder)
 
         # At this point we should see a valid behaviour on the builder:
         self.assertFalse(
             zope_isinstance(
-                builder.current_build_behavior, IdleBuildBehavior))
+                interactor.current_build_behavior, IdleBuildBehavior))
 
         # Now reset the job and try to rescue the builder.
         candidate.destroySelf()
         self.layer.txn.commit()
         builder = getUtility(IBuilderSet)[builder.name]
-        d = BuilderInteractor(builder).rescueIfLost()
+        d = interactor.rescueIfLost()
 
         def check_builder(ignored):
             self.assertIsInstance(
-                removeSecurityProxy(builder.current_build_behavior),
+                removeSecurityProxy(interactor.current_build_behavior),
                 IdleBuildBehavior)
 
         return d.addCallback(check_builder)
@@ -260,9 +259,8 @@
         # A slave that's 'lost' should be aborted; when the slave is
         # broken then abort() should also throw a fault.
         slave = LostBuildingBrokenSlave()
-        lostbuilding_builder = MockBuilder(behavior=CorruptBehavior())
-        d = BuilderInteractor(lostbuilding_builder, slave).updateStatus(
-            BufferLogger())
+        interactor = BuilderInteractor(MockBuilder(), slave, CorruptBehavior())
+        d = interactor.updateStatus(BufferLogger())
 
         def check_slave_status(failure):
             self.assertIn('abort', slave.call_log)
@@ -360,8 +358,8 @@
     def test_recover_ok_slave(self):
         # An idle slave is not rescued.
         slave = OkSlave()
-        builder = MockBuilder(behavior=TrivialBehavior())
-        d = BuilderInteractor(builder, slave).rescueIfLost()
+        d = BuilderInteractor(
+            MockBuilder(), slave, TrivialBehavior()).rescueIfLost()
 
         def check_slave_calls(ignored):
             self.assertNotIn('abort', slave.call_log)
@@ -373,8 +371,8 @@
         # rescueIfLost does not attempt to abort or clean a builder that is
         # WAITING.
         waiting_slave = WaitingSlave()
-        builder = MockBuilder(behavior=TrivialBehavior())
-        d = BuilderInteractor(builder, waiting_slave).rescueIfLost()
+        d = BuilderInteractor(
+            MockBuilder(), waiting_slave, TrivialBehavior()).rescueIfLost()
 
         def check_slave_calls(ignored):
             self.assertNotIn('abort', waiting_slave.call_log)
@@ -389,8 +387,8 @@
         # builder is reset for a new build, and the corrupt build is
         # discarded.
         waiting_slave = WaitingSlave()
-        builder = MockBuilder(behavior=CorruptBehavior())
-        d = BuilderInteractor(builder, waiting_slave).rescueIfLost()
+        d = BuilderInteractor(
+            MockBuilder(), waiting_slave, CorruptBehavior()).rescueIfLost()
 
         def check_slave_calls(ignored):
             self.assertNotIn('abort', waiting_slave.call_log)
@@ -402,8 +400,8 @@
         # rescueIfLost does not attempt to abort or clean a builder that is
         # BUILDING.
         building_slave = BuildingSlave()
-        builder = MockBuilder(behavior=TrivialBehavior())
-        d = BuilderInteractor(builder, building_slave).rescueIfLost()
+        d = BuilderInteractor(
+            MockBuilder(), building_slave, TrivialBehavior()).rescueIfLost()
 
         def check_slave_calls(ignored):
             self.assertNotIn('abort', building_slave.call_log)
@@ -415,8 +413,8 @@
         # If a slave is BUILDING with a build id we don't recognize, then we
         # abort the build, thus stopping it in its tracks.
         building_slave = BuildingSlave()
-        builder = MockBuilder(behavior=CorruptBehavior())
-        d = BuilderInteractor(builder, building_slave).rescueIfLost()
+        d = BuilderInteractor(
+            MockBuilder(), building_slave, CorruptBehavior()).rescueIfLost()
 
         def check_slave_calls(ignored):
             self.assertIn('abort', building_slave.call_log)
@@ -821,7 +819,7 @@
 
 
 class TestCurrentBuildBehavior(TestCaseWithFactory):
-    """This test ensures the get/set behavior of IBuilder's
+    """This test ensures the get/set behavior of BuilderInteractor's
     current_build_behavior property.
     """
 
@@ -831,6 +829,7 @@
         """Create a new builder ready for testing."""
         super(TestCurrentBuildBehavior, self).setUp()
         self.builder = self.factory.makeBuilder(name='builder')
+        self.interactor = BuilderInteractor(self.builder)
 
         # Have a publisher and a ppa handy for some of the tests below.
         self.publisher = make_publisher()
@@ -848,25 +847,17 @@
         nor a current build.
         """
         self.assertIsInstance(
-            self.builder.current_build_behavior, IdleBuildBehavior)
-
-    def test_set_behavior_sets_builder(self):
-        """Setting a builder's behavior also associates the behavior with the
-        builder."""
-        behavior = IBuildFarmJobBehavior(self.buildfarmjob)
-        self.builder.current_build_behavior = behavior
-
-        self.assertEqual(behavior, self.builder.current_build_behavior)
-        self.assertEqual(behavior._builder, self.builder)
+            self.interactor.current_build_behavior, IdleBuildBehavior)
 
     def test_current_job_behavior(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.
         self.build.buildqueue_record.builder = self.builder
-
-        self.assertIsInstance(
-            self.builder.current_build_behavior, BinaryPackageBuildBehavior)
+        behavior = removeSecurityProxy(self.interactor.current_build_behavior)
+        self.assertIsInstance(behavior, BinaryPackageBuildBehavior)
+        self.assertEqual(behavior._builder, self.builder)
+        self.assertEqual(behavior._interactor, self.interactor)
 
 
 class TestSlave(TestCase):

=== modified file 'lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py'
--- lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py	2013-07-29 17:31:27 +0000
+++ lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py	2013-08-27 07:55:38 +0000
@@ -16,7 +16,7 @@
 from lp.archiveuploader.uploadprocessor import parse_build_upload_leaf_name
 from lp.buildmaster.enums import BuildStatus
 from lp.buildmaster.interfaces.builder import CorruptBuildCookie
-from lp.buildmaster.model.builder import BuilderSlave
+from lp.buildmaster.model.builder import BuilderInteractor
 from lp.buildmaster.model.buildfarmjobbehavior import BuildFarmJobBehaviorBase
 from lp.buildmaster.tests.mock_slaves import WaitingSlave
 from lp.registry.interfaces.pocket import PackagePublishingPocket
@@ -217,11 +217,11 @@
         self.builder = self.factory.makeBuilder()
         self.build.buildqueue_record.builder = self.builder
         self.build.buildqueue_record.setDateStarted(UTC_NOW)
-        self.behavior = removeSecurityProxy(
-            self.builder.current_build_behavior)
         self.slave = WaitingSlave('BuildStatus.OK')
         self.slave.valid_file_hashes.append('test_file_hash')
-        self.patch(BuilderSlave, 'makeBuilderSlave', FakeMethod(self.slave))
+        self.interactor = BuilderInteractor(self.builder, self.slave)
+        self.behavior = removeSecurityProxy(
+            self.interactor.current_build_behavior)
 
         # We overwrite the buildmaster root to use a temp directory.
         tempdir = tempfile.mkdtemp()

=== modified file 'lib/lp/code/model/tests/test_recipebuilder.py'
--- lib/lp/code/model/tests/test_recipebuilder.py	2013-08-19 23:23:19 +0000
+++ lib/lp/code/model/tests/test_recipebuilder.py	2013-08-27 07:55:38 +0000
@@ -375,7 +375,8 @@
         queue_record.builder = self.factory.makeBuilder()
         slave = WaitingSlave('BuildStatus.OK')
         self.patch(BuilderSlave, 'makeBuilderSlave', FakeMethod(slave))
-        return removeSecurityProxy(queue_record.builder.current_build_behavior)
+        interactor = BuilderInteractor(queue_record.builder)
+        return removeSecurityProxy(interactor.current_build_behavior)
 
     def assertDeferredNotifyCount(self, status, behavior, expected_count):
         d = behavior.handleStatus(status, None, {'filemap': {}})

=== modified file 'lib/lp/soyuz/browser/tests/builder-views.txt'
--- lib/lp/soyuz/browser/tests/builder-views.txt	2013-01-22 02:06:59 +0000
+++ lib/lp/soyuz/browser/tests/builder-views.txt	2013-08-27 07:55:38 +0000
@@ -220,7 +220,6 @@
     >>> login("foo.bar@xxxxxxxxxxxxx")
     >>> private_job = store.get(BuildQueue, private_job_id)
     >>> private_job.builder = None
-    >>> frog.current_build_behavior = None
 
     >>> login('no-priv@xxxxxxxxxxxxx')
     >>> nopriv_view = getMultiAdapter((frog, empty_request), name="+index")

=== modified file 'lib/lp/soyuz/tests/test_binarypackagebuildbehavior.py'
--- lib/lp/soyuz/tests/test_binarypackagebuildbehavior.py	2013-08-22 04:07:24 +0000
+++ lib/lp/soyuz/tests/test_binarypackagebuildbehavior.py	2013-08-27 07:55:38 +0000
@@ -13,7 +13,6 @@
 from storm.store import Store
 from testtools.deferredruntest import AsynchronousDeferredRunTest
 import transaction
-from twisted.internet import defer
 from twisted.trial.unittest import TestCase as TrialTestCase
 from zope.component import getUtility
 from zope.security.proxy import removeSecurityProxy
@@ -48,6 +47,9 @@
     get_sources_list_for_building,
     )
 from lp.soyuz.enums import ArchivePurpose
+from lp.soyuz.model.binarypackagebuildbehavior import (
+    BinaryPackageBuildBehavior,
+    )
 from lp.testing import TestCaseWithFactory
 from lp.testing.dbuser import switch_dbuser
 from lp.testing.fakemethod import FakeMethod
@@ -71,16 +73,16 @@
         super(TestBinaryBuildPackageBehavior, self).setUp()
         switch_dbuser('testadmin')
 
-    def assertExpectedInteraction(self, ignored, call_log, builder, build,
+    def assertExpectedInteraction(self, ignored, call_log, interactor, build,
                                   chroot, archive, archive_purpose,
                                   component=None, extra_urls=None,
                                   filemap_names=None):
         expected = self.makeExpectedInteraction(
-            builder, build, chroot, archive, archive_purpose, component,
+            interactor, build, chroot, archive, archive_purpose, component,
             extra_urls, filemap_names)
         self.assertEqual(call_log, expected)
 
-    def makeExpectedInteraction(self, builder, build, chroot, archive,
+    def makeExpectedInteraction(self, interactor, 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.
@@ -96,7 +98,8 @@
             in order to trick the slave into building correctly.
         :return: A list of the calls we expect to be made.
         """
-        job = removeSecurityProxy(builder.current_build_behavior).buildfarmjob
+        job = removeSecurityProxy(
+            interactor.current_build_behavior).buildfarmjob
         build_id = job.generateSlaveBuildCookie()
         ds_name = build.distro_arch_series.distroseries.name
         suite = ds_name + pocketsuffix[build.pocket]
@@ -128,18 +131,12 @@
         build_log = [
             ('build', build_id, 'binarypackage', chroot.content.sha1,
              filemap_names, extra_args)]
-        if builder.virtualized:
+        if interactor.builder.virtualized:
             result = [('echo', 'ping')] + upload_logs + build_log
         else:
             result = upload_logs + build_log
         return result
 
-    def startBuild(self, builder, candidate):
-        builder = removeSecurityProxy(builder)
-        candidate = removeSecurityProxy(candidate)
-        return defer.maybeDeferred(
-            BuilderInteractor(builder).startBuild, candidate, BufferLogger())
-
     def test_non_virtual_ppa_dispatch(self):
         # When the BinaryPackageBuildBehavior dispatches PPA builds to
         # non-virtual builders, it stores the chroot on the server and
@@ -149,17 +146,18 @@
         archive = self.factory.makeArchive(virtualized=False)
         slave = OkSlave()
         builder = self.factory.makeBuilder(virtualized=False)
-        self.patch(BuilderSlave, 'makeBuilderSlave', FakeMethod(slave))
         build = self.factory.makeBinaryPackageBuild(
             builder=builder, archive=archive)
         lf = self.factory.makeLibraryFileAlias()
         transaction.commit()
         build.distro_arch_series.addOrUpdateChroot(lf)
-        candidate = build.queueBuild()
-        d = self.startBuild(builder, candidate)
+        bq = build.queueBuild()
+        bq.markAsBuilding(builder)
+        interactor = BuilderInteractor(builder, slave)
+        d = interactor._startBuild(bq, BufferLogger())
         d.addCallback(
-            self.assertExpectedInteraction, slave.call_log,
-            builder, build, lf, archive, ArchivePurpose.PRIMARY, 'universe')
+            self.assertExpectedInteraction, slave.call_log, interactor, build,
+            lf, archive, ArchivePurpose.PRIMARY, 'universe')
         return d
 
     def test_virtual_ppa_dispatch(self):
@@ -169,14 +167,15 @@
         slave = OkSlave()
         builder = self.factory.makeBuilder(
             virtualized=True, vm_host="foohost")
-        self.patch(BuilderSlave, 'makeBuilderSlave', FakeMethod(slave))
         build = self.factory.makeBinaryPackageBuild(
             builder=builder, archive=archive)
         lf = self.factory.makeLibraryFileAlias()
         transaction.commit()
         build.distro_arch_series.addOrUpdateChroot(lf)
-        candidate = build.queueBuild()
-        d = self.startBuild(builder, candidate)
+        bq = build.queueBuild()
+        bq.markAsBuilding(builder)
+        interactor = BuilderInteractor(builder, slave)
+        d = interactor._startBuild(bq, BufferLogger())
 
         def check_build(ignored):
             # We expect the first call to the slave to be a resume call,
@@ -184,8 +183,8 @@
             expected_resume_call = slave.call_log.pop(0)
             self.assertEqual('resume', expected_resume_call)
             self.assertExpectedInteraction(
-                ignored, slave.call_log,
-                builder, build, lf, archive, ArchivePurpose.PPA)
+                ignored, slave.call_log, interactor, build, lf, archive,
+                ArchivePurpose.PPA)
         return d.addCallback(check_build)
 
     def test_partner_dispatch_no_publishing_history(self):
@@ -193,17 +192,18 @@
             virtualized=False, purpose=ArchivePurpose.PARTNER)
         slave = OkSlave()
         builder = self.factory.makeBuilder(virtualized=False)
-        self.patch(BuilderSlave, 'makeBuilderSlave', FakeMethod(slave))
         build = self.factory.makeBinaryPackageBuild(
             builder=builder, archive=archive)
         lf = self.factory.makeLibraryFileAlias()
         transaction.commit()
         build.distro_arch_series.addOrUpdateChroot(lf)
-        candidate = build.queueBuild()
-        d = self.startBuild(builder, candidate)
+        bq = build.queueBuild()
+        bq.markAsBuilding(builder)
+        interactor = BuilderInteractor(builder, slave)
+        d = interactor._startBuild(bq, BufferLogger())
         d.addCallback(
-            self.assertExpectedInteraction, slave.call_log,
-            builder, build, lf, archive, ArchivePurpose.PARTNER)
+            self.assertExpectedInteraction, slave.call_log, interactor, build,
+            lf, archive, ArchivePurpose.PARTNER)
         return d
 
     def test_dont_dispatch_release_builds(self):


Follow ups