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