fix Typo.
[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 = ["perl", "./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                WithProperties("--%(configuration)s")]
361
362     def __init__(self, buildJSCTool=True, *args, **kwargs):
363         self.buildJSCTool = buildJSCTool
364         shell.Test.__init__(self, *args, **kwargs)
365         self.addFactoryArguments(buildJSCTool=buildJSCTool)
366
367     def start(self):
368         platform = self.getProperty('platform')
369         shouldAbortEarly = self.getProperty('shouldAbortEarly')
370         appendCustomBuildFlags(self, platform, self.getProperty('fullPlatform'))
371         additionalArguments = self.getProperty('additionalArguments')
372         if platform.startswith('mac'):
373             self.setCommand(self.command + ['--no-build'])
374         if shouldAbortEarly:
375             self.setCommand(self.command + ["--exit-after-n-crashes-or-timeouts", "50", "--exit-after-n-failures", "500"])
376         if platform == "win":
377             rootArgument = ['--root=' + os.path.join("WebKitBuild", self.getProperty('configuration'), "bin32")]
378             self.setCommand(self.command + ['--no-build'])
379         else:
380             rootArgument = ['--root=WebKitBuild/bin']
381         if not self.buildJSCTool:
382             self.setCommand(self.command + rootArgument)
383         if additionalArguments:
384             self.setCommand(self.command + additionalArguments)
385         return shell.Test.start(self)
386
387     def _parseOldRunWebKitTestsOutput(self, logText):
388         incorrectLayoutLines = []
389         for line in logText.splitlines():
390             if line.find('had incorrect layout') >= 0 or line.find('were new') >= 0 or line.find('was new') >= 0:
391                 incorrectLayoutLines.append(line)
392             elif line.find('test case') >= 0 and (line.find(' crashed') >= 0 or line.find(' timed out') >= 0):
393                 incorrectLayoutLines.append(line)
394             elif line.startswith("WARNING:") and line.find(' leak') >= 0:
395                 incorrectLayoutLines.append(line.replace('WARNING: ', ''))
396             elif line.find('Exiting early') >= 0:
397                 incorrectLayoutLines.append(line)
398
399             # FIXME: Detect and summarize leaks of RefCounted objects
400
401         self.incorrectLayoutLines = incorrectLayoutLines
402
403     # FIXME: This will break if new-run-webkit-tests changes its default log formatter.
404     nrwt_log_message_regexp = re.compile(r'\d{2}:\d{2}:\d{2}(\.\d+)?\s+\d+\s+(?P<message>.*)')
405
406     def _strip_python_logging_prefix(self, line):
407         match_object = self.nrwt_log_message_regexp.match(line)
408         if match_object:
409             return match_object.group('message')
410         return line
411
412     def _parseNewRunWebKitTestsOutput(self, logText):
413         incorrectLayoutLines = []
414         expressions = [
415             ('flakes', re.compile(r'Unexpected flakiness.+\((\d+)\)')),
416             ('new passes', re.compile(r'Expected to .+, but passed:\s+\((\d+)\)')),
417             ('missing results', re.compile(r'Regressions: Unexpected missing results\s+\((\d+)\)')),
418             ('failures', re.compile(r'Regressions: Unexpected.+\((\d+)\)')),
419         ]
420         testFailures = {}
421
422         for line in logText.splitlines():
423             if line.find('Exiting early') >= 0 or line.find('leaks found') >= 0:
424                 incorrectLayoutLines.append(self._strip_python_logging_prefix(line))
425                 continue
426             for name, expression in expressions:
427                 match = expression.search(line)
428
429                 if match:
430                     testFailures[name] = testFailures.get(name, 0) + int(match.group(1))
431                     break
432
433                 # FIXME: Parse file names and put them in results
434
435         for name in testFailures:
436             incorrectLayoutLines.append(str(testFailures[name]) + ' ' + name)
437
438         self.incorrectLayoutLines = incorrectLayoutLines
439
440     def commandComplete(self, cmd):
441         shell.Test.commandComplete(self, cmd)
442
443         logText = cmd.logs['stdio'].getText()
444         if logText.find("Collecting tests ...") >= 0:
445             self._parseNewRunWebKitTestsOutput(logText)
446         else:
447             self._parseOldRunWebKitTestsOutput(logText)
448
449     def evaluateCommand(self, cmd):
450         result = SUCCESS
451
452         if self.incorrectLayoutLines:
453             if len(self.incorrectLayoutLines) == 1:
454                 line = self.incorrectLayoutLines[0]
455                 if line.find('were new') >= 0 or line.find('was new') >= 0 or line.find(' leak') >= 0:
456                     return WARNINGS
457
458             for line in self.incorrectLayoutLines:
459                 if line.find('flakes') >= 0 or line.find('new passes') >= 0 or line.find('missing results') >= 0:
460                     result = WARNINGS
461                 else:
462                     return FAILURE
463
464         if cmd.rc != 0:
465             return FAILURE
466
467         return result
468
469     def getText(self, cmd, results):
470         return self.getText2(cmd, results)
471
472     def getText2(self, cmd, results):
473         if results != SUCCESS and self.incorrectLayoutLines:
474             return self.incorrectLayoutLines
475
476         return [self.name]
477
478
479 class RunUnitTests(TestWithFailureCount):
480     name = "run-api-tests"
481     description = ["unit tests running"]
482     descriptionDone = ["unit-tests"]
483     command = ["perl", "./Tools/Scripts/run-api-tests", WithProperties("--%(configuration)s"), "--verbose"]
484     failedTestsFormatString = "%d unit tests failed or timed out"
485
486     def start(self):
487         platform = self.getProperty('fullPlatform')
488         if platform.startswith('win'):
489             self.setCommand(self.command + ['--no-build'])
490         if platform.startswith('mac'):
491             self.setCommand(self.command + ['--no-build'])
492         return shell.Test.start(self)
493
494     def countFailures(self, cmd):
495         log_text = cmd.logs['stdio'].getText()
496         count = 0
497
498         split = re.split(r'\sTests that timed out:\s', log_text)
499         if len(split) > 1:
500             count += len(re.findall(r'^\s+\S+$', split[1], flags=re.MULTILINE))
501
502         split = re.split(r'\sTests that failed:\s', split[0])
503         if len(split) > 1:
504             count += len(re.findall(r'^\s+\S+$', split[1], flags=re.MULTILINE))
505
506         return count
507
508
509 class RunPythonTests(TestWithFailureCount):
510     name = "webkitpy-test"
511     description = ["python-tests running"]
512     descriptionDone = ["python-tests"]
513     command = ["python", "./Tools/Scripts/test-webkitpy", "--verbose"]
514     failedTestsFormatString = "%d python tests failed"
515
516     def start(self):
517         platform = self.getProperty('platform')
518         # Python tests are flaky on the GTK builders, running them serially
519         # helps and does not significantly prolong the cycle time.
520         if platform == 'gtk':
521             self.setCommand(self.command + ['--child-processes', '1'])
522         # Python tests fail on windows bots when running more than one child process
523         # https://bugs.webkit.org/show_bug.cgi?id=97465
524         if platform == 'win':
525             self.setCommand(self.command + ['--child-processes', '1'])
526         return shell.Test.start(self)
527
528     def countFailures(self, cmd):
529         logText = cmd.logs['stdio'].getText()
530         # We're looking for the line that looks like this: FAILED (failures=2, errors=1)
531         regex = re.compile(r'^FAILED \((?P<counts>[^)]+)\)')
532         for line in logText.splitlines():
533             match = regex.match(line)
534             if not match:
535                 continue
536             return sum(int(component.split('=')[1]) for component in match.group('counts').split(', '))
537         return 0
538
539
540 class RunPerlTests(TestWithFailureCount):
541     name = "webkitperl-test"
542     description = ["perl-tests running"]
543     descriptionDone = ["perl-tests"]
544     command = ["perl", "./Tools/Scripts/test-webkitperl"]
545     failedTestsFormatString = "%d perl tests failed"
546
547     def countFailures(self, cmd):
548         logText = cmd.logs['stdio'].getText()
549         # We're looking for the line that looks like this: Failed 2/19 test programs. 5/363 subtests failed.
550         regex = re.compile(r'^Failed \d+/\d+ test programs\. (?P<count>\d+)/\d+ subtests failed\.')
551         for line in logText.splitlines():
552             match = regex.match(line)
553             if not match:
554                 continue
555             return int(match.group('count'))
556         return 0
557
558
559 class RunCLOOPTests(TestWithFailureCount):
560     name = "webkit-jsc-cloop-test"
561     description = ["cloop-tests running"]
562     descriptionDone = ["cloop-tests"]
563     command = ["perl", "./Tools/Scripts/run-javascriptcore-tests", "--cloop", "--no-build", "--no-jsc-stress", WithProperties("--%(configuration)s")]
564     failedTestsFormatString = "%d regressions found."
565
566     def countFailures(self, cmd):
567         logText = cmd.logs['stdio'].getText()
568         # We're looking for the line that looks like this: 0 regressions found.\n0 tests fixed.
569         regex = re.compile(r'(?P<count>\d+) regressions found.')
570         for line in logText.splitlines():
571             match = regex.match(line)
572             if not match:
573                 continue
574             return int(match.group('count'))
575         return 0
576
577
578 class RunBindingsTests(shell.Test):
579     name = "bindings-generation-tests"
580     description = ["bindings-tests running"]
581     descriptionDone = ["bindings-tests"]
582     command = ["python", "./Tools/Scripts/run-bindings-tests"]
583
584
585 class RunEflAPITests(shell.Test):
586     name = "API tests"
587     description = ["API tests running"]
588     descriptionDone = ["API tests"]
589     command = ["perl", "./Tools/Scripts/run-efl-tests", WithProperties("--%(configuration)s")]
590
591
592 class RunGtkAPITests(shell.Test):
593     name = "API tests"
594     description = ["API tests running"]
595     descriptionDone = ["API tests"]
596     command = ["python", "./Tools/Scripts/run-gtk-tests", "--verbose", WithProperties("--%(configuration)s")]
597
598     def commandComplete(self, cmd):
599         shell.Test.commandComplete(self, cmd)
600
601         logText = cmd.logs['stdio'].getText()
602
603         self.incorrectTests = 0
604         self.crashedTests = 0
605         self.timedOutTests = 0
606         self.skippedTests = 0
607         self.statusLine = []
608
609         foundItems = re.findall("Tests failed \((\d+)\):", logText)
610         if (foundItems):
611             self.incorrectTests = int(foundItems[0])
612
613         foundItems = re.findall("Tests that crashed \((\d+)\):", logText)
614         if (foundItems):
615             self.crashedTests = int(foundItems[0])
616
617         foundItems = re.findall("Tests that timed out \((\d+)\):", logText)
618         if (foundItems):
619             self.timedOutTests = int(foundItems[0])
620
621         foundItems = re.findall("Tests skipped \((\d+)\):", logText)
622         if (foundItems):
623             self.skippedTests = int(foundItems[0])
624
625         self.totalFailedTests = self.incorrectTests + self.crashedTests + self.timedOutTests
626
627         if self.totalFailedTests > 0:
628             self.statusLine = [
629                 "%d API tests failed, %d crashed, %d timed out, %d skipped" %
630                 (self.incorrectTests, self.crashedTests, self.timedOutTests, self.skippedTests)
631             ]
632
633     def evaluateCommand(self, cmd):
634         if self.totalFailedTests > 0:
635             return FAILURE
636
637         if cmd.rc != 0:
638             return FAILURE
639
640         return SUCCESS
641
642     def getText(self, cmd, results):
643         return self.getText2(cmd, results)
644
645     def getText2(self, cmd, results):
646         if results != SUCCESS and self.totalFailedTests > 0:
647             return self.statusLine
648
649         return [self.name]
650
651 class RunWebKitLeakTests(RunWebKitTests):
652     warnOnWarnings = True
653     def start(self):
654         self.setCommand(self.command + ["--leaks"])
655         return RunWebKitTests.start(self)
656
657
658 class RunWebKit2Tests(RunWebKitTests):
659     def start(self):
660         self.setProperty("shouldAbortEarly", True)
661         self.setCommand(self.command + ["--webkit-test-runner"])
662
663         return RunWebKitTests.start(self)
664
665
666 class RunAndUploadPerfTests(shell.Test):
667     name = "perf-test"
668     description = ["perf-tests running"]
669     descriptionDone = ["perf-tests"]
670     command = ["python", "./Tools/Scripts/run-perf-tests",
671                "--output-json-path", "perf-test-results.json",
672                "--slave-config-json-path", "../../perf-test-config.json",
673                "--no-show-results",
674                "--reset-results",
675                "--test-results-server", "perf.webkit.org",
676                "--builder-name", WithProperties("%(buildername)s"),
677                "--build-number", WithProperties("%(buildnumber)s"),
678                "--platform", WithProperties("%(fullPlatform)s"),
679                WithProperties("--%(configuration)s")]
680
681     def start(self):
682         self.setCommand(self.command)
683         return shell.Test.start(self)
684
685     def getText(self, cmd, results):
686         return self.getText2(cmd, results)
687
688     def getText2(self, cmd, results):
689         if results != SUCCESS:
690             return ["%d perf tests failed" % cmd.rc]
691
692         return [self.name]
693
694
695 class RunAndUploadPerfTestsWebKit2(RunAndUploadPerfTests):
696     def start(self):
697         self.setCommand(self.command + ["--webkit-test-runner"])
698         return RunAndUploadPerfTests.start(self)
699
700
701 class ArchiveTestResults(shell.ShellCommand):
702     command = ["python", "./Tools/BuildSlaveSupport/test-result-archive",
703                WithProperties("--platform=%(platform)s"), WithProperties("--%(configuration)s"), "archive"]
704     name = "archive-test-results"
705     description = ["archiving test results"]
706     descriptionDone = ["archived test results"]
707     haltOnFailure = True
708
709
710 class UploadTestResults(transfer.FileUpload):
711     slavesrc = "layout-test-results.zip"
712     masterdest = WithProperties("public_html/results/%(buildername)s/r%(got_revision)s (%(buildnumber)s).zip")
713
714     def __init__(self, **kwargs):
715         kwargs['slavesrc'] = self.slavesrc
716         kwargs['masterdest'] = self.masterdest
717         kwargs['mode'] = 0644
718         transfer.FileUpload.__init__(self, **kwargs)
719
720
721 class ExtractTestResults(master.MasterShellCommand):
722     zipFile = WithProperties("public_html/results/%(buildername)s/r%(got_revision)s (%(buildnumber)s).zip")
723     resultDirectory = WithProperties("public_html/results/%(buildername)s/r%(got_revision)s (%(buildnumber)s)")
724     descriptionDone = ["uploaded results"]
725
726     def __init__(self, **kwargs):
727         kwargs['command'] = ""
728         master.MasterShellCommand.__init__(self, **kwargs)
729
730     def resultDirectoryURL(self):
731         return self.build.getProperties().render(self.resultDirectory).replace("public_html/", "/") + "/"
732
733     def start(self):
734         self.command = ["unzip", self.build.getProperties().render(self.zipFile), "-d", self.build.getProperties().render(self.resultDirectory)]
735         return master.MasterShellCommand.start(self)
736
737     def addCustomURLs(self):
738         url = self.resultDirectoryURL() + "results.html"
739         self.addURL("view results", url)
740
741     def finished(self, result):
742         self.addCustomURLs()
743         return master.MasterShellCommand.finished(self, result)
744
745
746 class ExtractTestResultsAndLeaks(ExtractTestResults):
747     def addCustomURLs(self):
748         ExtractTestResults.addCustomURLs(self)
749         url = "/LeaksViewer/?url=" + urllib.quote(self.resultDirectoryURL(), safe="")
750         self.addURL("view leaks", url)
751
752
753 class Factory(factory.BuildFactory):
754     def __init__(self, platform, configuration, architectures, buildOnly, additionalArguments, SVNMirror):
755         factory.BuildFactory.__init__(self)
756         self.addStep(ConfigureBuild(platform=platform, configuration=configuration, architecture=" ".join(architectures), buildOnly=buildOnly, additionalArguments=additionalArguments, SVNMirror=SVNMirror))
757         if SVNMirror:
758             self.addStep(WaitForSVNServer())
759         self.addStep(CheckOutSource(SVNMirror=SVNMirror))
760         self.addStep(KillOldProcesses())
761         self.addStep(CleanBuildIfScheduled())
762         self.addStep(DeleteStaleBuildFiles())
763         if platform == "win":
764             self.addStep(InstallWin32Dependencies())
765         if platform == "gtk":
766             self.addStep(InstallGtkDependencies())
767         if platform == "efl":
768             self.addStep(InstallEflDependencies())
769
770
771 class BuildFactory(Factory):
772     def __init__(self, platform, configuration, architectures, triggers=None, additionalArguments=None, SVNMirror=None):
773         Factory.__init__(self, platform, configuration, architectures, True, additionalArguments, SVNMirror)
774         self.addStep(CompileWebKit())
775         if triggers:
776             self.addStep(ArchiveBuiltProduct())
777             self.addStep(UploadBuiltProduct())
778             self.addStep(trigger.Trigger(schedulerNames=triggers))
779
780 def unitTestsSupported(configuration, platform):
781     if platform.startswith('mac') and configuration == "release":
782         return False; # https://bugs.webkit.org/show_bug.cgi?id=82652
783     return platform == 'win' or platform.startswith('mac')
784
785 def pickLatestBuild(builder, requests):
786     return max(requests, key=operator.attrgetter("submittedAt"))
787
788 class TestFactory(Factory):
789     LayoutTestClass = RunWebKitTests
790     ExtractTestResultsClass = ExtractTestResults
791     def __init__(self, platform, configuration, architectures, additionalArguments=None, SVNMirror=None):
792         Factory.__init__(self, platform, configuration, architectures, False, additionalArguments, SVNMirror)
793         self.addStep(DownloadBuiltProduct())
794         self.addStep(ExtractBuiltProduct())
795         self.addStep(RunJavaScriptCoreTests(buildJSCTool=False))
796         if self.LayoutTestClass:
797             self.addStep(self.LayoutTestClass(buildJSCTool=(platform != 'win')))
798
799         if unitTestsSupported(configuration, platform): 
800             self.addStep(RunUnitTests())
801         self.addStep(RunPythonTests())
802         self.addStep(RunPerlTests())
803         self.addStep(RunBindingsTests())
804         if self.LayoutTestClass:
805             self.addStep(ArchiveTestResults())
806             self.addStep(UploadTestResults())
807             self.addStep(self.ExtractTestResultsClass())
808         if platform == "efl":
809             self.addStep(RunEflAPITests)
810         if platform == "gtk":
811             self.addStep(RunGtkAPITests())
812
813 class BuildAndTestFactory(Factory):
814     CompileClass = CompileWebKit
815     LayoutTestClass = RunWebKitTests
816     ExtractTestResultsClass = ExtractTestResults
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(self.CompileClass())
820         self.addStep(RunJavaScriptCoreTests())
821         if self.LayoutTestClass:
822             self.addStep(self.LayoutTestClass())
823         if unitTestsSupported(configuration, platform): 
824             self.addStep(RunUnitTests())
825         self.addStep(RunPythonTests())
826         self.addStep(RunPerlTests())
827         self.addStep(RunBindingsTests())
828         if self.LayoutTestClass:
829             self.addStep(ArchiveTestResults())
830             self.addStep(UploadTestResults())
831             self.addStep(self.ExtractTestResultsClass())
832         if platform == "efl":
833             self.addStep(RunEflAPITests())
834         if platform == "gtk":
835             self.addStep(RunGtkAPITests())
836         if triggers:
837             self.addStep(ArchiveBuiltProduct())
838             self.addStep(UploadBuiltProduct())
839             self.addStep(trigger.Trigger(schedulerNames=triggers))
840
841 class BuildAndTestCLOOPFactory(BuildAndTestFactory):
842     CompileClass = CompileCLOOP
843     LayoutTestClass = RunCLOOPTests
844
845 class BuildAndTestWebKit2Factory(BuildAndTestFactory):
846     CompileClass = CompileWebKit
847     LayoutTestClass = RunWebKit2Tests
848
849 class BuildAndTestWebKit1OnlyFactory(BuildAndTestFactory):
850     CompileClass = CompileWebKit1Only
851
852 class BuildAndTestWebKit2OnlyFactory(BuildAndTestFactory):
853     CompileClass = CompileWebKit2Only
854     LayoutTestClass = RunWebKit2Tests
855
856 class BuildAndNonLayoutTestFactory(BuildAndTestFactory):
857     LayoutTestClass = None
858
859 class TestLeaksFactory(TestFactory):
860     LayoutTestClass = RunWebKitLeakTests
861     ExtractTestResultsClass = ExtractTestResultsAndLeaks
862
863
864 class TestWebKit2Factory(TestFactory):
865     LayoutTestClass = RunWebKit2Tests
866
867 class BuildAndPerfTestFactory(Factory):
868     def __init__(self, platform, configuration, architectures, additionalArguments=None, SVNMirror=None, **kwargs):
869         Factory.__init__(self, platform, configuration, architectures, False, additionalArguments, SVNMirror, **kwargs)
870         self.addStep(CompileWebKit())
871         self.addStep(RunAndUploadPerfTests())
872
873 class BuildAndPerfTestWebKit2Factory(Factory):
874     def __init__(self, platform, configuration, architectures, additionalArguments=None, SVNMirror=None, **kwargs):
875         Factory.__init__(self, platform, configuration, architectures, False, additionalArguments, SVNMirror, **kwargs)
876         self.addStep(CompileWebKit())
877         self.addStep(RunAndUploadPerfTestsWebKit2())
878
879 class DownloadAndPerfTestFactory(Factory):
880     def __init__(self, platform, configuration, architectures, additionalArguments=None, SVNMirror=None, **kwargs):
881         Factory.__init__(self, platform, configuration, architectures, False, additionalArguments, SVNMirror, **kwargs)
882         self.addStep(DownloadBuiltProduct())
883         self.addStep(ExtractBuiltProduct())
884         self.addStep(RunAndUploadPerfTests())
885
886 class DownloadAndPerfTestWebKit2Factory(Factory):
887     def __init__(self, platform, configuration, architectures, additionalArguments=None, SVNMirror=None, **kwargs):
888         Factory.__init__(self, platform, configuration, architectures, False, additionalArguments, SVNMirror, **kwargs)
889         self.addStep(DownloadBuiltProduct())
890         self.addStep(ExtractBuiltProduct())
891         self.addStep(RunAndUploadPerfTestsWebKit2())
892
893 class PlatformSpecificScheduler(AnyBranchScheduler):
894     def __init__(self, platform, branch, **kwargs):
895         self.platform = platform
896         filter = ChangeFilter(branch=[branch, None], filter_fn=self.filter)
897         AnyBranchScheduler.__init__(self, name=platform, change_filter=filter, **kwargs)
898
899     def filter(self, change):
900         return wkbuild.should_build(self.platform, change.files)
901
902 trunk_filter = ChangeFilter(branch=["trunk", None])
903
904 def loadBuilderConfig(c):
905     # FIXME: These file handles are leaked.
906     passwords = json.load(open('passwords.json'))
907     config = json.load(open('config.json'))
908
909     c['slaves'] = [BuildSlave(slave['name'], passwords[slave['name']], max_builds=1) for slave in config['slaves']]
910
911     c['schedulers'] = []
912     for scheduler in config['schedulers']:
913         if "change_filter" in scheduler:
914             scheduler["change_filter"] = globals()[scheduler["change_filter"]]
915         kls = globals()[scheduler.pop('type')]
916         # Python 2.6 can't handle unicode keys as keyword arguments:
917         # http://bugs.python.org/issue2646.  Modern versions of json return
918         # unicode strings from json.load, so we map all keys to str objects.
919         scheduler = dict(map(lambda key_value_pair: (str(key_value_pair[0]), key_value_pair[1]), scheduler.items()))
920
921         c['schedulers'].append(kls(**scheduler))
922
923     forceScheduler = ForceScheduler(
924         name="force",
925         builderNames=[str(builder['name']) for builder in config['builders']],
926         reason=StringParameter(name="reason", default="", size=40),
927
928         # Validate SVN revision: number or empty string
929         revision=StringParameter(name="revision", default="", regex=re.compile(r'^(\d*)$')),
930
931         # Disable default enabled input fields: branch, repository, project, additional properties
932         branch=FixedParameter(name="branch"),
933         repository=FixedParameter(name="repository"),
934         project=FixedParameter(name="project"),
935         properties=[BooleanParameter(name="is_clean", label="Force Clean build")]
936     )
937     c['schedulers'].append(forceScheduler)
938
939     c['builders'] = []
940     for builder in config['builders']:
941         for slaveName in builder['slavenames']:
942             for slave in config['slaves']:
943                 if slave['name'] != slaveName or slave['platform'] == '*':
944                     continue
945
946                 if slave['platform'] != builder['platform']:
947                     raise Exception, "Builder %r is for platform %r but has slave %r for platform %r!" % (builder['name'], builder['platform'], slave['name'], slave['platform'])
948
949                 break
950
951         platform = builder['platform']
952
953         builderType = builder.pop('type')
954         factory = globals()["%sFactory" % builderType]
955         factorykwargs = {}
956         for key in "platform", "configuration", "architectures", "triggers", "additionalArguments", "SVNMirror":
957             value = builder.pop(key, None)
958             if value:
959                 factorykwargs[key] = value
960
961         builder["factory"] = factory(**factorykwargs)
962
963         if platform.startswith('mac'):
964             builder["category"] = 'AppleMac'
965         elif platform == 'win':
966             builder["category"] = 'AppleWin'
967         elif platform.startswith('gtk'):
968             builder["category"] = 'GTK'
969         elif platform.startswith('efl'):
970             builder["category"] = "EFL"
971         else:
972             builder["category"] = 'misc'
973
974         if (builder['category'] == 'AppleMac' or builder['category'] == 'AppleWin') and builderType != 'Build':
975             builder['nextBuild'] = pickLatestBuild
976
977         c['builders'].append(builder)
978
979 loadBuilderConfig(c)