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.
        self.builder = self.factory.makeBuilder()
        self.build.buildqueue_record.markAsBuilding(self.builder)
        self.slave = WaitingSlave('BuildStatus.OK')
        self.slave.valid_file_hashes.append('test_file_hash')
        self.interactor = BuilderInteractor()
        self.behaviour = self.interactor.getBuildBehaviour(
            self.build.buildqueue_record, self.builder, 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 test_virtual_ppa_dispatch(self):
        # Make sure the builder slave gets reset before a build is
        # dispatched to it.
        archive = self.factory.makeArchive(virtualized=True)
        slave = OkSlave()
        builder = self.factory.makeBuilder(
            virtualized=True, vm_host="foohost")
        vitals = extract_vitals_from_db(builder)
        build = self.factory.makeBinaryPackageBuild(
            builder=builder, archive=archive)
        lf = self.factory.makeLibraryFileAlias()
        transaction.commit()
        build.distro_arch_series.addOrUpdateChroot(lf)
        bq = build.queueBuild()
        bq.markAsBuilding(builder)
        interactor = BuilderInteractor()
        d = interactor._startBuild(
            bq, vitals, builder, slave,
            interactor.getBuildBehavior(bq, builder, slave), BufferLogger())

        def check_build(ignored):
            # We expect the first call to the slave to be a resume call,
            # followed by the rest of the usual calls we expect.
            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)
        return d.addCallback(check_build)
 def test_non_virtual_ppa_dispatch_with_primary_ancestry(self):
     # If there is a primary component override, it is honoured for
     # non-virtual PPA builds too.
     archive = self.factory.makeArchive(virtualized=False)
     slave = OkSlave()
     builder = self.factory.makeBuilder(virtualized=False)
     builder.setCleanStatus(BuilderCleanStatus.CLEAN)
     vitals = extract_vitals_from_db(builder)
     build = self.factory.makeBinaryPackageBuild(builder=builder,
                                                 archive=archive)
     self.factory.makeSourcePackagePublishingHistory(
         distroseries=build.distro_series,
         archive=archive.distribution.main_archive,
         sourcepackagename=build.source_package_release.sourcepackagename,
         component='main')
     lf = self.factory.makeLibraryFileAlias()
     transaction.commit()
     build.distro_arch_series.addOrUpdateChroot(lf)
     bq = build.queueBuild()
     bq.markAsBuilding(builder)
     interactor = BuilderInteractor()
     yield interactor._startBuild(
         bq, vitals, builder, slave,
         interactor.getBuildBehaviour(bq, builder, slave), BufferLogger())
     yield self.assertExpectedInteraction(slave.call_log, builder, build,
                                          lf, archive,
                                          ArchivePurpose.PRIMARY, 'main')
Exemplo n.º 4
0
    def test_virtual_ppa_dispatch(self):
        # Make sure the builder slave gets reset before a build is
        # dispatched to it.
        archive = self.factory.makeArchive(virtualized=True)
        slave = OkSlave()
        builder = self.factory.makeBuilder(virtualized=True, vm_host="foohost")
        vitals = extract_vitals_from_db(builder)
        build = self.factory.makeBinaryPackageBuild(builder=builder,
                                                    archive=archive)
        lf = self.factory.makeLibraryFileAlias()
        transaction.commit()
        build.distro_arch_series.addOrUpdateChroot(lf)
        bq = build.queueBuild()
        bq.markAsBuilding(builder)
        interactor = BuilderInteractor()
        d = interactor._startBuild(
            bq, vitals, builder, slave,
            interactor.getBuildBehavior(bq, builder, slave), BufferLogger())

        def check_build(ignored):
            # We expect the first call to the slave to be a resume call,
            # followed by the rest of the usual calls we expect.
            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)

        return d.addCallback(check_build)
 def test_non_virtual_ppa_dispatch(self):
     # When the BinaryPackageBuildBehaviour dispatches PPA builds to
     # non-virtual builders, it stores the chroot on the server and
     # requests a binary package build, lying to say that the archive
     # purpose is "PRIMARY" because this ensures that the package mangling
     # tools will run over the built packages.
     archive = self.factory.makeArchive(virtualized=False)
     slave = OkSlave()
     builder = self.factory.makeBuilder(virtualized=False)
     builder.setCleanStatus(BuilderCleanStatus.CLEAN)
     vitals = extract_vitals_from_db(builder)
     build = self.factory.makeBinaryPackageBuild(builder=builder,
                                                 archive=archive)
     lf = self.factory.makeLibraryFileAlias()
     transaction.commit()
     build.distro_arch_series.addOrUpdateChroot(lf)
     bq = build.queueBuild()
     bq.markAsBuilding(builder)
     interactor = BuilderInteractor()
     yield interactor._startBuild(
         bq, vitals, builder, slave,
         interactor.getBuildBehaviour(bq, builder, slave), BufferLogger())
     yield self.assertExpectedInteraction(slave.call_log, builder, build,
                                          lf, archive,
                                          ArchivePurpose.PRIMARY,
                                          'universe')
