2011-03-03 Tony Chang <tony@chromium.org>
[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 in ("win", "chromium-win"):
424             self.addStep(KillOldProcesses)
425         if platform == "win":
426             self.addStep(InstallWin32Dependencies)
427         if platform.startswith("chromium"):
428             self.addStep(InstallChromiumDependencies)
429
430 class BuildFactory(Factory):
431     def __init__(self, platform, configuration, architectures, triggers=None):
432         Factory.__init__(self, platform, configuration, architectures, True)
433         self.addStep(CompileWebKit)
434         if triggers:
435             self.addStep(ArchiveBuiltProduct)
436             self.addStep(UploadBuiltProduct)
437             self.addStep(trigger.Trigger, schedulerNames=triggers)
438
439 class TestFactory(Factory):
440     TestClass = RunWebKitTests
441     def __init__(self, platform, configuration, architectures):
442         Factory.__init__(self, platform, configuration, architectures, False)
443         self.addStep(DownloadBuiltProduct)
444         self.addStep(ExtractBuiltProduct)
445         self.addStep(RunJavaScriptCoreTests, skipBuild=True)
446         self.addStep(self.TestClass, skipBuild=(platform == 'win'))
447         # Tiger's Python 2.3 is too old.  WebKit Python requires 2.5+.
448         # Sadly we have no way to detect the version on the slave from here.
449         if platform != "mac-tiger":
450             self.addStep(RunPythonTests)
451         self.addStep(RunPerlTests)
452         self.addStep(ArchiveTestResults)
453         self.addStep(UploadTestResults)
454         self.addStep(ExtractTestResults)
455
456 class BuildAndTestFactory(Factory):
457     TestClass = RunWebKitTests
458     def __init__(self, platform, configuration, architectures):
459         Factory.__init__(self, platform, configuration, architectures, False)
460         if platform.startswith("chromium"):
461             self.addStep(CleanupChromiumCrashLogs)
462         self.addStep(CompileWebKit)
463         if not platform.startswith("chromium"):
464             self.addStep(RunJavaScriptCoreTests)
465         if platform.startswith("chromium"):
466             self.addStep(RunChromiumWebKitUnitTests)
467         self.addStep(self.TestClass)
468         # Tiger's Python 2.3 is too old.  WebKit Python requires 2.5+.
469         # Sadly we have no way to detect the version on the slave from here.
470         # Chromium Win runs in non-Cygwin environment, which is not yet fit
471         # for running tests. This can be removed once bug 48166 is fixed.
472         if platform != "mac-tiger":
473             self.addStep(RunPythonTests)
474         # Chromium Win runs in non-Cygwin environment, which is not yet fit
475         # for running tests. This can be removed once bug 48166 is fixed.
476         if platform != "chromium-win":
477             self.addStep(RunPerlTests)
478         self.addStep(ArchiveTestResults)
479         self.addStep(UploadTestResults)
480         self.addStep(ExtractTestResults)
481         if platform == "gtk":
482             self.addStep(RunGtkAPITests)
483         if platform == "qt":
484             self.addStep(RunQtAPITests)
485
486 class BuildAndTestLeaksFactory(BuildAndTestFactory):
487     TestClass = RunWebKitLeakTests
488
489 class NewBuildAndTestFactory(BuildAndTestFactory):
490     TestClass = NewRunWebKitTests
491
492 class TestWebKit2Factory(TestFactory):
493     TestClass = RunWebKit2Tests
494
495 class PlatformSpecificScheduler(AnyBranchScheduler):
496     def __init__(self, platform, branch, **kwargs):
497         self.platform = platform
498         filter = ChangeFilter(branch=[branch, None], filter_fn=self.filter)
499         AnyBranchScheduler.__init__(self, name=platform, change_filter=filter, **kwargs)
500
501     def filter(self, change):
502         return wkbuild.should_build(self.platform, change.files)
503
504 trunk_filter = ChangeFilter(branch=["trunk", None])
505
506 def loadBuilderConfig(c):
507     # FIXME: These file handles are leaked.
508     passwords = simplejson.load(open('passwords.json'))
509     config = simplejson.load(open('config.json'))
510
511     # use webkitpy's buildbot module to test for core builders
512     wkbb = wkbuildbot()
513
514     c['slaves'] = [BuildSlave(slave['name'], passwords[slave['name']], max_builds=1) for slave in config['slaves']]
515
516     c['schedulers'] = []
517     for scheduler in config['schedulers']:
518         if "change_filter" in scheduler:
519             scheduler["change_filter"] = globals()[scheduler["change_filter"]]
520         kls = globals()[scheduler.pop('type')]
521         c['schedulers'].append(kls(**scheduler))
522
523     c['builders'] = []
524     for builder in config['builders']:
525         for slaveName in builder['slavenames']:
526             for slave in config['slaves']:
527                 if slave['name'] != slaveName or slave['platform'] == '*':
528                     continue
529
530                 if slave['platform'] != builder['platform']:
531                     raise Exception, "Builder %r is for platform %r but has slave %r for platform %r!" % (builder['name'], builder['platform'], slave['name'], slave['platform'])
532
533                 break
534
535         factory = globals()["%sFactory" % builder.pop('type')]
536         factoryArgs = []
537         for key in "platform", "configuration", "architectures", "triggers":
538             value = builder.pop(key, None)
539             if value:
540                 factoryArgs.append(value)
541
542         builder["factory"] = factory(*factoryArgs)
543
544         builder["category"] = "noncore"
545         if wkbb._is_core_builder(builder['name']):
546             builder["category"] = "core"
547
548         c['builders'].append(builder)
549
550 loadBuilderConfig(c)
551
552 c['change_source'] = PBChangeSource()
553
554 # permissions for WebStatus
555 authz = Authz(
556     forceBuild=True,
557     forceAllBuilds=True,
558     pingBuilder=True,
559     gracefulShutdown=False,
560     stopBuild=True,
561     stopAllBuilds=True,
562     cancelPendingBuild=True,
563     stopChange=True,
564     cleanShutdown=False)
565
566 c['status'] = []
567 c['status'].append(html.WebStatus(http_port=8710, 
568                                   revlink="http://trac.webkit.org/changeset/%s", 
569                                   authz=authz))
570
571 c['slavePortnum'] = 17000
572 c['projectName'] = "WebKit"
573 c['projectURL'] = "http://webkit.org"
574 c['buildbotURL'] = "http://build.webkit.org/"
575
576 c['buildHorizon'] = 1000
577 c['logHorizon'] = 500
578 c['eventHorizon'] = 200
579 c['buildCacheSize'] = 60