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."""
15 import platform
as platform_module
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'
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
,
40 sys
.path
.append(SCRIPT_DIR
)
41 sys
.path
.append(SITE_CONFIG_DIR
)
45 from slave
import gsutil_download
46 from slave
import slave_utils
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']),
55 if slave_utils
.GSUtilCopy(
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(),
66 base_name
= os
.path
.basename(server_log
)
67 util
.AddLink(base_name
, '%s/%s' % (SERVER_LOGS_LINK
, base_name
))
68 slave_utils
.GSUtilCopy(
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',
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.
91 platform: The platform that the test results log is for.
94 A dictionary where the keys are SVN revisions and the values are booleans
95 indicating whether the tests passed.
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
)
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.
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
)
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
)
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.
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
168 test_results_log: A test results log dictionary from _GetTestResultsLog().
169 revision: The revision to check at.
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
):
184 # No log exists for any prior revision, assume it 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]]:
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'
200 server_name
+= '.exe'
201 zip_path
= util
.Zip(os
.path
.join(chrome_paths
.GetBuildDir([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
)
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
),
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
):
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
))
249 for line
in output
.strip().split('\n'):
250 result
= candidate_pattern
.match(line
)
252 print 'Ignored line "%s"' % line
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
)
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
))
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
, [])
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
):
323 fixed_issues
+= ['Resolved issue %s: %s [%s]' % (issue_id
, desc
, labels
)]
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
:
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
),
336 with
open(temp_notes_fname
, 'w') as f
:
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
:
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
):
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
:
365 if slave_utils
.GSUtilCopy(temp_latest_release_fname
, latest_release_url
,
366 mimetype
='text/plain'):
367 util
.MarkBuildStepError()
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
383 def _WaitForLatestSnapshot(revision
):
384 util
.MarkBuildStepStart('wait_for_snapshot')
386 snapshot_revision
= archive
.GetLatestRevision(archive
.Site
.SNAPSHOT
)
387 if int(snapshot_revision
) >= int(revision
):
389 util
.PrintAndFlush('Waiting for snapshot >= %s, found %s' %
390 (revision
, snapshot_revision
))
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."""
399 if platform_name
== 'win32':
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 '
411 util
.MarkBuildStepStart('Add tools to PATH')
414 if not os
.path
.isdir(path
) or not os
.listdir(path
):
415 print 'Directory "%s" is not found or empty.' % path
419 util
.MarkBuildStepError()
421 os
.environ
['PATH'] += os
.pathsep
+ os
.pathsep
.join(paths
)
425 parser
= optparse
.OptionParser()
427 '', '--android-packages',
428 help=('Comma separated list of application package names, '
429 'if running tests on Android.'))
431 '-r', '--revision', type='int', help='Chromium revision')
433 '', '--update-log', action
='store_true',
434 help='Update the test results log (only applicable to Android)')
435 options
, _
= parser
.parse_args()
438 if util
.IsLinux() and platform_module
.architecture()[0] == '64bit':
440 platform
= '%s%s' % (util
.GetPlatformName(), bitness
)
441 if options
.android_packages
:
446 if platform
== 'android':
447 if not options
.revision
and options
.update_log
:
448 parser
.error('Must supply a --revision with --update-log')
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
)
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)
470 if platform
== 'android':
471 if options
.update_log
:
472 util
.MarkBuildStepStart('update test result log')
473 _UpdateTestResultsLog(platform
, options
.revision
, passed
)
475 _ArchiveGoodBuild(platform
, options
.revision
)
476 _MaybeRelease(platform
)
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__':