Exemplo n.º 6
0
    def test_scan_aborts_lost_slave_with_job(self):
        # SlaveScanner.scan uses BuilderInteractor.rescueIfLost to abort
        # slaves that don't have the expected job.
        slave = BuildingSlave('nontrivial')
        bq = FakeBuildQueue()

        # Instrument updateBuild.
        interactor = BuilderInteractor()
        interactor.updateBuild = FakeMethod()

        scanner = SlaveScanner('mock',
                               MockBuilderFactory(MockBuilder(), bq),
                               BufferLogger(),
                               interactor_factory=FakeMethod(interactor),
                               slave_factory=FakeMethod(slave),
                               behavior_factory=FakeMethod(TrivialBehavior()))
        # XXX: checkCancellation needs more than a FakeBuildQueue.
        scanner.checkCancellation = FakeMethod(defer.succeed(False))

        # A single scan will call status(), notice that the slave is
        # lost, abort() the slave, then reset() the job without calling
        # updateBuild().
        yield scanner.scan()
        self.assertEqual(['status', 'abort'], slave.call_log)
        self.assertEqual(0, interactor.updateBuild.call_count)
        self.assertEqual(1, bq.reset.call_count)
 def test_private_source_dispatch(self):
     archive = self.factory.makeArchive(private=True)
     slave = OkSlave()
     builder = self.factory.makeBuilder()
     builder.setCleanStatus(BuilderCleanStatus.CLEAN)
     vitals = extract_vitals_from_db(builder)
     build = self.factory.makeBinaryPackageBuild(builder=builder,
                                                 archive=archive)
     sprf = build.source_package_release.addFile(
         self.factory.makeLibraryFileAlias(db_only=True),
         filetype=SourcePackageFileType.ORIG_TARBALL)
     sprf_url = (
         'http://private-ppa.launchpad.dev/%s/%s/ubuntu/pool/%s/%s' %
         (archive.owner.name, archive.name,
          poolify(build.source_package_release.sourcepackagename.name,
                  'main'), sprf.libraryfile.filename))
     lf = self.factory.makeLibraryFileAlias()
     transaction.commit()
     build.distro_arch_series.addOrUpdateChroot(lf)
     bq = build.queueBuild()
     bq.markAsBuilding(builder)
     interactor = BuilderInteractor()
     yield interactor._startBuild(
         bq, vitals, builder, slave,
         interactor.getBuildBehaviour(bq, builder, slave), BufferLogger())
     yield self.assertExpectedInteraction(
         slave.call_log,
         builder,
         build,
         lf,
         archive,
         ArchivePurpose.PPA,
         extra_uploads=[(sprf_url, 'buildd', 'sekrit')],
         filemap_names=[sprf.libraryfile.filename])
 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
     # requests a binary package build, lying to say that the archive
     # purpose is "PRIMARY" because this ensures that the package mangling
     # tools will run over the built packages.
     archive = self.factory.makeArchive(virtualized=False)
     slave = OkSlave()
     builder = self.factory.makeBuilder(virtualized=False)
     vitals = extract_vitals_from_db(builder)
     build = self.factory.makeBinaryPackageBuild(
         builder=builder, archive=archive)
     lf = self.factory.makeLibraryFileAlias()
     transaction.commit()
     build.distro_arch_series.addOrUpdateChroot(lf)
     bq = build.queueBuild()
     bq.markAsBuilding(builder)
     interactor = BuilderInteractor()
     d = interactor._startBuild(
         bq, vitals, builder, slave,
         interactor.getBuildBehavior(bq, builder, slave), BufferLogger())
     d.addCallback(
         self.assertExpectedInteraction, slave.call_log, builder, build,
         lf, archive, ArchivePurpose.PRIMARY, 'universe')
     return d
Exemplo n.º 9
0
    def test_scan_aborts_lost_slave_with_job(self):
        # SlaveScanner.scan uses BuilderInteractor.rescueIfLost to abort
        # slaves that don't have the expected job.
        slave = BuildingSlave('nontrivial')
        bq = FakeBuildQueue()

        # Instrument updateBuild.
        interactor = BuilderInteractor()
        interactor.updateBuild = FakeMethod()

        scanner = SlaveScanner(
            'mock', MockBuilderFactory(MockBuilder(), bq), BufferLogger(),
            interactor_factory=FakeMethod(interactor),
            slave_factory=FakeMethod(slave),
            behavior_factory=FakeMethod(TrivialBehavior()))
        # XXX: checkCancellation needs more than a FakeBuildQueue.
        scanner.checkCancellation = FakeMethod(defer.succeed(False))

        # A single scan will call status(), notice that the slave is
        # lost, abort() the slave, then reset() the job without calling
        # updateBuild().
        yield scanner.scan()
        self.assertEqual(['status', 'abort'], slave.call_log)
        self.assertEqual(0, interactor.updateBuild.call_count)
        self.assertEqual(1, bq.reset.call_count)
Exemplo n.º 10
0
 def test_resetOrFail_resume_failure(self):
     reset_fail_config = """
         [builddmaster]
         vm_resume_command: /bin/false"""
     config.push('reset fail', reset_fail_config)
     self.addCleanup(config.pop, 'reset fail')
     builder = MockBuilder(virtualized=True, vm_host="pop", builderok=True)
     vitals = extract_vitals_from_db(builder)
     d = BuilderInteractor.resetOrFail(
         vitals, BuilderInteractor.makeSlaveFromVitals(vitals), builder,
         DevNullLogger(), Exception())
     return assert_fails_with(d, CannotResumeHost)
Exemplo n.º 11
0
    def test_makeSlaveFromVitals(self):
        # Builder.slave is a BuilderSlave that points at the actual Builder.
        # The Builder is only ever used in scripts that run outside of the
        # security context.
        builder = MockBuilder(virtualized=False)
        vitals = extract_vitals_from_db(builder)
        slave = BuilderInteractor.makeSlaveFromVitals(vitals)
        self.assertEqual(builder.url, slave.url)
        self.assertEqual(10, slave.timeout)

        builder = MockBuilder(virtualized=True)
        vitals = extract_vitals_from_db(builder)
        slave = BuilderInteractor.makeSlaveFromVitals(vitals)
        self.assertEqual(5, slave.timeout)
Exemplo n.º 12
0
    def test_makeSlaveFromVitals(self):
        # Builder.slave is a BuilderSlave that points at the actual Builder.
        # The Builder is only ever used in scripts that run outside of the
        # security context.
        builder = MockBuilder(virtualized=False)
        vitals = extract_vitals_from_db(builder)
        slave = BuilderInteractor.makeSlaveFromVitals(vitals)
        self.assertEqual(builder.url, slave.url)
        self.assertEqual(10, slave.timeout)

        builder = MockBuilder(virtualized=True)
        vitals = extract_vitals_from_db(builder)
        slave = BuilderInteractor.makeSlaveFromVitals(vitals)
        self.assertEqual(5, slave.timeout)
