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