def SetBuilderGraphURL(self, step_name, build_status): """Stores the graph URL used in emails for this builder.""" builder_name = build_status.getBuilder().getName() builder_results = self.GetEmailResults(builder_name) graph_url = GetGraphURL(step_name, build_status) latest_revision = build_utils.getLatestRevision(build_status) if latest_revision: graph_url = SetQueryParameter(graph_url, 'rev', latest_revision) builder_results[GRAPH_URL] = graph_url
def GetEmailSubject(self, builder_name, build_status, results, step_name): """Returns the subject of for an email based on perf results.""" project_name = self.master_status.getTitle() latest_revision = build_utils.getLatestRevision(build_status) result = 'changes' builders = [builder_name] if self.combine_results: builders = self.new_email_results.keys() return ('%s %s on %s, revision %s' % (project_name, result, ', '.join(builders), str(latest_revision)))
def getFinishedMessage(self, result, builder_name, build_status, step_name): """Closes the tree.""" GateKeeper.msg('Trying to close the tree at %s.' % self.tree_status_url) repository_url = build_status.getProperty('repository') git_repo = self.getGitRepo(repository_url) # isInterestingStep verified that latest_revision has expected properties. latest_revision = build_utils.getLatestRevision(build_status, git_repo) if not self.tree_status_url: self._last_closure_revision = latest_revision return if os.path.exists('.suppress_gatekeeper'): GateKeeper.msg('Suppression file is in place, will not close for ' 'rev %s' % str(latest_revision)) self._last_closure_revision = latest_revision return # Don't blame last committers if they are not to blame. blame_text = '' if self.shouldBlameCommitters(step_name): blame_text = (' from %s: %s' % (str(latest_revision), ', '.join(build_status.getResponsibleUsers()))) # Post a request to close the tree. tree_message = self.tree_message % { 'steps': step_name, 'builder': builder_name, 'blame': blame_text } params = urllib.urlencode( { 'message': tree_message, 'username': '******', 'password': self.password, }) def success(result): GateKeeper.msg('Tree closed successfully at rev %s' % str(latest_revision)) self._last_closure_revision = latest_revision def failure(result): GateKeeper.msg('Failed to close the tree at rev %s' % str(latest_revision)) # Trigger the HTTP POST request to update the tree status. headers = {'Content-Type': 'application/x-www-form-urlencoded'} connection = client.getPage(self.tree_status_url, method='POST', postdata=params, headers=headers, agent='buildbot') connection.addCallbacks(success, failure) return connection
def GetEmailSubject(self, builder_name, build_status, results, step_name): """Returns the subject of for an email based on perf results.""" project_name = self.master_status.getTitle() latest_revision = build_utils.getLatestRevision(build_status) result = 'changes' builders = [builder_name] if self.combine_results: builders = self.new_email_results.keys() return ( '%s %s on %s, revision %s' % (project_name, result, ', '.join(builders), str(latest_revision)))
def isInterestingStep(self, build_status, step_status, results): """Look at most cases that could make us ignore the step results. Do not look at current tree status here since it's too slow.""" # If we have not failed, or are not interested in this builder, # then we have nothing to do. if results[0] != FAILURE: return False # Check if the slave is still alive. We should not close the tree for # inactive slaves. slave_name = build_status.getSlavename() if slave_name in self.master_status.getSlaveNames(): # @type self.master_status: L{buildbot.status.builder.Status} # @type self.parent: L{buildbot.master.BuildMaster} # @rtype getSlave(): L{buildbot.status.builder.SlaveStatus} slave_status = self.master_status.getSlave(slave_name) if slave_status and not slave_status.isConnected(): GateKeeper.msg('Slave %s was disconnected, ' 'not closing the tree' % slave_name) return False # If the previous build step failed with the same result, we don't care # about this step. previous_build_status = build_status.getPreviousBuild() if previous_build_status: step_name = self.getName(step_status) step_type = self.getGenericName(step_name) previous_steps = [step for step in previous_build_status.getSteps() if self.getGenericName(self.getName(step)) == step_type] if len(previous_steps) == 1: if previous_steps[0].getResults()[0] == FAILURE: # The previous same step failed on the previous build. Ignore. GateKeeper.msg('Slave %s failed, but previously failed on ' 'the same step (%s). So not closing tree.' % ( (step_name, slave_name))) return False else: GateKeeper.msg('len(previous_steps) == %d which is weird' % len(previous_steps)) # If check_revisions=False that means that the tree closure request is # coming from nightly scheduled bots, that need not necessarily have the # revision info. if not self.check_revisions: return True # For the rest of the checks, we care about revision numbers, so we need to # be aware of if we're operating in Git- or SVN-mode. getGitRepo returns # a GitHelper instance if the repository is Git. repository_url = build_status.getProperty('repository') git_repo = self.getGitRepo(repository_url) latest_revision = build_utils.getLatestRevision(build_status, git_repo) # If we don't have a version stamp nor a blame list, then this is most # likely a build started manually, and we don't want to close the # tree. if not latest_revision or not build_status.getResponsibleUsers(): GateKeeper.msg('Slave %s failed, but no version stamp or responsible ' 'users, so skipping.' % slave_name) return False # self._last_closure_revision can be a number (svn revision) # or a string (git hash). Check for non-zero or non-empty value. if self._last_closure_revision: # If the tree is open, we don't want to close it again for the same # revision, or an earlier one in case the build that just finished is a # slow one and we already fixed the problem and manually opened the tree. if git_repo: this_rev, last_rev = git_repo.number( latest_revision, self._last_closure_revision) dbg = lambda m: GateKeeper.msg(m, logLevel=logging.DEBUG) dbg('Git mode. Hash mapping:') dbg('%s -> %s' % (latest_revision, this_rev)) dbg('%s -> %s' % (self._last_closure_revision, last_rev)) else: this_rev = latest_revision last_rev = self._last_closure_revision if this_rev <= last_rev: GateKeeper.msg('Slave %s failed, but we already closed it ' 'for a previous revision (old=%s, new=%s)' % ( slave_name, str(self._last_closure_revision), str(latest_revision))) return False GateKeeper.msg('Decided to close tree because of slave %s ' 'on revision %s' % (slave_name, str(latest_revision))) # Up to here, in theory we'd check if the tree is closed but this is too # slow to check here. Instead, take a look only when we want to close the # tree. return True
def isInterestingStep(self, build_status, step_status, results): """Look at most cases that could make us ignore the step results. """ # If the base class thinks we're not interesting -> skip it. if not chromium_notifier.ChromiumNotifier.isInterestingStep( self, build_status, step_status, results): return False # Check if the slave is still alive. We should not close the tree for # inactive slaves. slave_name = build_status.getSlavename() if slave_name in self.master_status.getSlaveNames(): # @type self.master_status: L{buildbot.status.builder.Status} # @type self.parent: L{buildbot.master.BuildMaster} # @rtype getSlave(): L{buildbot.status.builder.SlaveStatus} slave_status = self.master_status.getSlave(slave_name) if slave_status and not slave_status.isConnected(): log.msg('[failurenotifier] Slave %s was disconnected, ' 'not sending a warning' % slave_name) return False # If all the failure_ids were observed in older builds then this # failure is not interesting. Also, store the current failure. has_failures_to_report = False for l in step_status.getLogs(): failure = l.getName() # stdio or suppression hash or failed test or ? # TODO(timurrrr): put the gtest regexp into a common place. if (not re.match(r'^[\dA-F]{16}$', failure) and not re.match(r'((\w+/)?\w+\.\w+(/\d+)?)', failure)): # gtest name if failure != 'stdio': log.msg('[failurenotifier] Log `%s` is ignored since doesn\'t look ' 'like a memory suppression hash or test failure.' % failure) continue if '.FLAKY_' in failure or '.FAILS_' in failure: log.msg('[failurenotifier] Ignoring flaky/fails tests: `%s`' % failure) continue if (self.recent_failures.GetCount(failure) < self._IGNORE_FAILURES_THRESHOLD): has_failures_to_report = True log.msg('[failurenotifier] Failure `%s` ' 'is interesting' % failure) else: log.msg('[failurenotifier] Failure `%s` ' 'is not interesting - happened to often recently' % failure) self.recent_failures.Put(failure) # If we don't have a version stamp nor a blame list, then this is most # likely a build started manually, and we don't want to issue a warning. # # This code is intentionally put after Put calls so we don't send failure # notifications with the wrong blamelist once bots cycle for the second time # after a master restart. latest_revision = build_utils.getLatestRevision(build_status) if not latest_revision or not build_status.getResponsibleUsers(): log.msg('[failurenotifier] Slave %s failed, but no version stamp, ' 'so skipping.' % slave_name) return False if has_failures_to_report: log.msg('[failurenotifier] Decided to send a warning because of slave %s ' 'on revision %s' % (slave_name, str(latest_revision))) return True else: log.msg('[failurenotifier] Slave %s revision %s has no interesting ' 'failures' % (slave_name, str(latest_revision))) return False
def isInterestingStep(self, build_status, step_status, results): """Look at most cases that could make us ignore the step results. """ # If the base class thinks we're not interesting -> skip it. if not chromium_notifier.ChromiumNotifier.isInterestingStep( self, build_status, step_status, results): return False # Check if the slave is still alive. We should not close the tree for # inactive slaves. slave_name = build_status.getSlavename() if slave_name in self.master_status.getSlaveNames(): # @type self.master_status: L{buildbot.status.builder.Status} # @type self.parent: L{buildbot.master.BuildMaster} # @rtype getSlave(): L{buildbot.status.builder.SlaveStatus} slave_status = self.master_status.getSlave(slave_name) if slave_status and not slave_status.isConnected(): log.msg('[failurenotifier] Slave %s was disconnected, ' 'not sending a warning' % slave_name) return False # If all the failure_ids were observed in older builds then this # failure is not interesting. Also, store the current failure. has_failures_to_report = False for l in step_status.getLogs(): failure = l.getName( ) # stdio or suppression hash or failed test or ? # TODO(timurrrr): put the gtest regexp into a common place. if (not re.match(r'^[\dA-F]{16}$', failure) and not re.match( r'((\w+/)?\w+\.\w+(/\d+)?)', failure)): # gtest name if failure != 'stdio': log.msg( '[failurenotifier] Log `%s` is ignored since doesn\'t look ' 'like a memory suppression hash or test failure.' % failure) continue if '.FLAKY_' in failure or '.FAILS_' in failure: log.msg('[failurenotifier] Ignoring flaky/fails tests: `%s`' % failure) continue if (self.recent_failures.GetCount(failure) < self._IGNORE_FAILURES_THRESHOLD): has_failures_to_report = True log.msg('[failurenotifier] Failure `%s` ' 'is interesting' % failure) else: log.msg('[failurenotifier] Failure `%s` ' 'is not interesting - happened to often recently' % failure) self.recent_failures.Put(failure) # If we don't have a version stamp nor a blame list, then this is most # likely a build started manually, and we don't want to issue a warning. # # This code is intentionally put after Put calls so we don't send failure # notifications with the wrong blamelist once bots cycle for the second time # after a master restart. latest_revision = build_utils.getLatestRevision(build_status) if not latest_revision or not build_status.getResponsibleUsers(): log.msg('[failurenotifier] Slave %s failed, but no version stamp, ' 'so skipping.' % slave_name) return False if has_failures_to_report: log.msg( '[failurenotifier] Decided to send a warning because of slave %s ' 'on revision %s' % (slave_name, str(latest_revision))) return True else: log.msg( '[failurenotifier] Slave %s revision %s has no interesting ' 'failures' % (slave_name, str(latest_revision))) return False
def isInterestingStep(self, build_status, step_status, results): """ Override of gatekeeper.GateKeeper.isInterestingStep: http://src.chromium.org/viewvc/chrome/trunk/tools/build/scripts/master/gatekeeper.py?view=markup We modify it to comment out the SVN revision comparision to determine if the current build is older because Skia uses commit hashes. """ # If we have not failed, or are not interested in this builder, # then we have nothing to do. if results[0] != FAILURE: return False # Check if the slave is still alive. We should not close the tree for # inactive slaves. slave_name = build_status.getSlavename() if slave_name in self.master_status.getSlaveNames(): # @type self.master_status: L{buildbot.status.builder.Status} # @type self.parent: L{buildbot.master.BuildMaster} # @rtype getSlave(): L{buildbot.status.builder.SlaveStatus} slave_status = self.master_status.getSlave(slave_name) if slave_status and not slave_status.isConnected(): log.msg('[gatekeeper] Slave %s was disconnected, ' 'not closing the tree' % slave_name) return False # If the previous build step failed with the same result, we don't care # about this step. previous_build_status = build_status.getPreviousBuild() if previous_build_status: step_name = self.getName(step_status) step_type = self.getGenericName(step_name) previous_steps = [step for step in previous_build_status.getSteps() if self.getGenericName(self.getName(step)) == step_type] if len(previous_steps) == 1: if previous_steps[0].getResults()[0] == FAILURE: log.msg('[gatekeeper] Slave %s failed, but previously failed on ' 'the same step (%s). So not closing tree.' % ( (step_name, slave_name))) return False else: log.msg('[gatekeeper] len(previous_steps) == %d which is weird' % len(previous_steps)) # If check_revisions=False that means that the tree closure request is # coming from nightly scheduled bots, that need not necessarily have the # revision info. if not self.check_revisions: return True # If we don't have a version stamp nor a blame list, then this is most # likely a build started manually, and we don't want to close the # tree. latest_revision = build_utils.getLatestRevision(build_status) if not latest_revision or not build_status.getResponsibleUsers(): log.msg('[gatekeeper] Slave %s failed, but no version stamp, ' 'so skipping.' % slave_name) return False # If the tree is open, we don't want to close it again for the same # revision, or an earlier one in case the build that just finished is a # slow one and we already fixed the problem and manually opened the tree. ############################### Added by rmistry ########################### # rmistry: Commenting out the below SVN revision comparision because Skia # uses commit hashes. # TODO(rmistry): Figure out how to ensure that previous builds do not close # the tree again. # # if latest_revision <= self._last_closure_revision: # log.msg('[gatekeeper] Slave %s failed, but we already closed it ' # 'for a previous revision (old=%s, new=%s)' % ( # slave_name, str(self._last_closure_revision), # str(latest_revision))) # return False ########################################################################### log.msg('[gatekeeper] Decided to close tree because of slave %s ' 'on revision %s' % (slave_name, str(latest_revision))) # Up to here, in theory we'd check if the tree is closed but this is too # slow to check here. Instead, take a look only when we want to close the # tree. return True
def stepFinished(self, build, step, results): """A build step has just finished.""" builder_name = build.getBuilder().getName() # For some reason we sometimes get called even if we didn't subscribe. if not self.isInterestingBuilder(builder_name): log.msg('Was called for %s even if not subscribed' % builder_name) return if self.use_getname: step_text = step.getName() else: step_text = step.getText()[0] # We only need to deal with interesting steps. if not self.isInterestingBuildStep(builder_name, build, step_text): log.msg('not interested in step %s' % step_text) return # TODO(maruel): Support git. latest_revision = build_utils.getLatestRevision(build) if not latest_revision: log.msg('no lastest revision for build %s' % build.asDict()) return # If check_revisions=False that means that the tree closure request is # coming from nightly scheduled bots or a git poller, that need not # necessarily have the revision info or the revision is a hash that cannot # be compared. if self.check_revisions: latest_revision = int(latest_revision) # If we already succeeded for a more recent revision, # let's just forget about this one. if latest_revision <= self.last_known_good_revision: log.msg('revision too old') return # If we already failed for this revision, # there is nothing else we need to do. if latest_revision in self.failed_revisions: assert latest_revision not in self.succeeded_steps log.msg('revision already failed') return # If we have failed, we add this revision to our failure list and flush it # from the success dict, if it is there. We also store it on the status # server. if results[0] == FAILURE: log.msg('%s is a failed revision.' % str(latest_revision)) self.failed_revisions.append(latest_revision) # pop() with a default value allows us to remove an element # without having to test if it is there in the first place. self.succeeded_steps.pop(latest_revision, None) self.PostData(revision=latest_revision, success=0, steps_text=step.getText()) return # Now let's add the succeeded steps to our success dict. self.succeeded_steps.setdefault(latest_revision, {}) revision_status = self.succeeded_steps[latest_revision] revision_status.setdefault(builder_name, []) revision_status[builder_name].append(step_text) # We must complete all the requested steps for all builds, before we can # store this revision as a successful one and then forget about all # previous revisions info. for builder in self.getInterestingBuilders(): if builder not in revision_status: log.msg('Still missing builder %s to declare %s a good revision' % (builder, str(latest_revision))) return succeeded_steps = revision_status[builder] for required_step in self.getInterestingBuildSteps(builder, build): if required_step not in succeeded_steps: log.msg('Still missing step %s/%s to declare %s a good revision' % (builder, required_step, str(latest_revision))) return # Start by remembering this success. log.msg('Found LKGR = %s' % latest_revision) self.last_known_good_revision = latest_revision # Store it on the status server. self.PostData(revision=latest_revision, success=1) if self.check_revisions: # And now cleanup residual information from earlier revisions # Iterate through a list of keys to allow removal while we iterate. for revision in list(self.succeeded_steps.keys()): if revision <= latest_revision: del self.succeeded_steps[revision] for revision in self.failed_revisions: assert revision != latest_revision if revision < latest_revision: self.failed_revisions.remove(revision) else: # TODO(maruel): Use LRU discarding. Right now it's a memory leak. pass
def isInterestingStep(self, build_status, step_status, results): """Look at most cases that could make us ignore the step results. Do not look at current tree status here since it's too slow.""" # If we have not failed, or are not interested in this builder, # then we have nothing to do. if results[0] != FAILURE: return False # Check if the slave is still alive. We should not close the tree for # inactive slaves. slave_name = build_status.getSlavename() if slave_name in self.master_status.getSlaveNames(): # @type self.master_status: L{buildbot.status.builder.Status} # @type self.parent: L{buildbot.master.BuildMaster} # @rtype getSlave(): L{buildbot.status.builder.SlaveStatus} slave_status = self.master_status.getSlave(slave_name) if slave_status and not slave_status.isConnected(): log.msg('[gatekeeper] Slave %s was disconnected, ' 'not closing the tree' % slave_name) return False # If the previous build step failed with the same result, we don't care # about this step. previous_build_status = build_status.getPreviousBuild() if previous_build_status: step_name = self.getName(step_status) step_type = self.getGenericName(step_name) previous_steps = [step for step in previous_build_status.getSteps() if self.getGenericName(self.getName(step)) == step_type] if len(previous_steps) == 1: if previous_steps[0].getResults()[0] == FAILURE: # The previous same step failed on the previous build. Ignore. log.msg('[gatekeeper] Slave %s failed, but previously failed on ' 'the same step (%s). So not closing tree.' % ( (step_name, slave_name))) return False else: log.msg('[gatekeeper] len(previous_steps) == %d which is weird' % len(previous_steps)) # If check_revisions=False that means that the tree closure request is # coming from nightly scheduled bots, that need not necessarily have the # revision info. if not self.check_revisions: return True # If we don't have a version stamp nor a blame list, then this is most # likely a build started manually, and we don't want to close the # tree. latest_revision = build_utils.getLatestRevision(build_status) if not latest_revision or not build_status.getResponsibleUsers(): log.msg('[gatekeeper] Slave %s failed, but no version stamp, ' 'so skipping.' % slave_name) return False # If the tree is open, we don't want to close it again for the same # revision, or an earlier one in case the build that just finished is a # slow one and we already fixed the problem and manually opened the tree. # TODO(maruel): This is not git-friendly. if latest_revision <= self._last_closure_revision: log.msg('[gatekeeper] Slave %s failed, but we already closed it ' 'for a previous revision (old=%s, new=%s)' % ( slave_name, str(self._last_closure_revision), str(latest_revision))) return False log.msg('[gatekeeper] Decided to close tree because of slave %s ' 'on revision %s' % (slave_name, str(latest_revision))) # Up to here, in theory we'd check if the tree is closed but this is too # slow to check here. Instead, take a look only when we want to close the # tree. return True
def isInterestingStep(self, build_status, step_status, results): """ Override of gatekeeper.GateKeeper.isInterestingStep: http://src.chromium.org/viewvc/chrome/trunk/tools/build/scripts/master/gatekeeper.py?view=markup We modify it to comment out the SVN revision comparision to determine if the current build is older because Skia uses commit hashes. """ # If we have not failed, or are not interested in this builder, # then we have nothing to do. if results[0] != FAILURE: return False # Check if the slave is still alive. We should not close the tree for # inactive slaves. slave_name = build_status.getSlavename() if slave_name in self.master_status.getSlaveNames(): # @type self.master_status: L{buildbot.status.builder.Status} # @type self.parent: L{buildbot.master.BuildMaster} # @rtype getSlave(): L{buildbot.status.builder.SlaveStatus} slave_status = self.master_status.getSlave(slave_name) if slave_status and not slave_status.isConnected(): log.msg('[gatekeeper] Slave %s was disconnected, ' 'not closing the tree' % slave_name) return False # If the previous build step failed with the same result, we don't care # about this step. previous_build_status = build_status.getPreviousBuild() if previous_build_status: step_name = self.getName(step_status) step_type = self.getGenericName(step_name) previous_steps = [ step for step in previous_build_status.getSteps() if self.getGenericName(self.getName(step)) == step_type ] if len(previous_steps) == 1: if previous_steps[0].getResults()[0] == FAILURE: log.msg( '[gatekeeper] Slave %s failed, but previously failed on ' 'the same step (%s). So not closing tree.' % ((step_name, slave_name))) return False else: log.msg( '[gatekeeper] len(previous_steps) == %d which is weird' % len(previous_steps)) # If check_revisions=False that means that the tree closure request is # coming from nightly scheduled bots, that need not necessarily have the # revision info. if not self.check_revisions: return True # If we don't have a version stamp nor a blame list, then this is most # likely a build started manually, and we don't want to close the # tree. latest_revision = build_utils.getLatestRevision(build_status) if not latest_revision or not build_status.getResponsibleUsers(): log.msg('[gatekeeper] Slave %s failed, but no version stamp, ' 'so skipping.' % slave_name) return False # If the tree is open, we don't want to close it again for the same # revision, or an earlier one in case the build that just finished is a # slow one and we already fixed the problem and manually opened the tree. ############################### Added by rmistry ########################### # rmistry: Commenting out the below SVN revision comparision because Skia # uses commit hashes. # TODO(rmistry): Figure out how to ensure that previous builds do not close # the tree again. # # if latest_revision <= self._last_closure_revision: # log.msg('[gatekeeper] Slave %s failed, but we already closed it ' # 'for a previous revision (old=%s, new=%s)' % ( # slave_name, str(self._last_closure_revision), # str(latest_revision))) # return False ########################################################################### log.msg('[gatekeeper] Decided to close tree because of slave %s ' 'on revision %s' % (slave_name, str(latest_revision))) # Up to here, in theory we'd check if the tree is closed but this is too # slow to check here. Instead, take a look only when we want to close the # tree. return True
def stepFinished(self, build, step, results): """A build step has just finished.""" builder_name = build.getBuilder().getName() # For some reason we sometimes get called even if we didn't subscribe. if not self.isInterestingBuilder(builder_name): log.msg('Was called for %s even if not subscribed' % builder_name) return if self.use_getname: step_text = step.getName() else: step_text = step.getText()[0] # We only need to deal with interesting steps. if not self.isInterestingBuildStep(builder_name, build, step_text): log.msg('not interested in step %s' % step_text) return # TODO(maruel): Support git. latest_revision = build_utils.getLatestRevision(build) if not latest_revision: log.msg('no lastest revision for build %s' % build.asDict()) return # If check_revisions=False that means that the tree closure request is # coming from nightly scheduled bots or a git poller, that need not # necessarily have the revision info or the revision is a hash that cannot # be compared. if self.check_revisions: latest_revision = int(latest_revision) # If we already succeeded for a more recent revision, # let's just forget about this one. if latest_revision <= self.last_known_good_revision: log.msg('revision too old') return # If we already failed for this revision, # there is nothing else we need to do. if latest_revision in self.failed_revisions: assert latest_revision not in self.succeeded_steps log.msg('revision already failed') return # If we have failed, we add this revision to our failure list and flush it # from the success dict, if it is there. We also store it on the status # server. if results[0] == FAILURE: log.msg('%s is a failed revision.' % str(latest_revision)) self.failed_revisions.append(latest_revision) # pop() with a default value allows us to remove an element # without having to test if it is there in the first place. self.succeeded_steps.pop(latest_revision, None) self.PostData(revision=latest_revision, success=0, steps_text=step.getText()) return # Now let's add the succeeded steps to our success dict. self.succeeded_steps.setdefault(latest_revision, {}) revision_status = self.succeeded_steps[latest_revision] revision_status.setdefault(builder_name, []) revision_status[builder_name].append(step_text) # We must complete all the requested steps for all builds, before we can # store this revision as a successful one and then forget about all # previous revisions info. for builder in self.getInterestingBuilders(): if builder not in revision_status: log.msg('Still missing builder %s to declare %s a good revision' % (builder, str(latest_revision))) return succeeded_steps = revision_status[builder] for required_step in self.getInterestingBuildSteps(builder, build): if required_step not in succeeded_steps: log.msg('Still missing step %s\%s to declare %s a good revision' % (builder, required_step, str(latest_revision))) return # Start by remembering this success. log.msg('Found LKGR = %s' % latest_revision) self.last_known_good_revision = latest_revision # Store it on the status server. self.PostData(revision=latest_revision, success=1) if self.check_revisions: # And now cleanup residual information from earlier revisions # Iterate through a list of keys to allow removal while we iterate. for revision in list(self.succeeded_steps.keys()): if revision <= latest_revision: del self.succeeded_steps[revision] for revision in self.failed_revisions: assert revision != latest_revision if revision < latest_revision: self.failed_revisions.remove(revision) else: # TODO(maruel): Use LRU discarding. Right now it's a memory leak. pass