Exemplo n.º 13
0
 def test_recover_ok_slave(self):
     # An idle slave that's meant to be idle is not rescued.
     slave = OkSlave()
     lost = yield BuilderInteractor.rescueIfLost(
         extract_vitals_from_db(MockBuilder()), slave, None)
     self.assertFalse(lost)
     self.assertEqual([], slave.call_log)
    def setUp(self):
        super(TestBinaryBuildPackageBehaviourBuildCollection, self).setUp()
        switch_dbuser('testadmin')

        self.builder = self.factory.makeBuilder()
        self.interactor = BuilderInteractor()
        self.build = self.factory.makeBinaryPackageBuild(
            builder=self.builder, pocket=PackagePublishingPocket.RELEASE)
        lf = self.factory.makeLibraryFileAlias()
        transaction.commit()
        self.build.distro_arch_series.addOrUpdateChroot(lf)
        self.candidate = self.build.queueBuild()
        self.candidate.markAsBuilding(self.builder)
        # This is required so that uploaded files from the buildd don't
        # hang around between test runs.
        self.addCleanup(self._cleanup)
    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.
        self.builder = self.factory.makeBuilder()
        self.build.buildqueue_record.markAsBuilding(self.builder)
        self.slave = WaitingSlave('BuildStatus.OK')
        self.slave.valid_file_hashes.append('test_file_hash')
        self.interactor = BuilderInteractor()
        self.behavior = self.interactor.getBuildBehavior(
            self.build.buildqueue_record, self.builder, self.slave)

        # We overwrite the buildmaster root to use a temp directory.
        tempdir = tempfile.mkdtemp()
        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)
Exemplo n.º 16
0
 def test_recover_building_slave_with_good_id(self):
     # rescueIfLost does not attempt to abort or clean a builder that is
     # BUILDING.
     building_slave = BuildingSlave(build_id='trivial')
     lost = yield BuilderInteractor.rescueIfLost(
         extract_vitals_from_db(MockBuilder()), building_slave, 'trivial')
     self.assertFalse(lost)
     self.assertEqual(['status'], building_slave.call_log)
Exemplo n.º 17
0
 def test_recover_building_slave_with_bad_id(self):
     # 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(build_id='non-trivial')
     lost = yield BuilderInteractor.rescueIfLost(
         extract_vitals_from_db(MockBuilder()), building_slave, 'trivial')
     self.assertTrue(lost)
     self.assertEqual(['status', 'abort'], building_slave.call_log)
Exemplo n.º 18
0
 def test_recover_idle_slave(self):
     # An idle slave is not rescued, even if it's not meant to be
     # idle. SlaveScanner.scan() will clean up the DB side, because
     # we still report that it's lost.
     slave = OkSlave()
     lost = yield BuilderInteractor.rescueIfLost(
         extract_vitals_from_db(MockBuilder()), slave, 'trivial')
     self.assertTrue(lost)
     self.assertEqual([], slave.call_log)
Exemplo n.º 19
0
 def test_findAndStartJob_dirties_slave(self):
     # findAndStartJob marks its builder DIRTY before dispatching.
     builder, build = self._setupBinaryBuildAndBuilder()
     candidate = build.queueBuild()
     removeSecurityProxy(builder)._findBuildCandidate = FakeMethod(
         result=candidate)
     vitals = extract_vitals_from_db(builder)
     yield BuilderInteractor.findAndStartJob(vitals, builder, OkSlave())
     self.assertEqual(BuilderCleanStatus.DIRTY, builder.clean_status)
 def test_virtual_ppa_dispatch(self):
     archive = self.factory.makeArchive(virtualized=True)
     slave = OkSlave()
     builder = self.factory.makeBuilder(virtualized=True, vm_host="foohost")
     builder.setCleanStatus(BuilderCleanStatus.CLEAN)
     vitals = extract_vitals_from_db(builder)
     build = self.factory.makeBinaryPackageBuild(builder=builder,
                                                 archive=archive)
     lf = self.factory.makeLibraryFileAlias()
     transaction.commit()
     build.distro_arch_series.addOrUpdateChroot(lf)
     bq = build.queueBuild()
     bq.markAsBuilding(builder)
     interactor = BuilderInteractor()
     yield interactor._startBuild(
         bq, vitals, builder, slave,
         interactor.getBuildBehaviour(bq, builder, slave), BufferLogger())
     yield self.assertExpectedInteraction(slave.call_log, builder, build,
                                          lf, archive, ArchivePurpose.PPA)
 def got_status(status):
     slave_status = {
         'builder_status': status[0],
         'build_status': status[1],
         'build_id': status[2],
     }
     behavior.updateSlaveStatus(status, slave_status)
     return behavior.handleStatus(
         queue_item, BuilderInteractor.extractBuildStatus(slave_status),
         slave_status),
Exemplo n.º 22
0
 def test_partner_dispatch_no_publishing_history(self):
     archive = self.factory.makeArchive(virtualized=False,
                                        purpose=ArchivePurpose.PARTNER)
     slave = OkSlave()
     builder = self.factory.makeBuilder(virtualized=False)
     vitals = extract_vitals_from_db(builder)
     build = self.factory.makeBinaryPackageBuild(builder=builder,
                                                 archive=archive)
     lf = self.factory.makeLibraryFileAlias()
     transaction.commit()
     build.distro_arch_series.addOrUpdateChroot(lf)
     bq = build.queueBuild()
     bq.markAsBuilding(builder)
     interactor = BuilderInteractor()
     d = interactor._startBuild(
         bq, vitals, builder, slave,
         interactor.getBuildBehavior(bq, builder, slave), BufferLogger())
     d.addCallback(self.assertExpectedInteraction, slave.call_log, builder,
                   build, lf, archive, ArchivePurpose.PARTNER)
     return d
