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