9a02c304c470d1b86790accb3a6b0f92a9377e78
[WebKit-https.git] / Tools / BuildSlaveSupport / build.webkit.org-config / master.cfg
1 # -*- python -*-
2 # ex: set syntax=python:
3
4 from buildbot.buildslave import BuildSlave
5 from buildbot.changes.pb import PBChangeSource
6 from buildbot.scheduler import AnyBranchScheduler, Triggerable
7 from buildbot.schedulers.forcesched import FixedParameter, ForceScheduler, StringParameter
8 from buildbot.schedulers.filter import ChangeFilter
9 from buildbot.status import html
10 from buildbot.status.web.authz import Authz
11 from buildbot.process import buildstep, factory, properties
12 from buildbot.steps import master, shell, source, transfer, trigger
13 from buildbot.status.builder import SUCCESS, FAILURE, WARNINGS, SKIPPED
14
15 from twisted.internet import defer
16
17 import os
18 import re
19 import json
20 import operator
21 import urllib
22
23 from committer_auth import CommitterAuth
24 from webkitpy.common.config import build as wkbuild
25
26
27 c = BuildmasterConfig = {}
28
29 c['change_source'] = PBChangeSource()
30
31 # permissions for WebStatus
32 authz = Authz(
33     auth=CommitterAuth('auth.json'),
34     forceBuild='auth',
35     forceAllBuilds='auth',
36     pingBuilder=True,
37     gracefulShutdown=False,
38     stopBuild='auth',
39     stopAllBuilds='auth',
40     cancelPendingBuild='auth',
41     stopChange=True,
42     cleanShutdown=False)
43
44 c['status'] = []
45 c['status'].append(html.WebStatus(http_port=8710, 
46                                   revlink="http://trac.webkit.org/changeset/%s", 
47                                   authz=authz))
48
49 c['slavePortnum'] = 17000
50 c['projectName'] = "WebKit"
51 c['projectURL'] = "http://webkit.org"
52 c['buildbotURL'] = "http://build.webkit.org/"
53
54 c['buildHorizon'] = 1000
55 c['logHorizon'] = 500
56 c['eventHorizon'] = 200
57 c['buildCacheSize'] = 60
58
59 WithProperties = properties.WithProperties
60
61
62 class TestWithFailureCount(shell.Test):
63     failedTestsFormatString = "%d tests failed"
64
65     def countFailures(self, cmd):
66         return 0
67
68     def commandComplete(self, cmd):
69         shell.Test.commandComplete(self, cmd)
70         self.failedTestCount = self.countFailures(cmd)
71
72     def evaluateCommand(self, cmd):
73         if self.failedTestCount:
74             return FAILURE
75
76         if cmd.rc != 0:
77             return FAILURE
78
79         return SUCCESS
80
81     def getText(self, cmd, results):
82         return self.getText2(cmd, results)
83
84     def getText2(self, cmd, results):
85         if results != SUCCESS and self.failedTestCount:
86             return [self.failedTestsFormatString % self.failedTestCount]
87
88         return [self.name]
89
90
91 class ConfigureBuild(buildstep.BuildStep):
92     name = "configure build"
93     description = ["configuring build"]
94     descriptionDone = ["configured build"]
95     def __init__(self, platform, configuration, architecture, buildOnly, *args, **kwargs):
96         buildstep.BuildStep.__init__(self, *args, **kwargs)
97         self.platform = platform.split('-', 1)[0]
98         self.fullPlatform = platform
99         self.configuration = configuration
100         self.architecture = architecture
101         self.buildOnly = buildOnly
102         self.addFactoryArguments(platform=platform, configuration=configuration, architecture=architecture, buildOnly=buildOnly)
103
104     def start(self):
105         self.setProperty("platform", self.platform)
106         self.setProperty("fullPlatform", self.fullPlatform)
107         self.setProperty("configuration", self.configuration)
108         self.setProperty("architecture", self.architecture)
109         self.setProperty("buildOnly", self.buildOnly)
110         self.finished(SUCCESS)
111         return defer.succeed(None)
112
113
114 class CheckOutSource(source.SVN):
115     baseURL = "http://svn.webkit.org/repository/webkit/"
116     mode = "update"
117     def __init__(self, **kwargs):
118         kwargs['baseURL'] = self.baseURL
119         kwargs['defaultBranch'] = "trunk"
120         kwargs['mode'] = self.mode
121         source.SVN.__init__(self, **kwargs)
122
123
124
125 class InstallWin32Dependencies(shell.Compile):
126     description = ["installing dependencies"]
127     descriptionDone = ["installed dependencies"]
128     command = ["perl", "./Tools/Scripts/update-webkit-auxiliary-libs"]
129
130 class KillOldProcesses(shell.Compile):
131     name = "kill old processes"
132     description = ["killing old processes"]
133     descriptionDone = ["killed old processes"]
134     command = ["python", "./Tools/BuildSlaveSupport/kill-old-processes"]
135
136 class InstallEflDependencies(shell.ShellCommand):
137     name = "jhbuild"
138     description = ["updating efl dependencies"]
139     descriptionDone = ["updated efl dependencies"]
140     command = ["perl", "./Tools/Scripts/update-webkitefl-libs"]
141     haltOnFailure = True
142
143 class InstallGtkDependencies(shell.ShellCommand):
144     name = "jhbuild"
145     description = ["updating gtk dependencies"]
146     descriptionDone = ["updated gtk dependencies"]
147     command = ["perl", "./Tools/Scripts/update-webkitgtk-libs"]
148     haltOnFailure = True
149
150 class InstallChromiumDependencies(shell.ShellCommand):
151     name = "gclient"
152     description = ["updating chromium dependencies"]
153     descriptionDone = ["updated chromium dependencies"]
154     command = ["perl", "./Tools/Scripts/update-webkit-chromium", "--force"]
155     haltOnFailure = True
156     def start(self):
157         if self.getProperty('fullPlatform') == "chromium-android":
158             self.setCommand(self.command + ['--chromium-android'])
159
160         return shell.ShellCommand.start(self)
161
162 class CleanupChromiumCrashLogs(shell.ShellCommand):
163     name = "cleanup crash logs"
164     description = ["removing crash logs"]
165     descriptionDone = ["removed crash logs"]
166     command = ["python", "./Tools/BuildSlaveSupport/chromium/remove-crash-logs"]
167     haltOnFailure = False
168
169
170 def appendCustomBuildFlags(step, platform, fullPlatform=""):
171     if fullPlatform == "chromium-android":
172         step.setCommand(step.command + ['--chromium-android'])
173     elif platform in ('chromium', 'efl', 'gtk', 'qt', 'wincairo', 'wince', 'wx'):
174         step.setCommand(step.command + ['--' + platform])
175
176
177 class CompileWebKit(shell.Compile):
178     command = ["perl", "./Tools/Scripts/build-webkit", WithProperties("--%(configuration)s")]
179     env = {'MFLAGS':''}
180     name = "compile-webkit"
181     description = ["compiling"]
182     descriptionDone = ["compiled"]
183     warningPattern = ".*arning: .*"
184
185     def start(self):
186         platform = self.getProperty('platform')
187         buildOnly = self.getProperty('buildOnly')
188         if platform == 'mac' and buildOnly:
189             self.setCommand(self.command + ['DEBUG_INFORMATION_FORMAT=dwarf-with-dsym'])
190
191         appendCustomBuildFlags(self, platform, self.getProperty('fullPlatform'))
192
193         return shell.Compile.start(self)
194
195
196 class ArchiveBuiltProduct(shell.ShellCommand):
197     command = ["python", "./Tools/BuildSlaveSupport/built-product-archive",
198                WithProperties("--platform=%(platform)s"), WithProperties("--%(configuration)s"), "archive"]
199     name = "archive-built-product"
200     description = ["archiving built product"]
201     descriptionDone = ["archived built product"]
202     haltOnFailure = True
203
204
205 class ExtractBuiltProduct(shell.ShellCommand):
206     command = ["python", "./Tools/BuildSlaveSupport/built-product-archive",
207                WithProperties("--platform=%(platform)s"), WithProperties("--%(configuration)s"), "extract"]
208     name = "extract-built-product"
209     description = ["extracting built product"]
210     descriptionDone = ["extracted built product"]
211     haltOnFailure = True
212
213
214 class UploadBuiltProduct(transfer.FileUpload):
215     slavesrc = WithProperties("WebKitBuild/%(configuration)s.zip")
216     masterdest = WithProperties("archives/%(fullPlatform)s-%(architecture)s-%(configuration)s/%(got_revision)s.zip")
217     haltOnFailure = True
218
219     def __init__(self):
220         transfer.FileUpload.__init__(self, self.slavesrc, self.masterdest, mode=0644)
221
222
223 class DownloadBuiltProduct(shell.ShellCommand):
224     command = ["python", "./Tools/BuildSlaveSupport/download-built-product",
225         WithProperties("--platform=%(platform)s"), WithProperties("--%(configuration)s"),
226         WithProperties(c["buildbotURL"] + "archives/%(fullPlatform)s-%(architecture)s-%(configuration)s/%(got_revision)s.zip")]
227     name = "download-built-product"
228     description = ["downloading built product"]
229     descriptionDone = ["downloaded built product"]
230     haltOnFailure = True
231     flunkOnFailure = True
232
233
234 class RunJavaScriptCoreTests(shell.Test):
235     name = "jscore-test"
236     description = ["jscore-tests running"]
237     descriptionDone = ["jscore-tests"]
238     command = ["perl", "./Tools/Scripts/run-javascriptcore-tests", WithProperties("--%(configuration)s")]
239     logfiles = {'actual.html (source)': 'Source/JavaScriptCore/tests/mozilla/actual.html'}
240
241     def __init__(self, buildJSCTool=True, *args, **kwargs):
242         self.buildJSCTool = buildJSCTool
243         shell.Test.__init__(self, *args, **kwargs)
244         self.addFactoryArguments(buildJSCTool=buildJSCTool)
245
246     def start(self):
247         appendCustomBuildFlags(self, self.getProperty('platform'))
248         if not self.buildJSCTool:
249             self.setCommand(self.command + ['--no-build'])
250         return shell.Test.start(self)
251
252     def commandComplete(self, cmd):
253         shell.Test.commandComplete(self, cmd)
254
255         logText = cmd.logs['stdio'].getText()
256         statusLines = [line for line in logText.splitlines() if line.find('regression') >= 0 and line.find(' found.') >= 0]
257         if statusLines and statusLines[0].split()[0] != '0':
258             self.regressionLine = statusLines[0]
259         else:
260             self.regressionLine = None
261
262         if 'actual.html (source)' in cmd.logs:
263             self.addHTMLLog('actual.html', cmd.logs['actual.html (source)'].getText())
264
265     def evaluateCommand(self, cmd):
266         if self.regressionLine:
267             return FAILURE
268
269         if cmd.rc != 0:
270             return FAILURE
271
272         return SUCCESS
273
274     def getText(self, cmd, results):
275         return self.getText2(cmd, results)
276
277     def getText2(self, cmd, results):
278         if results != SUCCESS and self.regressionLine:
279             return [self.name, self.regressionLine]
280
281         return [self.name]
282
283
284 class RunWebKitTests(shell.Test):
285     name = "layout-test"
286     description = ["layout-tests running"]
287     descriptionDone = ["layout-tests"]
288     command = ["perl", "./Tools/Scripts/run-webkit-tests",
289                "--no-launch-safari",
290                "--no-new-test-results",
291                "--no-sample-on-timeout",
292                "--results-directory", "layout-test-results",
293                "--use-remote-links-to-tests",
294                "--builder-name", WithProperties("%(buildername)s"),
295                "--build-number", WithProperties("%(buildnumber)s"),
296                "--master-name", "webkit.org",
297                "--test-results-server", "test-results.appspot.com",
298                WithProperties("--%(configuration)s"),
299                "--exit-after-n-crashes-or-timeouts", "20",
300                "--exit-after-n-failures", "500"]
301
302     def __init__(self, buildJSCTool=True, *args, **kwargs):
303         self.buildJSCTool = buildJSCTool
304         shell.Test.__init__(self, *args, **kwargs)
305         self.addFactoryArguments(buildJSCTool=buildJSCTool)
306
307     def start(self):
308         platform = self.getProperty('platform')
309         appendCustomBuildFlags(self, platform, self.getProperty('fullPlatform'))
310         if platform == "win":
311             rootArgument = ['--root=' + os.path.join("WebKitBuild", self.getProperty('configuration'), "bin")]
312         else:
313             rootArgument = ['--root=WebKitBuild/bin']
314         if not self.buildJSCTool:
315             self.setCommand(self.command + rootArgument)
316         return shell.Test.start(self)
317
318     def _parseOldRunWebKitTestsOutput(self, logText):
319         incorrectLayoutLines = []
320         for line in logText.splitlines():
321             if line.find('had incorrect layout') >= 0 or line.find('were new') >= 0 or line.find('was new') >= 0:
322                 incorrectLayoutLines.append(line)
323             elif line.find('test case') >= 0 and (line.find(' crashed') >= 0 or line.find(' timed out') >= 0):
324                 incorrectLayoutLines.append(line)
325             elif line.startswith("WARNING:") and line.find(' leak') >= 0:
326                 incorrectLayoutLines.append(line.replace('WARNING: ', ''))
327             elif line.find('Exiting early') >= 0:
328                 incorrectLayoutLines.append(line)
329
330             # FIXME: Detect and summarize leaks of RefCounted objects
331
332         self.incorrectLayoutLines = incorrectLayoutLines
333
334     # FIXME: This will break if new-run-webkit-tests changes its default log formatter.
335     nrwt_log_message_regexp = re.compile(r'(?P<log_prefix>.*) (?P<log_level>DEBUG|INFO) (?P<message>.*)')
336
337     def _strip_python_logging_prefix(self, line):
338         match_object = self.nrwt_log_message_regexp.match(line)
339         if match_object:
340             return match_object.group('message')
341         return line
342
343     def _parseNewRunWebKitTestsOutput(self, logText):
344         incorrectLayoutLines = []
345         expressions = [
346             ('flakes', re.compile(r'Unexpected flakiness.+:?\s*\((\d+)\)')),
347             ('new passes', re.compile(r'Expected to .+, but passed:\s+\((\d+)\)')),
348             ('missing results', re.compile(r'no expected results found\s*:\s+\((\d+)\)')),
349             ('failures', re.compile(r'Regressions: Unexpected.+:?\s*\((\d+)\)')),
350         ]
351         testFailures = {}
352
353         for line in logText.splitlines():
354             if line.find('Exiting early') >= 0 or line.find('leaks found') >= 0:
355                 incorrectLayoutLines.append(self._strip_python_logging_prefix(line))
356                 continue
357             for name, expression in expressions:
358                 match = expression.search(line)
359
360                 if match:
361                     testFailures[name] = testFailures.get(name, 0) + int(match.group(1))
362                     break
363
364                 # FIXME: Parse file names and put them in results
365
366         for name in testFailures:
367             incorrectLayoutLines.append(str(testFailures[name]) + ' ' + name)
368
369         self.incorrectLayoutLines = incorrectLayoutLines
370
371     def commandComplete(self, cmd):
372         shell.Test.commandComplete(self, cmd)
373
374         logText = cmd.logs['stdio'].getText()
375         if logText.find("Collecting tests ...") >= 0:
376             self._parseNewRunWebKitTestsOutput(logText)
377         else:
378             self._parseOldRunWebKitTestsOutput(logText)
379
380     def evaluateCommand(self, cmd):
381         result = SUCCESS
382
383         if self.incorrectLayoutLines:
384             if len(self.incorrectLayoutLines) == 1:
385                 line = self.incorrectLayoutLines[0]
386                 if line.find('were new') >= 0 or line.find('was new') >= 0 or line.find(' leak') >= 0:
387                     return WARNINGS
388
389             for line in self.incorrectLayoutLines:
390                 if line.find('flakes') >= 0 or line.find('new passes') >= 0 or line.find('missing results') >= 0:
391                     result = WARNINGS
392                 else:
393                     return FAILURE
394
395         if cmd.rc != 0:
396             return FAILURE
397
398         return result
399
400     def getText(self, cmd, results):
401         return self.getText2(cmd, results)
402
403     def getText2(self, cmd, results):
404         if results != SUCCESS and self.incorrectLayoutLines:
405             return self.incorrectLayoutLines
406
407         return [self.name]
408
409
410 class RunUnitTests(TestWithFailureCount):
411     name = "run-api-tests"
412     description = ["unit tests running"]
413     descriptionDone = ["unit-tests"]
414     command = ["perl", "./Tools/Scripts/run-api-tests", WithProperties("--%(configuration)s"), "--verbose"]
415     failedTestsFormatString = "%d unit tests failed or timed out"
416
417     def start(self):
418         platform = self.getProperty('platform')
419         if platform == 'win':
420             self.setCommand(self.command + ['--no-build'])
421         if platform.startswith('chromium'):
422             self.setCommand(self.command + ['--chromium'])
423         return shell.Test.start(self)
424
425     def countFailures(self, cmd):
426         log_text = cmd.logs['stdio'].getText()
427         count = 0
428
429         split = re.split(r'\sTests that timed out:\s', log_text)
430         if len(split) > 1:
431             count += len(re.findall(r'^\s+\S+$', split[1], flags=re.MULTILINE))
432
433         split = re.split(r'\sTests that failed:\s', split[0])
434         if len(split) > 1:
435             count += len(re.findall(r'^\s+\S+$', split[1], flags=re.MULTILINE))
436
437         return count
438
439
440 class RunPythonTests(TestWithFailureCount):
441     name = "webkitpy-test"
442     description = ["python-tests running"]
443     descriptionDone = ["python-tests"]
444     command = ["python", "./Tools/Scripts/test-webkitpy"]
445     failedTestsFormatString = "%d python tests failed"
446
447     def countFailures(self, cmd):
448         logText = cmd.logs['stdio'].getText()
449         # We're looking for the line that looks like this: FAILED (failures=2, errors=1)
450         regex = re.compile(r'^FAILED \((?P<counts>[^)]+)\)')
451         for line in logText.splitlines():
452             match = regex.match(line)
453             if not match:
454                 continue
455             return sum(int(component.split('=')[1]) for component in match.group('counts').split(', '))
456         return 0
457
458
459 class RunPerlTests(TestWithFailureCount):
460     name = "webkitperl-test"
461     description = ["perl-tests running"]
462     descriptionDone = ["perl-tests"]
463     command = ["perl", "./Tools/Scripts/test-webkitperl"]
464     failedTestsFormatString = "%d perl tests failed"
465
466     def countFailures(self, cmd):
467         logText = cmd.logs['stdio'].getText()
468         # We're looking for the line that looks like this: Failed 2/19 test programs. 5/363 subtests failed.
469         regex = re.compile(r'^Failed \d+/\d+ test programs\. (?P<count>\d+)/\d+ subtests failed\.')
470         for line in logText.splitlines():
471             match = regex.match(line)
472             if not match:
473                 continue
474             return int(match.group('count'))
475         return 0
476
477
478 class RunBindingsTests(shell.Test):
479     name = "bindings-generation-tests"
480     description = ["bindings-tests running"]
481     descriptionDone = ["bindings-tests"]
482     command = ["python", "./Tools/Scripts/run-bindings-tests"]
483
484
485 class RunGtkAPITests(shell.Test):
486     name = "API tests"
487     description = ["API tests running"]
488     descriptionDone = ["API tests"]
489     command = ["python", "./Tools/Scripts/run-gtk-tests", "--verbose", WithProperties("--%(configuration)s")]
490
491     def commandComplete(self, cmd):
492         shell.Test.commandComplete(self, cmd)
493
494         logText = cmd.logs['stdio'].getText()
495         incorrectLines = []
496         for line in logText.splitlines():
497             if line.startswith('ERROR'):
498                 incorrectLines.append(line)
499
500         self.incorrectLines = incorrectLines
501
502     def evaluateCommand(self, cmd):
503         if self.incorrectLines:
504             return FAILURE
505
506         if cmd.rc != 0:
507             return FAILURE
508
509         return SUCCESS
510
511     def getText(self, cmd, results):
512         return self.getText2(cmd, results)
513
514     def getText2(self, cmd, results):
515         if results != SUCCESS and self.incorrectLines:
516             return ["%d API tests failed" % len(self.incorrectLines)]
517
518         return [self.name]
519
520 class RunQtAPITests(shell.Test):
521     name = "API tests"
522     description = ["API tests running"]
523     descriptionDone = ["API tests"]
524     command = ["python", "./Tools/Scripts/run-qtwebkit-tests",
525                "--output-file=qt-unit-tests.html", "--do-not-open-results", "--timeout=120",
526                WithProperties("WebKitBuild/%(configuration_pretty)s/Source/WebKit/qt/tests/", configuration_pretty=lambda build: build.getProperty("configuration").title())
527                 ]
528
529     def commandComplete(self, cmd):
530         shell.Test.commandComplete(self, cmd)
531
532         logText = cmd.logs['stdio'].getText()
533         foundItems = re.findall("TOTALS: (?P<passed>\d+) passed, (?P<failed>\d+) failed, (?P<skipped>\d+) skipped, (?P<crashed>\d+) crashed", logText)
534
535         self.incorrectTests = 0
536         self.crashedTests = 0
537         self.statusLine = []
538
539         if foundItems:
540             self.incorrectTests = int(foundItems[0][1])
541             self.crashedTests = int(foundItems[0][3])
542
543             if self.incorrectTests > 0 or self.crashedTests > 0:
544                 self.statusLine = [
545                     "%s passed, %s failed, %s skipped, %s crashed" % (foundItems[0][0], foundItems[0][1], foundItems[0][2], foundItems[0][3])
546                 ]
547
548     def evaluateCommand(self, cmd):
549         if self.crashedTests:
550             return FAILURE
551
552         if re.findall("Timeout, process", cmd.logs['stdio'].getText()):
553             self.statusLine = ["Failure: timeout occured during testing"]
554             return FAILURE
555
556         if self.incorrectTests:
557             return WARNINGS
558
559         if cmd.rc != 0:
560             return FAILURE
561
562         return SUCCESS
563
564     def getText(self, cmd, results):
565         return self.getText2(cmd, results)
566
567     def getText2(self, cmd, results):
568         if results != SUCCESS and self.incorrectTests:
569             return self.statusLine
570
571         return [self.name]
572
573 class RunWebKitLeakTests(RunWebKitTests):
574     warnOnWarnings = True
575     def start(self):
576         self.setCommand(self.command + ["--leaks"])
577         return RunWebKitTests.start(self)
578
579
580 class RunWebKit2Tests(RunWebKitTests):
581     def start(self):
582         self.setCommand(self.command + ["--webkit-test-runner"])
583         return RunWebKitTests.start(self)
584
585
586 class RunChromiumWebKitUnitTests(shell.Test):
587     name = "webkit-unit-tests"
588     description = ["webkit-unit-tests running"]
589     descriptionDone = ["webkit-unit-tests"]
590     command = ["perl", "./Tools/Scripts/run-chromium-webkit-unit-tests",
591                WithProperties("--%(configuration)s")]
592
593
594 class RunAndUploadPerfTests(shell.Test):
595    name = "perf-test"
596    description = ["perf-tests running"]
597    descriptionDone = ["perf-tests"]
598    command = ["python", "./Tools/Scripts/run-perf-tests",
599               "--output-json-path", "perf-test-results.json",
600               "--source-json-path", "../../perf-test-config.json",
601               "--test-results-server", "webkit-perf.appspot.com",
602               "--builder-name", WithProperties("%(buildername)s"),
603               "--build-number", WithProperties("%(buildnumber)s"),
604               "--platform", WithProperties("%(fullPlatform)s"),
605               WithProperties("--%(configuration)s")]
606
607    def start(self):
608        self.setCommand(self.command)
609        return shell.Test.start(self)
610
611
612 class RunAndUploadPerfTestsWebKit2(RunAndUploadPerfTests):
613     def start(self):
614         self.setCommand(self.command + ["--webkit-test-runner"])
615         return RunAndUploadPerfTests.start(self)
616
617
618 class ArchiveTestResults(shell.ShellCommand):
619     command = ["python", "./Tools/BuildSlaveSupport/test-result-archive",
620                WithProperties("--platform=%(platform)s"), WithProperties("--%(configuration)s"), "archive"]
621     name = "archive-test-results"
622     description = ["archiving test results"]
623     descriptionDone = ["archived test results"]
624     haltOnFailure = True
625
626
627 class UploadTestResults(transfer.FileUpload):
628     slavesrc = "layout-test-results.zip"
629     masterdest = WithProperties("public_html/results/%(buildername)s/r%(got_revision)s (%(buildnumber)s).zip")
630
631     def __init__(self):
632         transfer.FileUpload.__init__(self, self.slavesrc, self.masterdest, mode=0644)
633
634
635 class ExtractTestResults(master.MasterShellCommand):
636     zipFile = WithProperties("public_html/results/%(buildername)s/r%(got_revision)s (%(buildnumber)s).zip")
637     resultDirectory = WithProperties("public_html/results/%(buildername)s/r%(got_revision)s (%(buildnumber)s)")
638     descriptionDone = ["uploaded results"]
639
640     def __init__(self):
641         master.MasterShellCommand.__init__(self, "")
642
643     def resultDirectoryURL(self):
644         return self.build.getProperties().render(self.resultDirectory).replace("public_html/", "/") + "/"
645
646     def start(self):
647         self.command = ["ditto", "-k", "-x", "-V", self.build.getProperties().render(self.zipFile), self.build.getProperties().render(self.resultDirectory)]
648         return master.MasterShellCommand.start(self)
649
650     def addCustomURLs(self):
651         url = self.resultDirectoryURL() + "results.html"
652         self.addURL("view results", url)
653
654     def finished(self, result):
655         self.addCustomURLs()
656         return master.MasterShellCommand.finished(self, result)
657
658
659 class ExtractTestResultsAndLeaks(ExtractTestResults):
660     def addCustomURLs(self):
661         ExtractTestResults.addCustomURLs(self)
662         url = "/LeaksViewer/?url=" + urllib.quote(self.resultDirectoryURL(), safe="")
663         self.addURL("view leaks", url)
664
665
666 class Factory(factory.BuildFactory):
667     def __init__(self, platform, configuration, architectures, buildOnly):
668         factory.BuildFactory.__init__(self)
669         self.addStep(ConfigureBuild, platform=platform, configuration=configuration, architecture=" ".join(architectures), buildOnly=buildOnly)
670         self.addStep(CheckOutSource())
671         # There are multiple Qt slaves running on same machines, so buildslaves shouldn't kill the processes of other slaves.
672         if not platform.startswith("qt"):
673             self.addStep(KillOldProcesses)
674         if platform == "win":
675             self.addStep(InstallWin32Dependencies)
676         if platform.startswith("chromium"):
677             self.addStep(InstallChromiumDependencies)
678         if platform == "gtk":
679             self.addStep(InstallGtkDependencies)
680         if platform == "efl":
681             self.addStep(InstallEflDependencies)
682
683
684 class BuildFactory(Factory):
685     def __init__(self, platform, configuration, architectures, triggers=None):
686         Factory.__init__(self, platform, configuration, architectures, True)
687         self.addStep(CompileWebKit)
688         if triggers:
689             self.addStep(ArchiveBuiltProduct)
690             self.addStep(UploadBuiltProduct)
691             self.addStep(trigger.Trigger, schedulerNames=triggers)
692
693 def unitTestsSupported(configuration, platform):
694     if platform.startswith('mac') and configuration == "release":
695         return False; # https://bugs.webkit.org/show_bug.cgi?id=82652
696     return (platform == 'win' or platform.startswith('mac')
697             or (platform.startswith('chromium') and platform != 'chromium-android'))
698
699 def pickLatestBuild(builder, requests):
700     return max(requests, key=operator.attrgetter("submittedAt"))
701
702 class TestFactory(Factory):
703     TestClass = RunWebKitTests
704     ExtractTestResultsClass = ExtractTestResults
705     def __init__(self, platform, configuration, architectures):
706         Factory.__init__(self, platform, configuration, architectures, False)
707         if platform.startswith("chromium"):
708             self.addStep(CleanupChromiumCrashLogs)
709         self.addStep(DownloadBuiltProduct)
710         self.addStep(ExtractBuiltProduct)
711         if not platform.startswith("chromium"):
712             self.addStep(RunJavaScriptCoreTests, buildJSCTool=False)
713         if platform.startswith("chromium"):
714             self.addStep(RunChromiumWebKitUnitTests)
715         self.addStep(self.TestClass, buildJSCTool=(platform != 'win'))
716
717         if unitTestsSupported(configuration, platform): 
718             self.addStep(RunUnitTests)
719         self.addStep(RunPythonTests)
720         # Chromium Win runs in non-Cygwin environment, which is not yet fit
721         # for running tests. This can be removed once bug 48166 is fixed.
722         if platform != "chromium-win":
723             self.addStep(RunPerlTests)
724             self.addStep(RunBindingsTests)
725         self.addStep(ArchiveTestResults)
726         self.addStep(UploadTestResults)
727         self.addStep(self.ExtractTestResultsClass)
728         if platform == "gtk":
729             self.addStep(RunGtkAPITests)
730         if platform.startswith("qt"):
731             self.addStep(RunQtAPITests)
732
733 class BuildAndTestFactory(Factory):
734     TestClass = RunWebKitTests
735     ExtractTestResultsClass = ExtractTestResults
736     def __init__(self, platform, configuration, architectures, triggers=None, **kwargs):
737         Factory.__init__(self, platform, configuration, architectures, False, **kwargs)
738         if platform.startswith("chromium"):
739             self.addStep(CleanupChromiumCrashLogs)
740         self.addStep(CompileWebKit)
741         if not platform.startswith("chromium"):
742             self.addStep(RunJavaScriptCoreTests)
743         if platform.startswith("chromium"):
744             self.addStep(RunChromiumWebKitUnitTests)
745         self.addStep(self.TestClass)
746         if unitTestsSupported(configuration, platform): 
747             self.addStep(RunUnitTests)
748         self.addStep(RunPythonTests)
749         # Chromium Win runs in non-Cygwin environment, which is not yet fit
750         # for running tests. This can be removed once bug 48166 is fixed.
751         if platform != "chromium-win":
752             self.addStep(RunPerlTests)
753             self.addStep(RunBindingsTests)
754         self.addStep(ArchiveTestResults)
755         self.addStep(UploadTestResults)
756         self.addStep(self.ExtractTestResultsClass)
757         if platform == "gtk":
758             self.addStep(RunGtkAPITests)
759         if platform.startswith("qt"):
760             self.addStep(RunQtAPITests)
761         if triggers:
762             self.addStep(ArchiveBuiltProduct)
763             self.addStep(UploadBuiltProduct)
764             self.addStep(trigger.Trigger, schedulerNames=triggers)
765
766 class BuildAndTestLeaksFactory(BuildAndTestFactory):
767     TestClass = RunWebKitLeakTests
768     ExtractTestResultsClass = ExtractTestResultsAndLeaks
769
770
771 class TestWebKit2Factory(TestFactory):
772     TestClass = RunWebKit2Tests
773
774 class BuildAndPerfTestFactory(Factory):
775     def __init__(self, platform, configuration, architectures, **kwargs):
776         Factory.__init__(self, platform, configuration, architectures, False, **kwargs)
777         if platform.startswith("chromium"):
778             self.addStep(CleanupChromiumCrashLogs)
779         self.addStep(CompileWebKit)
780         self.addStep(RunAndUploadPerfTests)
781
782 class BuildAndPerfTestWebKit2Factory(Factory):
783     def __init__(self, platform, configuration, architectures, **kwargs):
784         Factory.__init__(self, platform, configuration, architectures, False, **kwargs)
785         if platform.startswith("chromium"):
786             self.addStep(CleanupChromiumCrashLogs)
787         self.addStep(CompileWebKit)
788         self.addStep(RunAndUploadPerfTestsWebKit2)
789
790 class DownloadAndPerfTestFactory(Factory):
791     def __init__(self, platform, configuration, architectures, **kwargs):
792         Factory.__init__(self, platform, configuration, architectures, False, **kwargs)
793         self.addStep(DownloadBuiltProduct)
794         self.addStep(ExtractBuiltProduct)
795         self.addStep(RunAndUploadPerfTests)
796
797 class DownloadAndPerfTestWebKit2Factory(Factory):
798     def __init__(self, platform, configuration, architectures, **kwargs):
799         Factory.__init__(self, platform, configuration, architectures, False, **kwargs)
800         self.addStep(DownloadBuiltProduct)
801         self.addStep(ExtractBuiltProduct)
802         self.addStep(RunAndUploadPerfTestsWebKit2)
803
804 class PlatformSpecificScheduler(AnyBranchScheduler):
805     def __init__(self, platform, branch, **kwargs):
806         self.platform = platform
807         filter = ChangeFilter(branch=[branch, None], filter_fn=self.filter)
808         AnyBranchScheduler.__init__(self, name=platform, change_filter=filter, **kwargs)
809
810     def filter(self, change):
811         return wkbuild.should_build(self.platform, change.files)
812
813 trunk_filter = ChangeFilter(branch=["trunk", None])
814
815 def loadBuilderConfig(c):
816     # FIXME: These file handles are leaked.
817     passwords = json.load(open('passwords.json'))
818     config = json.load(open('config.json'))
819
820     c['slaves'] = [BuildSlave(slave['name'], passwords[slave['name']], max_builds=1) for slave in config['slaves']]
821
822     c['schedulers'] = []
823     for scheduler in config['schedulers']:
824         if "change_filter" in scheduler:
825             scheduler["change_filter"] = globals()[scheduler["change_filter"]]
826         kls = globals()[scheduler.pop('type')]
827         # Python 2.6 can't handle unicode keys as keyword arguments:
828         # http://bugs.python.org/issue2646.  Modern versions of json return
829         # unicode strings from json.load, so we map all keys to str objects.
830         scheduler = dict(map(lambda key_value_pair: (str(key_value_pair[0]), key_value_pair[1]), scheduler.items()))
831
832         c['schedulers'].append(kls(**scheduler))
833
834     forceScheduler = ForceScheduler(
835         name="force",
836         builderNames=[builder['name'] for builder in config['builders']],
837         reason=StringParameter(name="reason", default="", size=40),
838
839         # Validate SVN revision: number or emtpy string
840         revision=StringParameter(name="revision", default="", regex=re.compile(r'^(\d*)$')),
841
842         # Disable default enabled input fields: branch, repository, project, additional properties
843         branch=FixedParameter(name="branch"),
844         repository=FixedParameter(name="repository"),
845         project=FixedParameter(name="project"),
846         properties=[]
847     )
848     c['schedulers'].append(forceScheduler)
849
850     c['builders'] = []
851     for builder in config['builders']:
852         for slaveName in builder['slavenames']:
853             for slave in config['slaves']:
854                 if slave['name'] != slaveName or slave['platform'] == '*':
855                     continue
856
857                 if slave['platform'] != builder['platform']:
858                     raise Exception, "Builder %r is for platform %r but has slave %r for platform %r!" % (builder['name'], builder['platform'], slave['name'], slave['platform'])
859
860                 break
861
862         platform = builder['platform']
863
864         builderType = builder.pop('type')
865         factory = globals()["%sFactory" % builderType]
866         factoryArgs = []
867         for key in "platform", "configuration", "architectures", "triggers":
868             value = builder.pop(key, None)
869             if value:
870                 factoryArgs.append(value)
871
872         builder["factory"] = factory(*factoryArgs)
873
874         if platform.startswith('chromium'):
875             builder["category"] = 'Chromium'
876         elif platform.startswith('mac'):
877             builder["category"] = 'AppleMac'
878         elif platform == 'win':
879             builder["category"] = 'AppleWin'
880         elif platform.startswith('gtk'):
881             builder["category"] = 'GTK'
882         elif platform.startswith('qt'):
883             builder["category"] = 'Qt'
884         elif platform.startswith('efl'):
885             builder["category"] = "EFL"
886         else:
887             builder["category"] = 'misc'
888
889         if (builder['category'] == 'AppleMac' or builder['category'] == 'AppleWin') and builderType != 'Build':
890             builder['nextBuild'] = pickLatestBuild
891
892         c['builders'].append(builder)
893
894 loadBuilderConfig(c)