launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #14904
[Merge] lp:~wgrant/launchpad/handleStatus-to-bfjb into lp:launchpad
William Grant has proposed merging lp:~wgrant/launchpad/handleStatus-to-bfjb into lp:launchpad.
Commit message:
Migrate Twisted bits of PackageBuild onto BuildFarmJobBehavior. More preparation for the BuildFarmJob flattening.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~wgrant/launchpad/handleStatus-to-bfjb/+merge/142642
As part of the BuildFarmJob flattening I need to minimise the visible interface, and particularly eliminate as many direct attribute writes as possible. The _handleStatus* methods are particularly bad offenders, so as a first step toward their refactoring I've moved them out of PackageBuild and onto BuildFarmJobBehavior. This has the benefit of removing Twisted code from the DB model objects.
The next step will be to make them use state transition methods on the BuildFarmJobs, rather than poking attributes directly.
Lots of tests needed updating for the move.
--
https://code.launchpad.net/~wgrant/launchpad/handleStatus-to-bfjb/+merge/142642
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/handleStatus-to-bfjb into lp:launchpad.
=== modified file 'lib/lp/archiveuploader/tests/test_uploadprocessor.py'
--- lib/lp/archiveuploader/tests/test_uploadprocessor.py 2013-01-04 07:52:40 +0000
+++ lib/lp/archiveuploader/tests/test_uploadprocessor.py 2013-01-10 07:23:23 +0000
@@ -48,6 +48,9 @@
BuildFarmJobType,
BuildStatus,
)
+from lp.buildmaster.interfaces.buildfarmjobbehavior import (
+ IBuildFarmJobBehavior,
+ )
from lp.registry.interfaces.distribution import IDistributionSet
from lp.registry.interfaces.person import IPersonSet
from lp.registry.interfaces.pocket import PackagePublishingPocket
@@ -2099,7 +2102,8 @@
# Commit so the build cookie has the right ids.
self.layer.txn.commit()
- leaf_name = build.getUploadDirLeaf(build.getBuildCookie())
+ behavior = IBuildFarmJobBehavior(build.buildqueue_record.specific_job)
+ leaf_name = behavior.getUploadDirLeaf(behavior.getBuildCookie())
os.mkdir(os.path.join(self.incoming_folder, leaf_name))
self.options.context = 'buildd'
self.options.builds = True
@@ -2141,7 +2145,8 @@
# Commit so the build cookie has the right ids.
self.layer.txn.commit()
- leaf_name = build.getUploadDirLeaf(build.getBuildCookie())
+ behavior = IBuildFarmJobBehavior(build.buildqueue_record.specific_job)
+ leaf_name = behavior.getUploadDirLeaf(behavior.getBuildCookie())
upload_dir = self.queueUpload("bar_1.0-1_binary",
queue_entry=leaf_name)
self.options.context = 'buildd'
@@ -2165,11 +2170,12 @@
distroseries=self.breezy, archive=archive,
requester=archive.owner)
self.assertEquals(archive.owner, build.requester)
- self.factory.makeSourcePackageRecipeBuildJob(recipe_build=build)
+ bq = self.factory.makeSourcePackageRecipeBuildJob(recipe_build=build)
self.switchToUploader()
# Commit so the build cookie has the right ids.
self.layer.txn.commit()
- leaf_name = build.getUploadDirLeaf(build.getBuildCookie())
+ behavior = IBuildFarmJobBehavior(bq.specific_job)
+ leaf_name = behavior.getUploadDirLeaf(behavior.getBuildCookie())
relative_path = "~%s/%s/%s/%s" % (
archive.owner.name, archive.name, self.breezy.distribution.name,
self.breezy.name)
@@ -2215,10 +2221,11 @@
archive.require_virtualized = False
build = self.factory.makeSourcePackageRecipeBuild(sourcename=u"bar",
distroseries=self.breezy, archive=archive)
- self.factory.makeSourcePackageRecipeBuildJob(recipe_build=build)
+ bq = self.factory.makeSourcePackageRecipeBuildJob(recipe_build=build)
# Commit so the build cookie has the right ids.
Store.of(build).flush()
- leaf_name = build.getUploadDirLeaf(build.getBuildCookie())
+ behavior = IBuildFarmJobBehavior(bq.specific_job)
+ leaf_name = behavior.getUploadDirLeaf(behavior.getBuildCookie())
os.mkdir(os.path.join(self.incoming_folder, leaf_name))
self.options.context = 'buildd'
self.options.builds = True
@@ -2262,11 +2269,12 @@
archive.require_virtualized = False
build = self.factory.makeSourcePackageRecipeBuild(sourcename=u"bar",
distroseries=self.breezy, archive=archive)
- self.factory.makeSourcePackageRecipeBuildJob(recipe_build=build)
+ bq = self.factory.makeSourcePackageRecipeBuildJob(recipe_build=build)
self.switchToUploader()
# Commit so the build cookie has the right ids.
Store.of(build).flush()
- leaf_name = build.getUploadDirLeaf(build.getBuildCookie())
+ behavior = IBuildFarmJobBehavior(bq.specific_job)
+ leaf_name = behavior.getUploadDirLeaf(behavior.getBuildCookie())
os.mkdir(os.path.join(self.incoming_folder, leaf_name))
self.options.context = 'buildd'
self.options.builds = True
@@ -2314,7 +2322,8 @@
# Commit so the build cookie has the right ids.
self.layer.txn.commit()
- leaf_name = build.getUploadDirLeaf(build.getBuildCookie())
+ behavior = IBuildFarmJobBehavior(build.buildqueue_record.specific_job)
+ leaf_name = behavior.getUploadDirLeaf(behavior.getBuildCookie())
upload_dir = self.queueUpload(
"bar_1.0-1_binary", queue_entry=leaf_name)
self.options.context = 'buildd'
=== modified file 'lib/lp/buildmaster/interfaces/packagebuild.py'
--- lib/lp/buildmaster/interfaces/packagebuild.py 2013-01-07 06:39:32 +0000
+++ lib/lp/buildmaster/interfaces/packagebuild.py 2013-01-10 07:23:23 +0000
@@ -92,35 +92,9 @@
title=_("Distribution series"), required=True,
description=_("Shortcut for its distribution series.")))
- def getUploadDirLeaf(build_cookie, now=None):
- """Return the directory-leaf where files to be uploaded are stored.
-
- :param build_cookie: The build cookie as returned by the slave.
- :param now: The `datetime` to use when constructing the leaf
- directory name. If not provided, defaults to now.
- """
-
- def getBuildCookie():
- """Return the build cookie (build id and build queue record id).
- """
-
- def getLogFromSlave(build):
- """Get last buildlog from slave.
-
- :return: A Deferred that fires with the librarian ID of the log
- when the log is finished downloading.
- """
-
def estimateDuration():
"""Estimate the build duration."""
- def storeBuildInfo(build, librarian, slave_status):
- """Store available information for the build job.
-
- Derived classes can override this as needed, and call it from
- custom status handlers, but it should not be called externally.
- """
-
def verifySuccessfulUpload():
"""Verify that the upload of this build completed succesfully."""
@@ -140,14 +114,6 @@
upload log.
"""
- def handleStatus(status, librarian, slave_status):
- """Handle a finished build status from a slave.
-
- :param status: Slave build status string with 'BuildStatus.' stripped.
- :param slave_status: A dict as returned by IBuilder.slaveStatus
- :return: A Deferred that fires when finished dealing with the build.
- """
-
def queueBuild(suspended=False):
"""Create a BuildQueue entry for this build.
=== modified file 'lib/lp/buildmaster/model/buildfarmjobbehavior.py'
--- lib/lp/buildmaster/model/buildfarmjobbehavior.py 2013-01-07 02:40:55 +0000
+++ lib/lp/buildmaster/model/buildfarmjobbehavior.py 2013-01-10 07:23:23 +0000
@@ -10,15 +10,23 @@
'IdleBuildBehavior',
]
+import datetime
import logging
+import os.path
import socket
import xmlrpclib
+import pytz
+from storm.store import Store
from twisted.internet import defer
from zope.component import getUtility
from zope.interface import implements
from zope.security.proxy import removeSecurityProxy
+from lp.buildmaster.enums import (
+ BuildFarmJobType,
+ BuildStatus,
+ )
from lp.buildmaster.interfaces.builder import (
BuildSlaveFailure,
CorruptBuildCookie,
@@ -28,10 +36,14 @@
IBuildFarmJobBehavior,
)
from lp.services import encoding
+from lp.services.config import config
from lp.services.job.interfaces.job import JobStatus
from lp.services.librarian.interfaces.client import ILibrarianClient
+SLAVE_LOG_FILENAME = 'buildlog'
+
+
class BuildFarmJobBehaviorBase:
"""Ensures that all behaviors inherit the same initialization.
@@ -67,6 +79,46 @@
if slave_build_cookie != expected_cookie:
raise CorruptBuildCookie("Invalid slave build cookie.")
+ def getBuildCookie(self):
+ """See `IPackageBuild`."""
+ return '%s-%s' % (self.build.job_type.name, self.build.id)
+
+ def getUploadDirLeaf(self, build_cookie, now=None):
+ """See `IPackageBuild`."""
+ if now is None:
+ now = datetime.datetime.now()
+ timestamp = now.strftime("%Y%m%d-%H%M%S")
+ return '%s-%s' % (timestamp, build_cookie)
+
+ @staticmethod
+ def getLogFromSlave(build, queue_item):
+ """See `IPackageBuild`."""
+ d = queue_item.builder.transferSlaveFileToLibrarian(
+ SLAVE_LOG_FILENAME, queue_item.getLogFileName(),
+ build.is_private)
+ return d
+
+ @classmethod
+ def storeBuildInfo(cls, build, librarian, slave_status):
+ """See `IPackageBuild`."""
+ def got_log(lfa_id):
+ # log, builder and date_finished are read-only, so we must
+ # currently remove the security proxy to set them.
+ naked_build = removeSecurityProxy(build)
+ naked_build.log = lfa_id
+ naked_build.builder = build.buildqueue_record.builder
+ # XXX cprov 20060615 bug=120584: Currently buildduration includes
+ # the scanner latency, it should really be asking the slave for
+ # the duration spent building locally.
+ naked_build.date_finished = datetime.datetime.now(pytz.UTC)
+ if slave_status.get('dependencies') is not None:
+ build.dependencies = unicode(slave_status.get('dependencies'))
+ else:
+ build.dependencies = None
+
+ d = cls.getLogFromSlave(build, build.buildqueue_record)
+ return d.addCallback(got_log)
+
def updateBuild(self, queueItem):
"""See `IBuildFarmJobBehavior`."""
logger = logging.getLogger('slave-scanner')
@@ -148,6 +200,7 @@
Clean the builder for another jobs.
"""
d = queueItem.builder.cleanSlave()
+
def got_cleaned(ignored):
queueItem.builder = None
if queueItem.job.status != JobStatus.FAILED:
@@ -186,10 +239,250 @@
build_status = self.extractBuildStatus(slave_status)
# XXX: dsilvers 2005-03-02: Confirm the builder has the right build?
-
- build = queueItem.specific_job.build
- d = build.handleStatus(build_status, librarian, slave_status)
- return d
+ d = self.handleStatus(build_status, librarian, slave_status)
+ return d
+
+ # The list of build status values for which email notifications are
+ # allowed to be sent. It is up to each callback as to whether it will
+ # consider sending a notification but it won't do so if the status is not
+ # in this list.
+ ALLOWED_STATUS_NOTIFICATIONS = ['OK', 'PACKAGEFAIL', 'CHROOTFAIL']
+
+ def handleStatus(self, status, librarian, slave_status):
+ """See `IPackageBuild`."""
+ from lp.buildmaster.manager import BUILDD_MANAGER_LOG_NAME
+ logger = logging.getLogger(BUILDD_MANAGER_LOG_NAME)
+ send_notification = status in self.ALLOWED_STATUS_NOTIFICATIONS
+ method = getattr(self, '_handleStatus_' + status, None)
+ if method is None:
+ logger.critical(
+ "Unknown BuildStatus '%s' for builder '%s'"
+ % (status, self.build.buildqueue_record.builder.url))
+ return
+ d = method(librarian, slave_status, logger, send_notification)
+ return d
+
+ def _release_builder_and_remove_queue_item(self):
+ # Release the builder for another job.
+ d = self.build.buildqueue_record.builder.cleanSlave()
+ # Remove BuildQueue record.
+ return d.addCallback(
+ lambda x: self.build.buildqueue_record.destroySelf())
+
+ def _handleStatus_OK(self, librarian, slave_status, logger,
+ send_notification):
+ """Handle a package that built successfully.
+
+ Once built successfully, we pull the files, store them in a
+ directory, store build information and push them through the
+ uploader.
+ """
+ build = self.build
+ filemap = slave_status['filemap']
+
+ logger.info("Processing successful build %s from builder %s" % (
+ build.buildqueue_record.specific_job.build.title,
+ build.buildqueue_record.builder.name))
+
+ # If this is a binary package build, discard it if its source is
+ # no longer published.
+ if build.build_farm_job_type == BuildFarmJobType.PACKAGEBUILD:
+ build = build.buildqueue_record.specific_job.build
+ if not build.current_source_publication:
+ build.status = BuildStatus.SUPERSEDED
+ return self._release_builder_and_remove_queue_item()
+
+ # Explode before collect a binary that is denied in this
+ # distroseries/pocket/archive
+ assert build.archive.canModifySuite(
+ build.distro_series, build.pocket), (
+ "%s (%s) can not be built for pocket %s in %s: illegal status"
+ % (build.title, build.id, build.pocket.name, build.archive))
+
+ # Ensure we have the correct build root as:
+ # <BUILDMASTER_ROOT>/incoming/<UPLOAD_LEAF>/<TARGET_PATH>/[FILES]
+ root = os.path.abspath(config.builddmaster.root)
+
+ # Create a single directory to store build result files.
+ upload_leaf = self.getUploadDirLeaf(self.getBuildCookie())
+ grab_dir = os.path.join(root, "grabbing", upload_leaf)
+ logger.debug("Storing build result at '%s'" % grab_dir)
+
+ # Build the right UPLOAD_PATH so the distribution and archive
+ # can be correctly found during the upload:
+ # <archive_id>/distribution_name
+ # for all destination archive types.
+ upload_path = os.path.join(
+ grab_dir, str(build.archive.id), build.distribution.name)
+ os.makedirs(upload_path)
+
+ slave = removeSecurityProxy(build.buildqueue_record.builder.slave)
+ successful_copy_from_slave = True
+ filenames_to_download = {}
+ for filename in filemap:
+ logger.info("Grabbing file: %s" % filename)
+ out_file_name = os.path.join(upload_path, filename)
+ # If the evaluated output file name is not within our
+ # upload path, then we don't try to copy this or any
+ # subsequent files.
+ if not os.path.realpath(out_file_name).startswith(upload_path):
+ successful_copy_from_slave = False
+ logger.warning(
+ "A slave tried to upload the file '%s' "
+ "for the build %d." % (filename, build.id))
+ break
+ filenames_to_download[filemap[filename]] = out_file_name
+
+ def build_info_stored(ignored):
+ # We only attempt the upload if we successfully copied all the
+ # files from the slave.
+ if successful_copy_from_slave:
+ logger.info(
+ "Gathered %s %d completely. Moving %s to uploader queue."
+ % (build.__class__.__name__, build.id, upload_leaf))
+ target_dir = os.path.join(root, "incoming")
+ build.status = BuildStatus.UPLOADING
+ else:
+ logger.warning(
+ "Copy from slave for build %s was unsuccessful.", build.id)
+ build.status = BuildStatus.FAILEDTOUPLOAD
+ if send_notification:
+ build.notify(
+ extra_info='Copy from slave was unsuccessful.')
+ target_dir = os.path.join(root, "failed")
+
+ if not os.path.exists(target_dir):
+ os.mkdir(target_dir)
+
+ # Release the builder for another job.
+ d = self._release_builder_and_remove_queue_item()
+
+ # Commit so there are no race conditions with archiveuploader
+ # about build.status.
+ Store.of(build).commit()
+
+ # Move the directory used to grab the binaries into
+ # the incoming directory so the upload processor never
+ # sees half-finished uploads.
+ os.rename(grab_dir, os.path.join(target_dir, upload_leaf))
+
+ return d
+
+ d = slave.getFiles(filenames_to_download)
+ # Store build information, build record was already updated during
+ # the binary upload.
+ d.addCallback(
+ lambda x: self.storeBuildInfo(build, librarian, slave_status))
+ d.addCallback(build_info_stored)
+ return d
+
+ def _handleStatus_PACKAGEFAIL(self, librarian, slave_status, logger,
+ send_notification):
+ """Handle a package that had failed to build.
+
+ Build has failed when trying the work with the target package,
+ set the job status as FAILEDTOBUILD, store available info and
+ remove Buildqueue entry.
+ """
+ self.build.status = BuildStatus.FAILEDTOBUILD
+
+ def build_info_stored(ignored):
+ if send_notification:
+ self.build.notify()
+ d = self.build.buildqueue_record.builder.cleanSlave()
+ return d.addCallback(
+ lambda x: self.build.buildqueue_record.destroySelf())
+
+ d = self.storeBuildInfo(self.build, librarian, slave_status)
+ return d.addCallback(build_info_stored)
+
+ def _handleStatus_DEPFAIL(self, librarian, slave_status, logger,
+ send_notification):
+ """Handle a package that had missing dependencies.
+
+ Build has failed by missing dependencies, set the job status as
+ MANUALDEPWAIT, store available information, remove BuildQueue
+ entry and release builder slave for another job.
+ """
+ self.build.status = BuildStatus.MANUALDEPWAIT
+
+ def build_info_stored(ignored):
+ logger.critical("***** %s is MANUALDEPWAIT *****"
+ % self.build.buildqueue_record.builder.name)
+ if send_notification:
+ self.build.notify()
+ d = self.build.buildqueue_record.builder.cleanSlave()
+ return d.addCallback(
+ lambda x: self.build.buildqueue_record.destroySelf())
+
+ d = self.storeBuildInfo(self.build, librarian, slave_status)
+ return d.addCallback(build_info_stored)
+
+ def _handleStatus_CHROOTFAIL(self, librarian, slave_status, logger,
+ send_notification):
+ """Handle a package that had failed when unpacking the CHROOT.
+
+ Build has failed when installing the current CHROOT, mark the
+ job as CHROOTFAIL, store available information, remove BuildQueue
+ and release the builder.
+ """
+ self.build.status = BuildStatus.CHROOTWAIT
+
+ def build_info_stored(ignored):
+ logger.critical("***** %s is CHROOTWAIT *****" %
+ self.build.buildqueue_record.builder.name)
+ if send_notification:
+ self.build.notify()
+ d = self.build.buildqueue_record.builder.cleanSlave()
+ return d.addCallback(
+ lambda x: self.build.buildqueue_record.destroySelf())
+
+ d = self.storeBuildInfo(self.build, librarian, slave_status)
+ return d.addCallback(build_info_stored)
+
+ def _handleStatus_BUILDERFAIL(self, librarian, slave_status, logger,
+ send_notification):
+ """Handle builder failures.
+
+ Build has been failed when trying to build the target package,
+ The environment is working well, so mark the job as NEEDSBUILD again
+ and 'clean' the builder to do another jobs.
+ """
+ logger.warning("***** %s has failed *****"
+ % self.build.buildqueue_record.builder.name)
+ self.build.buildqueue_record.builder.failBuilder(
+ "Builder returned BUILDERFAIL when asked for its status")
+
+ def build_info_stored(ignored):
+ # simply reset job
+ self.build.buildqueue_record.reset()
+ d = self.storeBuildInfo(self.build, librarian, slave_status)
+ return d.addCallback(build_info_stored)
+
+ def _handleStatus_GIVENBACK(self, librarian, slave_status, logger,
+ send_notification):
+ """Handle automatic retry requested by builder.
+
+ GIVENBACK pseudo-state represents a request for automatic retry
+ later, the build records is delayed by reducing the lastscore to
+ ZERO.
+ """
+ logger.warning(
+ "***** %s is GIVENBACK by %s *****"
+ % (self.build.buildqueue_record.specific_job.build.title,
+ self.build.buildqueue_record.builder.name))
+
+ def build_info_stored(ignored):
+ # XXX cprov 2006-05-30: Currently this information is not
+ # properly presented in the Web UI. We will discuss it in
+ # the next Paris Summit, infinity has some ideas about how
+ # to use this content. For now we just ensure it's stored.
+ d = self.build.buildqueue_record.builder.cleanSlave()
+ self.build.buildqueue_record.reset()
+ return d
+
+ d = self.storeBuildInfo(self.build, librarian, slave_status)
+ return d.addCallback(build_info_stored)
class IdleBuildBehavior(BuildFarmJobBehaviorBase):
=== modified file 'lib/lp/buildmaster/model/packagebuild.py'
--- lib/lp/buildmaster/model/packagebuild.py 2013-01-07 04:23:29 +0000
+++ lib/lp/buildmaster/model/packagebuild.py 2013-01-10 07:23:23 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010-2012 Canonical Ltd. This software is licensed under the
+# Copyright 2010-2013 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -11,12 +11,8 @@
from cStringIO import StringIO
-import datetime
-import logging
-import os.path
from lazr.delegates import delegates
-import pytz
from storm.expr import Desc
from storm.locals import (
Int,
@@ -30,12 +26,8 @@
classProvides,
implements,
)
-from zope.security.proxy import removeSecurityProxy
-from lp.buildmaster.enums import (
- BuildFarmJobType,
- BuildStatus,
- )
+from lp.buildmaster.enums import BuildStatus
from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSource
from lp.buildmaster.interfaces.packagebuild import (
IPackageBuild,
@@ -44,12 +36,11 @@
)
from lp.buildmaster.model.buildfarmjob import (
BuildFarmJob,
+ BuildFarmJobDerived,
BuildFarmJobMixin,
- BuildFarmJobDerived,
)
from lp.buildmaster.model.buildqueue import BuildQueue
from lp.registry.interfaces.pocket import PackagePublishingPocket
-from lp.services.config import config
from lp.services.database.enumcol import DBEnum
from lp.services.database.interfaces import (
DEFAULT_FLAVOR,
@@ -66,9 +57,6 @@
from lp.soyuz.interfaces.component import IComponentSet
-SLAVE_LOG_FILENAME = 'buildlog'
-
-
class PackageBuild(BuildFarmJobDerived, Storm):
"""An implementation of `IBuildFarmJob` for package builds."""
@@ -133,12 +121,6 @@
class PackageBuildMixin(BuildFarmJobMixin):
- # The list of build status values for which email notifications are
- # allowed to be sent. It is up to each callback as to whether it will
- # consider sending a notification but it won't do so if the status is not
- # in this list.
- ALLOWED_STATUS_NOTIFICATIONS = ['OK', 'PACKAGEFAIL', 'CHROOTFAIL']
-
@property
def current_component(self):
"""See `IPackageBuild`."""
@@ -163,48 +145,10 @@
"""See `IBuildFarmJob`"""
return self.archive.private
- def getUploadDirLeaf(self, build_cookie, now=None):
- """See `IPackageBuild`."""
- if now is None:
- now = datetime.datetime.now()
- timestamp = now.strftime("%Y%m%d-%H%M%S")
- return '%s-%s' % (timestamp, build_cookie)
-
- @staticmethod
- def getLogFromSlave(package_build):
- """See `IPackageBuild`."""
- builder = package_build.buildqueue_record.builder
- d = builder.transferSlaveFileToLibrarian(
- SLAVE_LOG_FILENAME,
- package_build.buildqueue_record.getLogFileName(),
- package_build.is_private)
- return d
-
def estimateDuration(self):
"""See `IPackageBuild`."""
raise NotImplementedError
- @staticmethod
- def storeBuildInfo(build, librarian, slave_status):
- """See `IPackageBuild`."""
- def got_log(lfa_id):
- # log, builder and date_finished are read-only, so we must
- # currently remove the security proxy to set them.
- naked_build = removeSecurityProxy(build)
- naked_build.log = lfa_id
- naked_build.builder = build.buildqueue_record.builder
- # XXX cprov 20060615 bug=120584: Currently buildduration includes
- # the scanner latency, it should really be asking the slave for
- # the duration spent building locally.
- naked_build.date_finished = datetime.datetime.now(pytz.UTC)
- if slave_status.get('dependencies') is not None:
- build.dependencies = unicode(slave_status.get('dependencies'))
- else:
- build.dependencies = None
-
- d = build.getLogFromSlave(build)
- return d.addCallback(got_log)
-
def verifySuccessfulUpload(self):
"""See `IPackageBuild`."""
raise NotImplementedError
@@ -249,10 +193,6 @@
"""See `IPackageBuild`."""
raise NotImplementedError
- def getBuildCookie(self):
- """See `IPackageBuild`."""
- return '%s-%s' % (self.job_type.name, self.id)
-
def queueBuild(self, suspended=False):
"""See `IPackageBuild`."""
specific_job = self.makeJob()
@@ -272,237 +212,6 @@
Store.of(self).add(queue_entry)
return queue_entry
- def handleStatus(self, status, librarian, slave_status):
- """See `IPackageBuild`."""
- from lp.buildmaster.manager import BUILDD_MANAGER_LOG_NAME
- logger = logging.getLogger(BUILDD_MANAGER_LOG_NAME)
- send_notification = status in self.ALLOWED_STATUS_NOTIFICATIONS
- method = getattr(self, '_handleStatus_' + status, None)
- if method is None:
- logger.critical("Unknown BuildStatus '%s' for builder '%s'"
- % (status, self.buildqueue_record.builder.url))
- return
- d = method(librarian, slave_status, logger, send_notification)
- return d
-
- def _release_builder_and_remove_queue_item(self):
- # Release the builder for another job.
- d = self.buildqueue_record.builder.cleanSlave()
- # Remove BuildQueue record.
- return d.addCallback(lambda x: self.buildqueue_record.destroySelf())
-
- def _handleStatus_OK(self, librarian, slave_status, logger,
- send_notification):
- """Handle a package that built successfully.
-
- Once built successfully, we pull the files, store them in a
- directory, store build information and push them through the
- uploader.
- """
- filemap = slave_status['filemap']
-
- logger.info("Processing successful build %s from builder %s" % (
- self.buildqueue_record.specific_job.build.title,
- self.buildqueue_record.builder.name))
-
- # If this is a binary package build, discard it if its source is
- # no longer published.
- if self.build_farm_job_type == BuildFarmJobType.PACKAGEBUILD:
- build = self.buildqueue_record.specific_job.build
- if not build.current_source_publication:
- build.status = BuildStatus.SUPERSEDED
- return self._release_builder_and_remove_queue_item()
-
- # Explode before collect a binary that is denied in this
- # distroseries/pocket/archive
- assert self.archive.canModifySuite(self.distro_series, self.pocket), (
- "%s (%s) can not be built for pocket %s in %s: illegal status"
- % (self.title, self.id, self.pocket.name, self.archive))
-
- # Ensure we have the correct build root as:
- # <BUILDMASTER_ROOT>/incoming/<UPLOAD_LEAF>/<TARGET_PATH>/[FILES]
- root = os.path.abspath(config.builddmaster.root)
-
- # Create a single directory to store build result files.
- upload_leaf = self.getUploadDirLeaf(self.getBuildCookie())
- grab_dir = os.path.join(root, "grabbing", upload_leaf)
- logger.debug("Storing build result at '%s'" % grab_dir)
-
- # Build the right UPLOAD_PATH so the distribution and archive
- # can be correctly found during the upload:
- # <archive_id>/distribution_name
- # for all destination archive types.
- upload_path = os.path.join(
- grab_dir, str(self.archive.id), self.distribution.name)
- os.makedirs(upload_path)
-
- slave = removeSecurityProxy(self.buildqueue_record.builder.slave)
- successful_copy_from_slave = True
- filenames_to_download = {}
- for filename in filemap:
- logger.info("Grabbing file: %s" % filename)
- out_file_name = os.path.join(upload_path, filename)
- # If the evaluated output file name is not within our
- # upload path, then we don't try to copy this or any
- # subsequent files.
- if not os.path.realpath(out_file_name).startswith(upload_path):
- successful_copy_from_slave = False
- logger.warning(
- "A slave tried to upload the file '%s' "
- "for the build %d." % (filename, self.id))
- break
- filenames_to_download[filemap[filename]] = out_file_name
-
- def build_info_stored(ignored):
- # We only attempt the upload if we successfully copied all the
- # files from the slave.
- if successful_copy_from_slave:
- logger.info(
- "Gathered %s %d completely. Moving %s to uploader queue."
- % (self.__class__.__name__, self.id, upload_leaf))
- target_dir = os.path.join(root, "incoming")
- self.status = BuildStatus.UPLOADING
- else:
- logger.warning(
- "Copy from slave for build %s was unsuccessful.", self.id)
- self.status = BuildStatus.FAILEDTOUPLOAD
- if send_notification:
- self.notify(
- extra_info='Copy from slave was unsuccessful.')
- target_dir = os.path.join(root, "failed")
-
- if not os.path.exists(target_dir):
- os.mkdir(target_dir)
-
- # Release the builder for another job.
- d = self._release_builder_and_remove_queue_item()
-
- # Commit so there are no race conditions with archiveuploader
- # about self.status.
- Store.of(self).commit()
-
- # Move the directory used to grab the binaries into
- # the incoming directory so the upload processor never
- # sees half-finished uploads.
- os.rename(grab_dir, os.path.join(target_dir, upload_leaf))
-
- return d
-
- d = slave.getFiles(filenames_to_download)
- # Store build information, build record was already updated during
- # the binary upload.
- d.addCallback(
- lambda x: self.storeBuildInfo(self, librarian, slave_status))
- d.addCallback(build_info_stored)
- return d
-
- def _handleStatus_PACKAGEFAIL(self, librarian, slave_status, logger,
- send_notification):
- """Handle a package that had failed to build.
-
- Build has failed when trying the work with the target package,
- set the job status as FAILEDTOBUILD, store available info and
- remove Buildqueue entry.
- """
- self.status = BuildStatus.FAILEDTOBUILD
-
- def build_info_stored(ignored):
- if send_notification:
- self.notify()
- d = self.buildqueue_record.builder.cleanSlave()
- return d.addCallback(
- lambda x: self.buildqueue_record.destroySelf())
-
- d = self.storeBuildInfo(self, librarian, slave_status)
- return d.addCallback(build_info_stored)
-
- def _handleStatus_DEPFAIL(self, librarian, slave_status, logger,
- send_notification):
- """Handle a package that had missing dependencies.
-
- Build has failed by missing dependencies, set the job status as
- MANUALDEPWAIT, store available information, remove BuildQueue
- entry and release builder slave for another job.
- """
- self.status = BuildStatus.MANUALDEPWAIT
-
- def build_info_stored(ignored):
- logger.critical("***** %s is MANUALDEPWAIT *****"
- % self.buildqueue_record.builder.name)
- if send_notification:
- self.notify()
- d = self.buildqueue_record.builder.cleanSlave()
- return d.addCallback(
- lambda x: self.buildqueue_record.destroySelf())
-
- d = self.storeBuildInfo(self, librarian, slave_status)
- return d.addCallback(build_info_stored)
-
- def _handleStatus_CHROOTFAIL(self, librarian, slave_status, logger,
- send_notification):
- """Handle a package that had failed when unpacking the CHROOT.
-
- Build has failed when installing the current CHROOT, mark the
- job as CHROOTFAIL, store available information, remove BuildQueue
- and release the builder.
- """
- self.status = BuildStatus.CHROOTWAIT
-
- def build_info_stored(ignored):
- logger.critical("***** %s is CHROOTWAIT *****" %
- self.buildqueue_record.builder.name)
- if send_notification:
- self.notify()
- d = self.buildqueue_record.builder.cleanSlave()
- return d.addCallback(
- lambda x: self.buildqueue_record.destroySelf())
-
- d = self.storeBuildInfo(self, librarian, slave_status)
- return d.addCallback(build_info_stored)
-
- def _handleStatus_BUILDERFAIL(self, librarian, slave_status, logger,
- send_notification):
- """Handle builder failures.
-
- Build has been failed when trying to build the target package,
- The environment is working well, so mark the job as NEEDSBUILD again
- and 'clean' the builder to do another jobs.
- """
- logger.warning("***** %s has failed *****"
- % self.buildqueue_record.builder.name)
- self.buildqueue_record.builder.failBuilder(
- "Builder returned BUILDERFAIL when asked for its status")
-
- def build_info_stored(ignored):
- # simply reset job
- self.buildqueue_record.reset()
- d = self.storeBuildInfo(self, librarian, slave_status)
- return d.addCallback(build_info_stored)
-
- def _handleStatus_GIVENBACK(self, librarian, slave_status, logger,
- send_notification):
- """Handle automatic retry requested by builder.
-
- GIVENBACK pseudo-state represents a request for automatic retry
- later, the build records is delayed by reducing the lastscore to
- ZERO.
- """
- logger.warning("***** %s is GIVENBACK by %s *****"
- % (self.buildqueue_record.specific_job.build.title,
- self.buildqueue_record.builder.name))
-
- def build_info_stored(ignored):
- # XXX cprov 2006-05-30: Currently this information is not
- # properly presented in the Web UI. We will discuss it in
- # the next Paris Summit, infinity has some ideas about how
- # to use this content. For now we just ensure it's stored.
- d = self.buildqueue_record.builder.cleanSlave()
- self.buildqueue_record.reset()
- return d
-
- d = self.storeBuildInfo(self, librarian, slave_status)
- return d.addCallback(build_info_stored)
-
class PackageBuildDerived:
"""Setup the delegation for package build.
=== modified file 'lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py'
--- lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py 2012-01-01 02:58:52 +0000
+++ lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py 2013-01-10 07:23:23 +0000
@@ -3,17 +3,34 @@
"""Unit tests for BuildFarmJobBehaviorBase."""
+__metaclass__ = type
+
+from datetime import datetime
+import os
+import shutil
+import tempfile
+
from zope.component import getUtility
from zope.security.proxy import removeSecurityProxy
+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.buildfarmjobbehavior import BuildFarmJobBehaviorBase
+from lp.buildmaster.tests.mock_slaves import WaitingSlave
from lp.registry.interfaces.pocket import PackagePublishingPocket
+from lp.services.config import config
+from lp.services.database.constants import UTC_NOW
from lp.soyuz.interfaces.processor import IProcessorFamilySet
from lp.testing import TestCaseWithFactory
+from lp.testing.factory import LaunchpadObjectFactory
from lp.testing.fakemethod import FakeMethod
-from lp.testing.layers import ZopelessDatabaseLayer
+from lp.testing.layers import (
+ LaunchpadZopelessLayer,
+ ZopelessDatabaseLayer,
+ )
+from lp.testing.mail_helpers import pop_notifications
class FakeBuildFarmJob:
@@ -141,3 +158,220 @@
self.assertNotEqual(
buildfarmjob1.generateSlaveBuildCookie(),
buildfarmjob2.generateSlaveBuildCookie())
+
+ def test_getUploadDirLeaf(self):
+ # getUploadDirLeaf returns the current time, followed by the build
+ # cookie.
+ now = datetime.now()
+ build_cookie = self.factory.getUniqueString()
+ upload_leaf = self._makeBehavior().getUploadDirLeaf(
+ build_cookie, now=now)
+ self.assertEqual(
+ '%s-%s' % (now.strftime("%Y%m%d-%H%M%S"), build_cookie),
+ upload_leaf)
+
+
+class TestGetUploadMethodsMixin:
+ """Tests for `IPackageBuild` that need objects from the rest of LP."""
+
+ layer = LaunchpadZopelessLayer
+
+ def makeBuild(self):
+ """Allow classes to override the build with which the test runs."""
+ raise NotImplemented
+
+ def setUp(self):
+ super(TestGetUploadMethodsMixin, self).setUp()
+ self.build = self.makeBuild()
+ self.behavior = removeSecurityProxy(
+ self.build.buildqueue_record.required_build_behavior)
+
+ def test_getUploadDirLeafCookie_parseable(self):
+ # getUploadDirLeaf should return a directory name
+ # that is parseable by the upload processor.
+ upload_leaf = self.behavior.getUploadDirLeaf(
+ self.behavior.getBuildCookie())
+ (job_type, job_id) = parse_build_upload_leaf_name(upload_leaf)
+ self.assertEqual(
+ (self.build.build_farm_job.job_type.name, self.build.id),
+ (job_type, job_id))
+
+
+class TestHandleStatusMixin:
+ """Tests for `IPackageBuild`s handleStatus method.
+
+ This should be run with a Trial TestCase.
+ """
+
+ layer = LaunchpadZopelessLayer
+
+ def makeBuild(self):
+ """Allow classes to override the build with which the test runs."""
+ raise NotImplementedError
+
+ def setUp(self):
+ super(TestHandleStatusMixin, self).setUp()
+ self.factory = LaunchpadObjectFactory()
+ self.build = self.makeBuild()
+ # For the moment, we require a builder for the build so that
+ # handleStatus_OK can get a reference to the slave.
+ builder = self.factory.makeBuilder()
+ self.build.buildqueue_record.builder = builder
+ self.build.buildqueue_record.setDateStarted(UTC_NOW)
+ self.behavior = removeSecurityProxy(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))
+
+ # We overwrite the buildmaster root to use a temp directory.
+ tempdir = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, tempdir)
+ self.upload_root = tempdir
+ tmp_builddmaster_root = """
+ [builddmaster]
+ root: %s
+ """ % self.upload_root
+ config.push('tmp_builddmaster_root', tmp_builddmaster_root)
+
+ # We stub out our builds getUploaderCommand() method so
+ # we can check whether it was called as well as
+ # verifySuccessfulUpload().
+ removeSecurityProxy(self.build).verifySuccessfulUpload = FakeMethod(
+ result=True)
+
+ def assertResultCount(self, count, result):
+ self.assertEquals(
+ 1, len(os.listdir(os.path.join(self.upload_root, result))))
+
+ def test_handleStatus_OK_normal_file(self):
+ # A filemap with plain filenames should not cause a problem.
+ # The call to handleStatus will attempt to get the file from
+ # the slave resulting in a URL error in this test case.
+ def got_status(ignored):
+ self.assertEqual(BuildStatus.UPLOADING, self.build.status)
+ self.assertResultCount(1, "incoming")
+
+ d = self.behavior.handleStatus('OK', None, {
+ 'filemap': {'myfile.py': 'test_file_hash'},
+ })
+ return d.addCallback(got_status)
+
+ def test_handleStatus_OK_absolute_filepath(self):
+ # A filemap that tries to write to files outside of
+ # the upload directory will result in a failed upload.
+ def got_status(ignored):
+ self.assertEqual(BuildStatus.FAILEDTOUPLOAD, self.build.status)
+ self.assertResultCount(0, "failed")
+ self.assertIdentical(None, self.build.buildqueue_record)
+
+ d = self.behavior.handleStatus('OK', None, {
+ 'filemap': {'/tmp/myfile.py': 'test_file_hash'},
+ })
+ return d.addCallback(got_status)
+
+ def test_handleStatus_OK_relative_filepath(self):
+ # A filemap that tries to write to files outside of
+ # the upload directory will result in a failed upload.
+ def got_status(ignored):
+ self.assertEqual(BuildStatus.FAILEDTOUPLOAD, self.build.status)
+ self.assertResultCount(0, "failed")
+
+ d = self.behavior.handleStatus('OK', None, {
+ 'filemap': {'../myfile.py': 'test_file_hash'},
+ })
+ return d.addCallback(got_status)
+
+ def test_handleStatus_OK_sets_build_log(self):
+ # The build log is set during handleStatus.
+ removeSecurityProxy(self.build).log = None
+ self.assertEqual(None, self.build.log)
+ d = self.behavior.handleStatus('OK', None, {
+ 'filemap': {'myfile.py': 'test_file_hash'},
+ })
+
+ def got_status(ignored):
+ self.assertNotEqual(None, self.build.log)
+
+ return d.addCallback(got_status)
+
+ def _test_handleStatus_notifies(self, status):
+ # An email notification is sent for a given build status if
+ # notifications are allowed for that status.
+
+ expected_notification = (
+ status in self.behavior.ALLOWED_STATUS_NOTIFICATIONS)
+
+ def got_status(ignored):
+ if expected_notification:
+ self.failIf(
+ len(pop_notifications()) == 0,
+ "No notifications received")
+ else:
+ self.failIf(
+ len(pop_notifications()) > 0,
+ "Notifications received")
+
+ d = self.behavior.handleStatus(status, None, {})
+ return d.addCallback(got_status)
+
+ def test_handleStatus_DEPFAIL_notifies(self):
+ return self._test_handleStatus_notifies("DEPFAIL")
+
+ def test_handleStatus_CHROOTFAIL_notifies(self):
+ return self._test_handleStatus_notifies("CHROOTFAIL")
+
+ def test_handleStatus_PACKAGEFAIL_notifies(self):
+ return self._test_handleStatus_notifies("PACKAGEFAIL")
+
+ def test_date_finished_set(self):
+ # The date finished is updated during handleStatus_OK.
+ removeSecurityProxy(self.build).date_finished = None
+ self.assertEqual(None, self.build.date_finished)
+ d = self.behavior.handleStatus('OK', None, {
+ 'filemap': {'myfile.py': 'test_file_hash'},
+ })
+
+ def got_status(ignored):
+ self.assertNotEqual(None, self.build.date_finished)
+
+ return d.addCallback(got_status)
+
+
+class TestStoreBuildInfo(TestCaseWithFactory):
+
+ layer = LaunchpadZopelessLayer
+
+ def setUp(self):
+ super(TestStoreBuildInfo, self).setUp()
+ self.build = self.factory.makeBinaryPackageBuild()
+ self.builder = self.factory.makeBuilder()
+ self.patch(BuilderSlave, 'makeBuilderSlave',
+ FakeMethod(WaitingSlave('BuildStatus.OK')))
+ bq = self.build.queueBuild()
+ bq.markAsBuilding(self.builder)
+ self.behavior = removeSecurityProxy(bq.required_build_behavior)
+
+ def testDependencies(self):
+ """Verify that storeBuildInfo sets any dependencies."""
+ self.behavior.storeBuildInfo(
+ self.build, None, {'dependencies': 'somepackage'})
+ self.assertIsNot(None, self.build.log)
+ self.assertEqual(self.builder, self.build.builder)
+ self.assertEqual(u'somepackage', self.build.dependencies)
+
+ def testWithoutDependencies(self):
+ """Verify that storeBuildInfo clears the build's dependencies."""
+ # Set something just to make sure that storeBuildInfo actually
+ # empties it.
+ self.build.dependencies = u'something'
+ self.behavior.storeBuildInfo(self.build, None, {})
+ self.assertIsNot(None, self.build.log)
+ self.assertEqual(self.builder, self.build.builder)
+ self.assertIs(None, self.build.dependencies)
+ self.assertIsNot(None, self.build.date_finished)
+
+ def test_sets_date_finished(self):
+ # storeBuildInfo should set date_finished on the BuildFarmJob.
+ self.assertIs(None, self.build.date_finished)
+ self.behavior.storeBuildInfo(self.build, None, {})
+ self.assertIsNot(None, self.build.date_finished)
=== modified file 'lib/lp/buildmaster/tests/test_packagebuild.py'
--- lib/lp/buildmaster/tests/test_packagebuild.py 2013-01-07 05:25:16 +0000
+++ lib/lp/buildmaster/tests/test_packagebuild.py 2013-01-10 07:23:23 +0000
@@ -5,18 +5,13 @@
__metaclass__ = type
-from datetime import datetime
import hashlib
-import os
-import shutil
-import tempfile
from storm.store import Store
from zope.component import getUtility
from zope.security.management import checkPermission
from zope.security.proxy import removeSecurityProxy
-from lp.archiveuploader.uploadprocessor import parse_build_upload_leaf_name
from lp.buildmaster.enums import (
BuildFarmJobType,
BuildStatus,
@@ -26,25 +21,15 @@
IPackageBuildSet,
IPackageBuildSource,
)
-from lp.buildmaster.model.builder import BuilderSlave
from lp.buildmaster.model.buildfarmjob import BuildFarmJob
from lp.buildmaster.model.packagebuild import PackageBuild
-from lp.buildmaster.tests.mock_slaves import WaitingSlave
from lp.registry.interfaces.pocket import PackagePublishingPocket
-from lp.services.config import config
-from lp.services.database.constants import UTC_NOW
from lp.testing import (
login,
login_person,
TestCaseWithFactory,
)
-from lp.testing.factory import LaunchpadObjectFactory
-from lp.testing.fakemethod import FakeMethod
-from lp.testing.layers import (
- LaunchpadFunctionalLayer,
- LaunchpadZopelessLayer,
- )
-from lp.testing.mail_helpers import pop_notifications
+from lp.testing.layers import LaunchpadFunctionalLayer
class TestPackageBuildBase(TestCaseWithFactory):
@@ -176,17 +161,6 @@
self.package_build.id, self.package_build.build_farm_job.id),
log_url)
- def test_getUploadDirLeaf(self):
- # getUploadDirLeaf returns the current time, followed by the build
- # cookie.
- now = datetime.now()
- build_cookie = self.factory.getUniqueString()
- upload_leaf = self.package_build.getUploadDirLeaf(
- build_cookie, now=now)
- self.assertEqual(
- '%s-%s' % (now.strftime("%Y%m%d-%H%M%S"), build_cookie),
- upload_leaf)
-
def test_view_package_build(self):
# Anonymous access can read public builds, but not edit.
self.assertTrue(checkPermission('launchpad.View', self.package_build))
@@ -251,167 +225,3 @@
self.package_builds[:1],
self.package_build_set.getBuildsForArchive(
self.archive, pocket=PackagePublishingPocket.UPDATES))
-
-
-class TestGetUploadMethodsMixin:
- """Tests for `IPackageBuild` that need objects from the rest of LP."""
-
- layer = LaunchpadZopelessLayer
-
- def makeBuild(self):
- """Allow classes to override the build with which the test runs."""
- raise NotImplemented
-
- def setUp(self):
- super(TestGetUploadMethodsMixin, self).setUp()
- self.build = self.makeBuild()
-
- def test_getUploadDirLeafCookie_parseable(self):
- # getUploadDirLeaf should return a directory name
- # that is parseable by the upload processor.
- upload_leaf = self.build.getUploadDirLeaf(
- self.build.getBuildCookie())
- (job_type, job_id) = parse_build_upload_leaf_name(upload_leaf)
- self.assertEqual(
- (self.build.build_farm_job.job_type.name, self.build.id),
- (job_type, job_id))
-
-
-class TestHandleStatusMixin:
- """Tests for `IPackageBuild`s handleStatus method.
-
- This should be run with a Trial TestCase.
- """
-
- layer = LaunchpadZopelessLayer
-
- def makeBuild(self):
- """Allow classes to override the build with which the test runs."""
- raise NotImplementedError
-
- def setUp(self):
- super(TestHandleStatusMixin, self).setUp()
- self.factory = LaunchpadObjectFactory()
- self.build = self.makeBuild()
- # For the moment, we require a builder for the build so that
- # handleStatus_OK can get a reference to the slave.
- builder = self.factory.makeBuilder()
- self.build.buildqueue_record.builder = builder
- self.build.buildqueue_record.setDateStarted(UTC_NOW)
- self.slave = WaitingSlave('BuildStatus.OK')
- self.slave.valid_file_hashes.append('test_file_hash')
- self.patch(BuilderSlave, 'makeBuilderSlave', FakeMethod(self.slave))
-
- # We overwrite the buildmaster root to use a temp directory.
- tempdir = tempfile.mkdtemp()
- self.addCleanup(shutil.rmtree, tempdir)
- self.upload_root = tempdir
- tmp_builddmaster_root = """
- [builddmaster]
- root: %s
- """ % self.upload_root
- config.push('tmp_builddmaster_root', tmp_builddmaster_root)
-
- # We stub out our builds getUploaderCommand() method so
- # we can check whether it was called as well as
- # verifySuccessfulUpload().
- removeSecurityProxy(self.build).verifySuccessfulUpload = FakeMethod(
- result=True)
-
- def assertResultCount(self, count, result):
- self.assertEquals(
- 1, len(os.listdir(os.path.join(self.upload_root, result))))
-
- def test_handleStatus_OK_normal_file(self):
- # A filemap with plain filenames should not cause a problem.
- # The call to handleStatus will attempt to get the file from
- # the slave resulting in a URL error in this test case.
- def got_status(ignored):
- self.assertEqual(BuildStatus.UPLOADING, self.build.status)
- self.assertResultCount(1, "incoming")
-
- d = self.build.handleStatus('OK', None, {
- 'filemap': {'myfile.py': 'test_file_hash'},
- })
- return d.addCallback(got_status)
-
- def test_handleStatus_OK_absolute_filepath(self):
- # A filemap that tries to write to files outside of
- # the upload directory will result in a failed upload.
- def got_status(ignored):
- self.assertEqual(BuildStatus.FAILEDTOUPLOAD, self.build.status)
- self.assertResultCount(0, "failed")
- self.assertIdentical(None, self.build.buildqueue_record)
-
- d = self.build.handleStatus('OK', None, {
- 'filemap': {'/tmp/myfile.py': 'test_file_hash'},
- })
- return d.addCallback(got_status)
-
- def test_handleStatus_OK_relative_filepath(self):
- # A filemap that tries to write to files outside of
- # the upload directory will result in a failed upload.
- def got_status(ignored):
- self.assertEqual(BuildStatus.FAILEDTOUPLOAD, self.build.status)
- self.assertResultCount(0, "failed")
-
- d = self.build.handleStatus('OK', None, {
- 'filemap': {'../myfile.py': 'test_file_hash'},
- })
- return d.addCallback(got_status)
-
- def test_handleStatus_OK_sets_build_log(self):
- # The build log is set during handleStatus.
- removeSecurityProxy(self.build).log = None
- self.assertEqual(None, self.build.log)
- d = self.build.handleStatus('OK', None, {
- 'filemap': {'myfile.py': 'test_file_hash'},
- })
-
- def got_status(ignored):
- self.assertNotEqual(None, self.build.log)
-
- return d.addCallback(got_status)
-
- def _test_handleStatus_notifies(self, status):
- # An email notification is sent for a given build status if
- # notifications are allowed for that status.
-
- naked_build = removeSecurityProxy(self.build)
- expected_notification = (
- status in naked_build.ALLOWED_STATUS_NOTIFICATIONS)
-
- def got_status(ignored):
- if expected_notification:
- self.failIf(
- len(pop_notifications()) == 0,
- "No notifications received")
- else:
- self.failIf(
- len(pop_notifications()) > 0,
- "Notifications received")
-
- d = self.build.handleStatus(status, None, {})
- return d.addCallback(got_status)
-
- def test_handleStatus_DEPFAIL_notifies(self):
- return self._test_handleStatus_notifies("DEPFAIL")
-
- def test_handleStatus_CHROOTFAIL_notifies(self):
- return self._test_handleStatus_notifies("CHROOTFAIL")
-
- def test_handleStatus_PACKAGEFAIL_notifies(self):
- return self._test_handleStatus_notifies("PACKAGEFAIL")
-
- def test_date_finished_set(self):
- # The date finished is updated during handleStatus_OK.
- removeSecurityProxy(self.build).date_finished = None
- self.assertEqual(None, self.build.date_finished)
- d = self.build.handleStatus('OK', None, {
- 'filemap': {'myfile.py': 'test_file_hash'},
- })
-
- def got_status(ignored):
- self.assertNotEqual(None, self.build.date_finished)
-
- return d.addCallback(got_status)
=== modified file 'lib/lp/code/model/recipebuilder.py'
--- lib/lp/code/model/recipebuilder.py 2012-04-24 06:00:11 +0000
+++ lib/lp/code/model/recipebuilder.py 2013-01-10 07:23:23 +0000
@@ -36,6 +36,13 @@
adapts(ISourcePackageRecipeBuildJob)
implements(IBuildFarmJobBehavior)
+ # The list of build status values for which email notifications are
+ # allowed to be sent. It is up to each callback as to whether it will
+ # consider sending a notification but it won't do so if the status is not
+ # in this list.
+ ALLOWED_STATUS_NOTIFICATIONS = [
+ 'OK', 'PACKAGEFAIL', 'DEPFAIL', 'CHROOTFAIL']
+
status = None
@property
@@ -133,9 +140,9 @@
d = self._builder.slave.cacheFile(logger, chroot)
def got_cache_file(ignored):
- # Generate a string which can be used to cross-check when obtaining
- # results so we know we are referring to the right database object in
- # subsequent runs.
+ # Generate a string which can be used to cross-check when
+ # obtaining results so we know we are referring to the right
+ # database object in subsequent runs.
buildid = "%s-%s" % (self.build.id, build_queue_id)
cookie = self.buildfarmjob.generateSlaveBuildCookie()
chroot_sha1 = chroot.content.sha1
=== modified file 'lib/lp/code/model/sourcepackagerecipebuild.py'
--- lib/lp/code/model/sourcepackagerecipebuild.py 2013-01-07 04:28:39 +0000
+++ lib/lp/code/model/sourcepackagerecipebuild.py 2013-01-10 07:23:23 +0000
@@ -89,13 +89,6 @@
id = Int(primary=True)
- # The list of build status values for which email notifications are
- # allowed to be sent. It is up to each callback as to whether it will
- # consider sending a notification but it won't do so if the status is not
- # in this list.
- ALLOWED_STATUS_NOTIFICATIONS = [
- 'OK', 'PACKAGEFAIL', 'DEPFAIL', 'CHROOTFAIL']
-
@property
def binary_builds(self):
"""See `ISourcePackageRecipeBuild`."""
=== modified file 'lib/lp/code/model/tests/test_recipebuilder.py'
--- lib/lp/code/model/tests/test_recipebuilder.py 2013-01-07 02:40:55 +0000
+++ lib/lp/code/model/tests/test_recipebuilder.py 2013-01-10 07:23:23 +0000
@@ -5,6 +5,9 @@
__metaclass__ = type
+from datetime import timedelta
+import shutil
+import tempfile
from textwrap import dedent
from testtools import run_test_with
@@ -15,21 +18,32 @@
from testtools.matchers import StartsWith
import transaction
from twisted.internet import defer
+from twisted.trial.unittest import TestCase as TrialTestCase
from zope.security.proxy import removeSecurityProxy
-from lp.buildmaster.enums import BuildFarmJobType
+from lp.buildmaster.enums import (
+ BuildFarmJobType,
+ BuildStatus,
+ )
from lp.buildmaster.interfaces.builder import CannotBuild
from lp.buildmaster.interfaces.buildfarmjobbehavior import (
IBuildFarmJobBehavior,
)
+from lp.buildmaster.model.builder import BuilderSlave
from lp.buildmaster.model.buildqueue import BuildQueue
from lp.buildmaster.tests.mock_slaves import (
MockBuilder,
OkSlave,
+ WaitingSlave,
+ )
+from lp.buildmaster.tests.test_buildfarmjobbehavior import (
+ TestGetUploadMethodsMixin,
+ TestHandleStatusMixin,
)
from lp.code.model.recipebuilder import RecipeBuildBehavior
from lp.code.model.sourcepackagerecipebuild import SourcePackageRecipeBuild
from lp.registry.interfaces.pocket import PackagePublishingPocket
+from lp.services.config import config
from lp.services.log.logger import BufferLogger
from lp.soyuz.adapters.archivedependencies import (
get_sources_list_for_building,
@@ -40,7 +54,9 @@
person_logged_in,
TestCaseWithFactory,
)
+from lp.testing.fakemethod import FakeMethod
from lp.testing.layers import LaunchpadZopelessLayer
+from lp.testing.mail_helpers import pop_notifications
class TestRecipeBuilder(TestCaseWithFactory):
@@ -136,6 +152,16 @@
AssertionError, job.verifyBuildRequest, BufferLogger())
self.assertIn('invalid pocket due to the series status of', str(e))
+ def test_getBuildCookie(self):
+ # A build cookie is made up of the job type and record id.
+ # The uploadprocessor relies on this format.
+ build = self.factory.makeSourcePackageRecipeBuild()
+ job = self.factory.makeSourcePackageRecipeBuildJob(recipe_build=build)
+ job = IBuildFarmJobBehavior(job.specific_job)
+ cookie = removeSecurityProxy(job).getBuildCookie()
+ expected_cookie = "RECIPEBRANCHBUILD-%d" % build.id
+ self.assertEquals(expected_cookie, cookie)
+
def _setBuilderConfig(self):
"""Setup a temporary builder config."""
self.pushConfig(
@@ -315,3 +341,83 @@
logger = BufferLogger()
d = defer.maybeDeferred(job.dispatchBuildToSlave, "someid", logger)
return assert_fails_with(d, CannotBuild)
+
+
+class TestBuildNotifications(TrialTestCase):
+
+ layer = LaunchpadZopelessLayer
+
+ def setUp(self):
+ super(TestBuildNotifications, self).setUp()
+ from lp.testing.factory import LaunchpadObjectFactory
+ self.factory = LaunchpadObjectFactory()
+
+ def prepareBehavior(self, fake_successful_upload=False):
+ queue_record = self.factory.makeSourcePackageRecipeBuildJob()
+ build = queue_record.specific_job.build
+ naked_build = removeSecurityProxy(build)
+ naked_build.status = BuildStatus.FULLYBUILT
+ naked_build.date_started = self.factory.getUniqueDate()
+ if fake_successful_upload:
+ naked_build.verifySuccessfulUpload = FakeMethod(
+ result=True)
+ # We overwrite the buildmaster root to use a temp directory.
+ tempdir = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, tempdir)
+ self.upload_root = tempdir
+ tmp_builddmaster_root = """
+ [builddmaster]
+ root: %s
+ """ % self.upload_root
+ config.push('tmp_builddmaster_root', tmp_builddmaster_root)
+ self.addCleanup(config.pop, 'tmp_builddmaster_root')
+ queue_record.builder = self.factory.makeBuilder()
+ slave = WaitingSlave('BuildStatus.OK')
+ self.patch(BuilderSlave, 'makeBuilderSlave', FakeMethod(slave))
+ return removeSecurityProxy(queue_record.builder.current_build_behavior)
+
+ def assertDeferredNotifyCount(self, status, behavior, expected_count):
+ d = behavior.handleStatus(status, None, {'filemap': {}})
+
+ def cb(result):
+ self.assertEqual(expected_count, len(pop_notifications()))
+
+ d.addCallback(cb)
+ return d
+
+ def test_handleStatus_PACKAGEFAIL(self):
+ """Failing to build the package immediately sends a notification."""
+ return self.assertDeferredNotifyCount(
+ "PACKAGEFAIL", self.prepareBehavior(), 1)
+
+ def test_handleStatus_OK(self):
+ """Building the source package does _not_ immediately send mail.
+
+ (The archive uploader mail send one later.
+ """
+ return self.assertDeferredNotifyCount(
+ "OK", self.prepareBehavior(), 0)
+
+ def test_handleStatus_OK_successful_upload(self):
+ return self.assertDeferredNotifyCount(
+ "OK", self.prepareBehavior(True), 0)
+
+
+class MakeSPRecipeBuildMixin:
+ """Provide the common makeBuild method returning a queued build."""
+
+ def makeBuild(self):
+ build = self.factory.makeSourcePackageRecipeBuild(
+ status=BuildStatus.FULLYBUILT, duration=timedelta(minutes=5))
+ build.queueBuild()
+ return build
+
+
+class TestGetUploadMethodsForSPRecipeBuild(
+ MakeSPRecipeBuildMixin, TestGetUploadMethodsMixin, TestCaseWithFactory):
+ """IPackageBuild.getUpload-related methods work with SPRecipe builds."""
+
+
+class TestHandleStatusForSPRBuild(
+ MakeSPRecipeBuildMixin, TestHandleStatusMixin, TrialTestCase):
+ """IPackageBuild.handleStatus works with SPRecipe builds."""
=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipebuild.py'
--- lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2013-01-07 07:53:54 +0000
+++ lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2013-01-10 07:23:23 +0000
@@ -10,13 +10,10 @@
timedelta,
)
import re
-import shutil
-import tempfile
from pytz import utc
from storm.locals import Store
import transaction
-from twisted.trial.unittest import TestCase as TrialTestCase
from zope.component import getUtility
from zope.security.proxy import removeSecurityProxy
@@ -24,14 +21,8 @@
from lp.app.errors import NotFoundError
from lp.buildmaster.enums import BuildStatus
from lp.buildmaster.interfaces.buildqueue import IBuildQueue
-from lp.buildmaster.model.builder import BuilderSlave
from lp.buildmaster.model.buildfarmjob import BuildFarmJob
from lp.buildmaster.model.packagebuild import PackageBuild
-from lp.buildmaster.tests.mock_slaves import WaitingSlave
-from lp.buildmaster.tests.test_packagebuild import (
- TestGetUploadMethodsMixin,
- TestHandleStatusMixin,
- )
from lp.code.interfaces.sourcepackagerecipebuild import (
ISourcePackageRecipeBuild,
ISourcePackageRecipeBuildJob,
@@ -43,12 +34,10 @@
from lp.code.model.sourcepackagerecipebuild import SourcePackageRecipeBuild
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.registry.interfaces.series import SeriesStatus
-from lp.services.config import config
from lp.services.database.lpstorm import IStore
from lp.services.log.logger import BufferLogger
from lp.services.mail.sendmail import format_address
from lp.services.webapp.authorization import check_permission
-from lp.soyuz.interfaces.processor import IProcessorFamilySet
from lp.soyuz.model.processor import ProcessorFamily
from lp.testing import (
ANONYMOUS,
@@ -57,7 +46,6 @@
TestCaseWithFactory,
verifyObject,
)
-from lp.testing.fakemethod import FakeMethod
from lp.testing.layers import (
LaunchpadFunctionalLayer,
LaunchpadZopelessLayer,
@@ -127,15 +115,6 @@
bq.processor)
self.assertEqual(bq, spb.buildqueue_record)
- def test_getBuildCookie(self):
- # A build cookie is made up of the job type and record id.
- # The uploadprocessor relies on this format.
- sprb = self.makeSourcePackageRecipeBuild()
- Store.of(sprb).flush()
- cookie = sprb.getBuildCookie()
- expected_cookie = "RECIPEBRANCHBUILD-%d" % sprb.id
- self.assertEquals(expected_cookie, cookie)
-
def test_title(self):
# A recipe build's title currently consists of the base
# branch's unique name.
@@ -599,92 +578,3 @@
build.notify()
notifications = pop_notifications()
self.assertEquals(0, len(notifications))
-
-
-class TestBuildNotifications(TrialTestCase):
-
- layer = LaunchpadZopelessLayer
-
- def setUp(self):
- super(TestBuildNotifications, self).setUp()
- from lp.testing.factory import LaunchpadObjectFactory
- self.factory = LaunchpadObjectFactory()
-
- def prepare_build(self, fake_successful_upload=False):
- queue_record = self.factory.makeSourcePackageRecipeBuildJob()
- build = queue_record.specific_job.build
- naked_build = removeSecurityProxy(build)
- naked_build.status = BuildStatus.FULLYBUILT
- naked_build.date_started = self.factory.getUniqueDate()
- if fake_successful_upload:
- naked_build.verifySuccessfulUpload = FakeMethod(
- result=True)
- # We overwrite the buildmaster root to use a temp directory.
- tempdir = tempfile.mkdtemp()
- self.addCleanup(shutil.rmtree, tempdir)
- self.upload_root = tempdir
- tmp_builddmaster_root = """
- [builddmaster]
- root: %s
- """ % self.upload_root
- config.push('tmp_builddmaster_root', tmp_builddmaster_root)
- self.addCleanup(config.pop, 'tmp_builddmaster_root')
- queue_record.builder = self.factory.makeBuilder()
- slave = WaitingSlave('BuildStatus.OK')
- self.patch(BuilderSlave, 'makeBuilderSlave', FakeMethod(slave))
- return build
-
- def assertDeferredNotifyCount(self, status, build, expected_count):
- d = build.handleStatus(status, None, {'filemap': {}})
-
- def cb(result):
- self.assertEqual(expected_count, len(pop_notifications()))
-
- d.addCallback(cb)
- return d
-
- def test_handleStatus_PACKAGEFAIL(self):
- """Failing to build the package immediately sends a notification."""
- return self.assertDeferredNotifyCount(
- "PACKAGEFAIL", self.prepare_build(), 1)
-
- def test_handleStatus_OK(self):
- """Building the source package does _not_ immediately send mail.
-
- (The archive uploader mail send one later.
- """
- return self.assertDeferredNotifyCount(
- "OK", self.prepare_build(), 0)
-
- def test_handleStatus_OK_successful_upload(self):
- return self.assertDeferredNotifyCount(
- "OK", self.prepare_build(True), 0)
-
-
-class MakeSPRecipeBuildMixin:
- """Provide the common makeBuild method returning a queued build."""
-
- def makeBuild(self):
- person = self.factory.makePerson()
- distroseries = self.factory.makeDistroSeries()
- processor_fam = getUtility(IProcessorFamilySet).getByName('x86')
- distroseries_i386 = distroseries.newArch(
- 'i386', processor_fam, False, person,
- supports_virtualized=True)
- distroseries.nominatedarchindep = distroseries_i386
- build = self.factory.makeSourcePackageRecipeBuild(
- distroseries=distroseries,
- status=BuildStatus.FULLYBUILT,
- duration=timedelta(minutes=5))
- build.queueBuild(build)
- return build
-
-
-class TestGetUploadMethodsForSPRecipeBuild(
- MakeSPRecipeBuildMixin, TestGetUploadMethodsMixin, TestCaseWithFactory):
- """IPackageBuild.getUpload-related methods work with SPRecipe builds."""
-
-
-class TestHandleStatusForSPRBuild(
- MakeSPRecipeBuildMixin, TestHandleStatusMixin, TrialTestCase):
- """IPackageBuild.handleStatus works with SPRecipe builds."""
=== modified file 'lib/lp/soyuz/tests/test_binarypackagebuild.py'
--- lib/lp/soyuz/tests/test_binarypackagebuild.py 2013-01-07 07:53:54 +0000
+++ lib/lp/soyuz/tests/test_binarypackagebuild.py 2013-01-10 07:23:23 +0000
@@ -10,20 +10,13 @@
import pytz
from storm.store import Store
-from twisted.trial.unittest import TestCase as TrialTestCase
from zope.component import getUtility
from zope.security.proxy import removeSecurityProxy
from lp.buildmaster.enums import BuildStatus
from lp.buildmaster.interfaces.buildqueue import IBuildQueue
from lp.buildmaster.interfaces.packagebuild import IPackageBuild
-from lp.buildmaster.model.builder import BuilderSlave
from lp.buildmaster.model.buildqueue import BuildQueue
-from lp.buildmaster.tests.mock_slaves import WaitingSlave
-from lp.buildmaster.tests.test_packagebuild import (
- TestGetUploadMethodsMixin,
- TestHandleStatusMixin,
- )
from lp.services.job.model.job import Job
from lp.services.webapp.interaction import ANONYMOUS
from lp.services.webapp.interfaces import OAuthPermission
@@ -48,7 +41,6 @@
logout,
TestCaseWithFactory,
)
-from lp.testing.fakemethod import FakeMethod
from lp.testing.layers import (
DatabaseFunctionalLayer,
LaunchpadZopelessLayer,
@@ -86,14 +78,6 @@
self.assertIsNotNone(bq.processor)
self.assertEqual(bq, self.build.buildqueue_record)
- def test_getBuildCookie(self):
- # A build cookie is made up of the job type and record id.
- # The uploadprocessor relies on this format.
- Store.of(self.build).flush()
- cookie = self.build.getBuildCookie()
- expected_cookie = "PACKAGEBUILD-%d" % self.build.id
- self.assertEqual(expected_cookie, cookie)
-
def test_estimateDuration(self):
# Without previous builds, a negligable package size estimate is 60s
self.assertEqual(60, self.build.estimateDuration().seconds)
@@ -470,71 +454,6 @@
self.assertContentEqual(builds, i386_builds)
-class TestStoreBuildInfo(TestCaseWithFactory):
-
- layer = LaunchpadZopelessLayer
-
- def setUp(self):
- super(TestStoreBuildInfo, self).setUp()
- self.publisher = SoyuzTestPublisher()
- self.publisher.prepareBreezyAutotest()
-
- gedit_src_hist = self.publisher.getPubSource(
- sourcename="gedit", status=PackagePublishingStatus.PUBLISHED)
- self.build = gedit_src_hist.createMissingBuilds()[0]
-
- self.builder = self.factory.makeBuilder()
- self.patch(BuilderSlave, 'makeBuilderSlave',
- FakeMethod(WaitingSlave('BuildStatus.OK')))
- self.build.buildqueue_record.markAsBuilding(self.builder)
-
- def testDependencies(self):
- """Verify that storeBuildInfo sets any dependencies."""
- self.build.storeBuildInfo(
- self.build, None, {'dependencies': 'somepackage'})
- self.assertIsNot(None, self.build.log)
- self.assertEqual(self.builder, self.build.builder)
- self.assertEqual(u'somepackage', self.build.dependencies)
-
- def testWithoutDependencies(self):
- """Verify that storeBuildInfo clears the build's dependencies."""
- # Set something just to make sure that storeBuildInfo actually
- # empties it.
- self.build.dependencies = u'something'
- self.build.storeBuildInfo(self.build, None, {})
- self.assertIsNot(None, self.build.log)
- self.assertEqual(self.builder, self.build.builder)
- self.assertIs(None, self.build.dependencies)
- self.assertIsNot(None, self.build.date_finished)
-
- def test_sets_date_finished(self):
- # storeBuildInfo should set date_finished on the BuildFarmJob.
- self.assertIs(None, self.build.date_finished)
- self.build.storeBuildInfo(self.build, None, {})
- self.assertIsNot(None, self.build.date_finished)
-
-
-class MakeBinaryPackageBuildMixin:
- """Provide the makeBuild method returning a queud build."""
-
- def makeBuild(self):
- test_publisher = SoyuzTestPublisher()
- test_publisher.prepareBreezyAutotest()
- binaries = test_publisher.getPubBinaries()
- return binaries[0].binarypackagerelease.build
-
-
-class TestGetUploadMethodsForBinaryPackageBuild(
- MakeBinaryPackageBuildMixin, TestGetUploadMethodsMixin,
- TestCaseWithFactory):
- """IPackageBuild.getUpload-related methods work with binary builds."""
-
-
-class TestHandleStatusForBinaryPackageBuild(
- MakeBinaryPackageBuildMixin, TestHandleStatusMixin, TrialTestCase):
- """IPackageBuild.handleStatus works with binary builds."""
-
-
class TestBinaryPackageBuildWebservice(TestCaseWithFactory):
"""Test cases for BinaryPackageBuild on the webservice.
=== modified file 'lib/lp/soyuz/tests/test_binarypackagebuildbehavior.py'
--- lib/lp/soyuz/tests/test_binarypackagebuildbehavior.py 2012-02-09 23:09:36 +0000
+++ lib/lp/soyuz/tests/test_binarypackagebuildbehavior.py 2013-01-10 07:23:23 +0000
@@ -14,6 +14,7 @@
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
@@ -26,12 +27,17 @@
OkSlave,
WaitingSlave,
)
+from lp.buildmaster.tests.test_buildfarmjobbehavior import (
+ TestGetUploadMethodsMixin,
+ TestHandleStatusMixin,
+ )
from lp.registry.interfaces.pocket import (
PackagePublishingPocket,
pocketsuffix,
)
from lp.registry.interfaces.series import SeriesStatus
from lp.services.config import config
+from lp.services.database.constants import UTC_NOW
from lp.services.job.interfaces.job import JobStatus
from lp.services.librarian.interfaces import ILibraryFileAliasSet
from lp.services.log.logger import BufferLogger
@@ -259,6 +265,16 @@
'Attempt to build virtual item on a non-virtual builder.',
str(e))
+ def test_getBuildCookie(self):
+ # A build cookie is made up of the job type and record id.
+ # The uploadprocessor relies on this format.
+ build = self.factory.makeBinaryPackageBuild()
+ candidate = build.queueBuild()
+ behavior = candidate.required_build_behavior
+ cookie = removeSecurityProxy(behavior).getBuildCookie()
+ expected_cookie = "PACKAGEBUILD-%d" % build.id
+ self.assertEqual(expected_cookie, cookie)
+
class TestBinaryBuildPackageBehaviorBuildCollection(TestCaseWithFactory):
"""Tests for the BinaryPackageBuildBehavior.
@@ -495,7 +511,8 @@
'buildlog', tmp_orig_file_name)
return d.addCallback(got_orig_log)
- d = self.build.getLogFromSlave(self.build)
+ d = removeSecurityProxy(self.behavior).getLogFromSlave(
+ self.build, self.build.buildqueue_record)
return d.addCallback(got_log)
def test_private_build_log_storage(self):
@@ -519,3 +536,25 @@
d = self.builder.updateBuild(self.candidate)
return d.addCallback(got_update)
+
+
+class MakeBinaryPackageBuildMixin:
+ """Provide the makeBuild method returning a queud build."""
+
+ def makeBuild(self):
+ build = self.factory.makeBinaryPackageBuild(
+ status=BuildStatus.FULLYBUILT)
+ build.date_started = UTC_NOW
+ build.queueBuild()
+ return build
+
+
+class TestGetUploadMethodsForBinaryPackageBuild(
+ MakeBinaryPackageBuildMixin, TestGetUploadMethodsMixin,
+ TestCaseWithFactory):
+ """IPackageBuild.getUpload-related methods work with binary builds."""
+
+
+class TestHandleStatusForBinaryPackageBuild(
+ MakeBinaryPackageBuildMixin, TestHandleStatusMixin, TrialTestCase):
+ """IPackageBuild.handleStatus works with binary builds."""
=== modified file 'lib/lp/translations/model/translationtemplatesbuildbehavior.py'
--- lib/lp/translations/model/translationtemplatesbuildbehavior.py 2012-02-01 05:57:16 +0000
+++ lib/lp/translations/model/translationtemplatesbuildbehavior.py 2013-01-10 07:23:23 +0000
@@ -122,30 +122,19 @@
def setBuildStatus(self, status):
self.build.status = status
- @staticmethod
- def getLogFromSlave(templates_build, queue_item):
- """See `IPackageBuild`."""
- SLAVE_LOG_FILENAME = 'buildlog'
- builder = queue_item.builder
- d = builder.transferSlaveFileToLibrarian(
- SLAVE_LOG_FILENAME,
- templates_build.buildfarmjob.getLogFileName(),
- False)
- return d
-
- @staticmethod
- def storeBuildInfo(build, queue_item, build_status):
+ @classmethod
+ def storeBuildInfo(cls, build, queue_item, build_status):
"""See `IPackageBuild`."""
def got_log(lfa_id):
- build.build.log = lfa_id
- build.build.builder = queue_item.builder
- build.build.date_started = queue_item.date_started
+ build.log = lfa_id
+ build.builder = queue_item.builder
+ build.date_started = queue_item.date_started
# XXX cprov 20060615 bug=120584: Currently buildduration includes
# the scanner latency, it should really be asking the slave for
# the duration spent building locally.
- build.build.date_finished = datetime.datetime.now(pytz.UTC)
+ build.date_finished = datetime.datetime.now(pytz.UTC)
- d = build.getLogFromSlave(build, queue_item)
+ d = cls.getLogFromSlave(build, queue_item)
return d.addCallback(got_log)
def updateBuild_WAITING(self, queue_item, slave_status, logtail, logger):
@@ -207,6 +196,6 @@
self.setBuildStatus(BuildStatus.FAILEDTOBUILD)
return clean_slave(None)
- d = self.storeBuildInfo(self, queue_item, build_status)
+ d = self.storeBuildInfo(self.build, queue_item, build_status)
d.addCallback(build_info_stored)
return d
=== modified file 'lib/lp/translations/tests/test_translationtemplatesbuildbehavior.py'
--- lib/lp/translations/tests/test_translationtemplatesbuildbehavior.py 2012-02-09 23:09:36 +0000
+++ lib/lp/translations/tests/test_translationtemplatesbuildbehavior.py 2013-01-10 07:23:23 +0000
@@ -65,6 +65,9 @@
self.date_started = datetime.datetime.now(pytz.UTC)
self.destroySelf = FakeMethod()
+ def getLogFileName(self):
+ return self.specific_job.getLogFileName()
+
class MakeBehaviorMixin(object):
"""Provide common test methods."""