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
, 'r', zip_path
):
80 util
.MarkBuildStepError()
82 util
.Unzip(zip_path
, chrome_paths
.GetBuildDir(['host_forwarder']))
85 def _GetTestResultsLog(platform
):
86 """Gets the test results log for the given platform.
89 platform: The platform that the test results log is for.
92 A dictionary where the keys are SVN revisions and the values are booleans
93 indicating whether the tests passed.
95 temp_log
= tempfile
.mkstemp()[1]
96 log_name
= TEST_LOG_FORMAT
% platform
97 result
= slave_utils
.GSUtilDownloadFile(
98 '%s/%s' % (GS_CHROMEDRIVER_DATA_BUCKET
, log_name
), temp_log
)
101 with
open(temp_log
, 'rb') as log_file
:
102 json_dict
= json
.load(log_file
)
103 # Workaround for json encoding dictionary keys as strings.
104 return dict([(int(v
[0]), v
[1]) for v
in json_dict
.items()])
107 def _PutTestResultsLog(platform
, test_results_log
):
108 """Pushes the given test results log to google storage."""
109 temp_dir
= util
.MakeTempDir()
110 log_name
= TEST_LOG_FORMAT
% platform
111 log_path
= os
.path
.join(temp_dir
, log_name
)
112 with
open(log_path
, 'wb') as log_file
:
113 json
.dump(test_results_log
, log_file
)
114 if slave_utils
.GSUtilCopyFile(log_path
, GS_CHROMEDRIVER_DATA_BUCKET
):
115 raise Exception('Failed to upload test results log to google storage')
118 def _UpdateTestResultsLog(platform
, revision
, passed
):
119 """Updates the test results log for the given platform.
122 platform: The platform name.
123 revision: The SVN revision number.
124 passed: Boolean indicating whether the tests passed at this revision.
126 assert isinstance(revision
, int), 'The revision must be an integer'
127 log
= _GetTestResultsLog(platform
)
129 del log
[min(log
.keys())]
130 assert revision
not in log
, 'Results already exist for revision %s' % revision
131 log
[revision
] = bool(passed
)
132 _PutTestResultsLog(platform
, log
)
136 """Get the current chromedriver version."""
137 with
open(os
.path
.join(_THIS_DIR
, 'VERSION'), 'r') as f
:
138 return f
.read().strip()
141 def _GetSupportedChromeVersions():
142 """Get the minimum and maximum supported Chrome versions.
145 A tuple of the form (min_version, max_version).
147 # Minimum supported Chrome version is embedded as:
148 # const int kMinimumSupportedChromeVersion[] = {27, 0, 1453, 0};
149 with
open(os
.path
.join(_THIS_DIR
, 'chrome', 'version.cc'), 'r') as f
:
150 lines
= f
.readlines()
151 chrome_min_version_line
= [
152 x
for x
in lines
if 'kMinimumSupportedChromeVersion' in x
]
153 chrome_min_version
= chrome_min_version_line
[0].split('{')[1].split(',')[0]
154 with
open(os
.path
.join(chrome_paths
.GetSrc(), 'chrome', 'VERSION'), 'r') as f
:
155 chrome_max_version
= f
.readlines()[0].split('=')[1].strip()
156 return (chrome_min_version
, chrome_max_version
)
159 def _RevisionState(test_results_log
, revision
):
160 """Check the state of tests at a given SVN revision.
162 Considers tests as having passed at a revision if they passed at revisons both
166 test_results_log: A test results log dictionary from _GetTestResultsLog().
167 revision: The revision to check at.
170 'passed', 'failed', or 'unknown'
172 assert isinstance(revision
, int), 'The revision must be an integer'
173 keys
= sorted(test_results_log
.keys())
174 # Return passed if the exact revision passed on Android.
175 if revision
in test_results_log
:
176 return 'passed' if test_results_log
[revision
] else 'failed'
177 # Tests were not run on this exact revision on Android.
178 index
= bisect
.bisect_right(keys
, revision
)
179 # Tests have not yet run on Android at or above this revision.
180 if index
== len(test_results_log
):
182 # No log exists for any prior revision, assume it failed.
185 # Return passed if the revisions on both sides passed.
186 if test_results_log
[keys
[index
]] and test_results_log
[keys
[index
- 1]]:
191 def _ArchiveGoodBuild(platform
, revision
):
192 """Archive chromedriver binary if the build is green."""
193 assert platform
!= 'android'
194 util
.MarkBuildStepStart('archive build')
196 server_name
= 'chromedriver'
198 server_name
+= '.exe'
199 zip_path
= util
.Zip(os
.path
.join(chrome_paths
.GetBuildDir([server_name
]),
202 build_name
= 'chromedriver_%s_%s.%s.zip' % (
203 platform
, _GetVersion(), revision
)
204 build_url
= '%s/%s' % (GS_CONTINUOUS_URL
, build_name
)
205 if slave_utils
.GSUtilCopy(zip_path
, build_url
):
206 util
.MarkBuildStepError()
208 (latest_fd
, latest_file
) = tempfile
.mkstemp()
209 os
.write(latest_fd
, build_name
)
211 latest_url
= '%s/latest_%s' % (GS_CONTINUOUS_URL
, platform
)
212 if slave_utils
.GSUtilCopy(latest_file
, latest_url
, mimetype
='text/plain'):
213 util
.MarkBuildStepError()
214 os
.remove(latest_file
)
217 def _WasReleased(version
, platform
):
218 """Check if the specified version is released for the given platform."""
219 result
, _
= slave_utils
.GSUtilListBucket(
220 '%s/%s/chromedriver_%s.zip' % (GS_CHROMEDRIVER_BUCKET
, version
, platform
),
225 def _MaybeRelease(platform
):
226 """Releases a release candidate if conditions are right."""
227 assert platform
!= 'android'
229 version
= _GetVersion()
231 # Check if the current version has already been released.
232 if _WasReleased(version
, platform
):
235 # Fetch Android test results.
236 android_test_results
= _GetTestResultsLog('android')
238 # Fetch release candidates.
239 result
, output
= slave_utils
.GSUtilListBucket(
240 '%s/chromedriver_%s_%s*' % (
241 GS_CONTINUOUS_URL
, platform
, version
),
243 assert result
== 0 and output
, 'No release candidates found'
244 candidate_pattern
= re
.compile(
245 r
'.*/chromedriver_%s_%s\.(\d+)\.zip$' % (platform
, version
))
247 for line
in output
.strip().split('\n'):
248 result
= candidate_pattern
.match(line
)
250 print 'Ignored line "%s"' % line
252 candidates
.append(int(result
.group(1)))
254 # Release the latest candidate build that passed Android, if any.
255 # In this way, if a hot fix is needed, we can delete the release from
256 # the chromedriver bucket instead of bumping up the release version number.
257 candidates
.sort(reverse
=True)
258 for revision
in candidates
:
259 android_result
= _RevisionState(android_test_results
, revision
)
260 if android_result
== 'failed':
261 print 'Android tests did not pass at revision', revision
262 elif android_result
== 'passed':
263 print 'Android tests passed at revision', revision
264 candidate
= 'chromedriver_%s_%s.%s.zip' % (platform
, version
, revision
)
265 _Release('%s/%s' % (GS_CONTINUOUS_URL
, candidate
), version
, platform
)
268 print 'Android tests have not run at a revision as recent as', revision
271 def _Release(build
, version
, platform
):
272 """Releases the given candidate build."""
273 release_name
= 'chromedriver_%s.zip' % platform
274 util
.MarkBuildStepStart('releasing %s' % release_name
)
275 slave_utils
.GSUtilCopy(
276 build
, '%s/%s/%s' % (GS_CHROMEDRIVER_BUCKET
, version
, release_name
))
278 _MaybeUploadReleaseNotes(version
)
279 _MaybeUpdateLatestRelease(version
)
282 def _GetWebPageContent(url
):
283 """Return the content of the web page specified by the given url."""
284 return urllib2
.urlopen(url
).read()
287 def _MaybeUploadReleaseNotes(version
):
288 """Upload release notes if conditions are right."""
289 # Check if the current version has already been released.
290 notes_name
= 'notes.txt'
291 notes_url
= '%s/%s/%s' % (GS_CHROMEDRIVER_BUCKET
, version
, notes_name
)
292 prev_version
= '.'.join([version
.split('.')[0],
293 str(int(version
.split('.')[1]) - 1)])
294 prev_notes_url
= '%s/%s/%s' % (
295 GS_CHROMEDRIVER_BUCKET
, prev_version
, notes_name
)
297 result
, _
= slave_utils
.GSUtilListBucket(notes_url
, [])
302 query
= ('https://code.google.com/p/chromedriver/issues/csv?'
303 'q=status%3AToBeReleased&colspec=ID%20Summary')
304 issues
= StringIO
.StringIO(_GetWebPageContent(query
).split('\n', 1)[1])
305 for issue
in csv
.reader(issues
):
311 fixed_issues
+= ['Resolved issue %s: %s [%s]' % (issue_id
, desc
, labels
)]
314 temp_notes_fname
= tempfile
.mkstemp()[1]
315 if not slave_utils
.GSUtilDownloadFile(prev_notes_url
, temp_notes_fname
):
316 with
open(temp_notes_fname
, 'rb') as f
:
319 new_notes
= '----------ChromeDriver v%s (%s)----------\n%s\n%s\n\n%s' % (
320 version
, datetime
.date
.today().isoformat(),
321 'Supports Chrome v%s-%s' % _GetSupportedChromeVersions(),
322 '\n'.join(fixed_issues
),
324 with
open(temp_notes_fname
, 'w') as f
:
327 if slave_utils
.GSUtilCopy(temp_notes_fname
, notes_url
, mimetype
='text/plain'):
328 util
.MarkBuildStepError()
331 def _MaybeUpdateLatestRelease(version
):
332 """Update the file LATEST_RELEASE with the latest release version number."""
333 latest_release_fname
= 'LATEST_RELEASE'
334 latest_release_url
= '%s/%s' % (GS_CHROMEDRIVER_BUCKET
, latest_release_fname
)
336 # Check if LATEST_RELEASE is up-to-date.
337 latest_released_version
= _GetWebPageContent(
338 '%s/%s' % (GS_CHROMEDRIVER_RELEASE_URL
, latest_release_fname
))
339 if version
== latest_released_version
:
342 # Check if chromedriver was released on all supported platforms.
343 supported_platforms
= ['linux32', 'linux64', 'mac32', 'win32']
344 for platform
in supported_platforms
:
345 if not _WasReleased(version
, platform
):
348 util
.MarkBuildStepStart('updating LATEST_RELEASE to %s' % version
)
350 temp_latest_release_fname
= tempfile
.mkstemp()[1]
351 with
open(temp_latest_release_fname
, 'w') as f
:
353 if slave_utils
.GSUtilCopy(temp_latest_release_fname
, latest_release_url
,
354 mimetype
='text/plain'):
355 util
.MarkBuildStepError()
359 tmp_dir
= tempfile
.gettempdir()
360 print 'cleaning temp directory:', tmp_dir
361 for file_name
in os
.listdir(tmp_dir
):
362 file_path
= os
.path
.join(tmp_dir
, file_name
)
363 if os
.path
.isdir(file_path
):
364 print 'deleting sub-directory', file_path
365 shutil
.rmtree(file_path
, True)
366 if file_name
.startswith('chromedriver_'):
367 print 'deleting file', file_path
371 def _WaitForLatestSnapshot(revision
):
372 util
.MarkBuildStepStart('wait_for_snapshot')
374 snapshot_revision
= archive
.GetLatestRevision(archive
.Site
.SNAPSHOT
)
375 if int(snapshot_revision
) >= int(revision
):
377 util
.PrintAndFlush('Waiting for snapshot >= %s, found %s' %
378 (revision
, snapshot_revision
))
380 util
.PrintAndFlush('Got snapshot revision %s' % snapshot_revision
)
383 def _AddToolsToPath(platform_name
):
384 """Add some tools like Ant and Java to PATH for testing steps to use."""
387 if platform_name
== 'win32':
389 # Path to Ant and Java, required for the java acceptance tests.
390 'C:\\Program Files (x86)\\Java\\ant\\bin',
391 'C:\\Program Files (x86)\\Java\\jre\\bin',
393 error_message
= ('Java test steps will fail as expected and '
394 'they can be ignored.\n'
395 'Ant, Java or others might not be installed on bot.\n'
396 'Please refer to page "WATERFALL" on site '
399 util
.MarkBuildStepStart('Add tools to PATH')
402 if not os
.path
.isdir(path
) or not os
.listdir(path
):
403 print 'Directory "%s" is not found or empty.' % path
407 util
.MarkBuildStepError()
409 os
.environ
['PATH'] += os
.pathsep
+ os
.pathsep
.join(paths
)
413 parser
= optparse
.OptionParser()
415 '', '--android-packages',
416 help=('Comma separated list of application package names, '
417 'if running tests on Android.'))
419 '-r', '--revision', type='int', help='Chromium revision')
421 '', '--update-log', action
='store_true',
422 help='Update the test results log (only applicable to Android)')
423 options
, _
= parser
.parse_args()
426 if util
.IsLinux() and platform_module
.architecture()[0] == '64bit':
428 platform
= '%s%s' % (util
.GetPlatformName(), bitness
)
429 if options
.android_packages
:
434 if platform
== 'android':
435 if not options
.revision
and options
.update_log
:
436 parser
.error('Must supply a --revision with --update-log')
439 if not options
.revision
:
440 parser
.error('Must supply a --revision')
441 if platform
== 'linux64':
442 _ArchivePrebuilts(options
.revision
)
443 _WaitForLatestSnapshot(options
.revision
)
445 _AddToolsToPath(platform
)
449 os
.path
.join(_THIS_DIR
, 'test', 'run_all_tests.py'),
451 if platform
== 'android':
452 cmd
.append('--android-packages=' + options
.android_packages
)
454 passed
= (util
.RunCommand(cmd
) == 0)
458 if platform
== 'android':
459 if options
.update_log
:
460 util
.MarkBuildStepStart('update test result log')
461 _UpdateTestResultsLog(platform
, options
.revision
, passed
)
463 _ArchiveGoodBuild(platform
, options
.revision
)
464 _MaybeRelease(platform
)
467 # Make sure the build is red if there is some uncaught exception during
468 # running run_all_tests.py.
469 util
.MarkBuildStepStart('run_all_tests.py')
470 util
.MarkBuildStepError()
472 # Add a "cleanup" step so that errors from runtest.py or bb_device_steps.py
473 # (which invoke this script) are kept in thier own build step.
474 util
.MarkBuildStepStart('cleanup')
477 if __name__
== '__main__':