[zorg] r182725 - Add AnnotatedCommand buildbot command, and a build factory to test various sanitizers.
Alexey Samsonov
samsonov at google.com
Mon May 27 00:53:53 PDT 2013
Author: samsonov
Date: Mon May 27 02:53:53 2013
New Revision: 182725
URL: http://llvm.org/viewvc/llvm-project?rev=182725&view=rev
Log:
Add AnnotatedCommand buildbot command, and a build factory to test various sanitizers.
Added:
zorg/trunk/zorg/buildbot/builders/SanitizerBuilder.py
zorg/trunk/zorg/buildbot/commands/AnnotatedCommand.py
Modified:
zorg/trunk/test/buildbot/builders/Import.py
Modified: zorg/trunk/test/buildbot/builders/Import.py
URL: http://llvm.org/viewvc/llvm-project/zorg/trunk/test/buildbot/builders/Import.py?rev=182725&r1=182724&r2=182725&view=diff
==============================================================================
--- zorg/trunk/test/buildbot/builders/Import.py (original)
+++ zorg/trunk/test/buildbot/builders/Import.py Mon May 27 02:53:53 2013
@@ -3,6 +3,7 @@
import zorg
from zorg.buildbot.builders import ClangBuilder, LLVMBuilder, LLVMGCCBuilder
from zorg.buildbot.builders import NightlytestBuilder
+from zorg.buildbot.builders import SanitizerBuilder
# Just check that we can instantiate the build factors, what else can we do?
@@ -13,3 +14,5 @@ print LLVMBuilder.getLLVMBuildFactory()
print LLVMGCCBuilder.getLLVMGCCBuildFactory()
print NightlytestBuilder.getFastNightlyTestBuildFactory('x86_64-apple-darwin10')
+
+print SanitizerBuilder.getSanitizerBuildFactory()
Added: zorg/trunk/zorg/buildbot/builders/SanitizerBuilder.py
URL: http://llvm.org/viewvc/llvm-project/zorg/trunk/zorg/buildbot/builders/SanitizerBuilder.py?rev=182725&view=auto
==============================================================================
--- zorg/trunk/zorg/buildbot/builders/SanitizerBuilder.py (added)
+++ zorg/trunk/zorg/buildbot/builders/SanitizerBuilder.py Mon May 27 02:53:53 2013
@@ -0,0 +1,55 @@
+import os
+
+import buildbot
+import buildbot.process.factory
+from buildbot.steps.source import SVN, Git
+from buildbot.process.properties import WithProperties
+from zorg.buildbot.commands.AnnotatedCommand import AnnotatedCommand
+
+def getSanitizerBuildFactory(
+ clean=False,
+ env=None,
+ timeout=1200):
+
+ # Prepare environmental variables. Set here all env we want everywhere.
+ merged_env = {
+ "TERM" : "dumb", # Make sure Clang doesn't use color escape sequences.
+ }
+ # Use env variables defined in the system.
+ merged_env.update(os.environ)
+ # Clobber bot if we need a clean build.
+ if clean:
+ merged_env["BUILDBOT_CLOBBER"] = "1"
+ # Overwrite pre-set items with the given ones, so user can set anything.
+ if env is not None:
+ merged_env.update(env)
+
+ f = buildbot.process.factory.BuildFactory()
+
+ # Determine the build directory.
+ f.addStep(buildbot.steps.shell.SetProperty(name="get_builddir",
+ command=["pwd"],
+ property="builddir",
+ description="set build dir",
+ workdir=".",
+ env=merged_env))
+
+ sanitizer_buildbot = "sanitizer_buildbot"
+ # Get sanitizer buildbot scripts.
+ f.addStep(SVN(name='svn-sanitizer-buildbot',
+ mode='update',
+ baseURL='http://address-sanitizer.googlecode.com/svn/',
+ defaultBranch='trunk',
+ workdir=sanitizer_buildbot))
+
+ sanitizer_script = os.path.join(sanitizer_buildbot, "build", "scripts",
+ "slave", "buildbot_selector.py")
+
+ # Run annotated command for sanitizer.
+ f.addStep(AnnotatedCommand(name="annotate",
+ description="annotate",
+ timeout=timeout,
+ haltOnFailure=True,
+ command="python " + sanitizer_script,
+ env=merged_env))
+ return f
Added: zorg/trunk/zorg/buildbot/commands/AnnotatedCommand.py
URL: http://llvm.org/viewvc/llvm-project/zorg/trunk/zorg/buildbot/commands/AnnotatedCommand.py?rev=182725&view=auto
==============================================================================
--- zorg/trunk/zorg/buildbot/commands/AnnotatedCommand.py (added)
+++ zorg/trunk/zorg/buildbot/commands/AnnotatedCommand.py Mon May 27 02:53:53 2013
@@ -0,0 +1,413 @@
+#!/usr/bin/python
+# Copyright (c) 2011 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE.chromium.TXT file.
+
+import copy
+import re
+import time
+
+
+from buildbot import util
+from buildbot.process import buildstep
+from buildbot.process.properties import WithProperties
+from buildbot.status import builder
+from buildbot.steps import shell
+from buildbot.steps import source
+
+
+class BuilderStatus(object):
+ # Order in asceding severity.
+ BUILD_STATUS_ORDERING = [
+ builder.SUCCESS,
+ builder.WARNINGS,
+ builder.FAILURE,
+ builder.EXCEPTION,
+ ]
+
+ @classmethod
+ def combine(cls, a, b):
+ """Combine two status, favoring the more severe."""
+ if a not in cls.BUILD_STATUS_ORDERING:
+ return b
+ if b not in cls.BUILD_STATUS_ORDERING:
+ return a
+ a_rank = cls.BUILD_STATUS_ORDERING.index(a)
+ b_rank = cls.BUILD_STATUS_ORDERING.index(b)
+ pick = max(a_rank, b_rank)
+ return cls.BUILD_STATUS_ORDERING[pick]
+
+
+class ProcessLogShellStep(shell.ShellCommand):
+ """ Step that can process log files.
+
+ Delegates actual processing to log_processor, which is a subclass of
+ process_log.PerformanceLogParser.
+
+ Sample usage:
+ # construct class that will have no-arg constructor.
+ log_processor_class = chromium_utils.PartiallyInitialize(
+ process_log.GraphingPageCyclerLogProcessor,
+ report_link='http://host:8010/report.html,
+ output_dir='~/www')
+ # We are partially constructing Step because the step final
+ # initialization is done by BuildBot.
+ step = chromium_utils.PartiallyInitialize(
+ chromium_step.ProcessLogShellStep,
+ log_processor_class)
+
+ """
+ def __init__(self, log_processor_class=None, *args, **kwargs):
+ """
+ Args:
+ log_processor_class: subclass of
+ process_log.PerformanceLogProcessor that will be initialized and
+ invoked once command was successfully completed.
+ """
+ self._result_text = []
+ self._log_processor = None
+ # If log_processor_class is not None, it should be a class. Create an
+ # instance of it.
+ if log_processor_class:
+ self._log_processor = log_processor_class()
+ shell.ShellCommand.__init__(self, *args, **kwargs)
+
+ def start(self):
+ """Overridden shell.ShellCommand.start method.
+
+ Adds a link for the activity that points to report ULR.
+ """
+ self._CreateReportLinkIfNeccessary()
+ shell.ShellCommand.start(self)
+
+ def _GetRevision(self):
+ """Returns the revision number for the build.
+
+ Result is the revision number of the latest change that went in
+ while doing gclient sync. Tries 'got_revision' (from log parsing)
+ then tries 'revision' (usually from forced build). If neither are
+ found, will return -1 instead.
+ """
+ revision = None
+ try:
+ revision = self.build.getProperty('got_revision')
+ except KeyError:
+ pass # 'got_revision' doesn't exist (yet)
+ if not revision:
+ try:
+ revision = self.build.getProperty('revision')
+ except KeyError:
+ pass # neither exist
+ if not revision:
+ revision = -1
+ return revision
+
+ def commandComplete(self, cmd):
+ """Callback implementation that will use log process to parse 'stdio' data.
+ """
+ if self._log_processor:
+ self._result_text = self._log_processor.Process(
+ self._GetRevision(), self.getLog('stdio').getText())
+
+ def getText(self, cmd, results):
+ text_list = self.describe(True)
+ if self._result_text:
+ self._result_text.insert(0, '<div class="BuildResultInfo">')
+ self._result_text.append('</div>')
+ text_list = text_list + self._result_text
+ return text_list
+
+ def evaluateCommand(self, cmd):
+ shell_result = shell.ShellCommand.evaluateCommand(self, cmd)
+ log_result = None
+ if self._log_processor and 'evaluateCommand' in dir(self._log_processor):
+ log_result = self._log_processor.evaluateCommand(cmd)
+ return BuilderStatus.combine(shell_result, log_result)
+
+ def _CreateReportLinkIfNeccessary(self):
+ if self._log_processor and self._log_processor.ReportLink():
+ self.addURL('results', "%s" % self._log_processor.ReportLink())
+
+
+class AnnotationObserver(buildstep.LogLineObserver):
+ """This class knows how to understand annotations.
+
+ Here are a list of the currently supported annotations:
+
+ @@@BUILD_STEP <stepname>@@@
+ Add a new step <stepname>. End the current step, marking with last available
+ status.
+
+ @@@STEP_LINK@<label>@<url>@@@
+ Add a link with label <label> linking to <url> to the current stage.
+
+ @@@STEP_WARNINGS@@@
+ Mark the current step as having warnings (oragnge).
+
+ @@@STEP_FAILURE@@@
+ Mark the current step as having failed (red).
+
+ @@@STEP_EXCEPTION@@@
+ Mark the current step as having exceptions (magenta).
+
+ @@@STEP_CLEAR@@@
+ Reset the text description of the current step.
+
+ @@@STEP_SUMMARY_CLEAR@@@
+ Reset the text summary of the current step.
+
+ @@@STEP_TEXT@<msg>@@@
+ Append <msg> to the current step text.
+
+ @@@STEP_SUMMARY_TEXT@<msg>@@@
+ Append <msg> to the step summary (appears on top of the waterfall).
+
+ @@@HALT_ON_FAILURE@@@
+ Halt if exception or failure steps are encountered (default is not).
+
+ @@@HONOR_ZERO_RETURN_CODE@@@
+ Honor the return code being zero (success), even if steps have other results.
+
+ Deprecated annotations:
+ TODO(bradnelson): drop these when all users have been tracked down.
+
+ @@@BUILD_WARNINGS@@@
+ Equivalent to @@@STEP_WARNINGS@@@
+
+ @@@BUILD_FAILED@@@
+ Equivalent to @@@STEP_FAILURE@@@
+
+ @@@BUILD_EXCEPTION@@@
+ Equivalent to @@@STEP_EXCEPTION@@@
+
+ @@@link@<label>@<url>@@@
+ Equivalent to @@@STEP_LINK@<label>@<url>@@@
+ """
+
+ def __init__(self, command=None, *args, **kwargs):
+ buildstep.LogLineObserver.__init__(self, *args, **kwargs)
+ self.command = command
+ self.sections = []
+ self.annotate_status = builder.SUCCESS
+ self.halt_on_failure = False
+ self.honor_zero_return_code = False
+
+ def initialSection(self):
+ if self.sections:
+ return
+ # Add a log section for output before the first section heading.
+ log = self.command.addLog('preamble')
+ self.sections.append({
+ 'name': 'preamble',
+ 'step': self.command.step_status.getBuild().steps[-1],
+ 'log': log,
+ 'status': builder.SUCCESS,
+ 'links': [],
+ 'step_summary_text': [],
+ 'step_text': [],
+ })
+
+ def fixupLast(self, status=None):
+ # Potentially start initial section here, as initial section might have
+ # no output at all.
+ self.initialSection()
+
+ last = self.sections[-1]
+ # Update status if set as an argument.
+ if status is not None:
+ last['status'] = status
+ # Final update of text.
+ self.updateText()
+ # Add timing info.
+ (start, end) = self.command.step_status.getTimes()
+ msg = '\n\n' + '-' * 80 + '\n'
+ if start is None:
+ msg += 'Not Started\n'
+ else:
+ if end is None:
+ end = util.now()
+ msg += '\n'.join([
+ 'started: %s' % time.ctime(start),
+ 'ended: %s' % time.ctime(end),
+ 'duration: %s' % util.formatInterval(end - start),
+ '', # So we get a final \n
+ ])
+ last['log'].addStdout(msg)
+ # Change status (unless handling the preamble).
+ if len(self.sections) != 1:
+ last['step'].stepFinished(last['status'])
+ # Finish log.
+ last['log'].finish()
+
+ def errLineReceived(self, line):
+ self.outLineReceived(line)
+
+ def updateStepStatus(self, status):
+ """Update current step status and annotation status based on a new event."""
+ self.annotate_status = BuilderStatus.combine(self.annotate_status, status)
+ last = self.sections[-1]
+ last['status'] = BuilderStatus.combine(last['status'], status)
+ if self.halt_on_failure and last['status'] in [
+ builder.FAILURE, builder.EXCEPTION]:
+ self.fixupLast()
+ self.command.finished(last['status'])
+
+ def updateText(self):
+ # Don't update the main phase's text.
+ if len(self.sections) == 1:
+ return
+
+ last = self.sections[-1]
+
+ # Reflect step status in text2.
+ if last['status'] == builder.EXCEPTION:
+ result = ['exception', last['name']]
+ elif last['status'] == builder.FAILURE:
+ result = ['failed', last['name']]
+ else:
+ result = []
+
+ last['step'].setText([last['name']] + last['step_text'])
+ last['step'].setText2(result + last['step_summary_text'])
+
+ def outLineReceived(self, line):
+ """This is called once with each line of the test log."""
+ # Add \n if not there, which seems to be the case for log lines from
+ # windows agents, but not others.
+ if not line.endswith('\n'):
+ line += '\n'
+ # Handle initial setup here, as step_status might not exist yet at init.
+ self.initialSection()
+ # Support: @@@STEP_LINK@<name>@<url>@@@ (emit link)
+ # Also support depreceated @@@link@<name>@<url>@@@
+ m = re.match('^@@@STEP_LINK@(.*)@(.*)@@@', line)
+ if not m:
+ m = re.match('^@@@link@(.*)@(.*)@@@', line)
+ if m:
+ link_label = m.group(1)
+ link_url = m.group(2)
+ self.sections[-1]['links'].append((link_label, link_url))
+ self.sections[-1]['step'].addURL(link_label, link_url)
+ # Support: @@@STEP_WARNINGS@@@ (warn on a stage)
+ # Also support deprecated @@@BUILD_WARNINGS@@@
+ if (line.startswith('@@@STEP_WARNINGS@@@') or
+ line.startswith('@@@BUILD_WARNINGS@@@')):
+ self.updateStepStatus(builder.WARNINGS)
+ # Support: @@@STEP_FAILURE@@@ (fail a stage)
+ # Also support deprecated @@@BUILD_FAILED@@@
+ if (line.startswith('@@@STEP_FAILURE@@@') or
+ line.startswith('@@@BUILD_FAILED@@@')):
+ self.updateStepStatus(builder.FAILURE)
+ # Support: @@@STEP_EXCEPTION@@@ (exception on a stage)
+ # Also support deprecated @@@BUILD_FAILED@@@
+ if (line.startswith('@@@STEP_EXCEPTION@@@') or
+ line.startswith('@@@BUILD_EXCEPTION@@@')):
+ self.updateStepStatus(builder.EXCEPTION)
+ # Support: @@@HALT_ON_FAILURE@@@ (halt if a step fails immediately)
+ if line.startswith('@@@HALT_ON_FAILURE@@@'):
+ self.halt_on_failure = True
+ # Support: @@@HONOR_ZERO_RETURN_CODE@@@ (succeed on 0 return, even if some
+ # steps have failed)
+ if line.startswith('@@@HONOR_ZERO_RETURN_CODE@@@'):
+ self.honor_zero_return_code = True
+ # Support: @@@STEP_CLEAR@@@ (reset step description)
+ if line.startswith('@@@STEP_CLEAR@@@'):
+ self.sections[-1]['step_text'] = []
+ self.updateText()
+ # Support: @@@STEP_SUMMARY_CLEAR@@@ (reset step summary)
+ if line.startswith('@@@STEP_SUMMARY_CLEAR@@@'):
+ self.sections[-1]['step_summary_text'] = []
+ self.updateText()
+ # Support: @@@STEP_TEXT@<msg>@@@
+ m = re.match('^@@@STEP_TEXT@(.*)@@@', line)
+ if m:
+ self.sections[-1]['step_text'].append(m.group(1))
+ self.updateText()
+ # Support: @@@STEP_SUMMARY_TEXT@<msg>@@@
+ m = re.match('^@@@STEP_SUMMARY_TEXT@(.*)@@@', line)
+ if m:
+ self.sections[-1]['step_summary_text'].append(m.group(1))
+ self.updateText()
+ # Support: @@@BUILD_STEP <step_name>@@@ (start a new section)
+ m = re.match('^@@@BUILD_STEP (.*)@@@', line)
+ if m:
+ step_name = m.group(1)
+ # Ignore duplicate consecutive step labels (for robustness).
+ if step_name != self.sections[-1]['name']:
+ # Finish up last section.
+ self.fixupLast()
+ # Add new one.
+ step = self.command.step_status.getBuild().addStepWithName(step_name)
+ step.stepStarted()
+ step.setText([step_name])
+ log = step.addLog('stdio')
+ self.sections.append({
+ 'name': step_name,
+ 'step': step,
+ 'log': log,
+ 'status': builder.SUCCESS,
+ 'links': [],
+ 'step_summary_text': [],
+ 'step_text': [],
+ })
+ # Add to the current secondary log.
+ # Doing this last so that @@@BUILD_STEP... occurs in the log of the new
+ # step.
+ self.sections[-1]['log'].addStdout(line)
+
+ def handleReturnCode(self, return_code):
+ # Treat all non-zero return codes as failure.
+ # We could have a special return code for warnings/exceptions, however,
+ # this might conflict with some existing use of a return code.
+ # Besides, applications can always intercept return codes and emit
+ # STEP_* tags.
+ if return_code == 0:
+ self.fixupLast()
+ if self.honor_zero_return_code:
+ self.annotate_status = builder.SUCCESS
+ else:
+ self.annotate_status = builder.FAILURE
+ self.fixupLast(builder.FAILURE)
+
+
+class AnnotatedCommand(ProcessLogShellStep):
+ """Buildbot command that knows how to display annotations."""
+
+ def __init__(self, *args, **kwargs):
+ # Inject standard tags into the environment.
+ env = {
+ 'BUILDBOT_BLAMELIST': WithProperties('%(blamelist:-[])s'),
+ 'BUILDBOT_BRANCH': WithProperties('%(branch:-None)s'),
+ 'BUILDBOT_BUILDERNAME': WithProperties('%(buildername:-None)s'),
+ 'BUILDBOT_BUILDNUMBER': WithProperties('%(buildnumber:-None)s'),
+ 'BUILDBOT_CLOBBER': WithProperties('%(clobber:+1)s'),
+ 'BUILDBOT_GOT_REVISION': WithProperties('%(got_revision:-None)s'),
+ 'BUILDBOT_REVISION': WithProperties('%(revision:-None)s'),
+ 'BUILDBOT_SCHEDULER': WithProperties('%(scheduler:-None)s'),
+ 'BUILDBOT_SLAVENAME': WithProperties('%(slavename:-None)s'),
+ }
+ # Apply the passed in environment on top.
+ old_env = kwargs.get('env')
+ if not old_env:
+ old_env = {}
+ env.update(old_env)
+ # Change passed in args (ok as a copy is made internally).
+ kwargs['env'] = env
+
+ ProcessLogShellStep.__init__(self, *args, **kwargs)
+ self.script_observer = AnnotationObserver(self)
+ self.addLogObserver('stdio', self.script_observer)
+
+ def interrupt(self, reason):
+ self.script_observer.fixupLast(builder.EXCEPTION)
+ return ProcessLogShellStep.interrupt(self, reason)
+
+ def evaluateCommand(self, cmd):
+ observer_result = self.script_observer.annotate_status
+ # Check if ProcessLogShellStep detected a failure or warning also.
+ log_processor_result = ProcessLogShellStep.evaluateCommand(self, cmd)
+ return BuilderStatus.combine(observer_result, log_processor_result)
+
+ def commandComplete(self, cmd):
+ self.script_observer.handleReturnCode(cmd.rc)
+ return ProcessLogShellStep.commandComplete(self, cmd)
More information about the llvm-commits
mailing list