Cast: Stop logging kVideoFrameSentToEncoder and rename a couple events.
[chromium-blink-merge.git] / chrome / test / chromedriver / run_buildbot_steps.py
blobc6c2c7cf21ae051357d4fa5953dc0b5740f01641
1 #!/usr/bin/env python
2 # Copyright (c) 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 """Runs all the buildbot steps for ChromeDriver except for update/compile."""
8 import bisect
9 import csv
10 import datetime
11 import glob
12 import json
13 import optparse
14 import os
15 import platform as platform_module
16 import re
17 import shutil
18 import StringIO
19 import sys
20 import tempfile
21 import time
22 import urllib2
24 _THIS_DIR = os.path.abspath(os.path.dirname(__file__))
25 GS_CHROMEDRIVER_BUCKET = 'gs://chromedriver'
26 GS_CHROMEDRIVER_DATA_BUCKET = 'gs://chromedriver-data'
27 GS_CHROMEDRIVER_RELEASE_URL = 'http://chromedriver.storage.googleapis.com'
28 GS_CONTINUOUS_URL = GS_CHROMEDRIVER_DATA_BUCKET + '/continuous'
29 GS_PREBUILTS_URL = GS_CHROMEDRIVER_DATA_BUCKET + '/prebuilts'
30 GS_SERVER_LOGS_URL = GS_CHROMEDRIVER_DATA_BUCKET + '/server_logs'
31 SERVER_LOGS_LINK = (
32 'http://chromedriver-data.storage.googleapis.com/server_logs')
33 TEST_LOG_FORMAT = '%s_log.json'
35 SCRIPT_DIR = os.path.join(_THIS_DIR, os.pardir, os.pardir, os.pardir, os.pardir,
36 os.pardir, os.pardir, os.pardir, 'scripts')
37 SITE_CONFIG_DIR = os.path.join(_THIS_DIR, os.pardir, os.pardir, os.pardir,
38 os.pardir, os.pardir, os.pardir, os.pardir,
39 'site_config')
40 sys.path.append(SCRIPT_DIR)
41 sys.path.append(SITE_CONFIG_DIR)
43 import archive
44 import chrome_paths
45 from slave import gsutil_download
46 from slave import slave_utils
47 import util
50 def _ArchivePrebuilts(revision):
51 """Uploads the prebuilts to google storage."""
52 util.MarkBuildStepStart('archive prebuilts')
53 zip_path = util.Zip(os.path.join(chrome_paths.GetBuildDir(['chromedriver']),
54 'chromedriver'))
55 if slave_utils.GSUtilCopy(
56 zip_path,
57 '%s/%s' % (GS_PREBUILTS_URL, 'r%s.zip' % revision)):
58 util.MarkBuildStepError()
61 def _ArchiveServerLogs():
62 """Uploads chromedriver server logs to google storage."""
63 util.MarkBuildStepStart('archive chromedriver server logs')
64 for server_log in glob.glob(os.path.join(tempfile.gettempdir(),
65 'chromedriver_*')):
66 base_name = os.path.basename(server_log)
67 util.AddLink(base_name, '%s/%s' % (SERVER_LOGS_LINK, base_name))
68 slave_utils.GSUtilCopy(
69 server_log,
70 '%s/%s' % (GS_SERVER_LOGS_URL, base_name),
71 mimetype='text/plain')
74 def _DownloadPrebuilts():
75 """Downloads the most recent prebuilts from google storage."""
76 util.MarkBuildStepStart('Download latest chromedriver')
78 zip_path = os.path.join(util.MakeTempDir(), 'build.zip')
79 if gsutil_download.DownloadLatestFile(GS_PREBUILTS_URL,
80 GS_PREBUILTS_URL + '/r',
81 zip_path):
82 util.MarkBuildStepError()
84 util.Unzip(zip_path, chrome_paths.GetBuildDir(['host_forwarder']))
87 def _GetTestResultsLog(platform):
88 """Gets the test results log for the given platform.
90 Args:
91 platform: The platform that the test results log is for.
93 Returns:
94 A dictionary where the keys are SVN revisions and the values are booleans
95 indicating whether the tests passed.
96 """
97 temp_log = tempfile.mkstemp()[1]
98 log_name = TEST_LOG_FORMAT % platform
99 result = slave_utils.GSUtilDownloadFile(
100 '%s/%s' % (GS_CHROMEDRIVER_DATA_BUCKET, log_name), temp_log)
101 if result:
102 return {}
103 with open(temp_log, 'rb') as log_file:
104 json_dict = json.load(log_file)
105 # Workaround for json encoding dictionary keys as strings.
106 return dict([(int(v[0]), v[1]) for v in json_dict.items()])
109 def _PutTestResultsLog(platform, test_results_log):
110 """Pushes the given test results log to google storage."""
111 temp_dir = util.MakeTempDir()
112 log_name = TEST_LOG_FORMAT % platform
113 log_path = os.path.join(temp_dir, log_name)
114 with open(log_path, 'wb') as log_file:
115 json.dump(test_results_log, log_file)
116 if slave_utils.GSUtilCopyFile(log_path, GS_CHROMEDRIVER_DATA_BUCKET):
117 raise Exception('Failed to upload test results log to google storage')
120 def _UpdateTestResultsLog(platform, revision, passed):
121 """Updates the test results log for the given platform.
123 Args:
124 platform: The platform name.
125 revision: The SVN revision number.
126 passed: Boolean indicating whether the tests passed at this revision.
128 assert isinstance(revision, int), 'The revision must be an integer'
129 log = _GetTestResultsLog(platform)
130 if len(log) > 500:
131 del log[min(log.keys())]
132 assert revision not in log, 'Results already exist for revision %s' % revision
133 log[revision] = bool(passed)
134 _PutTestResultsLog(platform, log)
137 def _GetVersion():
138 """Get the current chromedriver version."""
139 with open(os.path.join(_THIS_DIR, 'VERSION'), 'r') as f:
140 return f.read().strip()
143 def _GetSupportedChromeVersions():
144 """Get the minimum and maximum supported Chrome versions.
146 Returns:
147 A tuple of the form (min_version, max_version).
149 # Minimum supported Chrome version is embedded as:
150 # const int kMinimumSupportedChromeVersion[] = {27, 0, 1453, 0};
151 with open(os.path.join(_THIS_DIR, 'chrome', 'version.cc'), 'r') as f:
152 lines = f.readlines()
153 chrome_min_version_line = [
154 x for x in lines if 'kMinimumSupportedChromeVersion' in x]
155 chrome_min_version = chrome_min_version_line[0].split('{')[1].split(',')[0]
156 with open(os.path.join(chrome_paths.GetSrc(), 'chrome', 'VERSION'), 'r') as f:
157 chrome_max_version = f.readlines()[0].split('=')[1].strip()
158 return (chrome_min_version, chrome_max_version)
161 def _RevisionState(test_results_log, revision):
162 """Check the state of tests at a given SVN revision.
164 Considers tests as having passed at a revision if they passed at revisons both
165 before and after.
167 Args:
168 test_results_log: A test results log dictionary from _GetTestResultsLog().
169 revision: The revision to check at.
171 Returns:
172 'passed', 'failed', or 'unknown'
174 assert isinstance(revision, int), 'The revision must be an integer'
175 keys = sorted(test_results_log.keys())
176 # Return passed if the exact revision passed on Android.
177 if revision in test_results_log:
178 return 'passed' if test_results_log[revision] else 'failed'
179 # Tests were not run on this exact revision on Android.
180 index = bisect.bisect_right(keys, revision)
181 # Tests have not yet run on Android at or above this revision.
182 if index == len(test_results_log):
183 return 'unknown'
184 # No log exists for any prior revision, assume it failed.
185 if index == 0:
186 return 'failed'
187 # Return passed if the revisions on both sides passed.
188 if test_results_log[keys[index]] and test_results_log[keys[index - 1]]:
189 return 'passed'
190 return 'failed'
193 def _ArchiveGoodBuild(platform, revision):
194 """Archive chromedriver binary if the build is green."""
195 assert platform != 'android'
196 util.MarkBuildStepStart('archive build')
198 server_name = 'chromedriver'
199 if util.IsWindows():
200 server_name += '.exe'
201 zip_path = util.Zip(os.path.join(chrome_paths.GetBuildDir([server_name]),
202 server_name))
204 build_name = 'chromedriver_%s_%s.%s.zip' % (
205 platform, _GetVersion(), revision)
206 build_url = '%s/%s' % (GS_CONTINUOUS_URL, build_name)
207 if slave_utils.GSUtilCopy(zip_path, build_url):
208 util.MarkBuildStepError()
210 (latest_fd, latest_file) = tempfile.mkstemp()
211 os.write(latest_fd, build_name)
212 os.close(latest_fd)
213 latest_url = '%s/latest_%s' % (GS_CONTINUOUS_URL, platform)
214 if slave_utils.GSUtilCopy(latest_file, latest_url, mimetype='text/plain'):
215 util.MarkBuildStepError()
216 os.remove(latest_file)
219 def _WasReleased(version, platform):
220 """Check if the specified version is released for the given platform."""
221 result, _ = slave_utils.GSUtilListBucket(
222 '%s/%s/chromedriver_%s.zip' % (GS_CHROMEDRIVER_BUCKET, version, platform),
224 return result == 0
227 def _MaybeRelease(platform):
228 """Releases a release candidate if conditions are right."""
229 assert platform != 'android'
231 version = _GetVersion()
233 # Check if the current version has already been released.
234 if _WasReleased(version, platform):
235 return
237 # Fetch Android test results.
238 android_test_results = _GetTestResultsLog('android')
240 # Fetch release candidates.
241 result, output = slave_utils.GSUtilListBucket(
242 '%s/chromedriver_%s_%s*' % (
243 GS_CONTINUOUS_URL, platform, version),
245 assert result == 0 and output, 'No release candidates found'
246 candidate_pattern = re.compile(
247 r'.*/chromedriver_%s_%s\.(\d+)\.zip$' % (platform, version))
248 candidates = []
249 for line in output.strip().split('\n'):
250 result = candidate_pattern.match(line)
251 if not result:
252 print 'Ignored line "%s"' % line
253 continue
254 candidates.append(int(result.group(1)))
256 # Release the latest candidate build that passed Android, if any.
257 # In this way, if a hot fix is needed, we can delete the release from
258 # the chromedriver bucket instead of bumping up the release version number.
259 candidates.sort(reverse=True)
260 for revision in candidates:
261 android_result = _RevisionState(android_test_results, revision)
262 if android_result == 'failed':
263 print 'Android tests did not pass at revision', revision
264 elif android_result == 'passed':
265 print 'Android tests passed at revision', revision
266 candidate = 'chromedriver_%s_%s.%s.zip' % (platform, version, revision)
267 _Release('%s/%s' % (GS_CONTINUOUS_URL, candidate), version, platform)
268 break
269 else:
270 print 'Android tests have not run at a revision as recent as', revision
273 def _Release(build, version, platform):
274 """Releases the given candidate build."""
275 release_name = 'chromedriver_%s.zip' % platform
276 util.MarkBuildStepStart('releasing %s' % release_name)
277 temp_dir = util.MakeTempDir()
278 slave_utils.GSUtilCopy(build, temp_dir)
279 zip_path = os.path.join(temp_dir, os.path.basename(build))
281 if util.IsLinux():
282 util.Unzip(zip_path, temp_dir)
283 server_path = os.path.join(temp_dir, 'chromedriver')
284 util.RunCommand(['strip', server_path])
285 zip_path = util.Zip(server_path)
287 slave_utils.GSUtilCopy(
288 zip_path, '%s/%s/%s' % (GS_CHROMEDRIVER_BUCKET, version, release_name))
290 _MaybeUploadReleaseNotes(version)
291 _MaybeUpdateLatestRelease(version)
294 def _GetWebPageContent(url):
295 """Return the content of the web page specified by the given url."""
296 return urllib2.urlopen(url).read()
299 def _MaybeUploadReleaseNotes(version):
300 """Upload release notes if conditions are right."""
301 # Check if the current version has already been released.
302 notes_name = 'notes.txt'
303 notes_url = '%s/%s/%s' % (GS_CHROMEDRIVER_BUCKET, version, notes_name)
304 prev_version = '.'.join([version.split('.')[0],
305 str(int(version.split('.')[1]) - 1)])
306 prev_notes_url = '%s/%s/%s' % (
307 GS_CHROMEDRIVER_BUCKET, prev_version, notes_name)
309 result, _ = slave_utils.GSUtilListBucket(notes_url, [])
310 if result == 0:
311 return
313 fixed_issues = []
314 query = ('https://code.google.com/p/chromedriver/issues/csv?'
315 'q=status%3AToBeReleased&colspec=ID%20Summary')
316 issues = StringIO.StringIO(_GetWebPageContent(query).split('\n', 1)[1])
317 for issue in csv.reader(issues):
318 if not issue:
319 continue
320 issue_id = issue[0]
321 desc = issue[1]
322 labels = issue[2]
323 fixed_issues += ['Resolved issue %s: %s [%s]' % (issue_id, desc, labels)]
325 old_notes = ''
326 temp_notes_fname = tempfile.mkstemp()[1]
327 if not slave_utils.GSUtilDownloadFile(prev_notes_url, temp_notes_fname):
328 with open(temp_notes_fname, 'rb') as f:
329 old_notes = f.read()
331 new_notes = '----------ChromeDriver v%s (%s)----------\n%s\n%s\n\n%s' % (
332 version, datetime.date.today().isoformat(),
333 'Supports Chrome v%s-%s' % _GetSupportedChromeVersions(),
334 '\n'.join(fixed_issues),
335 old_notes)
336 with open(temp_notes_fname, 'w') as f:
337 f.write(new_notes)
339 if slave_utils.GSUtilCopy(temp_notes_fname, notes_url, mimetype='text/plain'):
340 util.MarkBuildStepError()
343 def _MaybeUpdateLatestRelease(version):
344 """Update the file LATEST_RELEASE with the latest release version number."""
345 latest_release_fname = 'LATEST_RELEASE'
346 latest_release_url = '%s/%s' % (GS_CHROMEDRIVER_BUCKET, latest_release_fname)
348 # Check if LATEST_RELEASE is up-to-date.
349 latest_released_version = _GetWebPageContent(
350 '%s/%s' % (GS_CHROMEDRIVER_RELEASE_URL, latest_release_fname))
351 if version == latest_released_version:
352 return
354 # Check if chromedriver was released on all supported platforms.
355 supported_platforms = ['linux32', 'linux64', 'mac32', 'win32']
356 for platform in supported_platforms:
357 if not _WasReleased(version, platform):
358 return
360 util.MarkBuildStepStart('updating LATEST_RELEASE to %s' % version)
362 temp_latest_release_fname = tempfile.mkstemp()[1]
363 with open(temp_latest_release_fname, 'w') as f:
364 f.write(version)
365 if slave_utils.GSUtilCopy(temp_latest_release_fname, latest_release_url,
366 mimetype='text/plain'):
367 util.MarkBuildStepError()
370 def _CleanTmpDir():
371 tmp_dir = tempfile.gettempdir()
372 print 'cleaning temp directory:', tmp_dir
373 for file_name in os.listdir(tmp_dir):
374 file_path = os.path.join(tmp_dir, file_name)
375 if os.path.isdir(file_path):
376 print 'deleting sub-directory', file_path
377 shutil.rmtree(file_path, True)
378 if file_name.startswith('chromedriver_'):
379 print 'deleting file', file_path
380 os.remove(file_path)
383 def _WaitForLatestSnapshot(revision):
384 util.MarkBuildStepStart('wait_for_snapshot')
385 while True:
386 snapshot_revision = archive.GetLatestRevision(archive.Site.SNAPSHOT)
387 if int(snapshot_revision) >= int(revision):
388 break
389 util.PrintAndFlush('Waiting for snapshot >= %s, found %s' %
390 (revision, snapshot_revision))
391 time.sleep(60)
392 util.PrintAndFlush('Got snapshot revision %s' % snapshot_revision)
395 def _AddToolsToPath(platform_name):
396 """Add some tools like Ant and Java to PATH for testing steps to use."""
397 paths = []
398 error_message = ''
399 if platform_name == 'win32':
400 paths = [
401 # Path to Ant and Java, required for the java acceptance tests.
402 'C:\\Program Files (x86)\\Java\\ant\\bin',
403 'C:\\Program Files (x86)\\Java\\jre\\bin',
405 error_message = ('Java test steps will fail as expected and '
406 'they can be ignored.\n'
407 'Ant, Java or others might not be installed on bot.\n'
408 'Please refer to page "WATERFALL" on site '
409 'go/chromedriver.')
410 if paths:
411 util.MarkBuildStepStart('Add tools to PATH')
412 path_missing = False
413 for path in paths:
414 if not os.path.isdir(path) or not os.listdir(path):
415 print 'Directory "%s" is not found or empty.' % path
416 path_missing = True
417 if path_missing:
418 print error_message
419 util.MarkBuildStepError()
420 return
421 os.environ['PATH'] += os.pathsep + os.pathsep.join(paths)
424 def main():
425 parser = optparse.OptionParser()
426 parser.add_option(
427 '', '--android-packages',
428 help=('Comma separated list of application package names, '
429 'if running tests on Android.'))
430 parser.add_option(
431 '-r', '--revision', type='int', help='Chromium revision')
432 parser.add_option(
433 '', '--update-log', action='store_true',
434 help='Update the test results log (only applicable to Android)')
435 options, _ = parser.parse_args()
437 bitness = '32'
438 if util.IsLinux() and platform_module.architecture()[0] == '64bit':
439 bitness = '64'
440 platform = '%s%s' % (util.GetPlatformName(), bitness)
441 if options.android_packages:
442 platform = 'android'
444 _CleanTmpDir()
446 if platform == 'android':
447 if not options.revision and options.update_log:
448 parser.error('Must supply a --revision with --update-log')
449 _DownloadPrebuilts()
450 else:
451 if not options.revision:
452 parser.error('Must supply a --revision')
453 if platform == 'linux64':
454 _ArchivePrebuilts(options.revision)
455 _WaitForLatestSnapshot(options.revision)
457 _AddToolsToPath(platform)
459 cmd = [
460 sys.executable,
461 os.path.join(_THIS_DIR, 'test', 'run_all_tests.py'),
463 if platform == 'android':
464 cmd.append('--android-packages=' + options.android_packages)
466 passed = (util.RunCommand(cmd) == 0)
468 _ArchiveServerLogs()
470 if platform == 'android':
471 if options.update_log:
472 util.MarkBuildStepStart('update test result log')
473 _UpdateTestResultsLog(platform, options.revision, passed)
474 elif passed:
475 _ArchiveGoodBuild(platform, options.revision)
476 _MaybeRelease(platform)
478 if not passed:
479 # Make sure the build is red if there is some uncaught exception during
480 # running run_all_tests.py.
481 util.MarkBuildStepStart('run_all_tests.py')
482 util.MarkBuildStepError()
484 # Add a "cleanup" step so that errors from runtest.py or bb_device_steps.py
485 # (which invoke this script) are kept in thier own build step.
486 util.MarkBuildStepStart('cleanup')
489 if __name__ == '__main__':
490 main()