Teach the Apple port how to build the test tools in build-webkit
[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.startswith('mac'):
311             self.setCommand(self.command + ['--no-build'])
312         if platform == "win":
313             rootArgument = ['--root=' + os.path.join("WebKitBuild", self.getProperty('configuration'), "bin")]
314             self.setCommand(self.command + ['--no-build'])
315         else:
316             rootArgument = ['--root=WebKitBuild/bin']
317         if not self.buildJSCTool:
318             self.setCommand(self.command + rootArgument)
319         return shell.Test.start(self)
320
321     def _parseOldRunWebKitTestsOutput(self, logText):
322         incorrectLayoutLines = []
323         for line in logText.splitlines():
324             if line.find('had incorrect layout') >= 0 or line.find('were new') >= 0 or line.find('was new') >= 0:
325                 incorrectLayoutLines.append(line)
326             elif line.find('test case') >= 0 and (line.find(' crashed') >= 0 or line.find(' timed out') >= 0):
327                 incorrectLayoutLines.append(line)
328             elif line.startswith("WARNING:") and line.find(' leak') >= 0:
329                 incorrectLayoutLines.append(line.replace('WARNING: ', ''))
330             elif line.find('Exiting early') >= 0:
331                 incorrectLayoutLines.append(line)
332
333             # FIXME: Detect and summarize leaks of RefCounted objects
334
335         self.incorrectLayoutLines = incorrectLayoutLines
336
337     # FIXME: This will break if new-run-webkit-tests changes its default log formatter.
338     nrwt_log_message_regexp = re.compile(r'(?P<log_prefix>.*) (?P<log_level>DEBUG|INFO) (?P<message>.*)')
339
340     def _strip_python_logging_prefix(self, line):
341         match_object = self.nrwt_log_message_regexp.match(line)
342         if match_object:
343             return match_object.group('message')
344         return line
345
346     def _parseNewRunWebKitTestsOutput(self, logText):
347         incorrectLayoutLines = []
348         expressions = [
349             ('flakes', re.compile(r'Unexpected flakiness.+:?\s*\((\d+)\)')),
350             ('new passes', re.compile(r'Expected to .+, but passed:\s+\((\d+)\)')),
351             ('missing results', re.compile(r'no expected results found\s*:\s+\((\d+)\)')),
352             ('failures', re.compile(r'Regressions: Unexpected.+:?\s*\((\d+)\)')),
353         ]
354         testFailures = {}
355
356         for line in logText.splitlines():
357             if line.find('Exiting early') >= 0 or line.find('leaks found') >= 0:
358                 incorrectLayoutLines.append(self._strip_python_logging_prefix(line))
359                 continue
360             for name, expression in expressions:
361                 match = expression.search(line)
362
363                 if match:
364                     testFailures[name] = testFailures.get(name, 0) + int(match.group(1))
365                     break
366
367                 # FIXME: Parse file names and put them in results
368
369         for name in testFailures:
370             incorrectLayoutLines.append(str(testFailures[name]) + ' ' + name)
371
372         self.incorrectLayoutLines = incorrectLayoutLines
373
374     def commandComplete(self, cmd):
375         shell.Test.commandComplete(self, cmd)
376
377         logText = cmd.logs['stdio'].getText()
378         if logText.find("Collecting tests ...") >= 0:
379             self._parseNewRunWebKitTestsOutput(logText)
380         else:
381             self._parseOldRunWebKitTestsOutput(logText)
382
383     def evaluateCommand(self, cmd):
384         result = SUCCESS
385
386         if self.incorrectLayoutLines:
387             if len(self.incorrectLayoutLines) == 1:
388                 line = self.incorrectLayoutLines[0]
389                 if line.find('were new') >= 0 or line.find('was new') >= 0 or line.find(' leak') >= 0:
390                     return WARNINGS
391
392             for line in self.incorrectLayoutLines:
393                 if line.find('flakes') >= 0 or line.find('new passes') >= 0 or line.find('missing results') >= 0:
394                     result = WARNINGS
395                 else:
396                     return FAILURE
397
398         if cmd.rc != 0:
399             return FAILURE
400
401         return result
402
403     def getText(self, cmd, results):
404         return self.getText2(cmd, results)
405
406     def getText2(self, cmd, results):
407         if results != SUCCESS and self.incorrectLayoutLines:
408             return self.incorrectLayoutLines
409
410         return [self.name]
411
412
413 class RunUnitTests(TestWithFailureCount):
414     name = "run-api-tests"
415     description = ["unit tests running"]
416     descriptionDone = ["unit-tests"]
417     command = ["perl", "./Tools/Scripts/run-api-tests", WithProperties("--%(configuration)s"), "--verbose"]
418     failedTestsFormatString = "%d unit tests failed or timed out"
419
420     def start(self):
421         platform = self.getProperty('platform')
422         if platform == 'win':
423             self.setCommand(self.command + ['--no-build'])
424         if platform.startswith('mac'):
425             self.setCommand(self.command + ['--no-build'])
426         if platform.startswith('chromium'):
427             self.setCommand(self.command + ['--chromium'])
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 countFailures(self, cmd):
453         logText = cmd.logs['stdio'].getText()
454         # We're looking for the line that looks like this: FAILED (failures=2, errors=1)
455         regex = re.compile(r'^FAILED \((?P<counts>[^)]+)\)')
456         for line in logText.splitlines():
457             match = regex.match(line)
458             if not match:
459                 continue
460             return sum(int(component.split('=')[1]) for component in match.group('counts').split(', '))
461         return 0
462
463
464 class RunPerlTests(TestWithFailureCount):
465     name = "webkitperl-test"
466     description = ["perl-tests running"]
467     descriptionDone = ["perl-tests"]
468     command = ["perl", "./Tools/Scripts/test-webkitperl"]
469     failedTestsFormatString = "%d perl tests failed"
470
471     def countFailures(self, cmd):
472         logText = cmd.logs['stdio'].getText()
473         # We're looking for the line that looks like this: Failed 2/19 test programs. 5/363 subtests failed.
474         regex = re.compile(r'^Failed \d+/\d+ test programs\. (?P<count>\d+)/\d+ subtests failed\.')
475         for line in logText.splitlines():
476             match = regex.match(line)
477             if not match:
478                 continue
479             return int(match.group('count'))
480         return 0
481
482
483 class RunBindingsTests(shell.Test):
484     name = "bindings-generation-tests"
485     description = ["bindings-tests running"]
486     descriptionDone = ["bindings-tests"]
487     command = ["python", "./Tools/Scripts/run-bindings-tests"]
488
489
490 class RunGtkAPITests(shell.Test):
491     name = "API tests"
492     description = ["API tests running"]
493     descriptionDone = ["API tests"]
494     command = ["python", "./Tools/Scripts/run-gtk-tests", "--verbose", WithProperties("--%(configuration)s")]
495
496     def commandComplete(self, cmd):
497         shell.Test.commandComplete(self, cmd)
498
499         logText = cmd.logs['stdio'].getText()
500         incorrectLines = []
501         for line in logText.splitlines():
502             if line.startswith('ERROR'):
503                 incorrectLines.append(line)
504
505         self.incorrectLines = incorrectLines
506
507     def evaluateCommand(self, cmd):
508         if self.incorrectLines:
509             return FAILURE
510
511         if cmd.rc != 0:
512             return FAILURE
513
514         return SUCCESS
515
516     def getText(self, cmd, results):
517         return self.getText2(cmd, results)
518
519     def getText2(self, cmd, results):
520         if results != SUCCESS and self.incorrectLines:
521             return ["%d API tests failed" % len(self.incorrectLines)]
522
523         return [self.name]
524
525 class RunQtAPITests(shell.Test):
526     name = "API tests"
527     description = ["API tests running"]
528     descriptionDone = ["API tests"]
529     command = ["python", "./Tools/Scripts/run-qtwebkit-tests",
530                "--output-file=qt-unit-tests.html", "--do-not-open-results", "--timeout=120",
531                WithProperties("WebKitBuild/%(configuration_pretty)s/Source/WebKit/qt/tests/", configuration_pretty=lambda build: build.getProperty("configuration").title())
532                 ]
533
534     def commandComplete(self, cmd):
535         shell.Test.commandComplete(self, cmd)
536
537         logText = cmd.logs['stdio'].getText()
538         foundItems = re.findall("TOTALS: (?P<passed>\d+) passed, (?P<failed>\d+) failed, (?P<skipped>\d+) skipped, (?P<crashed>\d+) crashed", logText)
539
540         self.incorrectTests = 0
541         self.crashedTests = 0
542         self.statusLine = []
543
544         if foundItems:
545             self.incorrectTests = int(foundItems[0][1])
546             self.crashedTests = int(foundItems[0][3])
547
548             if self.incorrectTests > 0 or self.crashedTests > 0:
549                 self.statusLine = [
550                     "%s passed, %s failed, %s skipped, %s crashed" % (foundItems[0][0], foundItems[0][1], foundItems[0][2], foundItems[0][3])
551                 ]
552
553     def evaluateCommand(self, cmd):
554         if self.crashedTests:
555             return FAILURE
556
557         if re.findall("Timeout, process", cmd.logs['stdio'].getText()):
558             self.statusLine = ["Failure: timeout occured during testing"]
559             return FAILURE
560
561         if self.incorrectTests:
562             return WARNINGS
563
564         if cmd.rc != 0:
565             return FAILURE
566
567         return SUCCESS
568
569     def getText(self, cmd, results):
570         return self.getText2(cmd, results)
571
572     def getText2(self, cmd, results):
573         if results != SUCCESS and self.incorrectTests:
574             return self.statusLine
575
576         return [self.name]
577
578 class RunWebKitLeakTests(RunWebKitTests):
579     warnOnWarnings = True
580     def start(self):
581         self.setCommand(self.command + ["--leaks"])
582         return RunWebKitTests.start(self)
583
584
585 class RunWebKit2Tests(RunWebKitTests):
586     def start(self):
587         self.setCommand(self.command + ["--webkit-test-runner"])
588         return RunWebKitTests.start(self)
589
590
591 class RunChromiumWebKitUnitTests(shell.Test):
592     name = "webkit-unit-tests"
593     description = ["webkit-unit-tests running"]
594     descriptionDone = ["webkit-unit-tests"]
595     command = ["perl", "./Tools/Scripts/run-chromium-webkit-unit-tests",
596                WithProperties("--%(configuration)s")]
597
598
599 class RunAndUploadPerfTests(shell.Test):
600    name = "perf-test"
601    description = ["perf-tests running"]
602    descriptionDone = ["perf-tests"]
603    command = ["python", "./Tools/Scripts/run-perf-tests",
604               "--output-json-path", "perf-test-results.json",
605               "--source-json-path", "../../perf-test-config.json",
606               "--test-results-server", "webkit-perf.appspot.com",
607               "--builder-name", WithProperties("%(buildername)s"),
608               "--build-number", WithProperties("%(buildnumber)s"),
609               "--platform", WithProperties("%(fullPlatform)s"),
610               WithProperties("--%(configuration)s")]
611
612    def start(self):
613        self.setCommand(self.command)
614        return shell.Test.start(self)
615
616
617 class RunAndUploadPerfTestsWebKit2(RunAndUploadPerfTests):
618     def start(self):
619         self.setCommand(self.command + ["--webkit-test-runner"])
620         return RunAndUploadPerfTests.start(self)
621
622
623 class ArchiveTestResults(shell.ShellCommand):
624     command = ["python", "./Tools/BuildSlaveSupport/test-result-archive",
625                WithProperties("--platform=%(platform)s"), WithProperties("--%(configuration)s"), "archive"]
626     name = "archive-test-results"
627     description = ["archiving test results"]
628     descriptionDone = ["archived test results"]
629     haltOnFailure = True
630
631
632 class UploadTestResults(transfer.FileUpload):
633     slavesrc = "layout-test-results.zip"
634     masterdest = WithProperties("public_html/results/%(buildername)s/r%(got_revision)s (%(buildnumber)s).zip")
635
636     def __init__(self):
637         transfer.FileUpload.__init__(self, self.slavesrc, self.masterdest, mode=0644)
638
639
640 class ExtractTestResults(master.MasterShellCommand):
641     zipFile = WithProperties("public_html/results/%(buildername)s/r%(got_revision)s (%(buildnumber)s).zip")
642     resultDirectory = WithProperties("public_html/results/%(buildername)s/r%(got_revision)s (%(buildnumber)s)")
643     descriptionDone = ["uploaded results"]
644
645     def __init__(self):
646         master.MasterShellCommand.__init__(self, "")
647
648     def resultDirectoryURL(self):
649         return self.build.getProperties().render(self.resultDirectory).replace("public_html/", "/") + "/"
650
651     def start(self):
652         self.command = ["ditto", "-k", "-x", "-V", self.build.getProperties().render(self.zipFile), self.build.getProperties().render(self.resultDirectory)]
653         return master.MasterShellCommand.start(self)
654
655     def addCustomURLs(self):
656         url = self.resultDirectoryURL() + "results.html"
657         self.addURL("view results", url)
658
659     def finished(self, result):
660         self.addCustomURLs()
661         return master.MasterShellCommand.finished(self, result)
662
663
664 class ExtractTestResultsAndLeaks(ExtractTestResults):
665     def addCustomURLs(self):
666         ExtractTestResults.addCustomURLs(self)
667         url = "/LeaksViewer/?url=" + urllib.quote(self.resultDirectoryURL(), safe="")
668         self.addURL("view leaks", url)
669
670
671 class Factory(factory.BuildFactory):
672     def __init__(self, platform, configuration, architectures, buildOnly):
673         factory.BuildFactory.__init__(self)
674         self.addStep(ConfigureBuild, platform=platform, configuration=configuration, architecture=" ".join(architectures), buildOnly=buildOnly)
675         self.addStep(CheckOutSource())
676         # There are multiple Qt slaves running on same machines, so buildslaves shouldn't kill the processes of other slaves.
677         if not platform.startswith("qt"):
678             self.addStep(KillOldProcesses)
679         if platform == "win":
680             self.addStep(InstallWin32Dependencies)
681         if platform.startswith("chromium"):
682             self.addStep(InstallChromiumDependencies)
683         if platform == "gtk":
684             self.addStep(InstallGtkDependencies)
685         if platform == "efl":
686             self.addStep(InstallEflDependencies)
687
688
689 class BuildFactory(Factory):
690     def __init__(self, platform, configuration, architectures, triggers=None):
691         Factory.__init__(self, platform, configuration, architectures, True)
692         self.addStep(CompileWebKit)
693         if triggers:
694             self.addStep(ArchiveBuiltProduct)
695             self.addStep(UploadBuiltProduct)
696             self.addStep(trigger.Trigger, schedulerNames=triggers)
697
698 def unitTestsSupported(configuration, platform):
699     if platform.startswith('mac') and configuration == "release":
700         return False; # https://bugs.webkit.org/show_bug.cgi?id=82652
701     return (platform == 'win' or platform.startswith('mac')
702             or (platform.startswith('chromium') and platform != 'chromium-android'))
703
704 def pickLatestBuild(builder, requests):
705     return max(requests, key=operator.attrgetter("submittedAt"))
706
707 class TestFactory(Factory):
708     TestClass = RunWebKitTests
709     ExtractTestResultsClass = ExtractTestResults
710     def __init__(self, platform, configuration, architectures):
711         Factory.__init__(self, platform, configuration, architectures, False)
712         if platform.startswith("chromium"):
713             self.addStep(CleanupChromiumCrashLogs)
714         self.addStep(DownloadBuiltProduct)
715         self.addStep(ExtractBuiltProduct)
716         if not platform.startswith("chromium"):
717             self.addStep(RunJavaScriptCoreTests, buildJSCTool=False)
718         if platform.startswith("chromium"):
719             self.addStep(RunChromiumWebKitUnitTests)
720         self.addStep(self.TestClass, buildJSCTool=(platform != 'win'))
721
722         if unitTestsSupported(configuration, platform): 
723             self.addStep(RunUnitTests)
724         self.addStep(RunPythonTests)
725         # Chromium Win runs in non-Cygwin environment, which is not yet fit
726         # for running tests. This can be removed once bug 48166 is fixed.
727         if platform != "chromium-win":
728             self.addStep(RunPerlTests)
729             self.addStep(RunBindingsTests)
730         self.addStep(ArchiveTestResults)
731         self.addStep(UploadTestResults)
732         self.addStep(self.ExtractTestResultsClass)
733         if platform == "gtk":
734             self.addStep(RunGtkAPITests)
735         if platform.startswith("qt"):
736             self.addStep(RunQtAPITests)
737
738 class BuildAndTestFactory(Factory):
739     TestClass = RunWebKitTests
740     ExtractTestResultsClass = ExtractTestResults
741     def __init__(self, platform, configuration, architectures, triggers=None, **kwargs):
742         Factory.__init__(self, platform, configuration, architectures, False, **kwargs)
743         if platform.startswith("chromium"):
744             self.addStep(CleanupChromiumCrashLogs)
745         self.addStep(CompileWebKit)
746         if not platform.startswith("chromium"):
747             self.addStep(RunJavaScriptCoreTests)
748         if platform.startswith("chromium"):
749             self.addStep(RunChromiumWebKitUnitTests)
750         self.addStep(self.TestClass)
751         if unitTestsSupported(configuration, platform): 
752             self.addStep(RunUnitTests)
753         self.addStep(RunPythonTests)
754         # Chromium Win runs in non-Cygwin environment, which is not yet fit
755         # for running tests. This can be removed once bug 48166 is fixed.
756         if platform != "chromium-win":
757             self.addStep(RunPerlTests)
758             self.addStep(RunBindingsTests)
759         self.addStep(ArchiveTestResults)
760         self.addStep(UploadTestResults)
761         self.addStep(self.ExtractTestResultsClass)
762         if platform == "gtk":
763             self.addStep(RunGtkAPITests)
764         if platform.startswith("qt"):
765             self.addStep(RunQtAPITests)
766         if triggers:
767             self.addStep(ArchiveBuiltProduct)
768             self.addStep(UploadBuiltProduct)
769             self.addStep(trigger.Trigger, schedulerNames=triggers)
770
771 class BuildAndTestLeaksFactory(BuildAndTestFactory):
772     TestClass = RunWebKitLeakTests
773     ExtractTestResultsClass = ExtractTestResultsAndLeaks
774
775
776 class TestWebKit2Factory(TestFactory):
777     TestClass = RunWebKit2Tests
778
779 class BuildAndPerfTestFactory(Factory):
780     def __init__(self, platform, configuration, architectures, **kwargs):
781         Factory.__init__(self, platform, configuration, architectures, False, **kwargs)
782         if platform.startswith("chromium"):
783             self.addStep(CleanupChromiumCrashLogs)
784         self.addStep(CompileWebKit)
785         self.addStep(RunAndUploadPerfTests)
786
787 class BuildAndPerfTestWebKit2Factory(Factory):
788     def __init__(self, platform, configuration, architectures, **kwargs):
789         Factory.__init__(self, platform, configuration, architectures, False, **kwargs)
790         if platform.startswith("chromium"):
791             self.addStep(CleanupChromiumCrashLogs)
792         self.addStep(CompileWebKit)
793         self.addStep(RunAndUploadPerfTestsWebKit2)
794
795 class DownloadAndPerfTestFactory(Factory):
796     def __init__(self, platform, configuration, architectures, **kwargs):
797         Factory.__init__(self, platform, configuration, architectures, False, **kwargs)
798         self.addStep(DownloadBuiltProduct)
799         self.addStep(ExtractBuiltProduct)
800         self.addStep(RunAndUploadPerfTests)
801
802 class DownloadAndPerfTestWebKit2Factory(Factory):
803     def __init__(self, platform, configuration, architectures, **kwargs):
804         Factory.__init__(self, platform, configuration, architectures, False, **kwargs)
805         self.addStep(DownloadBuiltProduct)
806         self.addStep(ExtractBuiltProduct)
807         self.addStep(RunAndUploadPerfTestsWebKit2)
808
809 class PlatformSpecificScheduler(AnyBranchScheduler):
810     def __init__(self, platform, branch, **kwargs):
811         self.platform = platform
812         filter = ChangeFilter(branch=[branch, None], filter_fn=self.filter)
813         AnyBranchScheduler.__init__(self, name=platform, change_filter=filter, **kwargs)
814
815     def filter(self, change):
816         return wkbuild.should_build(self.platform, change.files)
817
818 trunk_filter = ChangeFilter(branch=["trunk", None])
819
820 def loadBuilderConfig(c):
821     # FIXME: These file handles are leaked.
822     passwords = json.load(open('passwords.json'))
823     config = json.load(open('config.json'))
824
825     c['slaves'] = [BuildSlave(slave['name'], passwords[slave['name']], max_builds=1) for slave in config['slaves']]
826
827     c['schedulers'] = []
828     for scheduler in config['schedulers']:
829         if "change_filter" in scheduler:
830             scheduler["change_filter"] = globals()[scheduler["change_filter"]]
831         kls = globals()[scheduler.pop('type')]
832         # Python 2.6 can't handle unicode keys as keyword arguments:
833         # http://bugs.python.org/issue2646.  Modern versions of json return
834         # unicode strings from json.load, so we map all keys to str objects.
835         scheduler = dict(map(lambda key_value_pair: (str(key_value_pair[0]), key_value_pair[1]), scheduler.items()))
836
837         c['schedulers'].append(kls(**scheduler))
838
839     forceScheduler = ForceScheduler(
840         name="force",
841         builderNames=[builder['name'] for builder in config['builders']],
842         reason=StringParameter(name="reason", default="", size=40),
843
844         # Validate SVN revision: number or emtpy string
845         revision=StringParameter(name="revision", default="", regex=re.compile(r'^(\d*)$')),
846
847         # Disable default enabled input fields: branch, repository, project, additional properties
848         branch=FixedParameter(name="branch"),
849         repository=FixedParameter(name="repository"),
850         project=FixedParameter(name="project"),
851         properties=[]
852     )
853     c['schedulers'].append(forceScheduler)
854
855     c['builders'] = []
856     for builder in config['builders']:
857         for slaveName in builder['slavenames']:
858             for slave in config['slaves']:
859                 if slave['name'] != slaveName or slave['platform'] == '*':
860                     continue
861
862                 if slave['platform'] != builder['platform']:
863                     raise Exception, "Builder %r is for platform %r but has slave %r for platform %r!" % (builder['name'], builder['platform'], slave['name'], slave['platform'])
864
865                 break
866
867         platform = builder['platform']
868
869         builderType = builder.pop('type')
870         factory = globals()["%sFactory" % builderType]
871         factoryArgs = []
872         for key in "platform", "configuration", "architectures", "triggers":
873             value = builder.pop(key, None)
874             if value:
875                 factoryArgs.append(value)
876
877         builder["factory"] = factory(*factoryArgs)
878
879         if platform.startswith('chromium'):
880             builder["category"] = 'Chromium'
881         elif platform.startswith('mac'):
882             builder["category"] = 'AppleMac'
883         elif platform == 'win':
884             builder["category"] = 'AppleWin'
885         elif platform.startswith('gtk'):
886             builder["category"] = 'GTK'
887         elif platform.startswith('qt'):
888             builder["category"] = 'Qt'
889         elif platform.startswith('efl'):
890             builder["category"] = "EFL"
891         else:
892             builder["category"] = 'misc'
893
894         if (builder['category'] == 'AppleMac' or builder['category'] == 'AppleWin') and builderType != 'Build':
895             builder['nextBuild'] = pickLatestBuild
896
897         c['builders'].append(builder)
898
899 loadBuilderConfig(c)