Exemplo n.º 23
0
 def test_recover_waiting_slave_with_bad_id(self):
     # If a slave is WAITING with a build for us to get, and the build
     # cookie cannot be verified, which means we don't recognize the build,
     # then rescueBuilderIfLost should attempt to abort it, so that the
     # builder is reset for a new build, and the corrupt build is
     # discarded.
     waiting_slave = WaitingSlave(build_id='non-trivial')
     lost = yield BuilderInteractor.rescueIfLost(
         extract_vitals_from_db(MockBuilder()), waiting_slave, 'trivial')
     self.assertTrue(lost)
     self.assertEqual(['status', 'clean'], waiting_slave.call_log)
Exemplo n.º 24
0
 def test_findAndStartJob_requires_clean_slave(self):
     # findAndStartJob ensures that its slave starts CLEAN.
     builder, build = self._setupBinaryBuildAndBuilder()
     builder.setCleanStatus(BuilderCleanStatus.DIRTY)
     candidate = build.queueBuild()
     removeSecurityProxy(builder)._findBuildCandidate = FakeMethod(
         result=candidate)
     vitals = extract_vitals_from_db(builder)
     with ExpectedException(BuildDaemonIsolationError,
                            "Attempted to start build on a dirty slave."):
         yield BuilderInteractor.findAndStartJob(vitals, builder, OkSlave())
 def got_status(status):
     slave_status = {
         'builder_status': status[0],
         'build_status': status[1],
         'build_id': status[2],
         }
     behavior.updateSlaveStatus(status, slave_status)
     return behavior.handleStatus(
         queue_item,
         BuilderInteractor.extractBuildStatus(slave_status),
         slave_status),
 def got_status(status):
     slave_call_log = slave.call_log
     slave_status = {
         'builder_status': status[0],
         'build_status': status[1],
         'filemap': {
             'translation-templates.tar.gz': 'foo'
         },
     }
     return (behavior.handleStatus(
         queue_item, BuilderInteractor.extractBuildStatus(slave_status),
         slave_status), slave_call_log)
Exemplo n.º 27
0
    def test_scan_aborts_lost_slave_when_idle(self):
        # SlaveScanner.scan uses BuilderInteractor.rescueIfLost to abort
        # slaves that aren't meant to have a job.
        slave = BuildingSlave()

        # Instrument updateBuild.
        interactor = BuilderInteractor()
        interactor.updateBuild = FakeMethod()

        scanner = SlaveScanner(
            'mock', MockBuilderFactory(MockBuilder(), None), BufferLogger(),
            interactor_factory=FakeMethod(interactor),
            slave_factory=FakeMethod(slave),
            behavior_factory=FakeMethod(None))

        # A single scan will call status(), notice that the slave is
        # lost, abort() the slave, then reset() the job without calling
        # updateBuild().
        yield scanner.scan()
        self.assertEqual(['status', 'abort'], slave.call_log)
        self.assertEqual(0, interactor.updateBuild.call_count)
 def test_partner_dispatch_no_publishing_history(self):
     archive = self.factory.makeArchive(
         virtualized=False, purpose=ArchivePurpose.PARTNER)
     slave = OkSlave()
     builder = self.factory.makeBuilder(virtualized=False)
     vitals = extract_vitals_from_db(builder)
     build = self.factory.makeBinaryPackageBuild(
         builder=builder, archive=archive)
     lf = self.factory.makeLibraryFileAlias()
     transaction.commit()
     build.distro_arch_series.addOrUpdateChroot(lf)
     bq = build.queueBuild()
     bq.markAsBuilding(builder)
     interactor = BuilderInteractor()
     d = interactor._startBuild(
         bq, vitals, builder, slave,
         interactor.getBuildBehavior(bq, builder, slave), BufferLogger())
     d.addCallback(
         self.assertExpectedInteraction, slave.call_log, builder, build,
         lf, archive, ArchivePurpose.PARTNER)
     return d
Exemplo n.º 29
0
    def test_scan_with_job(self):
        # SlaveScanner.scan calls updateBuild() when a job is building.
        slave = BuildingSlave('trivial')
        bq = FakeBuildQueue()

        # Instrument updateBuild.
        interactor = BuilderInteractor()
        interactor.updateBuild = FakeMethod()

        scanner = SlaveScanner(
            'mock', MockBuilderFactory(MockBuilder(), bq), BufferLogger(),
            interactor_factory=FakeMethod(interactor),
            slave_factory=FakeMethod(slave),
            behavior_factory=FakeMethod(TrivialBehavior()))
        # XXX: checkCancellation needs more than a FakeBuildQueue.
        scanner.checkCancellation = FakeMethod(defer.succeed(False))

        yield scanner.scan()
        self.assertEqual(['status'], slave.call_log)
        self.assertEqual(1, interactor.updateBuild.call_count)
        self.assertEqual(0, bq.reset.call_count)
Exemplo n.º 30
0
 def test_findAndStartJob_returns_candidate(self):
     # findAndStartJob finds the next queued job using _findBuildCandidate.
     # We don't care about the type of build at all.
     builder, build = self._setupRecipeBuildAndBuilder()
     candidate = build.queueBuild()
     # _findBuildCandidate is tested elsewhere, we just make sure that
     # findAndStartJob delegates to it.
     removeSecurityProxy(builder)._findBuildCandidate = FakeMethod(
         result=candidate)
     vitals = extract_vitals_from_db(builder)
     d = BuilderInteractor.findAndStartJob(vitals, builder, OkSlave())
     return d.addCallback(self.assertEqual, candidate)
Exemplo n.º 31
0
 def test_virtual_no_protocol(self):
     # Virtual builders fail to clean unless vm_reset_protocol is
     # set.
     builder = MockBuilder(virtualized=True,
                           clean_status=BuilderCleanStatus.DIRTY,
                           vm_host='lol')
     builder.vm_reset_protocol = None
     with ExpectedException(CannotResumeHost,
                            "Invalid vm_reset_protocol: None"):
         yield BuilderInteractor.cleanSlave(
             extract_vitals_from_db(builder), OkSlave(),
             MockBuilderFactory(builder, None))
