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