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