Exemplo n.º 32
0
 def test_findAndStartJob_returns_candidate(self):
     # findAndStartJob finds the next queued job using _findBuildCandidate.
     # We don't care about the type of build at all.
     builder, build = self._setupRecipeBuildAndBuilder()
     candidate = build.queueBuild()
     # _findBuildCandidate is tested elsewhere, we just make sure that
     # findAndStartJob delegates to it.
     removeSecurityProxy(builder)._findBuildCandidate = FakeMethod(
         result=candidate)
     vitals = extract_vitals_from_db(builder)
     d = BuilderInteractor.findAndStartJob(vitals, builder, OkSlave())
     return d.addCallback(self.assertEqual, candidate)
Exemplo n.º 33
0
    def test_scan_aborts_lost_slave_when_idle(self):
        # SlaveScanner.scan uses BuilderInteractor.rescueIfLost to abort
        # slaves that aren't meant to have a job.
        slave = BuildingSlave()

        # Instrument updateBuild.
        interactor = BuilderInteractor()
        interactor.updateBuild = FakeMethod()

        scanner = SlaveScanner('mock',
                               MockBuilderFactory(MockBuilder(), None),
                               BufferLogger(),
                               interactor_factory=FakeMethod(interactor),
                               slave_factory=FakeMethod(slave),
                               behavior_factory=FakeMethod(None))

        # A single scan will call status(), notice that the slave is
        # lost, abort() the slave, then reset() the job without calling
        # updateBuild().
        yield scanner.scan()
        self.assertEqual(['status', 'abort'], slave.call_log)
        self.assertEqual(0, interactor.updateBuild.call_count)
Exemplo n.º 34
0
    def test_scan_with_job(self):
        # SlaveScanner.scan calls updateBuild() when a job is building.
        slave = BuildingSlave('trivial')
        bq = FakeBuildQueue()

        # Instrument updateBuild.
        interactor = BuilderInteractor()
        interactor.updateBuild = FakeMethod()

        scanner = SlaveScanner('mock',
                               MockBuilderFactory(MockBuilder(), bq),
                               BufferLogger(),
                               interactor_factory=FakeMethod(interactor),
                               slave_factory=FakeMethod(slave),
                               behavior_factory=FakeMethod(TrivialBehavior()))
        # XXX: checkCancellation needs more than a FakeBuildQueue.
        scanner.checkCancellation = FakeMethod(defer.succeed(False))

        yield scanner.scan()
        self.assertEqual(['status'], slave.call_log)
        self.assertEqual(1, interactor.updateBuild.call_count)
        self.assertEqual(0, bq.reset.call_count)
Exemplo n.º 35
0
 def test_getBuildBehaviour_building(self):
     """The current behaviour is set automatically from the current job."""
     # Set the builder attribute on the buildqueue record so that our
     # builder will think it has a current build.
     builder = self.factory.makeBuilder(name='builder')
     slave = BuildingSlave()
     build = self.factory.makeBinaryPackageBuild()
     bq = build.queueBuild()
     bq.markAsBuilding(builder)
     behaviour = BuilderInteractor.getBuildBehaviour(bq, builder, slave)
     self.assertIsInstance(behaviour, BinaryPackageBuildBehaviour)
     self.assertEqual(behaviour._builder, builder)
     self.assertEqual(behaviour._slave, slave)
 def got_status(status):
     slave_call_log = slave.call_log
     slave_status = {
         'builder_status': status[0],
         'build_status': status[1],
         'filemap': {'translation-templates.tar.gz': 'foo'},
         }
     return (
         behavior.handleStatus(
             queue_item,
             BuilderInteractor.extractBuildStatus(slave_status),
             slave_status),
         slave_call_log)
Exemplo n.º 37
0
 def test_getBuildBehavior_building(self):
     """The current behavior is set automatically from the current job."""
     # Set the builder attribute on the buildqueue record so that our
     # builder will think it has a current build.
     builder = self.factory.makeBuilder(name='builder')
     slave = BuildingSlave()
     build = self.factory.makeBinaryPackageBuild()
     bq = build.queueBuild()
     bq.markAsBuilding(builder)
     behavior = BuilderInteractor.getBuildBehavior(bq, builder, slave)
     self.assertIsInstance(behavior, BinaryPackageBuildBehavior)
     self.assertEqual(behavior._builder, builder)
     self.assertEqual(behavior._slave, slave)
Exemplo n.º 38
0
 def test_nonvirtual_broken(self):
     # A broken non-virtual builder is probably unrecoverable, so the
     # method just crashes.
     builder = MockBuilder(virtualized=False,
                           clean_status=BuilderCleanStatus.DIRTY)
     vitals = extract_vitals_from_db(builder)
     slave = LostBuildingBrokenSlave()
     try:
         yield BuilderInteractor.cleanSlave(
             vitals, slave, MockBuilderFactory(builder, None))
     except xmlrpclib.Fault:
         self.assertEqual(['status', 'abort'], slave.call_log)
     else:
         self.fail("abort() should crash.")
Exemplo n.º 39
0
    def test_rescueIfLost_aborts_lost_and_broken_slave(self):
        # A slave that's 'lost' should be aborted; when the slave is
        # broken then abort() should also throw a fault.
        slave = LostBuildingBrokenSlave()
        d = BuilderInteractor.rescueIfLost(
            extract_vitals_from_db(MockBuilder()), slave, 'trivial')

        def check_slave_status(failure):
            self.assertIn('abort', slave.call_log)
            # 'Fault' comes from the LostBuildingBrokenSlave, this is
            # just testing that the value is passed through.
            self.assertIsInstance(failure.value, xmlrpclib.Fault)

        return d.addBoth(check_slave_status)
