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