[ews-build] Use https for ews-build server
[WebKit-https.git] / Tools / BuildSlaveSupport / ews-build / steps.py
1 # Copyright (C) 2018 Apple Inc. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions
5 # are met:
6 # 1.  Redistributions of source code must retain the above copyright
7 #     notice, this list of conditions and the following disclaimer.
8 # 2.  Redistributions in binary form must reproduce the above copyright
9 #     notice, this list of conditions and the following disclaimer in the
10 #     documentation and/or other materials provided with the distribution.
11 #
12 # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
13 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15 # DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
16 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
17 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
18 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
19 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
20 # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
21 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22
23 from buildbot.process import buildstep, logobserver, properties
24 from buildbot.process.results import Results, SUCCESS, FAILURE, WARNINGS, SKIPPED, EXCEPTION, RETRY
25 from buildbot.steps import master, shell, transfer
26 from buildbot.steps.source import git
27 from buildbot.steps.worker import CompositeStepMixin
28 from twisted.internet import defer
29
30 import re
31 import requests
32
33 BUG_SERVER_URL = 'https://bugs.webkit.org/'
34 EWS_URL = 'https://ews-build.webkit-uat.org/'
35 WithProperties = properties.WithProperties
36 Interpolate = properties.Interpolate
37
38
39 class ConfigureBuild(buildstep.BuildStep):
40     name = "configure-build"
41     description = ["configuring build"]
42     descriptionDone = ["configured build"]
43
44     def __init__(self, platform, configuration, architectures, buildOnly, additionalArguments):
45         super(ConfigureBuild, self).__init__()
46         self.platform = platform
47         if platform != 'jsc-only':
48             self.platform = platform.split('-', 1)[0]
49         self.fullPlatform = platform
50         self.configuration = configuration
51         self.architecture = " ".join(architectures) if architectures else None
52         self.buildOnly = buildOnly
53         self.additionalArguments = additionalArguments
54
55     def start(self):
56         if self.platform and self.platform != '*':
57             self.setProperty('platform', self.platform, 'config.json')
58         if self.fullPlatform and self.fullPlatform != '*':
59             self.setProperty('fullPlatform', self.fullPlatform, 'ConfigureBuild')
60         if self.configuration:
61             self.setProperty('configuration', self.configuration, 'config.json')
62         if self.architecture:
63             self.setProperty('architecture', self.architecture, 'config.json')
64         if self.buildOnly:
65             self.setProperty("buildOnly", self.buildOnly, 'config.json')
66         if self.additionalArguments:
67             self.setProperty("additionalArguments", self.additionalArguments, 'config.json')
68
69         self.add_patch_id_url()
70         self.add_bug_id_url()
71         self.finished(SUCCESS)
72         return defer.succeed(None)
73
74     def add_patch_id_url(self):
75         patch_id = self.getProperty('patch_id', '')
76         if patch_id:
77             self.addURL('Patch {}'.format(patch_id), self.getPatchURL(patch_id))
78
79     def add_bug_id_url(self):
80         bug_id = self.getProperty('bug_id', '')
81         if bug_id:
82             self.addURL('Bug {}'.format(bug_id), self.getBugURL(bug_id))
83
84     def getPatchURL(self, patch_id):
85         if not patch_id:
86             return None
87         return '{}attachment.cgi?id={}'.format(BUG_SERVER_URL, patch_id)
88
89     def getBugURL(self, bug_id):
90         if not bug_id:
91             return None
92         return '{}show_bug.cgi?id={}'.format(BUG_SERVER_URL, bug_id)
93
94
95 class CheckOutSource(git.Git):
96     name = 'clean-and-update-working-directory'
97     CHECKOUT_DELAY_AND_MAX_RETRIES_PAIR = (0, 2)
98
99     def __init__(self, **kwargs):
100         self.repourl = 'https://git.webkit.org/git/WebKit.git'
101         super(CheckOutSource, self).__init__(repourl=self.repourl,
102                                                 retry=self.CHECKOUT_DELAY_AND_MAX_RETRIES_PAIR,
103                                                 timeout=2 * 60 * 60,
104                                                 alwaysUseLatest=True,
105                                                 progress=True,
106                                                 **kwargs)
107
108
109 class ApplyPatch(shell.ShellCommand, CompositeStepMixin):
110     name = 'apply-patch'
111     description = ['applying-patch']
112     descriptionDone = ['apply-patch']
113     flunkOnFailure = True
114     haltOnFailure = True
115     command = ['Tools/Scripts/svn-apply', '--force', '.buildbot-diff']
116
117     def _get_patch(self):
118         sourcestamp = self.build.getSourceStamp(self.getProperty('codebase', ''))
119         if not sourcestamp or not sourcestamp.patch:
120             return None
121         return sourcestamp.patch[1]
122
123     def start(self):
124         patch = self._get_patch()
125         if not patch:
126             self.finished(FAILURE)
127             return None
128
129         d = self.downloadFileContentToWorker('.buildbot-diff', patch)
130         d.addCallback(lambda _: self.downloadFileContentToWorker('.buildbot-patched', 'patched\n'))
131         d.addCallback(lambda res: shell.ShellCommand.start(self))
132
133
134 class CheckPatchRelevance(buildstep.BuildStep):
135     name = 'check-patch-relevance'
136     description = ['check-patch-relevance running']
137     descriptionDone = ['check-patch-relevance']
138     flunkOnFailure = True
139     haltOnFailure = True
140
141     bindings_paths = [
142         "Source/WebCore",
143         "Tools",
144     ]
145
146     jsc_paths = [
147         "JSTests/",
148         "Source/JavaScriptCore/",
149         "Source/WTF/",
150         "Source/bmalloc/",
151         "Makefile",
152         "Makefile.shared",
153         "Source/Makefile",
154         "Source/Makefile.shared",
155         "Tools/Scripts/build-webkit",
156         "Tools/Scripts/build-jsc",
157         "Tools/Scripts/jsc-stress-test-helpers/",
158         "Tools/Scripts/run-jsc",
159         "Tools/Scripts/run-jsc-benchmarks",
160         "Tools/Scripts/run-jsc-stress-tests",
161         "Tools/Scripts/run-javascriptcore-tests",
162         "Tools/Scripts/run-layout-jsc",
163         "Tools/Scripts/update-javascriptcore-test-results",
164         "Tools/Scripts/webkitdirs.pm",
165     ]
166
167     webkitpy_paths = [
168         "Tools/Scripts/webkitpy/",
169         "Tools/QueueStatusServer/",
170     ]
171
172     group_to_paths_mapping = {
173         'bindings': bindings_paths,
174         'jsc': jsc_paths,
175         'webkitpy': webkitpy_paths,
176     }
177
178     def _patch_is_relevant(self, patch, builderName):
179         group = [group for group in self.group_to_paths_mapping.keys() if group in builderName.lower()]
180         if not group:
181             # This builder doesn't have paths defined, all patches are relevant.
182             return True
183
184         relevant_paths = self.group_to_paths_mapping[group[0]]
185
186         for change in patch.splitlines():
187             for path in relevant_paths:
188                 if re.search(path, change, re.IGNORECASE):
189                     return True
190         return False
191
192     def _get_patch(self):
193         sourcestamp = self.build.getSourceStamp(self.getProperty('codebase', ''))
194         if not sourcestamp or not sourcestamp.patch:
195             return None
196         return sourcestamp.patch[1]
197
198     @defer.inlineCallbacks
199     def _addToLog(self, logName, message):
200         try:
201             log = self.getLog(logName)
202         except KeyError:
203             log = yield self.addLog(logName)
204         log.addStdout(message)
205
206     def start(self):
207         patch = self._get_patch()
208         if not patch:
209             # This build doesn't have a patch, it might be a force build.
210             self.finished(SUCCESS)
211             return None
212
213         if self._patch_is_relevant(patch, self.getProperty('buildername', '')):
214             self._addToLog('stdio', 'This patch contains relevant changes.')
215             self.finished(SUCCESS)
216             return None
217
218         self._addToLog('stdio', 'This patch does not have relevant changes.')
219         self.finished(FAILURE)
220         self.build.results = SKIPPED
221         self.build.buildFinished(['Patch {} doesn\'t have relevant changes'.format(self.getProperty('patch_id', ''))], SKIPPED)
222         return None
223
224
225 class ValidatePatch(buildstep.BuildStep):
226     name = 'validate-patch'
227     description = ['validate-patch running']
228     descriptionDone = ['validate-patch']
229     flunkOnFailure = True
230     haltOnFailure = True
231     bug_open_statuses = ["UNCONFIRMED", "NEW", "ASSIGNED", "REOPENED"]
232     bug_closed_statuses = ["RESOLVED", "VERIFIED", "CLOSED"]
233
234     @defer.inlineCallbacks
235     def _addToLog(self, logName, message):
236         try:
237             log = self.getLog(logName)
238         except KeyError:
239             log = yield self.addLog(logName)
240         log.addStdout(message)
241
242     def fetch_data_from_url(self, url):
243         response = None
244         try:
245             response = requests.get(url)
246         except Exception as e:
247             if response:
248                 self._addToLog('stdio', 'Failed to access {url} with status code {status_code}.\n'.format(url=url, status_code=response.status_code))
249             else:
250                 self._addToLog('stdio', 'Failed to access {url} with exception: {exception}\n'.format(url=url, exception=e))
251             return None
252         if response.status_code != 200:
253             self._addToLog('stdio', 'Accessed {url} with unexpected status code {status_code}.\n'.format(url=url, status_code=response.status_code))
254             return None
255         return response
256
257     def get_patch_json(self, patch_id):
258         patch_url = '{}rest/bug/attachment/{}'.format(BUG_SERVER_URL, patch_id)
259         patch = self.fetch_data_from_url(patch_url)
260         if not patch:
261             return None
262         patch_json = patch.json().get('attachments')
263         if not patch_json or len(patch_json) == 0:
264             return None
265         return patch_json.get(str(patch_id))
266
267     def get_bug_json(self, bug_id):
268         bug_url = '{}rest/bug/{}'.format(BUG_SERVER_URL, bug_id)
269         bug = self.fetch_data_from_url(bug_url)
270         if not bug:
271             return None
272         bugs_json = bug.json().get('bugs')
273         if not bugs_json or len(bugs_json) == 0:
274             return None
275         return bugs_json[0]
276
277     def get_bug_id_from_patch(self, patch_id):
278         patch_json = self.get_patch_json(patch_id)
279         if not patch_json:
280             self._addToLog('stdio', 'Unable to fetch patch {}.\n'.format(patch_id))
281             return -1
282         return patch_json.get('bug_id')
283
284     def _is_patch_obsolete(self, patch_id):
285         patch_json = self.get_patch_json(patch_id)
286         if not patch_json:
287             self._addToLog('stdio', 'Unable to fetch patch {}.\n'.format(patch_id))
288             return -1
289
290         if str(patch_json.get('id')) != self.getProperty('patch_id', ''):
291             self._addToLog('stdio', 'Fetched patch id {} does not match with requested patch id {}. Unable to validate.\n'.format(patch_json.get('id'), self.getProperty('patch_id', '')))
292             return -1
293
294         return patch_json.get('is_obsolete')
295
296     def _is_patch_review_denied(self, patch_id):
297         patch_json = self.get_patch_json(patch_id)
298         if not patch_json:
299             self._addToLog('stdio', 'Unable to fetch patch {}.\n'.format(patch_id))
300             return -1
301
302         for flag in patch_json.get('flags', []):
303             if flag.get('name') == 'review' and flag.get('status') == '-':
304                 return 1
305         return 0
306
307     def _is_bug_closed(self, bug_id):
308         bug_json = self.get_bug_json(bug_id)
309         if not bug_json or not bug_json.get('status'):
310             self._addToLog('stdio', 'Unable to fetch bug {}.\n'.format(bug_id))
311             return -1
312
313         if bug_json.get('status') in self.bug_closed_statuses:
314             return 1
315         return 0
316
317     def skip_build(self, reason):
318         self._addToLog('stdio', reason)
319         self.finished(FAILURE)
320         self.build.results = SKIPPED
321         self.build.buildFinished([reason], SKIPPED)
322
323     def start(self):
324         patch_id = self.getProperty('patch_id', '')
325         if not patch_id:
326             self._addToLog('stdio', 'No patch_id found. Unable to proceed without patch_id.\n')
327             self.finished(FAILURE)
328             return None
329
330         bug_id = self.getProperty('bug_id', self.get_bug_id_from_patch(patch_id))
331
332         bug_closed = self._is_bug_closed(bug_id)
333         if bug_closed == 1:
334             self.skip_build('Bug {} is already closed'.format(bug_id))
335             return None
336
337         obsolete = self._is_patch_obsolete(patch_id)
338         if obsolete == 1:
339             self.skip_build('Patch {} is obsolete'.format(patch_id))
340             return None
341
342         review_denied = self._is_patch_review_denied(patch_id)
343         if review_denied == 1:
344             self.skip_build('Patch {} is marked r-'.format(patch_id))
345             return None
346
347         if obsolete == -1 or review_denied == -1 or bug_closed == -1:
348             self.finished(WARNINGS)
349             return None
350
351         self._addToLog('stdio', 'Bug is open.\nPatch is not obsolete.\nPatch is not marked r-.\n')
352         self.finished(SUCCESS)
353         return None
354
355
356 class UnApplyPatchIfRequired(CheckOutSource):
357     name = 'unapply-patch'
358
359     def doStepIf(self, step):
360         return self.getProperty('patchFailedToBuild') or self.getProperty('patchFailedJSCTests')
361
362     def hideStepIf(self, results, step):
363         return not self.doStepIf(step)
364
365
366 class TestWithFailureCount(shell.Test):
367     failedTestsFormatString = "%d test%s failed"
368     failedTestCount = 0
369
370     def start(self):
371         self.log_observer = logobserver.BufferLogObserver(wantStderr=True)
372         self.addLogObserver('stdio', self.log_observer)
373         return shell.Test.start(self)
374
375     def countFailures(self, cmd):
376         raise NotImplementedError
377
378     def commandComplete(self, cmd):
379         shell.Test.commandComplete(self, cmd)
380         self.failedTestCount = self.countFailures(cmd)
381         self.failedTestPluralSuffix = "" if self.failedTestCount == 1 else "s"
382
383     def evaluateCommand(self, cmd):
384         if self.failedTestCount:
385             return FAILURE
386
387         if cmd.rc != 0:
388             return FAILURE
389
390         return SUCCESS
391
392     def getResultSummary(self):
393         status = self.name
394
395         if self.results != SUCCESS and self.failedTestCount:
396             status = self.failedTestsFormatString % (self.failedTestCount, self.failedTestPluralSuffix)
397
398         if self.results != SUCCESS:
399             status += u' ({})'.format(Results[self.results])
400
401         return {u'step': status}
402
403
404 class CheckStyle(TestWithFailureCount):
405     name = 'check-webkit-style'
406     description = ['check-webkit-style running']
407     descriptionDone = ['check-webkit-style']
408     flunkOnFailure = True
409     failedTestsFormatString = '%d style error%s'
410     command = ['Tools/Scripts/check-webkit-style']
411
412     def countFailures(self, cmd):
413         log_text = self.log_observer.getStdout() + self.log_observer.getStderr()
414
415         match = re.search(r'Total errors found: (?P<errors>\d+) in (?P<files>\d+) files', log_text)
416         if not match:
417             return 0
418         return int(match.group('errors'))
419
420
421 class RunBindingsTests(shell.ShellCommand):
422     name = 'bindings-tests'
423     description = ['bindings-tests running']
424     descriptionDone = ['bindings-tests']
425     flunkOnFailure = True
426     jsonFileName = 'bindings_test_results.json'
427     logfiles = {'json': jsonFileName}
428     command = ['Tools/Scripts/run-bindings-tests', '--json-output={0}'.format(jsonFileName)]
429
430
431 class RunWebKitPerlTests(shell.ShellCommand):
432     name = 'webkitperl-tests'
433     description = ['webkitperl-tests running']
434     descriptionDone = ['webkitperl-tests']
435     flunkOnFailure = True
436     command = ['Tools/Scripts/test-webkitperl']
437
438     def __init__(self, **kwargs):
439         super(RunWebKitPerlTests, self).__init__(timeout=2 * 60, **kwargs)
440
441
442 class RunWebKitPyTests(shell.ShellCommand):
443     name = 'webkitpy-tests'
444     description = ['webkitpy-tests running']
445     descriptionDone = ['webkitpy-tests']
446     flunkOnFailure = True
447     jsonFileName = 'webkitpy_test_results.json'
448     logfiles = {'json': jsonFileName}
449     command = ['Tools/Scripts/test-webkitpy', '--json-output={0}'.format(jsonFileName)]
450
451     def __init__(self, **kwargs):
452         super(RunWebKitPyTests, self).__init__(timeout=2 * 60, **kwargs)
453
454
455 def appendCustomBuildFlags(step, platform, fullPlatform):
456     # FIXME: Make a common 'supported platforms' list.
457     if platform not in ('gtk', 'wincairo', 'ios', 'jsc-only', 'wpe'):
458         return
459     if fullPlatform.startswith('ios-simulator'):
460         platform = 'ios-simulator'
461     elif platform == 'ios':
462         platform = 'device'
463     step.setCommand(step.command + ['--' + platform])
464
465
466 class CompileWebKit(shell.Compile):
467     name = "compile-webkit"
468     description = ["compiling"]
469     descriptionDone = ["compiled"]
470     env = {'MFLAGS': ''}
471     warningPattern = ".*arning: .*"
472     haltOnFailure = False
473     command = ["perl", "Tools/Scripts/build-webkit", WithProperties("--%(configuration)s")]
474
475     def start(self):
476         platform = self.getProperty('platform')
477         buildOnly = self.getProperty('buildOnly')
478         architecture = self.getProperty('architecture')
479         additionalArguments = self.getProperty('additionalArguments')
480
481         if additionalArguments:
482             self.setCommand(self.command + additionalArguments)
483         if platform in ('mac', 'ios') and architecture:
484             self.setCommand(self.command + ['ARCHS=' + architecture])
485             if platform == 'ios':
486                 self.setCommand(self.command + ['ONLY_ACTIVE_ARCH=NO'])
487         if platform in ('mac', 'ios') and buildOnly:
488             # For build-only bots, the expectation is that tests will be run on separate machines,
489             # so we need to package debug info as dSYMs. Only generating line tables makes
490             # this much faster than full debug info, and crash logs still have line numbers.
491             self.setCommand(self.command + ['DEBUG_INFORMATION_FORMAT=dwarf-with-dsym'])
492             self.setCommand(self.command + ['CLANG_DEBUG_INFORMATION_LEVEL=line-tables-only'])
493
494         appendCustomBuildFlags(self, platform, self.getProperty('fullPlatform'))
495
496         return shell.Compile.start(self)
497
498     def evaluateCommand(self, cmd):
499         if cmd.didFail():
500             self.setProperty('patchFailedToBuild', True)
501
502         return super(CompileWebKit, self).evaluateCommand(cmd)
503
504
505 class CompileWebKitToT(CompileWebKit):
506     name = 'compile-webkit-tot'
507     haltOnFailure = True
508
509     def doStepIf(self, step):
510         return self.getProperty('patchFailedToBuild')
511
512     def hideStepIf(self, results, step):
513         return not self.doStepIf(step)
514
515
516 class CompileJSCOnly(CompileWebKit):
517     name = "build-jsc"
518     command = ["perl", "Tools/Scripts/build-jsc", WithProperties("--%(configuration)s")]
519
520
521 class CompileJSCOnlyToT(CompileJSCOnly):
522     name = 'build-jsc-tot'
523
524     def doStepIf(self, step):
525         return self.getProperty('patchFailedToBuild')
526
527     def hideStepIf(self, results, step):
528         return not self.doStepIf(step)
529
530
531 class RunJavaScriptCoreTests(shell.Test):
532     name = 'jscore-test'
533     description = ['jscore-tests running']
534     descriptionDone = ['jscore-tests']
535     flunkOnFailure = True
536     jsonFileName = 'jsc_results.json'
537     logfiles = {"json": jsonFileName}
538     command = ['perl', 'Tools/Scripts/run-javascriptcore-tests', '--no-build', '--no-fail-fast', '--json-output={0}'.format(jsonFileName), WithProperties('--%(configuration)s')]
539
540     def start(self):
541         appendCustomBuildFlags(self, self.getProperty('platform'), self.getProperty('fullPlatform'))
542         return shell.Test.start(self)
543
544     def evaluateCommand(self, cmd):
545         if cmd.didFail():
546             self.setProperty('patchFailedJSCTests', True)
547
548         return super(RunJavaScriptCoreTests, self).evaluateCommand(cmd)
549
550
551 class ReRunJavaScriptCoreTests(RunJavaScriptCoreTests):
552     name = 'jscore-test-rerun'
553
554     def doStepIf(self, step):
555         return self.getProperty('patchFailedJSCTests')
556
557     def hideStepIf(self, results, step):
558         return not self.doStepIf(step)
559
560     def evaluateCommand(self, cmd):
561         self.setProperty('patchFailedJSCTests', cmd.didFail())
562         return super(RunJavaScriptCoreTests, self).evaluateCommand(cmd)
563
564
565 class RunJavaScriptCoreTestsToT(RunJavaScriptCoreTests):
566     name = 'jscore-test-tot'
567     jsonFileName = 'jsc_results.json'
568     command = ['perl', 'Tools/Scripts/run-javascriptcore-tests', '--no-fail-fast', '--json-output={0}'.format(jsonFileName), WithProperties('--%(configuration)s')]
569
570     def doStepIf(self, step):
571         return self.getProperty('patchFailedJSCTests')
572
573     def hideStepIf(self, results, step):
574         return not self.doStepIf(step)
575
576
577 class CleanBuild(shell.Compile):
578     name = "delete-WebKitBuild-directory"
579     description = ["deleting WebKitBuild directory"]
580     descriptionDone = ["deleted WebKitBuild directory"]
581     command = ["python", "Tools/BuildSlaveSupport/clean-build", WithProperties("--platform=%(fullPlatform)s"), WithProperties("--%(configuration)s")]
582
583
584 class KillOldProcesses(shell.Compile):
585     name = "kill-old-processes"
586     description = ["killing old processes"]
587     descriptionDone = ["killed old processes"]
588     command = ["python", "Tools/BuildSlaveSupport/kill-old-processes", "buildbot"]
589
590     def __init__(self, **kwargs):
591         super(KillOldProcesses, self).__init__(timeout=60, **kwargs)
592
593
594 class RunWebKitTests(shell.Test):
595     name = 'layout-tests'
596     description = ['layout-tests running']
597     descriptionDone = ['layout-tests']
598     resultDirectory = 'layout-test-results'
599     command = ['python', 'Tools/Scripts/run-webkit-tests',
600                '--no-build',
601                '--no-new-test-results',
602                '--no-show-results',
603                '--exit-after-n-failures', '30',
604                '--skip-failing-tests',
605                WithProperties('--%(configuration)s')]
606
607     def start(self):
608         platform = self.getProperty('platform')
609         appendCustomBuildFlags(self, platform, self.getProperty('fullPlatform'))
610         additionalArguments = self.getProperty('additionalArguments')
611
612         self.setCommand(self.command + ['--results-directory', self.resultDirectory])
613         self.setCommand(self.command + ['--debug-rwt-logging'])
614
615         if additionalArguments:
616             self.setCommand(self.command + additionalArguments)
617         return shell.Test.start(self)
618
619
620 class RunWebKit1Tests(RunWebKitTests):
621     def start(self):
622         self.setCommand(self.command + ['--dump-render-tree'])
623
624         return RunWebKitTests.start(self)
625
626
627 class ArchiveBuiltProduct(shell.ShellCommand):
628     command = ['python', 'Tools/BuildSlaveSupport/built-product-archive',
629                WithProperties('--platform=%(fullPlatform)s'), WithProperties('--%(configuration)s'), 'archive']
630     name = 'archive-built-product'
631     description = ['archiving built product']
632     descriptionDone = ['archived built product']
633     haltOnFailure = True
634
635
636 class UploadBuiltProduct(transfer.FileUpload):
637     name = 'upload-built-product'
638     workersrc = WithProperties('WebKitBuild/%(configuration)s.zip')
639     masterdest = WithProperties('public_html/archives/%(fullPlatform)s-%(architecture)s-%(configuration)s/%(patch_id)s.zip')
640     haltOnFailure = True
641
642     def __init__(self, **kwargs):
643         kwargs['workersrc'] = self.workersrc
644         kwargs['masterdest'] = self.masterdest
645         kwargs['mode'] = 0644
646         kwargs['blocksize'] = 1024 * 256
647         transfer.FileUpload.__init__(self, **kwargs)
648
649
650 class DownloadBuiltProduct(shell.ShellCommand):
651     command = ['python', 'Tools/BuildSlaveSupport/download-built-product',
652         WithProperties('--platform=%(platform)s'), WithProperties('--%(configuration)s'),
653         WithProperties(EWS_URL + 'archives/%(fullPlatform)s-%(architecture)s-%(configuration)s/%(patch_id)s.zip')]
654     name = 'download-built-product'
655     description = ['downloading built product']
656     descriptionDone = ['downloaded built product']
657     haltOnFailure = True
658     flunkOnFailure = True
659
660
661 class ExtractBuiltProduct(shell.ShellCommand):
662     command = ['python', 'Tools/BuildSlaveSupport/built-product-archive',
663                WithProperties('--platform=%(fullPlatform)s'), WithProperties('--%(configuration)s'), 'extract']
664     name = 'extract-built-product'
665     description = ['extracting built product']
666     descriptionDone = ['extracted built product']
667     haltOnFailure = True
668     flunkOnFailure = True
669
670
671 class RunAPITests(TestWithFailureCount):
672     name = 'run-api-tests'
673     description = ['api tests running']
674     descriptionDone = ['api-tests']
675     jsonFileName = 'api_test_results.json'
676     logfiles = {'json': jsonFileName}
677     command = ['python', 'Tools/Scripts/run-api-tests', '--no-build',
678                WithProperties('--%(configuration)s'), '--verbose', '--json-output={0}'.format(jsonFileName)]
679     failedTestsFormatString = '%d api test%s failed or timed out'
680
681     def start(self):
682         appendCustomBuildFlags(self, self.getProperty('platform'), self.getProperty('fullPlatform'))
683         return TestWithFailureCount.start(self)
684
685     def countFailures(self, cmd):
686         log_text = self.log_observer.getStdout() + self.log_observer.getStderr()
687
688         match = re.search(r'Ran (?P<ran>\d+) tests of (?P<total>\d+) with (?P<passed>\d+) successful', log_text)
689         if not match:
690             return 0
691         return int(match.group('ran')) - int(match.group('passed'))
692
693
694 class ArchiveTestResults(shell.ShellCommand):
695     command = ['python', 'Tools/BuildSlaveSupport/test-result-archive',
696                Interpolate('--platform=%(prop:platform)s'), Interpolate('--%(prop:configuration)s'), 'archive']
697     name = 'archive-test-results'
698     description = ['archiving test results']
699     descriptionDone = ['archived test results']
700     haltOnFailure = True
701
702
703 class UploadTestResults(transfer.FileUpload):
704     name = 'upload-test-results'
705     workersrc = 'layout-test-results.zip'
706     masterdest = Interpolate('public_html/results/%(prop:buildername)s/r%(prop:patch_id)s-%(prop:buildnumber)s.zip')
707     haltOnFailure = True
708
709     def __init__(self, **kwargs):
710         kwargs['workersrc'] = self.workersrc
711         kwargs['masterdest'] = self.masterdest
712         kwargs['mode'] = 0644
713         kwargs['blocksize'] = 1024 * 256
714         transfer.FileUpload.__init__(self, **kwargs)
715
716
717 class ExtractTestResults(master.MasterShellCommand):
718     name = 'extract-test-results'
719     zipFile = Interpolate('public_html/results/%(prop:buildername)s/r%(prop:patch_id)s-%(prop:buildnumber)s.zip')
720     resultDirectory = Interpolate('public_html/results/%(prop:buildername)s/r%(prop:patch_id)s-%(prop:buildnumber)s')
721
722     descriptionDone = ['uploaded results']
723     command = ['unzip', zipFile, '-d', resultDirectory]
724     renderables = ['resultDirectory']
725
726     def __init__(self):
727         super(ExtractTestResults, self).__init__(self.command)
728
729     def resultDirectoryURL(self):
730         return self.resultDirectory.replace('public_html/', '/') + '/'
731
732     def addCustomURLs(self):
733         self.addURL('view layout test results', self.resultDirectoryURL() + 'results.html')
734
735     def finished(self, result):
736         self.addCustomURLs()
737         return master.MasterShellCommand.finished(self, result)