Exemplo n.º 40
0
    def test_virtual_job_dispatch_pings_before_building(self):
        # We need to send a ping to the builder to work around a bug
        # where sometimes the first network packet sent is dropped.
        builder, build = self._setupBinaryBuildAndBuilder()
        candidate = build.queueBuild()
        removeSecurityProxy(builder)._findBuildCandidate = FakeMethod(
            result=candidate)
        vitals = extract_vitals_from_db(builder)
        slave = OkSlave()
        d = BuilderInteractor.findAndStartJob(vitals, builder, slave)

        def check_build_started(candidate):
            self.assertIn(('echo', 'ping'), slave.call_log)

        return d.addCallback(check_build_started)
 def got_status(status):
     raw_status = (
         'BuilderStatus.WAITING',
         'BuildStatus.OK',
         status[2],
     )
     slave_status = {
         'builder_status': raw_status[0],
         'build_status': raw_status[1],
     }
     behavior.updateSlaveStatus(raw_status, slave_status)
     self.assertFalse('filemap' in slave_status)
     return behavior.handleStatus(
         queue_item, BuilderInteractor.extractBuildStatus(slave_status),
         slave_status),
Exemplo n.º 42
0
    def test_findAndStartJob_starts_job(self):
        # findAndStartJob finds the next queued job using _findBuildCandidate
        # and then starts it.
        # We don't care about the type of build at all.
        builder, build = self._setupRecipeBuildAndBuilder()
        candidate = build.queueBuild()
        removeSecurityProxy(builder)._findBuildCandidate = FakeMethod(
            result=candidate)
        vitals = extract_vitals_from_db(builder)
        d = BuilderInteractor.findAndStartJob(vitals, builder, OkSlave())

        def check_build_started(candidate):
            self.assertEqual(candidate.builder, builder)
            self.assertEqual(BuildStatus.BUILDING, build.status)

        return d.addCallback(check_build_started)
 def got_status(status):
     raw_status = (
         'BuilderStatus.WAITING',
         'BuildStatus.OK',
         status[2],
         )
     slave_status = {
         'builder_status': raw_status[0],
         'build_status': raw_status[1],
         }
     behavior.updateSlaveStatus(raw_status, slave_status)
     self.assertFalse('filemap' in slave_status)
     return behavior.handleStatus(
         queue_item,
         BuilderInteractor.extractBuildStatus(slave_status),
         slave_status),
    def setUp(self):
        super(TestBinaryBuildPackageBehaviorBuildCollection, self).setUp()
        switch_dbuser('testadmin')

        self.builder = self.factory.makeBuilder()
        self.interactor = BuilderInteractor()
        self.build = self.factory.makeBinaryPackageBuild(
            builder=self.builder, pocket=PackagePublishingPocket.RELEASE)
        lf = self.factory.makeLibraryFileAlias()
        transaction.commit()
        self.build.distro_arch_series.addOrUpdateChroot(lf)
        self.candidate = self.build.queueBuild()
        self.candidate.markAsBuilding(self.builder)
        # This is required so that uploaded files from the buildd don't
        # hang around between test runs.
        self.addCleanup(self._cleanup)
Exemplo n.º 45
0
    def test_findAndStartJob_starts_job(self):
        # findAndStartJob finds the next queued job using _findBuildCandidate
        # and then starts it.
        # We don't care about the type of build at all.
        builder, build = self._setupRecipeBuildAndBuilder()
        candidate = build.queueBuild()
        removeSecurityProxy(builder)._findBuildCandidate = FakeMethod(
            result=candidate)
        vitals = extract_vitals_from_db(builder)
        d = BuilderInteractor.findAndStartJob(vitals, builder, OkSlave())

        def check_build_started(candidate):
            self.assertEqual(candidate.builder, builder)
            self.assertEqual(BuildStatus.BUILDING, build.status)

        return d.addCallback(check_build_started)
Exemplo n.º 46
0
    def assertStatus(self, slave, builder_status=None,
                     build_status=None, logtail=False, filemap=None,
                     dependencies=None):
        statuses = yield BuilderInteractor.slaveStatus(slave)
        status_dict = statuses[1]

        expected = {}
        if builder_status is not None:
            expected["builder_status"] = builder_status
        if build_status is not None:
            expected["build_status"] = build_status
        if dependencies is not None:
            expected["dependencies"] = dependencies

        # We don't care so much about the content of the logtail,
        # just that it's there.
        if logtail:
            tail = status_dict.pop("logtail")
            self.assertIsInstance(tail, xmlrpclib.Binary)

        self.assertEqual(expected, status_dict)
