Merge from default production-0.8 FENNEC_20_0_1_BUILD1 FENNEC_20_0_1_RELEASE FENNEC_21_0b1_BUILD1 FENNEC_21_0b1_RELEASE FENNEC_21_0b2_BUILD1 FENNEC_21_0b2_BUILD2 FENNEC_21_0b2_RELEASE FENNEC_21_0b3_BUILD1 FENNEC_21_0b3_RELEASE FENNEC_21_0b4_BUILD1 FENNEC_21_0b4_RELEASE FENNEC_21_0b6_BUILD1 FENNEC_21_0b6_RELEASE FIREFOX_17_0_5esr_BUILD1 FIREFOX_17_0_5esr_RELEASE FIREFOX_20_0_1_BUILD1 FIREFOX_20_0_1_RELEASE FIREFOX_21_0b1_BUILD1 FIREFOX_21_0b1_RELEASE FIREFOX_21_0b2_BUILD1 FIREFOX_21_0b2_RELEASE FIREFOX_21_0b3_BUILD1 FIREFOX_21_0b3_RELEASE FIREFOX_21_0b4_BUILD1 FIREFOX_21_0b4_RELEASE FIREFOX_21_0b5_BUILD1 FIREFOX_21_0b5_RELEASE FIREFOX_21_0b6_BUILD1 FIREFOX_21_0b6_RELEASE THUNDERBIRD_17_0_5_BUILD1 THUNDERBIRD_17_0_5_RELEASE THUNDERBIRD_17_0_5esr_BUILD1 THUNDERBIRD_17_0_5esr_RELEASE THUNDERBIRD_21_0b1_BUILD1 THUNDERBIRD_21_0b1_RELEASE
authorEd Morley <emorley@mozilla.com>
Thu, 28 Mar 2013 12:40:40 +0000
branchproduction-0.8
changeset 749 bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb
parent 672 41fc8a9db7a0b0233c00d887b83cc237f8904c7e (current diff)
parent 748 797a4d4b2c01a945e06a29fba98fb376f52cf35f (diff)
child 769 0ba2f2a46a9326dc2821d619ed61da44ac2df228
push id517
push useremorley@mozilla.com
push dateThu, 28 Mar 2013 12:45:26 +0000
Merge from default
--- a/.hgtags
+++ b/.hgtags
@@ -822,8 +822,173 @@ bd4812420e639f5d19cebc2c1d9f50ad7fbac409
 bd4812420e639f5d19cebc2c1d9f50ad7fbac409 FIREFOX_17_0_1_RELEASE
 bd4812420e639f5d19cebc2c1d9f50ad7fbac409 FIREFOX_17_0_1_BUILD1
 bd4812420e639f5d19cebc2c1d9f50ad7fbac409 FIREFOX_17_0_1esr_RELEASE
 bd4812420e639f5d19cebc2c1d9f50ad7fbac409 FIREFOX_17_0_1esr_BUILD1
 bd4812420e639f5d19cebc2c1d9f50ad7fbac409 FENNEC_18_0b3_RELEASE
 bd4812420e639f5d19cebc2c1d9f50ad7fbac409 FENNEC_18_0b3_BUILD1
 bd4812420e639f5d19cebc2c1d9f50ad7fbac409 FIREFOX_18_0b3_RELEASE
 bd4812420e639f5d19cebc2c1d9f50ad7fbac409 FIREFOX_18_0b3_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_18_0b4_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_18_0b4_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_18_0b4_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_18_0b4_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_18_0b4_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_18_0b4_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_18_0b4_BUILD2
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_18_0b5_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_18_0b5_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_18_0b5_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_18_0b5_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_18_0b6_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_18_0b6_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_18_0b6_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_18_0b6_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_18_0b7_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_18_0b7_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_18_0b7_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_18_0b7_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_10_0_12esr_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_10_0_12esr_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_10_0_12esr_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_10_0_12esr_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_18_0_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_18_0_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_17_0_2_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_17_0_2_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_17_0_2esr_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_17_0_2esr_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_17_0_2esr_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_17_0_2esr_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_18_0_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_18_0_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_17_0_2esr_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_17_0_2esr_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_17_0_2esr_BUILD2
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_10_0_12esr_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_10_0_12esr_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_10_0_12esr_BUILD2
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_17_0_2esr_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_17_0_2esr_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_17_0_2esr_BUILD2
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_17_0_2_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_17_0_2_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_17_0_2_BUILD2
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_17_0_2esr_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_17_0_2esr_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_17_0_2esr_BUILD3
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0b1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0b1_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0b1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0b1_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0b1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0b1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0b1_BUILD2
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0b1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0b1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0b1_BUILD2
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0b1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0b1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0b1_BUILD3
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0b2_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0b2_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0b2_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0b2_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_18_0_1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_18_0_1_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_19_0b1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_19_0b1_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0b3_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0b3_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0b3_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0b3_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0b4_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0b4_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0b4_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0b4_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_18_0_2_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_18_0_2_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0b5_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0b5_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0b5_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0b5_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0b5_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0b5_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0b5_BUILD2
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_18_0_2_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_18_0_2_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0b6_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0b6_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0b6_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0b6_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_17_0_3esr_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_17_0_3esr_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_17_0_3esr_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_17_0_3esr_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_17_0_3_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_17_0_3_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b1_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0b1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0b1_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0b1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0b1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0b1_BUILD2
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b1_BUILD2
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b1_BUILD3
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0_1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0_1_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b2_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b2_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0b2_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0b2_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b2_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b2_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b2_BUILD2
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_20_0b1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_20_0b1_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b3_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b3_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0b3_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0b3_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0_1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0_1_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0_2_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_19_0_2_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0_2_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_19_0_2_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b4_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b4_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_17_0_4esr_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_17_0_4esr_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0b4_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0b4_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_17_0_4_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_17_0_4_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_17_0_4esr_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_17_0_4esr_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_20_0b1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_20_0b1_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e THUNDERBIRD_20_0b1_BUILD2
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b5_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b5_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0b5_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0b5_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b6_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b6_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0b6_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0b6_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b7_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b7_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0b7_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0b7_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0_BUILD1
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0_RELEASE
+41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0_BUILD1
--- a/master/buildbot/process/base.py
+++ b/master/buildbot/process/base.py
@@ -4,17 +4,17 @@ import types
 
 from zope.interface import implements
 from twisted.python import log
 from twisted.python.failure import Failure
 from twisted.internet import reactor, defer, error
 
 from buildbot import interfaces, locks
 from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE, EXCEPTION, \
