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