Exemplo n.º 47
0
 def prepareBehavior(self, fake_successful_upload=False):
     self.queue_record = self.factory.makeSourcePackageRecipeBuildJob()
     build = self.queue_record.specific_job.build
     build.updateStatus(BuildStatus.FULLYBUILT)
     if fake_successful_upload:
         removeSecurityProxy(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")
     self.queue_record.builder = self.factory.makeBuilder()
     slave = WaitingSlave("BuildStatus.OK")
     return BuilderInteractor.getBuildBehavior(self.queue_record, self.queue_record.builder, slave)
Exemplo n.º 48
0
 def resumeSlaveHost(self, builder):
     vitals = extract_vitals_from_db(builder)
     return BuilderInteractor.resumeSlaveHost(
         vitals, BuilderInteractor.makeSlaveFromVitals(vitals))
class TestBinaryBuildPackageBehaviorBuildCollection(TestCaseWithFactory):
    """Tests for the BinaryPackageBuildBehavior.

    Using various mock slaves, we check how updateBuild() behaves in
    various scenarios.
    """

    # XXX: These tests replace part of the old buildd-slavescanner.txt
    # It was checking that each call to updateBuild was sending 3 (!)
    # emails but this behaviour is so ill-defined and dependent on the
    # sample data that I've not replicated that here.  We need to
    # examine that behaviour separately somehow, but the old tests gave
    # NO clue as to what, exactly, they were testing.

    layer = LaunchpadZopelessLayer
    run_tests_with = AsynchronousDeferredRunTest

    def _cleanup(self):
        if os.path.exists(config.builddmaster.root):
            shutil.rmtree(config.builddmaster.root)

    def setUp(self):
        super(TestBinaryBuildPackageBehaviorBuildCollection, self).setUp()
        switch_dbuser('testadmin')

        self.builder = self.factory.makeBuilder()
        self.interactor = BuilderInteractor()
        self.build = self.factory.makeBinaryPackageBuild(
            builder=self.builder, pocket=PackagePublishingPocket.RELEASE)
        lf = self.factory.makeLibraryFileAlias()
        transaction.commit()
        self.build.distro_arch_series.addOrUpdateChroot(lf)
        self.candidate = self.build.queueBuild()
        self.candidate.markAsBuilding(self.builder)
        # This is required so that uploaded files from the buildd don't
        # hang around between test runs.
        self.addCleanup(self._cleanup)

    def updateBuild(self, candidate, slave):
        bf = MockBuilderFactory(self.builder, candidate)
        return self.interactor.updateBuild(
            bf.getVitals('foo'), slave, bf, self.interactor.getBuildBehavior)

    def assertBuildProperties(self, build):
        """Check that a build happened by making sure some of its properties
        are set."""
        self.assertIsNot(None, build.builder)
        self.assertIsNot(None, build.date_finished)
        self.assertIsNot(None, build.duration)
        self.assertIsNot(None, build.log)

    def test_packagefail_collection(self):
        # When a package fails to build, make sure the builder notes are
        # stored and the build status is set as failed.
        def got_update(ignored):
            self.assertBuildProperties(self.build)
            self.assertEqual(BuildStatus.FAILEDTOBUILD, self.build.status)

        d = self.updateBuild(
            self.candidate, WaitingSlave('BuildStatus.PACKAGEFAIL'))
        return d.addCallback(got_update)

    def test_depwait_collection(self):
        # Package build was left in dependency wait.
        DEPENDENCIES = 'baz (>= 1.0.1)'

        def got_update(ignored):
            self.assertBuildProperties(self.build)
            self.assertEqual(BuildStatus.MANUALDEPWAIT, self.build.status)
            self.assertEqual(DEPENDENCIES, self.build.dependencies)

        d = self.updateBuild(
            self.candidate, WaitingSlave('BuildStatus.DEPFAIL', DEPENDENCIES))
        return d.addCallback(got_update)

    def test_chrootfail_collection(self):
        # There was a chroot problem for this build.
        def got_update(ignored):
            self.assertBuildProperties(self.build)
            self.assertEqual(BuildStatus.CHROOTWAIT, self.build.status)

        d = self.updateBuild(
            self.candidate, WaitingSlave('BuildStatus.CHROOTFAIL'))
        return d.addCallback(got_update)

    def test_builderfail_collection(self):
        # The builder failed after we dispatched the build.
        def got_update(ignored):
            self.assertEqual(
                "Builder returned BUILDERFAIL when asked for its status",
                self.builder.failnotes)
            self.assertIs(None, self.candidate.builder)
            self.assertEqual(BuildStatus.NEEDSBUILD, self.build.status)
            job = self.candidate.specific_job.job
            self.assertEqual(JobStatus.WAITING, job.status)

        d = self.updateBuild(
            self.candidate, WaitingSlave('BuildStatus.BUILDERFAIL'))
        return d.addCallback(got_update)

    def test_building_collection(self):
        # The builder is still building the package.
        def got_update(ignored):
            # The fake log is returned from the BuildingSlave() mock.
            self.assertEqual("This is a build log", self.candidate.logtail)

        d = self.updateBuild(self.candidate, BuildingSlave())
        return d.addCallback(got_update)

    def test_aborting_collection(self):
        # The builder is in the process of aborting.
        def got_update(ignored):
            self.assertEqual(
                "Waiting for slave process to be terminated",
                self.candidate.logtail)

        d = self.updateBuild(self.candidate, AbortingSlave())
        return d.addCallback(got_update)

    def test_collection_for_deleted_source(self):
        # If we collected a build for a superseded/deleted source then
        # the build should get marked superseded as the build results
        # get discarded.
        spr = removeSecurityProxy(self.build.source_package_release)
        pub = self.build.current_source_publication
        pub.requestDeletion(spr.creator)

        def got_update(ignored):
            self.assertEqual(
                BuildStatus.SUPERSEDED, self.build.status)

        d = self.updateBuild(self.candidate, WaitingSlave('BuildStatus.OK'))
        return d.addCallback(got_update)

    def test_uploading_collection(self):
        # After a successful build, the status should be UPLOADING.
        def got_update(ignored):
            self.assertEqual(self.build.status, BuildStatus.UPLOADING)
            # We do not store any upload log information when the binary
            # upload processing succeeded.
            self.assertIs(None, self.build.upload_log)

        d = self.updateBuild(self.candidate, WaitingSlave('BuildStatus.OK'))
        return d.addCallback(got_update)

    def test_givenback_collection(self):
        score = self.candidate.lastscore

        def got_update(ignored):
            self.assertIs(None, self.candidate.builder)
            self.assertIs(None, self.candidate.date_started)
            self.assertEqual(score, self.candidate.lastscore)
            self.assertEqual(BuildStatus.NEEDSBUILD, self.build.status)
            job = self.candidate.specific_job.job
            self.assertEqual(JobStatus.WAITING, job.status)

        d = self.updateBuild(
            self.candidate, WaitingSlave('BuildStatus.GIVENBACK'))
        return d.addCallback(got_update)

    def test_log_file_collection(self):
        self.build.updateStatus(BuildStatus.FULLYBUILT)
        old_tmps = sorted(os.listdir('/tmp'))

        slave = WaitingSlave('BuildStatus.OK')

        def got_log(logfile_lfa_id):
            # Grabbing logs should not leave new files in /tmp (bug #172798)
            logfile_lfa = getUtility(ILibraryFileAliasSet)[logfile_lfa_id]
            new_tmps = sorted(os.listdir('/tmp'))
            self.assertEqual(old_tmps, new_tmps)

            # The new librarian file is stored compressed with a .gz
            # extension and text/plain file type for easy viewing in
            # browsers, as it decompresses and displays the file inline.
            self.assertTrue(
                logfile_lfa.filename.endswith('_FULLYBUILT.txt.gz'))
            self.assertEqual('text/plain', logfile_lfa.mimetype)
            self.layer.txn.commit()

            # LibrarianFileAlias does not implement tell() or seek(), which
            # are required by gzip.open(), so we need to read the file out
            # of the librarian first.
            fd, fname = tempfile.mkstemp()
            self.addCleanup(os.remove, fname)
            tmp = os.fdopen(fd, 'wb')
            tmp.write(logfile_lfa.read())
            tmp.close()
            uncompressed_file = gzip.open(fname).read()

            # Now make a temp filename that getFile() can write to.
            fd, tmp_orig_file_name = tempfile.mkstemp()
            self.addCleanup(os.remove, tmp_orig_file_name)

            # Check that the original file from the slave matches the
            # uncompressed file in the librarian.
            def got_orig_log(ignored):
                orig_file_content = open(tmp_orig_file_name).read()
                self.assertEqual(orig_file_content, uncompressed_file)

            d = removeSecurityProxy(slave).getFile(
                'buildlog', tmp_orig_file_name)
            return d.addCallback(got_orig_log)

        behavior = IBuildFarmJobBehavior(self.candidate.specific_job)
        behavior.setBuilder(self.builder, slave)
        d = behavior.getLogFromSlave(self.build.buildqueue_record)
        return d.addCallback(got_log)

    def test_private_build_log_storage(self):
        # Builds in private archives should have their log uploaded to
        # the restricted librarian.

        # Go behind Storm's back since the field validator on
        # Archive.private prevents us from setting it to True with
        # existing published sources.
        Store.of(self.build).execute("""
            UPDATE archive SET private=True,buildd_secret='foo'
            WHERE archive.id = %s""" % self.build.archive.id)
        Store.of(self.build).invalidate()

        def got_update(ignored):
            # Librarian needs a commit.  :(
            self.layer.txn.commit()
            self.assertTrue(self.build.log.restricted)

        d = self.updateBuild(self.candidate, WaitingSlave('BuildStatus.OK'))
        return d.addCallback(got_update)
Exemplo n.º 50
0
 def test_extractBuildStatus_baseline(self):
     # extractBuildStatus picks the name of the build status out of a
     # dict describing the slave's status.
     slave_status = {'build_status': 'BuildStatus.BUILDING'}
     self.assertEqual(
         'BUILDING', BuilderInteractor.extractBuildStatus(slave_status))
Exemplo n.º 51
0
 def test_getBuildBehavior_idle(self):
     """An idle builder has no build behavior."""
     self.assertIs(
         None,
         BuilderInteractor.getBuildBehavior(None, MockBuilder(), None))
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.
        self.builder = self.factory.makeBuilder()
        self.build.buildqueue_record.markAsBuilding(self.builder)
        self.slave = WaitingSlave('BuildStatus.OK')
        self.slave.valid_file_hashes.append('test_file_hash')
        self.interactor = BuilderInteractor()
        self.behavior = self.interactor.getBuildBehavior(
            self.build.buildqueue_record, self.builder, self.slave)

        # We overwrite the buildmaster root to use a temp directory.
        tempdir = tempfile.mkdtemp()
        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(
            self.build.buildqueue_record, 'OK',
            {'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(
            self.build.buildqueue_record, 'OK',
            {'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(
            self.build.buildqueue_record, 'OK',
            {'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.
        self.assertEqual(None, self.build.log)
        d = self.behavior.handleStatus(
            self.build.buildqueue_record, 'OK',
            {'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(
            self.build.buildqueue_record, status, {})
        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_handleStatus_ABORTED_cancels_cancelling(self):
        self.build.updateStatus(BuildStatus.CANCELLING)

        def got_status(ignored):
            self.assertEqual(
                0, len(pop_notifications()), "Notifications received")
            self.assertEqual(BuildStatus.CANCELLED, self.build.status)

        d = self.behavior.handleStatus(
            self.build.buildqueue_record, "ABORTED", {})
        return d.addCallback(got_status)

    def test_handleStatus_ABORTED_recovers_building(self):
        self.builder.vm_host = "fake_vm_host"
        self.behavior = self.interactor.getBuildBehavior(
            self.build.buildqueue_record, self.builder, self.slave)
        self.build.updateStatus(BuildStatus.BUILDING)

        def got_status(ignored):
            self.assertEqual(
                0, len(pop_notifications()), "Notifications received")
            self.assertEqual(BuildStatus.NEEDSBUILD, self.build.status)
            self.assertEqual(1, self.builder.failure_count)
            self.assertEqual(1, self.build.failure_count)
            self.assertIn("clean", self.slave.call_log)

        d = self.behavior.handleStatus(
            self.build.buildqueue_record, "ABORTED", {})
        return d.addCallback(got_status)

    @defer.inlineCallbacks
    def test_handleStatus_ABORTED_cancelling_sets_build_log(self):
        # If a build is intentionally cancelled, the build log is set.
        self.assertEqual(None, self.build.log)
        self.build.updateStatus(BuildStatus.CANCELLING)
        yield self.behavior.handleStatus(
            self.build.buildqueue_record, "ABORTED", {})
        self.assertNotEqual(None, self.build.log)

    def test_date_finished_set(self):
        # The date finished is updated during handleStatus_OK.
        self.assertEqual(None, self.build.date_finished)
        d = self.behavior.handleStatus(
            self.build.buildqueue_record, 'OK',
            {'filemap': {'myfile.py': 'test_file_hash'}})

        def got_status(ignored):
            self.assertNotEqual(None, self.build.date_finished)

        return d.addCallback(got_status)