-  RETRY, SKIPPED, worst_status
+  RETRY, SKIPPED, CANCELLED, worst_status
 from buildbot.status.builder import Results
 from buildbot.status.progress import BuildProgress
 
 
 class Build:
     """I represent a single build by a single slave. Specialized Builders can
     use subclasses of Build to hold status information unique to those build
     processes.
@@ -401,17 +401,17 @@ class Build:
                 terminate = True
         elif result == WARNINGS:
             if not step.warnOnWarnings:
                 possible_overall_result = SUCCESS
             else:
                 possible_overall_result = WARNINGS
             if step.flunkOnWarnings:
                 possible_overall_result = FAILURE
-        elif result in (EXCEPTION, RETRY):
+        elif result in (EXCEPTION, RETRY, CANCELLED):
             terminate = True
 
         # if we skipped this step, then don't adjust the build status
         if result != SKIPPED:
             self.result = worst_status(self.result, possible_overall_result)
 
         return terminate
 
@@ -438,30 +438,32 @@ class Build:
         if self.finished:
             return
         # TODO: include 'reason' in this point event
         self.builder.builder_status.addPointEvent(['interrupt'])
         self.stopped = True
         if self.currentStep:
             self.currentStep.interrupt(reason)
 
-        self.result = EXCEPTION
+        self.result = CANCELLED
 
         if self._acquiringLock:
             lock, access, d = self._acquiringLock
             lock.stopWaitingUntilAvailable(self, access, d)
             d.callback(None)
 
     def allStepsDone(self):
         if self.result == FAILURE:
             text = ["failed"]
         elif self.result == WARNINGS:
             text = ["warnings"]
         elif self.result == EXCEPTION:
             text = ["exception"]
+        elif self.result == CANCELLED:
+            text = ["cancelled"]
         else:
             text = ["build", "successful"]
         text.extend(self.text)
         return self.buildFinished(text, self.result)
 
     def buildException(self, why):
         log.msg("%s.buildException" % self)
         log.err(why)
--- a/master/buildbot/process/buildstep.py
+++ b/master/buildbot/process/buildstep.py
@@ -8,17 +8,17 @@ from twisted.protocols import basic
 from twisted.spread import pb
 from twisted.python import log
 from twisted.python.failure import Failure
 from twisted.web.util import formatFailure
 
 from buildbot import interfaces, locks
 from buildbot.status import progress
 from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE, SKIPPED, \
-     EXCEPTION, RETRY, worst_status
+     EXCEPTION, RETRY, CANCELLED, worst_status
 
 """
 BuildStep and RemoteCommand classes for master-side representation of the
 build process
 """
 
 class RemoteCommand(pb.Referenceable):
     """
