| # -*- python -*- |
| # ex: set syntax=python: |
| |
| c = BuildmasterConfig = {} |
| |
| from buildbot.buildslave import BuildSlave |
| from buildbot.changes.pb import PBChangeSource |
| from buildbot.scheduler import AnyBranchScheduler, Triggerable |
| from buildbot.schedulers.filter import ChangeFilter |
| from buildbot.status import html |
| from buildbot.status.web.authz import Authz |
| from buildbot.process import buildstep, factory, properties |
| from buildbot.steps import master, shell, source, transfer, trigger |
| from buildbot.status.builder import SUCCESS, FAILURE, WARNINGS, SKIPPED |
| |
| from twisted.internet import defer |
| |
| import os |
| import re |
| import simplejson |
| import urllib |
| |
| from webkitpy.common.config import build as wkbuild |
| from webkitpy.common.net.buildbot import BuildBot as wkbuildbot |
| |
| WithProperties = properties.WithProperties |
| |
| class ConfigureBuild(buildstep.BuildStep): |
| name = "configure build" |
| description = ["configuring build"] |
| descriptionDone = ["configured build"] |
| def __init__(self, platform, configuration, architecture, buildOnly, *args, **kwargs): |
| buildstep.BuildStep.__init__(self, *args, **kwargs) |
| self.platform = platform.split('-', 1)[0] |
| self.fullPlatform = platform |
| self.configuration = configuration |
| self.architecture = architecture |
| self.buildOnly = buildOnly |
| self.addFactoryArguments(platform=platform, configuration=configuration, architecture=architecture, buildOnly=buildOnly) |
| |
| def start(self): |
| self.setProperty("platform", self.platform) |
| self.setProperty("fullPlatform", self.fullPlatform) |
| self.setProperty("configuration", self.configuration) |
| self.setProperty("architecture", self.architecture) |
| self.setProperty("buildOnly", self.buildOnly) |
| self.finished(SUCCESS) |
| return defer.succeed(None) |
| |
| |
| class CheckOutSource(source.SVN): |
| baseURL = "http://svn.webkit.org/repository/webkit/" |
| mode = "update" |
| def __init__(self, *args, **kwargs): |
| source.SVN.__init__(self, baseURL=self.baseURL, defaultBranch="trunk", mode=self.mode, *args, **kwargs) |
| |
| |
| class InstallWin32Dependencies(shell.Compile): |
| description = ["installing dependencies"] |
| descriptionDone = ["installed dependencies"] |
| command = ["perl", "./Tools/Scripts/update-webkit-auxiliary-libs"] |
| |
| class KillOldProcesses(shell.Compile): |
| name = "kill old processes" |
| description = ["killing old processes"] |
| descriptionDone = ["killed old processes"] |
| command = ["python", "./Tools/BuildSlaveSupport/win/kill-old-processes"] |
| |
| class InstallChromiumDependencies(shell.ShellCommand): |
| name = "gclient" |
| description = ["updating chromium dependencies"] |
| descriptionDone = ["updated chromium dependencies"] |
| command = ["perl", "./Tools/Scripts/update-webkit-chromium", "--force"] |
| haltOnFailure = True |
| |
| class CleanupChromiumCrashLogs(shell.ShellCommand): |
| name = "cleanup crash logs" |
| description = ["removing crash logs"] |
| descriptionDone = ["removed crash logs"] |
| command = ["python", "./Tools/BuildSlaveSupport/chromium/remove-crash-logs"] |
| haltOnFailure = False |
| |
| |
| def appendCustomBuildFlags(step, platform): |
| if platform in ('chromium', 'efl', 'gtk', 'qt', 'wincairo', 'wince', 'wx'): |
| step.setCommand(step.command + ['--' + platform]) |
| |
| |
| class CompileWebKit(shell.Compile): |
| command = ["perl", "./Tools/Scripts/build-webkit", WithProperties("--%(configuration)s")] |
| env = {'MFLAGS':''} |
| name = "compile-webkit" |
| description = ["compiling"] |
| descriptionDone = ["compiled"] |
| warningPattern = ".*arning: .*" |
| |
| def start(self): |
| platform = self.getProperty('platform') |
| buildOnly = self.getProperty('buildOnly') |
| if platform == 'mac' and buildOnly: |
| self.setCommand(self.command + ['DEBUG_INFORMATION_FORMAT=dwarf-with-dsym']) |
| |
| appendCustomBuildFlags(self, platform) |
| return shell.Compile.start(self) |
| |
| |
| class ArchiveBuiltProduct(shell.ShellCommand): |
| command = ["python", "./Tools/BuildSlaveSupport/built-product-archive", |
| WithProperties("--platform=%(platform)s"), WithProperties("--%(configuration)s"), "archive"] |
| name = "archive-built-product" |
| description = ["archiving built product"] |
| descriptionDone = ["archived built product"] |
| haltOnFailure = True |
| |
| |
| class ExtractBuiltProduct(shell.ShellCommand): |
| command = ["python", "./Tools/BuildSlaveSupport/built-product-archive", |
| WithProperties("--platform=%(platform)s"), WithProperties("--%(configuration)s"), "extract"] |
| name = "extract-built-product" |
| description = ["extracting built product"] |
| descriptionDone = ["extracted built product"] |
| haltOnFailure = True |
| |
| |
| class UploadBuiltProduct(transfer.FileUpload): |
| slavesrc = WithProperties("WebKitBuild/%(configuration)s.zip") |
| masterdest = WithProperties("archives/%(fullPlatform)s-%(architecture)s-%(configuration)s/%(got_revision)s.zip") |
| haltOnFailure = True |
| |
| def __init__(self): |
| transfer.FileUpload.__init__(self, self.slavesrc, self.masterdest, mode=0644) |
| |
| |
| class DownloadBuiltProduct(transfer.FileDownload): |
| slavedest = WithProperties("WebKitBuild/%(configuration)s.zip") |
| mastersrc = WithProperties("archives/%(fullPlatform)s-%(architecture)s-%(configuration)s/%(got_revision)s.zip") |
| haltOnFailure = True |
| flunkOnFailure = True |
| |
| def __init__(self): |
| transfer.FileDownload.__init__(self, self.mastersrc, self.slavedest) |
| |
| |
| class RunJavaScriptCoreTests(shell.Test): |
| name = "jscore-test" |
| description = ["jscore-tests running"] |
| descriptionDone = ["jscore-tests"] |
| command = ["perl", "./Tools/Scripts/run-javascriptcore-tests", WithProperties("--%(configuration)s")] |
| logfiles = {'actual.html (source)': 'Source/JavaScriptCore/tests/mozilla/actual.html'} |
| |
| def __init__(self, skipBuild=False, *args, **kwargs): |
| self.skipBuild = skipBuild |
| shell.Test.__init__(self, *args, **kwargs) |
| self.addFactoryArguments(skipBuild=skipBuild) |
| |
| def start(self): |
| appendCustomBuildFlags(self, self.getProperty('platform')) |
| if self.skipBuild: |
| self.setCommand(self.command + ['--skip-build']) |
| return shell.Test.start(self) |
| |
| def commandComplete(self, cmd): |
| shell.Test.commandComplete(self, cmd) |
| |
| logText = cmd.logs['stdio'].getText() |
| statusLines = [line for line in logText.splitlines() if line.find('regression') >= 0 and line.find(' found.') >= 0] |
| if statusLines and statusLines[0].split()[0] != '0': |
| self.regressionLine = statusLines[0] |
| else: |
| self.regressionLine = None |
| |
| if 'actual.html (source)' in cmd.logs: |
| self.addHTMLLog('actual.html', cmd.logs['actual.html (source)'].getText()) |
| |
| def evaluateCommand(self, cmd): |
| if self.regressionLine: |
| return FAILURE |
| |
| if cmd.rc != 0: |
| return FAILURE |
| |
| return SUCCESS |
| |
| def getText(self, cmd, results): |
| return self.getText2(cmd, results) |
| |
| def getText2(self, cmd, results): |
| if results != SUCCESS and self.regressionLine: |
| return [self.name, self.regressionLine] |
| |
| return [self.name] |
| |
| |
| class RunWebKitTests(shell.Test): |
| name = "layout-test" |
| description = ["layout-tests running"] |
| descriptionDone = ["layout-tests"] |
| command = ["perl", "./Tools/Scripts/run-webkit-tests", "--no-launch-safari", "--no-new-test-results", |
| "--no-sample-on-timeout", "--results-directory", "layout-test-results", "--use-remote-links-to-tests", |
| WithProperties("--%(configuration)s"), "--exit-after-n-crashes-or-timeouts", "20", "--exit-after-n-failures", "500"] |
| |
| def __init__(self, skipBuild=False, *args, **kwargs): |
| self.skipBuild = skipBuild |
| shell.Test.__init__(self, *args, **kwargs) |
| self.addFactoryArguments(skipBuild=skipBuild) |
| |
| def start(self): |
| platform = self.getProperty('platform') |
| appendCustomBuildFlags(self, platform) |
| if platform == "win": |
| rootArgument = ['--root=' + os.path.join("WebKitBuild", self.getProperty('configuration'), "bin")] |
| else: |
| rootArgument = ['--root=WebKitBuild/bin'] |
| if self.skipBuild: |
| self.setCommand(self.command + rootArgument) |
| return shell.Test.start(self) |
| |
| def commandComplete(self, cmd): |
| shell.Test.commandComplete(self, cmd) |
| |
| logText = cmd.logs['stdio'].getText() |
| incorrectLayoutLines = [] |
| for line in logText.splitlines(): |
| if line.find('had incorrect layout') >= 0 or line.find('were new') >= 0 or line.find('was new') >= 0: |
| incorrectLayoutLines.append(line) |
| elif line.find('test case') >= 0 and (line.find(' crashed') >= 0 or line.find(' timed out') >= 0): |
| incorrectLayoutLines.append(line) |
| elif line.startswith("WARNING:") and line.find(' leak') >= 0: |
| incorrectLayoutLines.append(line.replace('WARNING: ', '')) |
| elif line.find('Exiting early') >= 0: |
| incorrectLayoutLines.append(line) |
| |
| # FIXME: Detect and summarize leaks of RefCounted objects |
| |
| self.incorrectLayoutLines = incorrectLayoutLines |
| |
| def evaluateCommand(self, cmd): |
| if self.incorrectLayoutLines: |
| if len(self.incorrectLayoutLines) == 1: |
| line = self.incorrectLayoutLines[0] |
| if line.find('were new') >= 0 or line.find('was new') >= 0 or line.find(' leak') >= 0: |
| return WARNINGS |
| |
| return FAILURE |
| |
| if cmd.rc != 0: |
| return FAILURE |
| |
| return SUCCESS |
| |
| def getText(self, cmd, results): |
| return self.getText2(cmd, results) |
| |
| def getText2(self, cmd, results): |
| if results != SUCCESS and self.incorrectLayoutLines: |
| return self.incorrectLayoutLines |
| |
| return [self.name] |
| |
| |
| class NewRunWebKitTests(RunWebKitTests): |
| command = ["python", "./Tools/Scripts/new-run-webkit-tests", "--noshow-results", |
| "--verbose", "--results-directory", "layout-test-results", |
| "--builder-name", WithProperties("%(buildername)s"), |
| "--build-number", WithProperties("%(buildnumber)s"), |
| "--master-name", "webkit.org", |
| "--test-results-server", "test-results.appspot.com", |
| WithProperties("--%(configuration)s")] |
| |
| |
| class RunPythonTests(shell.Test): |
| name = "webkitpy-test" |
| description = ["python-tests running"] |
| descriptionDone = ["python-tests"] |
| command = ["python", "./Tools/Scripts/test-webkitpy"] |
| |
| |
| class RunPerlTests(shell.Test): |
| name = "webkitperl-test" |
| description = ["perl-tests running"] |
| descriptionDone = ["perl-tests"] |
| command = ["perl", "./Tools/Scripts/test-webkitperl"] |
| |
| |
| class RunGtkAPITests(shell.Test): |
| name = "API tests" |
| description = ["API tests running"] |
| descriptionDone = ["API tests"] |
| command = ["perl", "./Tools/Scripts/run-gtk-tests", WithProperties("--%(configuration)s")] |
| |
| def commandComplete(self, cmd): |
| shell.Test.commandComplete(self, cmd) |
| |
| logText = cmd.logs['stdio'].getText() |
| incorrectLines = [] |
| for line in logText.splitlines(): |
| if line.startswith('ERROR'): |
| incorrectLines.append(line) |
| |
| self.incorrectLines = incorrectLines |
| |
| def evaluateCommand(self, cmd): |
| if self.incorrectLines: |
| return FAILURE |
| |
| if cmd.rc != 0: |
| return FAILURE |
| |
| return SUCCESS |
| |
| def getText(self, cmd, results): |
| return self.getText2(cmd, results) |
| |
| def getText2(self, cmd, results): |
| if results != SUCCESS and self.incorrectLines: |
| return ["%d API tests failed" % len(self.incorrectLines)] |
| |
| return [self.name] |
| |
| class RunQtAPITests(shell.Test): |
| name = "API tests" |
| description = ["API tests running"] |
| descriptionDone = ["API tests"] |
| command = ["python", "./Tools/Scripts/run-qtwebkit-tests", |
| "--output-file=qt-unit-tests.html", "--do-not-open-results", "--timeout=120", |
| WithProperties("WebKitBuild/%(configuration_pretty)s/WebKit/qt/tests/")] |
| |
| def start(self): |
| self.setProperty("configuration_pretty", self.getProperty("configuration").title()) |
| return shell.Test.start(self) |
| |
| def commandComplete(self, cmd): |
| shell.Test.commandComplete(self, cmd) |
| |
| logText = cmd.logs['stdio'].getText() |
| foundItems = re.findall("TOTALS: (?P<passed>\d+) passed, (?P<failed>\d+) failed, (?P<skipped>\d+) skipped", logText) |
| |
| self.incorrectTests = 0 |
| self.statusLine = [] |
| |
| if foundItems: |
| self.incorrectTests = int(foundItems[0][1]) |
| if self.incorrectTests > 0: |
| self.statusLine = [ |
| "%s passed, %s failed, %s skipped" % (foundItems[0][0], foundItems[0][1], foundItems[0][2]) |
| ] |
| |
| def evaluateCommand(self, cmd): |
| if self.incorrectTests: |
| return WARNINGS |
| |
| if cmd.rc != 0: |
| return FAILURE |
| |
| return SUCCESS |
| |
| def getText(self, cmd, results): |
| return self.getText2(cmd, results) |
| |
| def getText2(self, cmd, results): |
| if results != SUCCESS and self.incorrectTests: |
| return self.statusLine |
| |
| return [self.name] |
| |
| class RunWebKitLeakTests(RunWebKitTests): |
| warnOnWarnings = True |
| def start(self): |
| self.setCommand(self.command + ["--leaks"]) |
| return RunWebKitTests.start(self) |
| |
| |
| class RunWebKit2Tests(RunWebKitTests): |
| def start(self): |
| self.setCommand(self.command + ["--webkit-test-runner"]) |
| return RunWebKitTests.start(self) |
| |
| |
| class RunChromiumWebKitUnitTests(shell.Test): |
| name = "webkit-unit-tests" |
| description = ["webkit-unit-tests running"] |
| descriptionDone = ["webkit-unit-tests"] |
| command = ["perl", "./Tools/Scripts/run-chromium-webkit-unit-tests", |
| WithProperties("--%(configuration)s")] |
| |
| |
| class ArchiveTestResults(shell.ShellCommand): |
| command = ["python", "./Tools/BuildSlaveSupport/test-result-archive", |
| WithProperties("--platform=%(platform)s"), WithProperties("--%(configuration)s"), "archive"] |
| name = "archive-test-results" |
| description = ["archiving test results"] |
| descriptionDone = ["archived test results"] |
| haltOnFailure = True |
| |
| |
| class UploadTestResults(transfer.FileUpload): |
| slavesrc = "layout-test-results.zip" |
| masterdest = WithProperties("public_html/results/%(buildername)s/r%(got_revision)s (%(buildnumber)s).zip") |
| |
| def __init__(self): |
| transfer.FileUpload.__init__(self, self.slavesrc, self.masterdest, mode=0644) |
| |
| |
| class ExtractTestResults(master.MasterShellCommand): |
| zipFile = WithProperties("public_html/results/%(buildername)s/r%(got_revision)s (%(buildnumber)s).zip") |
| resultDirectory = WithProperties("public_html/results/%(buildername)s/r%(got_revision)s (%(buildnumber)s)") |
| descriptionDone = ["uploaded results"] |
| |
| def __init__(self): |
| master.MasterShellCommand.__init__(self, "") |
| |
| def resultDirectoryURL(self): |
| return self.build.getProperties().render(self.resultDirectory).replace("public_html/", "/") + "/" |
| |
| def start(self): |
| self.command = ["ditto", "-k", "-x", "-V", self.build.getProperties().render(self.zipFile), self.build.getProperties().render(self.resultDirectory)] |
| return master.MasterShellCommand.start(self) |
| |
| def addCustomURLs(self): |
| url = self.resultDirectoryURL() + "results.html" |
| self.addURL("view results", url) |
| |
| def finished(self, result): |
| self.addCustomURLs() |
| return master.MasterShellCommand.finished(self, result) |
| |
| |
| class ExtractTestResultsAndLeaks(ExtractTestResults): |
| def addCustomURLs(self): |
| ExtractTestResults.addCustomURLs(self) |
| url = "/LeaksViewer/?url=" + urllib.quote(self.resultDirectoryURL(), safe="") |
| self.addURL("view leaks", url) |
| |
| |
| class Factory(factory.BuildFactory): |
| def __init__(self, platform, configuration, architectures, buildOnly): |
| factory.BuildFactory.__init__(self) |
| self.addStep(ConfigureBuild, platform=platform, configuration=configuration, architecture=" ".join(architectures), buildOnly=buildOnly) |
| self.addStep(CheckOutSource) |
| if platform in ("win", "chromium-win"): |
| self.addStep(KillOldProcesses) |
| if platform == "win": |
| self.addStep(InstallWin32Dependencies) |
| if platform.startswith("chromium"): |
| self.addStep(InstallChromiumDependencies) |
| |
| class BuildFactory(Factory): |
| def __init__(self, platform, configuration, architectures, triggers=None): |
| Factory.__init__(self, platform, configuration, architectures, True) |
| self.addStep(CompileWebKit) |
| if triggers: |
| self.addStep(ArchiveBuiltProduct) |
| self.addStep(UploadBuiltProduct) |
| self.addStep(trigger.Trigger, schedulerNames=triggers) |
| |
| class TestFactory(Factory): |
| TestClass = RunWebKitTests |
| ExtractTestResultsClass = ExtractTestResults |
| def __init__(self, platform, configuration, architectures): |
| Factory.__init__(self, platform, configuration, architectures, False) |
| self.addStep(DownloadBuiltProduct) |
| self.addStep(ExtractBuiltProduct) |
| self.addStep(RunJavaScriptCoreTests, skipBuild=True) |
| self.addStep(self.TestClass, skipBuild=(platform == 'win')) |
| # Tiger's Python 2.3 is too old. WebKit Python requires 2.5+. |
| # Sadly we have no way to detect the version on the slave from here. |
| if platform != "mac-tiger": |
| self.addStep(RunPythonTests) |
| self.addStep(RunPerlTests) |
| self.addStep(ArchiveTestResults) |
| self.addStep(UploadTestResults) |
| self.addStep(self.ExtractTestResultsClass) |
| |
| class BuildAndTestFactory(Factory): |
| TestClass = RunWebKitTests |
| ExtractTestResultsClass = ExtractTestResults |
| def __init__(self, platform, configuration, architectures): |
| Factory.__init__(self, platform, configuration, architectures, False) |
| if platform.startswith("chromium"): |
| self.addStep(CleanupChromiumCrashLogs) |
| self.addStep(CompileWebKit) |
| if not platform.startswith("chromium"): |
| self.addStep(RunJavaScriptCoreTests) |
| if platform.startswith("chromium"): |
| self.addStep(RunChromiumWebKitUnitTests) |
| self.addStep(self.TestClass) |
| # Tiger's Python 2.3 is too old. WebKit Python requires 2.5+. |
| # Sadly we have no way to detect the version on the slave from here. |
| # Chromium Win runs in non-Cygwin environment, which is not yet fit |
| # for running tests. This can be removed once bug 48166 is fixed. |
| if platform != "mac-tiger": |
| self.addStep(RunPythonTests) |
| # Chromium Win runs in non-Cygwin environment, which is not yet fit |
| # for running tests. This can be removed once bug 48166 is fixed. |
| if platform != "chromium-win": |
| self.addStep(RunPerlTests) |
| self.addStep(ArchiveTestResults) |
| self.addStep(UploadTestResults) |
| self.addStep(self.ExtractTestResultsClass) |
| if platform == "gtk": |
| self.addStep(RunGtkAPITests) |
| if platform == "qt": |
| self.addStep(RunQtAPITests) |
| |
| class BuildAndTestLeaksFactory(BuildAndTestFactory): |
| TestClass = RunWebKitLeakTests |
| ExtractTestResultsClass = ExtractTestResultsAndLeaks |
| |
| class NewBuildAndTestFactory(BuildAndTestFactory): |
| TestClass = NewRunWebKitTests |
| |
| class TestWebKit2Factory(TestFactory): |
| TestClass = RunWebKit2Tests |
| |
| class PlatformSpecificScheduler(AnyBranchScheduler): |
| def __init__(self, platform, branch, **kwargs): |
| self.platform = platform |
| filter = ChangeFilter(branch=[branch, None], filter_fn=self.filter) |
| AnyBranchScheduler.__init__(self, name=platform, change_filter=filter, **kwargs) |
| |
| def filter(self, change): |
| return wkbuild.should_build(self.platform, change.files) |
| |
| trunk_filter = ChangeFilter(branch=["trunk", None]) |
| |
| def loadBuilderConfig(c): |
| # FIXME: These file handles are leaked. |
| passwords = simplejson.load(open('passwords.json')) |
| config = simplejson.load(open('config.json')) |
| |
| # use webkitpy's buildbot module to test for core builders |
| wkbb = wkbuildbot() |
| |
| c['slaves'] = [BuildSlave(slave['name'], passwords[slave['name']], max_builds=1) for slave in config['slaves']] |
| |
| c['schedulers'] = [] |
| for scheduler in config['schedulers']: |
| if "change_filter" in scheduler: |
| scheduler["change_filter"] = globals()[scheduler["change_filter"]] |
| kls = globals()[scheduler.pop('type')] |
| c['schedulers'].append(kls(**scheduler)) |
| |
| c['builders'] = [] |
| for builder in config['builders']: |
| for slaveName in builder['slavenames']: |
| for slave in config['slaves']: |
| if slave['name'] != slaveName or slave['platform'] == '*': |
| continue |
| |
| if slave['platform'] != builder['platform']: |
| raise Exception, "Builder %r is for platform %r but has slave %r for platform %r!" % (builder['name'], builder['platform'], slave['name'], slave['platform']) |
| |
| break |
| |
| factory = globals()["%sFactory" % builder.pop('type')] |
| factoryArgs = [] |
| for key in "platform", "configuration", "architectures", "triggers": |
| value = builder.pop(key, None) |
| if value: |
| factoryArgs.append(value) |
| |
| builder["factory"] = factory(*factoryArgs) |
| |
| builder["category"] = "noncore" |
| if wkbb._is_core_builder(builder['name']): |
| builder["category"] = "core" |
| |
| c['builders'].append(builder) |
| |
| loadBuilderConfig(c) |
| |
| c['change_source'] = PBChangeSource() |
| |
| # permissions for WebStatus |
| authz = Authz( |
| forceBuild=True, |
| forceAllBuilds=True, |
| pingBuilder=True, |
| gracefulShutdown=False, |
| stopBuild=True, |
| stopAllBuilds=True, |
| cancelPendingBuild=True, |
| stopChange=True, |
| cleanShutdown=False) |
| |
| c['status'] = [] |
| c['status'].append(html.WebStatus(http_port=8710, |
| revlink="http://trac.webkit.org/changeset/%s", |
| authz=authz)) |
| |
| c['slavePortnum'] = 17000 |
| c['projectName'] = "WebKit" |
| c['projectURL'] = "http://webkit.org" |
| c['buildbotURL'] = "http://build.webkit.org/" |
| |
| c['buildHorizon'] = 1000 |
| c['logHorizon'] = 500 |
| c['eventHorizon'] = 200 |
| c['buildCacheSize'] = 60 |