• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# -*- python -*-
2# ex: set syntax=python:
3
4c = BuildmasterConfig = {}
5
6from buildbot.buildslave import BuildSlave
7from buildbot.changes.pb import PBChangeSource
8from buildbot.scheduler import AnyBranchScheduler, Triggerable
9from buildbot.schedulers.filter import ChangeFilter
10from buildbot.status import html
11from buildbot.status.web.authz import Authz
12from buildbot.process import buildstep, factory, properties
13from buildbot.steps import master, shell, source, transfer, trigger
14from buildbot.status.builder import SUCCESS, FAILURE, WARNINGS, SKIPPED
15
16from twisted.internet import defer
17
18import os
19import re
20import simplejson
21import urllib
22
23from webkitpy.common.config import build as wkbuild
24from webkitpy.common.net.buildbot import BuildBot as wkbuildbot
25
26WithProperties = properties.WithProperties
27
28class ConfigureBuild(buildstep.BuildStep):
29    name = "configure build"
30    description = ["configuring build"]
31    descriptionDone = ["configured build"]
32    def __init__(self, platform, configuration, architecture, buildOnly, *args, **kwargs):
33        buildstep.BuildStep.__init__(self, *args, **kwargs)
34        self.platform = platform.split('-', 1)[0]
35        self.fullPlatform = platform
36        self.configuration = configuration
37        self.architecture = architecture
38        self.buildOnly = buildOnly
39        self.addFactoryArguments(platform=platform, configuration=configuration, architecture=architecture, buildOnly=buildOnly)
40
41    def start(self):
42        self.setProperty("platform", self.platform)
43        self.setProperty("fullPlatform", self.fullPlatform)
44        self.setProperty("configuration", self.configuration)
45        self.setProperty("architecture", self.architecture)
46        self.setProperty("buildOnly", self.buildOnly)
47        self.finished(SUCCESS)
48        return defer.succeed(None)
49
50
51class CheckOutSource(source.SVN):
52    baseURL = "http://svn.webkit.org/repository/webkit/"
53    mode = "update"
54    def __init__(self, *args, **kwargs):
55        source.SVN.__init__(self, baseURL=self.baseURL, defaultBranch="trunk", mode=self.mode, *args, **kwargs)
56
57
58class InstallWin32Dependencies(shell.Compile):
59    description = ["installing dependencies"]
60    descriptionDone = ["installed dependencies"]
61    command = ["perl", "./Tools/Scripts/update-webkit-auxiliary-libs"]
62
63class KillOldProcesses(shell.Compile):
64    name = "kill old processes"
65    description = ["killing old processes"]
66    descriptionDone = ["killed old processes"]
67    command = ["python", "./Tools/BuildSlaveSupport/win/kill-old-processes"]
68
69class InstallChromiumDependencies(shell.ShellCommand):
70    name = "gclient"
71    description = ["updating chromium dependencies"]
72    descriptionDone = ["updated chromium dependencies"]
73    command = ["perl", "./Tools/Scripts/update-webkit-chromium", "--force"]
74    haltOnFailure = True
75
76class CleanupChromiumCrashLogs(shell.ShellCommand):
77    name = "cleanup crash logs"
78    description = ["removing crash logs"]
79    descriptionDone = ["removed crash logs"]
80    command = ["python", "./Tools/BuildSlaveSupport/chromium/remove-crash-logs"]
81    haltOnFailure = False
82
83
84def appendCustomBuildFlags(step, platform):
85    if platform in ('chromium', 'efl', 'gtk', 'qt', 'wincairo', 'wince', 'wx'):
86        step.setCommand(step.command + ['--' + platform])
87
88
89class CompileWebKit(shell.Compile):
90    command = ["perl", "./Tools/Scripts/build-webkit", WithProperties("--%(configuration)s")]
91    env = {'MFLAGS':''}
92    name = "compile-webkit"
93    description = ["compiling"]
94    descriptionDone = ["compiled"]
95    warningPattern = ".*arning: .*"
96
97    def start(self):
98        platform = self.getProperty('platform')
99        buildOnly = self.getProperty('buildOnly')
100        if platform == 'mac' and buildOnly:
101            self.setCommand(self.command + ['DEBUG_INFORMATION_FORMAT=dwarf-with-dsym'])
102
103        appendCustomBuildFlags(self, platform)
104        return shell.Compile.start(self)
105
106
107class ArchiveBuiltProduct(shell.ShellCommand):
108    command = ["python", "./Tools/BuildSlaveSupport/built-product-archive",
109               WithProperties("--platform=%(platform)s"), WithProperties("--%(configuration)s"), "archive"]
110    name = "archive-built-product"
111    description = ["archiving built product"]
112    descriptionDone = ["archived built product"]
113    haltOnFailure = True
114
115
116class ExtractBuiltProduct(shell.ShellCommand):
117    command = ["python", "./Tools/BuildSlaveSupport/built-product-archive",
118               WithProperties("--platform=%(platform)s"), WithProperties("--%(configuration)s"), "extract"]
119    name = "extract-built-product"
120    description = ["extracting built product"]
121    descriptionDone = ["extracted built product"]
122    haltOnFailure = True
123
124
125class UploadBuiltProduct(transfer.FileUpload):
126    slavesrc = WithProperties("WebKitBuild/%(configuration)s.zip")
127    masterdest = WithProperties("archives/%(fullPlatform)s-%(architecture)s-%(configuration)s/%(got_revision)s.zip")
128    haltOnFailure = True
129
130    def __init__(self):
131        transfer.FileUpload.__init__(self, self.slavesrc, self.masterdest, mode=0644)
132
133
134class DownloadBuiltProduct(transfer.FileDownload):
135    slavedest = WithProperties("WebKitBuild/%(configuration)s.zip")
136    mastersrc = WithProperties("archives/%(fullPlatform)s-%(architecture)s-%(configuration)s/%(got_revision)s.zip")
137    haltOnFailure = True
138    flunkOnFailure = True
139
140    def __init__(self):
141        transfer.FileDownload.__init__(self, self.mastersrc, self.slavedest)
142
143
144class RunJavaScriptCoreTests(shell.Test):
145    name = "jscore-test"
146    description = ["jscore-tests running"]
147    descriptionDone = ["jscore-tests"]
148    command = ["perl", "./Tools/Scripts/run-javascriptcore-tests", WithProperties("--%(configuration)s")]
149    logfiles = {'actual.html (source)': 'Source/JavaScriptCore/tests/mozilla/actual.html'}
150
151    def __init__(self, skipBuild=False, *args, **kwargs):
152        self.skipBuild = skipBuild
153        shell.Test.__init__(self, *args, **kwargs)
154        self.addFactoryArguments(skipBuild=skipBuild)
155
156    def start(self):
157        appendCustomBuildFlags(self, self.getProperty('platform'))
158        if self.skipBuild:
159            self.setCommand(self.command + ['--skip-build'])
160        return shell.Test.start(self)
161
162    def commandComplete(self, cmd):
163        shell.Test.commandComplete(self, cmd)
164
165        logText = cmd.logs['stdio'].getText()
166        statusLines = [line for line in logText.splitlines() if line.find('regression') >= 0 and line.find(' found.') >= 0]
167        if statusLines and statusLines[0].split()[0] != '0':
168            self.regressionLine = statusLines[0]
169        else:
170            self.regressionLine = None
171
172        if 'actual.html (source)' in cmd.logs:
173            self.addHTMLLog('actual.html', cmd.logs['actual.html (source)'].getText())
174
175    def evaluateCommand(self, cmd):
176        if self.regressionLine:
177            return FAILURE
178
179        if cmd.rc != 0:
180            return FAILURE
181
182        return SUCCESS
183
184    def getText(self, cmd, results):
185        return self.getText2(cmd, results)
186
187    def getText2(self, cmd, results):
188        if results != SUCCESS and self.regressionLine:
189            return [self.name, self.regressionLine]
190
191        return [self.name]
192
193
194class RunWebKitTests(shell.Test):
195    name = "layout-test"
196    description = ["layout-tests running"]
197    descriptionDone = ["layout-tests"]
198    command = ["perl", "./Tools/Scripts/run-webkit-tests", "--no-launch-safari", "--no-new-test-results",
199               "--no-sample-on-timeout", "--results-directory", "layout-test-results", "--use-remote-links-to-tests",
200               WithProperties("--%(configuration)s"), "--exit-after-n-crashes-or-timeouts", "20",  "--exit-after-n-failures", "500"]
201
202    def __init__(self, skipBuild=False, *args, **kwargs):
203        self.skipBuild = skipBuild
204        shell.Test.__init__(self, *args, **kwargs)
205        self.addFactoryArguments(skipBuild=skipBuild)
206
207    def start(self):
208        platform = self.getProperty('platform')
209        appendCustomBuildFlags(self, platform)
210        if platform == "win":
211            rootArgument = ['--root=' + os.path.join("WebKitBuild", self.getProperty('configuration'), "bin")]
212        else:
213            rootArgument = ['--root=WebKitBuild/bin']
214        if self.skipBuild:
215            self.setCommand(self.command + rootArgument)
216        return shell.Test.start(self)
217
218    def commandComplete(self, cmd):
219        shell.Test.commandComplete(self, cmd)
220
221        logText = cmd.logs['stdio'].getText()
222        incorrectLayoutLines = []
223        for line in logText.splitlines():
224            if line.find('had incorrect layout') >= 0 or line.find('were new') >= 0 or line.find('was new') >= 0:
225                incorrectLayoutLines.append(line)
226            elif line.find('test case') >= 0 and (line.find(' crashed') >= 0 or line.find(' timed out') >= 0):
227                incorrectLayoutLines.append(line)
228            elif line.startswith("WARNING:") and line.find(' leak') >= 0:
229                incorrectLayoutLines.append(line.replace('WARNING: ', ''))
230            elif line.find('Exiting early') >= 0:
231                incorrectLayoutLines.append(line)
232
233            # FIXME: Detect and summarize leaks of RefCounted objects
234
235        self.incorrectLayoutLines = incorrectLayoutLines
236
237    def evaluateCommand(self, cmd):
238        if self.incorrectLayoutLines:
239            if len(self.incorrectLayoutLines) == 1:
240                line = self.incorrectLayoutLines[0]
241                if line.find('were new') >= 0 or line.find('was new') >= 0 or line.find(' leak') >= 0:
242                    return WARNINGS
243
244            return FAILURE
245
246        if cmd.rc != 0:
247            return FAILURE
248
249        return SUCCESS
250
251    def getText(self, cmd, results):
252        return self.getText2(cmd, results)
253
254    def getText2(self, cmd, results):
255        if results != SUCCESS and self.incorrectLayoutLines:
256            return self.incorrectLayoutLines
257
258        return [self.name]
259
260
261class NewRunWebKitTests(RunWebKitTests):
262    command = ["python", "./Tools/Scripts/new-run-webkit-tests", "--noshow-results",
263               "--verbose", "--results-directory", "layout-test-results",
264               "--builder-name", WithProperties("%(buildername)s"),
265               "--build-number", WithProperties("%(buildnumber)s"),
266               "--master-name", "webkit.org",
267               "--test-results-server", "test-results.appspot.com",
268               WithProperties("--%(configuration)s")]
269
270
271class RunPythonTests(shell.Test):
272    name = "webkitpy-test"
273    description = ["python-tests running"]
274    descriptionDone = ["python-tests"]
275    command = ["python", "./Tools/Scripts/test-webkitpy"]
276
277
278class RunPerlTests(shell.Test):
279    name = "webkitperl-test"
280    description = ["perl-tests running"]
281    descriptionDone = ["perl-tests"]
282    command = ["perl", "./Tools/Scripts/test-webkitperl"]
283
284
285class RunGtkAPITests(shell.Test):
286    name = "API tests"
287    description = ["API tests running"]
288    descriptionDone = ["API tests"]
289    command = ["perl", "./Tools/Scripts/run-gtk-tests", WithProperties("--%(configuration)s")]
290
291    def commandComplete(self, cmd):
292        shell.Test.commandComplete(self, cmd)
293
294        logText = cmd.logs['stdio'].getText()
295        incorrectLines = []
296        for line in logText.splitlines():
297            if line.startswith('ERROR'):
298                incorrectLines.append(line)
299
300        self.incorrectLines = incorrectLines
301
302    def evaluateCommand(self, cmd):
303        if self.incorrectLines:
304            return FAILURE
305
306        if cmd.rc != 0:
307            return FAILURE
308
309        return SUCCESS
310
311    def getText(self, cmd, results):
312        return self.getText2(cmd, results)
313
314    def getText2(self, cmd, results):
315        if results != SUCCESS and self.incorrectLines:
316            return ["%d API tests failed" % len(self.incorrectLines)]
317
318        return [self.name]
319
320class RunQtAPITests(shell.Test):
321    name = "API tests"
322    description = ["API tests running"]
323    descriptionDone = ["API tests"]
324    command = ["python", "./Tools/Scripts/run-qtwebkit-tests",
325               "--output-file=qt-unit-tests.html", "--do-not-open-results", "--timeout=120",
326               WithProperties("WebKitBuild/%(configuration_pretty)s/WebKit/qt/tests/")]
327
328    def start(self):
329        self.setProperty("configuration_pretty", self.getProperty("configuration").title())
330        return shell.Test.start(self)
331
332    def commandComplete(self, cmd):
333        shell.Test.commandComplete(self, cmd)
334
335        logText = cmd.logs['stdio'].getText()
336        foundItems = re.findall("TOTALS: (?P<passed>\d+) passed, (?P<failed>\d+) failed, (?P<skipped>\d+) skipped", logText)
337
338        self.incorrectTests = 0
339        self.statusLine = []
340
341        if foundItems:
342            self.incorrectTests = int(foundItems[0][1])
343            if self.incorrectTests > 0:
344                self.statusLine = [
345                    "%s passed, %s failed, %s skipped" % (foundItems[0][0], foundItems[0][1], foundItems[0][2])
346                ]
347
348    def evaluateCommand(self, cmd):
349        if self.incorrectTests:
350            return WARNINGS
351
352        if cmd.rc != 0:
353            return FAILURE
354
355        return SUCCESS
356
357    def getText(self, cmd, results):
358        return self.getText2(cmd, results)
359
360    def getText2(self, cmd, results):
361        if results != SUCCESS and self.incorrectTests:
362            return self.statusLine
363
364        return [self.name]
365
366class RunWebKitLeakTests(RunWebKitTests):
367    warnOnWarnings = True
368    def start(self):
369        self.setCommand(self.command + ["--leaks"])
370        return RunWebKitTests.start(self)
371
372
373class RunWebKit2Tests(RunWebKitTests):
374    def start(self):
375        self.setCommand(self.command + ["--webkit-test-runner"])
376        return RunWebKitTests.start(self)
377
378
379class RunChromiumWebKitUnitTests(shell.Test):
380    name = "webkit-unit-tests"
381    description = ["webkit-unit-tests running"]
382    descriptionDone = ["webkit-unit-tests"]
383    command = ["perl", "./Tools/Scripts/run-chromium-webkit-unit-tests",
384               WithProperties("--%(configuration)s")]
385
386
387class ArchiveTestResults(shell.ShellCommand):
388    command = ["python", "./Tools/BuildSlaveSupport/test-result-archive",
389               WithProperties("--platform=%(platform)s"), WithProperties("--%(configuration)s"), "archive"]
390    name = "archive-test-results"
391    description = ["archiving test results"]
392    descriptionDone = ["archived test results"]
393    haltOnFailure = True
394
395
396class UploadTestResults(transfer.FileUpload):
397    slavesrc = "layout-test-results.zip"
398    masterdest = WithProperties("public_html/results/%(buildername)s/r%(got_revision)s (%(buildnumber)s).zip")
399
400    def __init__(self):
401        transfer.FileUpload.__init__(self, self.slavesrc, self.masterdest, mode=0644)
402
403
404class ExtractTestResults(master.MasterShellCommand):
405    zipFile = WithProperties("public_html/results/%(buildername)s/r%(got_revision)s (%(buildnumber)s).zip")
406    resultDirectory = WithProperties("public_html/results/%(buildername)s/r%(got_revision)s (%(buildnumber)s)")
407    descriptionDone = ["uploaded results"]
408
409    def __init__(self):
410        master.MasterShellCommand.__init__(self, "")
411
412    def resultDirectoryURL(self):
413        return self.build.getProperties().render(self.resultDirectory).replace("public_html/", "/") + "/"
414
415    def start(self):
416        self.command = ["ditto", "-k", "-x", "-V", self.build.getProperties().render(self.zipFile), self.build.getProperties().render(self.resultDirectory)]
417        return master.MasterShellCommand.start(self)
418
419    def addCustomURLs(self):
420        url = self.resultDirectoryURL() + "results.html"
421        self.addURL("view results", url)
422
423    def finished(self, result):
424        self.addCustomURLs()
425        return master.MasterShellCommand.finished(self, result)
426
427
428class ExtractTestResultsAndLeaks(ExtractTestResults):
429    def addCustomURLs(self):
430        ExtractTestResults.addCustomURLs(self)
431        url = "/LeaksViewer/?url=" + urllib.quote(self.resultDirectoryURL(), safe="")
432        self.addURL("view leaks", url)
433
434
435class Factory(factory.BuildFactory):
436    def __init__(self, platform, configuration, architectures, buildOnly):
437        factory.BuildFactory.__init__(self)
438        self.addStep(ConfigureBuild, platform=platform, configuration=configuration, architecture=" ".join(architectures), buildOnly=buildOnly)
439        self.addStep(CheckOutSource)
440        if platform in ("win", "chromium-win"):
441            self.addStep(KillOldProcesses)
442        if platform == "win":
443            self.addStep(InstallWin32Dependencies)
444        if platform.startswith("chromium"):
445            self.addStep(InstallChromiumDependencies)
446
447class BuildFactory(Factory):
448    def __init__(self, platform, configuration, architectures, triggers=None):
449        Factory.__init__(self, platform, configuration, architectures, True)
450        self.addStep(CompileWebKit)
451        if triggers:
452            self.addStep(ArchiveBuiltProduct)
453            self.addStep(UploadBuiltProduct)
454            self.addStep(trigger.Trigger, schedulerNames=triggers)
455
456class TestFactory(Factory):
457    TestClass = RunWebKitTests
458    ExtractTestResultsClass = ExtractTestResults
459    def __init__(self, platform, configuration, architectures):
460        Factory.__init__(self, platform, configuration, architectures, False)
461        self.addStep(DownloadBuiltProduct)
462        self.addStep(ExtractBuiltProduct)
463        self.addStep(RunJavaScriptCoreTests, skipBuild=True)
464        self.addStep(self.TestClass, skipBuild=(platform == 'win'))
465        # Tiger's Python 2.3 is too old.  WebKit Python requires 2.5+.
466        # Sadly we have no way to detect the version on the slave from here.
467        if platform != "mac-tiger":
468            self.addStep(RunPythonTests)
469        self.addStep(RunPerlTests)
470        self.addStep(ArchiveTestResults)
471        self.addStep(UploadTestResults)
472        self.addStep(self.ExtractTestResultsClass)
473
474class BuildAndTestFactory(Factory):
475    TestClass = RunWebKitTests
476    ExtractTestResultsClass = ExtractTestResults
477    def __init__(self, platform, configuration, architectures):
478        Factory.__init__(self, platform, configuration, architectures, False)
479        if platform.startswith("chromium"):
480            self.addStep(CleanupChromiumCrashLogs)
481        self.addStep(CompileWebKit)
482        if not platform.startswith("chromium"):
483            self.addStep(RunJavaScriptCoreTests)
484        if platform.startswith("chromium"):
485            self.addStep(RunChromiumWebKitUnitTests)
486        self.addStep(self.TestClass)
487        # Tiger's Python 2.3 is too old.  WebKit Python requires 2.5+.
488        # Sadly we have no way to detect the version on the slave from here.
489        # Chromium Win runs in non-Cygwin environment, which is not yet fit
490        # for running tests. This can be removed once bug 48166 is fixed.
491        if platform != "mac-tiger":
492            self.addStep(RunPythonTests)
493        # Chromium Win runs in non-Cygwin environment, which is not yet fit
494        # for running tests. This can be removed once bug 48166 is fixed.
495        if platform != "chromium-win":
496            self.addStep(RunPerlTests)
497        self.addStep(ArchiveTestResults)
498        self.addStep(UploadTestResults)
499        self.addStep(self.ExtractTestResultsClass)
500        if platform == "gtk":
501            self.addStep(RunGtkAPITests)
502        if platform == "qt":
503            self.addStep(RunQtAPITests)
504
505class BuildAndTestLeaksFactory(BuildAndTestFactory):
506    TestClass = RunWebKitLeakTests
507    ExtractTestResultsClass = ExtractTestResultsAndLeaks
508
509class NewBuildAndTestFactory(BuildAndTestFactory):
510    TestClass = NewRunWebKitTests
511
512class TestWebKit2Factory(TestFactory):
513    TestClass = RunWebKit2Tests
514
515class PlatformSpecificScheduler(AnyBranchScheduler):
516    def __init__(self, platform, branch, **kwargs):
517        self.platform = platform
518        filter = ChangeFilter(branch=[branch, None], filter_fn=self.filter)
519        AnyBranchScheduler.__init__(self, name=platform, change_filter=filter, **kwargs)
520
521    def filter(self, change):
522        return wkbuild.should_build(self.platform, change.files)
523
524trunk_filter = ChangeFilter(branch=["trunk", None])
525
526def loadBuilderConfig(c):
527    # FIXME: These file handles are leaked.
528    passwords = simplejson.load(open('passwords.json'))
529    config = simplejson.load(open('config.json'))
530
531    # use webkitpy's buildbot module to test for core builders
532    wkbb = wkbuildbot()
533
534    c['slaves'] = [BuildSlave(slave['name'], passwords[slave['name']], max_builds=1) for slave in config['slaves']]
535
536    c['schedulers'] = []
537    for scheduler in config['schedulers']:
538        if "change_filter" in scheduler:
539            scheduler["change_filter"] = globals()[scheduler["change_filter"]]
540        kls = globals()[scheduler.pop('type')]
541        c['schedulers'].append(kls(**scheduler))
542
543    c['builders'] = []
544    for builder in config['builders']:
545        for slaveName in builder['slavenames']:
546            for slave in config['slaves']:
547                if slave['name'] != slaveName or slave['platform'] == '*':
548                    continue
549
550                if slave['platform'] != builder['platform']:
551                    raise Exception, "Builder %r is for platform %r but has slave %r for platform %r!" % (builder['name'], builder['platform'], slave['name'], slave['platform'])
552
553                break
554
555        factory = globals()["%sFactory" % builder.pop('type')]
556        factoryArgs = []
557        for key in "platform", "configuration", "architectures", "triggers":
558            value = builder.pop(key, None)
559            if value:
560                factoryArgs.append(value)
561
562        builder["factory"] = factory(*factoryArgs)
563
564        builder["category"] = "noncore"
565        if wkbb._is_core_builder(builder['name']):
566            builder["category"] = "core"
567
568        c['builders'].append(builder)
569
570loadBuilderConfig(c)
571
572c['change_source'] = PBChangeSource()
573
574# permissions for WebStatus
575authz = Authz(
576    forceBuild=True,
577    forceAllBuilds=True,
578    pingBuilder=True,
579    gracefulShutdown=False,
580    stopBuild=True,
581    stopAllBuilds=True,
582    cancelPendingBuild=True,
583    stopChange=True,
584    cleanShutdown=False)
585
586c['status'] = []
587c['status'].append(html.WebStatus(http_port=8710,
588                                  revlink="http://trac.webkit.org/changeset/%s",
589                                  authz=authz))
590
591c['slavePortnum'] = 17000
592c['projectName'] = "WebKit"
593c['projectURL'] = "http://webkit.org"
594c['buildbotURL'] = "http://build.webkit.org/"
595
596c['buildHorizon'] = 1000
597c['logHorizon'] = 500
598c['eventHorizon'] = 200
599c['buildCacheSize'] = 60
600