@@ -1175,16 +1175,18 @@ class LoggingBuildStep(BuildStep):
 
     def getText(self, cmd, results):
         if results == SUCCESS:
             return self.describe(True)
         elif results == WARNINGS:
             return self.describe(True) + ["warnings"]
         elif results == EXCEPTION:
             return self.describe(True) + ["exception"]
+        elif results == CANCELLED:
+            return self.describe(True) + ["cancelled"]
         else:
             return self.describe(True) + ["failed"]
 
     def getText2(self, cmd, results):
         """We have decided to add a short note about ourselves to the overall
         build description, probably because something went wrong. Return a
         short list of short strings. If your subclass counts test failures or
         warnings of some sort, this is a good place to announce the count."""
--- a/master/buildbot/status/builder.py
+++ b/master/buildbot/status/builder.py
@@ -17,24 +17,24 @@ import time
 from cPickle import load, dump
 from cStringIO import StringIO
 from bz2 import BZ2File
 from gzip import GzipFile
 
 # sibling imports
 from buildbot import interfaces, util, sourcestamp
 
-SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTION, RETRY = range(6)
-Results = ["success", "warnings", "failure", "skipped", "exception", "retry"]
+SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTION, RETRY, CANCELLED = range(7)
+Results = ["success", "warnings", "failure", "skipped", "exception",
+           "retry", "cancelled"]
 
 def worst_status(a, b):
-    # SUCCESS > SKIPPED > WARNINGS > FAILURE > EXCEPTION > RETRY
-    # Retry needs to be considered the worst so that conusmers don't have to
-    # worry about other failures undermining the RETRY.
-    for s in (RETRY, EXCEPTION, FAILURE, WARNINGS, SKIPPED, SUCCESS):
+    # SUCCESS > SKIPPED > WARNINGS > FAILURE > EXCEPTION > RETRY > CANCELLED
+    # Cancelled needs to be considered the worst.
+    for s in (CANCELLED, RETRY, EXCEPTION, FAILURE, WARNINGS, SKIPPED, SUCCESS):
         if s in (a, b):
             return s
 
 # build processes call the following methods:
 #
 #  setDefaults
 #
 #  currentlyBuilding
@@ -2089,18 +2089,18 @@ class BuilderStatus(styles.Versioned):
         if state == "offline":
             client.currentlyOffline()
         elif state == "idle":
             client.currentlyIdle()
         elif state == "building":
             client.currentlyBuilding()
         else:
             log.msg("Hey, self.currentBigState is weird:", state)
-            
-    
+
+
     ## HTML display interface
 
     def getEventNumbered(self, num):
         # deal with dropped events, pruned events
         first = self.events[0].number
         if first + len(self.events)-1 != self.events[-1].number:
             log.msg(self,
                     "lost an event somewhere: [0] is %d, [%d] is %d" % \
--- a/master/buildbot/status/mail.py
+++ b/master/buildbot/status/mail.py
@@ -18,17 +18,17 @@ have_ssl = True
 try:
     from twisted.internet import ssl
     from OpenSSL.SSL import SSLv3_METHOD
 except ImportError:
     have_ssl = False
 
 from buildbot import interfaces, util
 from buildbot.status import base
-from buildbot.status.builder import FAILURE, SUCCESS, Results
+from buildbot.status.builder import FAILURE, SUCCESS, CANCELLED, Results
 
 VALID_EMAIL = re.compile("[a-zA-Z0-9\.\_\%\-\+]+@[a-zA-Z0-9\.\_\%\-]+.[a-zA-Z]{2,6}")
 
 ENCODING = 'utf8'
 
 class Domain(util.ComparableMixin):
     implements(interfaces.IEmailLookup)
     compare_attrs = ["domain"]
@@ -54,16 +54,18 @@ def defaultMessage(mode, name, build, re
     if mode == "all":
         text += "The Buildbot has finished a build"
     elif mode == "failing":
         text += "The Buildbot has detected a failed build"
     elif mode == "passing":
         text += "The Buildbot has detected a passing build"
     elif mode == "change" and result == 'success':
         text += "The Buildbot has detected a restored build"
+    elif mode == "cancelled":
+        text += "The user has cancelled the build."
     else:    
         text += "The Buildbot has detected a new failure"
     if ss and ss.project:
         project = ss.project
     else:
         project = master_status.getProjectName()
     text += " on builder %s while building %s.\n" % (name, project)
     if master_status.getURLForThing(build):
@@ -97,16 +99,18 @@ def defaultMessage(mode, name, build, re
         t = ": " + " ".join(t)
     else:
         t = ""
 
     if result == 'success':
         text += "Build succeeded!\n"
     elif result == 'warnings':
         text += "Build Had Warnings%s\n" % t
+    elif result == 'cancelled':
+        text += "Build was cancelled.\n"
     else:
         text += "BUILD FAILED%s\n" % t
 
     text += "\n"
     text += "sincerely,\n"
     text += " -The Buildbot\n"
     text += "\n"
     return { 'body' : text, 'type' : 'plain' }
@@ -258,17 +262,17 @@ class MailNotifier(base.StatusReceiverMu
         assert isinstance(extraRecipients, (list, tuple))
         for r in extraRecipients:
             assert isinstance(r, str)
             # require full email addresses, not User names
             assert VALID_EMAIL.search(r), "%s is not a valid email" % r 
         self.extraRecipients = extraRecipients
         self.sendToInterestedUsers = sendToInterestedUsers
         self.fromaddr = fromaddr
-        assert mode in ('all', 'failing', 'problem', 'change', 'passing')
+        assert mode in ('all', 'failing', 'problem', 'change', 'passing', 'cancelled')
         self.mode = mode
         self.categories = categories
         self.builders = builders
         self.addLogs = addLogs
         self.relayhost = relayhost
         self.subject = subject
         if lookup is not None:
             if type(lookup) is str:
@@ -346,16 +350,18 @@ class MailNotifier(base.StatusReceiverMu
                 return
             prev = build.getPreviousBuild()
             if prev and prev.getResults() == FAILURE:
                 return
         if self.mode == "change":
             prev = build.getPreviousBuild()
             if not prev or prev.getResults() == results:
                 return
+        if self.mode == "cancelled":
+            return
         # for testing purposes, buildMessage returns a Deferred that fires
         # when the mail has been sent. To help unit tests, we return that
         # Deferred here even though the normal IStatusReceiver.buildFinished
         # signature doesn't do anything with it. If that changes (if
         # .buildFinished's return value becomes significant), we need to
         # rearrange this.
         return self.buildMessage(name, build, results)
 
--- a/master/buildbot/status/tinderbox.py
+++ b/master/buildbot/status/tinderbox.py
@@ -2,17 +2,18 @@
 from email.Message import Message
 from email.Utils import formatdate
 
 from zope.interface import implements
 from twisted.internet import defer
 
 from buildbot import interfaces
 from buildbot.status import mail
-from buildbot.status.builder import SUCCESS, WARNINGS, EXCEPTION, RETRY
+from buildbot.status.builder import SUCCESS, WARNINGS, EXCEPTION, \
+  RETRY, CANCELLED
 from buildbot.steps.shell import WithProperties
 
 import gzip, bz2, base64, re, cStringIO
 
 # TODO: docs, maybe a test of some sort just to make sure it actually imports
 # and can format email without raising an exception.
 
 class TinderboxMailNotifier(mail.MailNotifier):
@@ -169,16 +170,19 @@ class TinderboxMailNotifier(mail.MailNot
             res = "success"
             text += res
         elif results == WARNINGS:
             res = "testfailed"
             text += res
         elif results in (EXCEPTION, RETRY):
             res = "exception"
             text += res
+        elif results == CANCELLED:
+            res = "cancelled"
+            text += res
         else:
             res += "busted"
             text += res
 
         text += "\n";
 
         if self.columnName is None:
             # use the builder name
--- a/master/buildbot/status/web/base.py
+++ b/master/buildbot/status/web/base.py
@@ -1,17 +1,18 @@
 
 import urlparse, urllib, time, re
 import os, cgi, sys, locale
 import jinja2
 from zope.interface import Interface
 from twisted.web import resource, static
 from twisted.python import log
 from buildbot.status import builder
-from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTION, RETRY
+from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE, SKIPPED, \
+  EXCEPTION, RETRY, CANCELLED
 from buildbot import version, util
 from buildbot.process.properties import Properties
 
 class ITopBox(Interface):
     """I represent a box in the top row of the waterfall display: the one
     which shows the status of the last build for each builder."""
     def getBox(self, request):
         """Return a Box instance, which can produce a <td> cell.
@@ -34,16 +35,17 @@ class IHTMLLog(Interface):
     pass
 
 css_classes = {SUCCESS: "success",
                WARNINGS: "warnings",
                FAILURE: "failure",
                SKIPPED: "skipped",
                EXCEPTION: "exception",
                RETRY: "retry",
+               CANCELLED: "cancelled",
                None: "",
                }
 
 
 def getAndCheckProperties(req):
     """
 Fetch custom build properties from the HTTP request of a "Force build" or
 "Resubmit build" HTML form.
@@ -147,19 +149,19 @@ class Box:
 
     def td(self, **props):
         props.update(self.parms)
         text = self.text
         if not text and self.show_idle:
             text = ["[idle]"]
         props['class'] = self.class_
         props['text'] = text;
-        return props    
-    
-    
+        return props
+
+
 class ContextMixin(object):
     def getContext(self, request):
         status = self.getStatus(request)
         rootpath = path_to_root(request)
         locale_enc = locale.getdefaultlocale()[1]
         if locale_enc is not None:
             locale_tz = unicode(time.tzname[time.localtime()[-1]], locale_enc)
         else:
@@ -300,17 +302,17 @@ class StaticFile(static.File):
                                    self.listNames(),
                                    self.contentTypes,
                                    self.contentEncodings,
                                    self.defaultType)
         else:
             return static.Data("""
    Directory Listings require Twisted-9.0.0 or later
                 """, "text/plain")
-        
+
 
 MINUTE = 60
 HOUR = 60*MINUTE
 DAY = 24*HOUR
 WEEK = 7*DAY
 MONTH = 30*DAY
 
 def plural(word, words, num):
@@ -349,17 +351,17 @@ class BuildLineMixin:
                 rev = "??"
         except KeyError:
             rev = "??"
         rev = str(rev)
         css_class = css_classes.get(results, "")
         repo = build.getSourceStamp().repository
 
         if type(text) == list:
-            text = " ".join(text)            
+            text = " ".join(text)
 
         values = {'class': css_class,
                   'builder_name': builder_name,
                   'buildnum': build.getNumber(),
                   'results': css_class,
                   'text': " ".join(build.getText()),
                   'buildurl': path_to_build(req, build),
                   'builderurl': path_to_builder(req, build.getBuilder()),
@@ -384,180 +386,180 @@ def map_branches(branches):
 
 
 # jinja utilities 
 
 def createJinjaEnv(revlink=None, changecommentlink=None,
                      repositories=None, projects=None):
     ''' Create a jinja environment changecommentlink is used to
         render HTML in the WebStatus and for mail changes
-    
+
         @type changecommentlink: C{None}, tuple (2 or 3 strings), dict (string -> 2- or 3-tuple) or callable
         @param changecommentlink: see changelinkfilter()
 
         @type revlink: C{None}, format-string, dict (repository -> format string) or callable
         @param revlink: see revlinkfilter()
-        
+
         @type repositories: C{None} or dict (string -> url)
         @param repositories: an (optinal) mapping from repository identifiers
              (as given by Change sources) to URLs. Is used to create a link
              on every place where a repository is listed in the WebStatus.
 
         @type projects: C{None} or dict (string -> url)
         @param projects: similar to repositories, but for projects.
     '''
-    
+
     # See http://buildbot.net/trac/ticket/658
     assert not hasattr(sys, "frozen"), 'Frozen config not supported with jinja (yet)'
 
     default_loader = jinja2.PackageLoader('buildbot.status.web', 'templates')
     root = os.path.join(os.getcwd(), 'templates')
     loader = jinja2.ChoiceLoader([jinja2.FileSystemLoader(root),
                                   default_loader])
     env = jinja2.Environment(loader=loader,
                              extensions=['jinja2.ext.i18n'],
                              trim_blocks=True,
                              undefined=AlmostStrictUndefined)
-    
+
     env.install_null_translations() # needed until we have a proper i18n backend
-    
+
     env.filters.update(dict(
         urlencode = urllib.quote,
         email = emailfilter,
         user = userfilter,
         shortrev = shortrevfilter(revlink, env),
         revlink = revlinkfilter(revlink, env),
         changecomment = changelinkfilter(changecommentlink), 
         repolink = dictlinkfilter(repositories),
         projectlink = dictlinkfilter(projects)
         ))
-    
-    return env    
+
+    return env
 
 def emailfilter(value):
     ''' Escape & obfuscate e-mail addresses
-    
+
         replacing @ with <span style="display:none> reportedly works well against web-spiders
-        and the next level is to use rot-13 (or something) and decode in javascript '''    
-    
+        and the next level is to use rot-13 (or something) and decode in javascript '''
+
     user = jinja2.escape(value)
     obfuscator = jinja2.Markup('<span style="display:none">ohnoyoudont</span>@')
     output = user.replace('@', obfuscator)
     return output
- 
- 
+
+
 def userfilter(value):
     ''' Hide e-mail address from user name when viewing changes
-    
+
         We still include the (obfuscated) e-mail so that we can show
-        it on mouse-over or similar etc 
+        it on mouse-over or similar etc
     '''
     r = re.compile('(.*) +<(.*)>')
     m = r.search(value)
     if m:
         user = jinja2.escape(m.group(1))
-        email = emailfilter(m.group(2))        
+        email = emailfilter(m.group(2))
         return jinja2.Markup('<div class="user">%s<div class="email">%s</div></div>' % (user, email))
     else:
         return emailfilter(value) # filter for emails here for safety
-        
+
 def _revlinkcfg(replace, templates):
     '''Helper function that returns suitable macros and functions
        for building revision links depending on replacement mechanism
 '''
-   
+
     assert not replace or callable(replace) or isinstance(replace, dict) or \
            isinstance(replace, str) or isinstance(replace, unicode)
-    
+
     if not replace:
         return lambda rev, repo: None
     else:
         if callable(replace):
             return  lambda rev, repo: replace(rev, repo)
         elif isinstance(replace, dict): # TODO: test for [] instead
             def filter(rev, repo):
                 url = replace.get(repo)
                 if url:
                     return url % urllib.quote(rev)
                 else:
                     return None
-                
+
             return filter
         else:
-            return lambda rev, repo: replace % urllib.quote(rev)            
-    
+            return lambda rev, repo: replace % urllib.quote(rev)
+
     assert False, '_replace has a bad type, but we should never get here'
 
 
 def _revlinkmacros(replace, templates): 
     '''return macros for use with revision links, depending 
         on whether revlinks are configured or not'''
 
     macros = templates.get_template("revmacros.html").module
 
     if not replace:
         id = macros.id
-        short = macros.shorten        
+        short = macros.shorten
     else:
         id = macros.id_replace
-        short = macros.shorten_replace            
+        short = macros.shorten_replace
 
     return (id, short)
         
 
 def shortrevfilter(replace, templates):
     ''' Returns a function which shortens the revisison string 
         to 12-chars (chosen as this is the Mercurial short-id length) 
         and add link if replacement string is set. 
         
         (The full id is still visible in HTML, for mouse-over events etc.)
 
         @param replace: see revlinkfilter()
         @param templates: a jinja2 environment
     ''' 
-    
+
     url_f = _revlinkcfg(replace, templates)  
-        
+
     def filter(rev, repo):
         if not rev:
             return u''
-            
+
         id_html, short_html = _revlinkmacros(replace, templates)
         rev = unicode(rev)
         url = url_f(rev, repo)
         rev = jinja2.escape(rev)
         shortrev = rev[:12] # TODO: customize this depending on vc type
         
         if shortrev == rev:
             if url:
                 return id_html(rev=rev, url=url) 
             else:
                 return rev
         else:
             if url:
                 return short_html(short=shortrev, rev=rev, url=url)
             else:
                 return shortrev + '...'
-        
+
     return filter
 
 
 def revlinkfilter(replace, templates):
-    ''' Returns a function which adds an url link to a 
+    ''' Returns a function which adds an url link to a
         revision identifiers.
-   
+
         Takes same params as shortrevfilter()
         
         @param replace: either a python format string with an %s,
                         or a dict mapping repositories to format strings,
                         or a callable taking (revision, repository) arguments
                           and return an URL (or None, if no URL is available),
-                        or None, in which case revisions do not get decorated 
+                        or None, in which case revisions do not get decorated
                           with links
-   
+
         @param templates: a jinja2 environment
     ''' 
 
     url_f = _revlinkcfg(replace, templates)
   
     def filter(rev, repo):
         if not rev:
             return u''
@@ -577,36 +579,36 @@ def changelinkfilter(changelink):
     ''' Returns function that does regex search/replace in 
         comments to add links to bug ids and similar.
         
         @param changelink: 
             Either C{None}
             or: a tuple (2 or 3 elements) 
                 1. a regex to match what we look for 
                 2. an url with regex refs (\g<0>, \1, \2, etc) that becomes the 'href' attribute
-                3. (optional) an title string with regex ref regex 
+                3. (optional) an title string with regex ref regex
             or: a dict mapping projects to above tuples
                 (no links will be added if the project isn't found)
-            or: a callable taking (changehtml, project) args 
-                (where the changetext is HTML escaped in the 
+            or: a callable taking (changehtml, project) args
+                (where the changetext is HTML escaped in the
                 form of a jinja2.Markup instance) and
-                returning another jinja2.Markup instance with 
-                the same change text plus any HTML tags added to it.            
+                returning another jinja2.Markup instance with
+                the same change text plus any HTML tags added to it.
     '''
 
     assert not changelink or isinstance(changelink, dict) or \
         isinstance(changelink, tuple) or callable(changelink)
     
     def replace_from_tuple(t):
         search, url_replace = t[:2]
         if len(t) == 3:
             title_replace = t[2]
         else:
             title_replace = ''
-        
+
         search_re = re.compile(search)
 
         def replacement_unmatched(text):
             return jinja2.escape(text)
         def replacement_matched(mo):
             # expand things *after* application of the regular expressions
             url = jinja2.escape(mo.expand(url_replace))
             title = jinja2.escape(mo.expand(title_replace))
@@ -640,56 +642,56 @@ def changelinkfilter(changelink):
             # TODO: Optimize and cache return value from replace_from_tuple so
             #       we only compile regex once per project, not per view
             
             t = changelink.get(project)
             if t:
                 return replace_from_tuple(t)(text, project)
             else:
                 return cgi.escape(text)
-            
+
         return dict_filter
-        
+
     elif isinstance(changelink, tuple):
         return replace_from_tuple(changelink)
-            
+
     elif callable(changelink):
         def callable_filter(text, project):
             text = jinja2.escape(text)
             return changelink(text, project)
-        
+
         return callable_filter
-        
+
     assert False, 'changelink has unsupported type, but that is checked before'
 
 
 def dictlinkfilter(links):
     '''A filter that encloses the given value in a link tag
        given that the value exists in the dictionary'''
 
     assert not links or callable(links) or isinstance(links, dict)
-       
+
     if not links:
         return jinja2.escape
 
     def filter(key):
         if callable(links):
-            url = links(key)            
+            url = links(key)
         else:
             url = links.get(key)
 
         safe_key = jinja2.escape(key)
-            
+
         if url:
-            return jinja2.Markup(r'<a href="%s">%s</a>' % (url, safe_key)) 
+            return jinja2.Markup(r'<a href="%s">%s</a>' % (url, safe_key))
         else:
             return safe_key
-        
+
     return filter
 
 class AlmostStrictUndefined(jinja2.StrictUndefined):
     ''' An undefined that allows boolean testing but 
         fails properly on every other use.
-        
+
         Much better than the default Undefined, but not
         fully as strict as StrictUndefined '''
     def __nonzero__(self):
         return False
--- a/master/buildbot/status/web/files/default.css
+++ b/master/buildbot/status/web/files/default.css
@@ -359,16 +359,22 @@ div.BuildWaterfall {
 }
 
 .exception,.retry {
 	color: #FFFFFF;
 	background-color: #f6f;
 	border-color: #ACA0B3;
 }
 
+.cancelled {
+	color: #F433FF;
+	background-color: #000;
+	border-color: #FFFFFF;
+}
+
 .start,.running,.waiting,td.building {
 	color: #666666;
 	background-color: #ff6;
 	border-color: #C5C56D;
 }
 
 .offline,td.offline {
     color: #FFFFFF;
--- a/master/buildbot/test/unit/test_process_base.py
+++ b/master/buildbot/test/unit/test_process_base.py
@@ -1,15 +1,16 @@
 from twisted.trial import unittest
 from twisted.internet import defer
 
 from buildbot.process.base import Build
 from buildbot.process.properties import Properties
 from buildbot.buildrequest import BuildRequest
-from buildbot.status.builder import FAILURE, SUCCESS, WARNINGS, RETRY, EXCEPTION
+from buildbot.status.builder import FAILURE, SUCCESS, WARNINGS, RETRY, \
+  EXCEPTION, CANCELLED
 from buildbot.locks import SlaveLock, RealSlaveLock
 from buildbot.process.buildstep import BuildStep, LoggingBuildStep
 
 from mock import Mock
 
 class FakeChange:
     properties = Properties()
     who = "me"
@@ -86,19 +87,19 @@ class TestBuild(unittest.TestCase):
         def startStep(*args, **kw):
             # Now interrupt the build
             b.stopBuild("stop it")
             return defer.Deferred()
         step.startStep = startStep
 
         b.startBuild(status, None, slavebuilder)
 
-        self.assertEqual(b.result, EXCEPTION)
+        self.assertEqual(b.result, CANCELLED)
 
-        self.assert_( ('interrupt', ('stop it',), {}) in step.method_calls)
+        self.assert_( ('cancelled', ('stop it',), {}) in step.method_calls)
 
     def testAlwaysRunStepStopBuild(self):
         """Test that steps marked with alwaysRun=True still get run even if
         the build is stopped."""
 
         # Create a build with 2 steps, the first one will get interrupted, and
         # the second one is marked with alwaysRun=True
         r = FakeRequest()
@@ -131,18 +132,18 @@ class TestBuild(unittest.TestCase):
         def startStep2(*args, **kw):
             step2Started[0] = True
             return defer.succeed( SUCCESS )
         step2.startStep = startStep2
         step1.stepDone.return_value = False
 
         d = b.startBuild(status, None, slavebuilder)
         def check(ign):
-            self.assertEqual(b.result, EXCEPTION)
-            self.assert_( ('interrupt', ('stop it',), {}) in step1.method_calls)
+            self.assertEqual(b.result, CANCELLED)
+            self.assert_( ('cancelled', ('stop it',), {}) in step1.method_calls)
             self.assert_(step2Started[0])
         d.addCallback(check)
         return d
 
     def testBuildLocksAcquired(self):
         r = FakeRequest()
 
         b = Build([r])
@@ -239,17 +240,17 @@ class TestBuild(unittest.TestCase):
             return retval
         b.acquireLocks = acquireLocks
 
         b.startBuild(status, None, slavebuilder)
 
         self.assert_( ('startStep', (b.remote,), {}) not in step.method_calls)
         self.assert_(b.currentStep is None)
         self.assertEqual(b.result, EXCEPTION)
-        self.assert_( ('interrupt', ('stop it',), {}) not in step.method_calls)
+        self.assert_( ('cancelled', ('stop it',), {}) not in step.method_calls)
 
     def testStopBuildWaitingForStepLocks(self):
         r = FakeRequest()
 
         b = Build([r])
         b.setBuilder(Mock())
         b.builder.botmaster = FakeMaster()
         slavebuilder = Mock()