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
29 _THIS_DIR
= os
.path
.abspath(os
.path
.dirname(__file__
))
30 GS_CHROMEDRIVER_BUCKET
= 'gs://chromedriver'
31 GS_CHROMEDRIVER_DATA_BUCKET
= 'gs://chromedriver-data'
32 GS_CONTINUOUS_URL
= GS_CHROMEDRIVER_DATA_BUCKET
+ '/continuous'
33 GS_PREBUILTS_URL
= GS_CHROMEDRIVER_DATA_BUCKET
+ '/prebuilts'
34 GS_SERVER_LOGS_URL
= GS_CHROMEDRIVER_DATA_BUCKET
+ '/server_logs'
36 'http://chromedriver-data.storage.googleapis.com/server_logs')
37 TEST_LOG_FORMAT
= '%s_log.json'
39 SCRIPT_DIR
= os
.path
.join(_THIS_DIR
, os
.pardir
, os
.pardir
, os
.pardir
, os
.pardir
,
40 os
.pardir
, os
.pardir
, os
.pardir
, 'scripts')
41 SITE_CONFIG_DIR
= os
.path
.join(_THIS_DIR
, os
.pardir
, os
.pardir
, os
.pardir
,
42 os
.pardir
, os
.pardir
, os
.pardir
, os
.pardir
,
44 sys
.path
.append(SCRIPT_DIR
)
45 sys
.path
.append(SITE_CONFIG_DIR
)
46 from slave
import gsutil_download
47 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 A dictionary where the keys are SVN revisions and the values are booleans
90 indicating whether the tests passed.
92 temp_log
= tempfile
.mkstemp()[1]
93 log_name
= TEST_LOG_FORMAT
% platform
94 result
= slave_utils
.GSUtilDownloadFile(
95 '%s/%s' % (GS_CHROMEDRIVER_DATA_BUCKET
, log_name
), temp_log
)
98 with
open(temp_log
, 'rb') as log_file
:
99 json_dict
= json
.load(log_file
)
100 # Workaround for json encoding dictionary keys as strings.
101 return dict([(int(v
[0]), v
[1]) for v
in json_dict
.items()])
104 def _PutTestResultsLog(platform
, test_results_log
):
105 """Pushes the given test results log to google storage."""
106 temp_dir
= util
.MakeTempDir()
107 log_name
= TEST_LOG_FORMAT
% platform
108 log_path
= os
.path
.join(temp_dir
, log_name
)
109 with
open(log_path
, 'wb') as log_file
:
110 json
.dump(test_results_log
, log_file
)
111 if slave_utils
.GSUtilCopyFile(log_path
, GS_CHROMEDRIVER_DATA_BUCKET
):
112 raise Exception('Failed to upload test results log to google storage')
115 def _UpdateTestResultsLog(platform
, revision
, passed
):
116 """Updates the test results log for the given platform.
119 platform: The platform name.
120 revision: The SVN revision number.
121 passed: Boolean indicating whether the tests passed at this revision.
123 assert isinstance(revision
, int), 'The revision must be an integer'
124 log
= _GetTestResultsLog(platform
)
126 del log
[min(log
.keys())]
127 assert revision
not in log
, 'Results already exist for revision %s' % revision
128 log
[revision
] = bool(passed
)
129 _PutTestResultsLog(platform
, log
)
133 """Get the current chromedriver version."""
134 with
open(os
.path
.join(_THIS_DIR
, 'VERSION'), 'r') as f
:
135 return f
.read().strip()
138 def _GetSupportedChromeVersions():
139 """Get the minimum and maximum supported Chrome versions.
142 A tuple of the form (min_version, max_version).
144 # Minimum supported Chrome version is embedded as:
145 # const int kMinimumSupportedChromeVersion[] = {27, 0, 1453, 0};
146 with
open(os
.path
.join(_THIS_DIR
, 'chrome', 'version.cc'), 'r') as f
:
147 lines
= f
.readlines()
148 chrome_min_version_line
= filter(
149 lambda x
: 'kMinimumSupportedChromeVersion' in x
, lines
)
150 chrome_min_version
= chrome_min_version_line
[0].split('{')[1].split(',')[0]
151 with
open(os
.path
.join(chrome_paths
.GetSrc(), 'chrome', 'VERSION'), 'r') as f
:
152 chrome_max_version
= f
.readlines()[0].split('=')[1].strip()
153 return (chrome_min_version
, chrome_max_version
)
156 def _RevisionState(test_results_log
, revision
):
157 """Check the state of tests at a given SVN revision.
159 Considers tests as having passed at a revision if they passed at revisons both
163 test_results_log: A test results log dictionary from _GetTestResultsLog().
164 revision: The revision to check at.
167 'passed', 'failed', or 'unknown'
169 assert isinstance(revision
, int), 'The revision must be an integer'
170 keys
= sorted(test_results_log
.keys())
171 # Return passed if the exact revision passed on Android.
172 if revision
in test_results_log
:
173 return 'passed' if test_results_log
[revision
] else 'failed'
174 # Tests were not run on this exact revision on Android.
175 index
= bisect
.bisect_right(keys
, revision
)
176 # Tests have not yet run on Android at or above this revision.
177 if index
== len(test_results_log
):
179 # No log exists for any prior revision, assume it failed.
182 # Return passed if the revisions on both sides passed.
183 if test_results_log
[keys
[index
]] and test_results_log
[keys
[index
- 1]]:
188 def _ArchiveGoodBuild(platform
, revision
):
189 assert platform
!= 'android'
190 util
.MarkBuildStepStart('archive build')
192 server_name
= 'chromedriver'
194 server_name
+= '.exe'
195 zip_path
= util
.Zip(os
.path
.join(chrome_paths
.GetBuildDir([server_name
]),
198 build_name
= 'chromedriver_%s_%s.%s.zip' % (
199 platform
, _GetVersion(), revision
)
200 build_url
= '%s/%s' % (GS_CONTINUOUS_URL
, build_name
)
201 if slave_utils
.GSUtilCopy(zip_path
, build_url
):
202 util
.MarkBuildStepError()
204 (latest_fd
, latest_file
) = tempfile
.mkstemp()
205 os
.write(latest_fd
, build_name
)
207 latest_url
= '%s/latest_%s' % (GS_CONTINUOUS_URL
, platform
)
208 if slave_utils
.GSUtilCopy(latest_file
, latest_url
, mimetype
='text/plain'):
209 util
.MarkBuildStepError()
210 os
.remove(latest_file
)
213 def _MaybeRelease(platform
):
214 """Releases a release candidate if conditions are right."""
215 assert platform
!= 'android'
217 # Check if the current version has already been released.
218 result
, _
= slave_utils
.GSUtilListBucket(
219 '%s/%s/chromedriver_%s*' % (
220 GS_CHROMEDRIVER_BUCKET
, _GetVersion(), platform
),
225 # Fetch Android test results.
226 android_test_results
= _GetTestResultsLog('android')
228 # Fetch release candidates.
229 result
, output
= slave_utils
.GSUtilListBucket(
230 '%s/chromedriver_%s_%s*' % (
231 GS_CONTINUOUS_URL
, platform
, _GetVersion()),
233 assert result
== 0 and output
, 'No release candidates found'
234 candidates
= [b
.split('/')[-1] for b
in output
.strip().split('\n')]
235 candidate_pattern
= re
.compile('chromedriver_%s_%s\.\d+\.zip'
236 % (platform
, _GetVersion()))
238 # Release the first candidate build that passed Android, if any.
239 for candidate
in candidates
:
240 if not candidate_pattern
.match(candidate
):
241 print 'Ignored candidate "%s"' % candidate
243 revision
= candidate
.split('.')[-2]
244 android_result
= _RevisionState(android_test_results
, int(revision
))
245 if android_result
== 'failed':
246 print 'Android tests did not pass at revision', revision
247 elif android_result
== 'passed':
248 print 'Android tests passed at revision', revision
249 _Release('%s/%s' % (GS_CONTINUOUS_URL
, candidate
), platform
)
252 print 'Android tests have not run at a revision as recent as', revision
255 def _Release(build
, platform
):
256 """Releases the given candidate build."""
257 release_name
= 'chromedriver_%s.zip' % platform
258 util
.MarkBuildStepStart('releasing %s' % release_name
)
259 slave_utils
.GSUtilCopy(
260 build
, '%s/%s/%s' % (GS_CHROMEDRIVER_BUCKET
, _GetVersion(), release_name
))
262 _MaybeUploadReleaseNotes()
265 def _MaybeUploadReleaseNotes():
266 """Upload release notes if conditions are right."""
267 # Check if the current version has already been released.
268 version
= _GetVersion()
269 notes_name
= 'notes.txt'
270 notes_url
= '%s/%s/%s' % (GS_CHROMEDRIVER_BUCKET
, version
, notes_name
)
271 prev_version
= '.'.join([version
.split('.')[0],
272 str(int(version
.split('.')[1]) - 1)])
273 prev_notes_url
= '%s/%s/%s' % (
274 GS_CHROMEDRIVER_BUCKET
, prev_version
, notes_name
)
276 result
, _
= slave_utils
.GSUtilListBucket(notes_url
, [])
281 query
= ('https://code.google.com/p/chromedriver/issues/csv?'
282 'q=status%3AToBeReleased&colspec=ID%20Summary')
283 issues
= StringIO
.StringIO(urllib2
.urlopen(query
).read().split('\n', 1)[1])
284 for issue
in csv
.reader(issues
):
290 fixed_issues
+= ['Resolved issue %s: %s [%s]' % (id, desc
, labels
)]
293 temp_notes_fname
= tempfile
.mkstemp()[1]
294 if not slave_utils
.GSUtilDownloadFile(prev_notes_url
, temp_notes_fname
):
295 with
open(temp_notes_fname
, 'rb') as f
:
298 new_notes
= '----------ChromeDriver v%s (%s)----------\n%s\n%s\n\n%s' % (
299 version
, datetime
.date
.today().isoformat(),
300 'Supports Chrome v%s-%s' % _GetSupportedChromeVersions(),
301 '\n'.join(fixed_issues
),
303 with
open(temp_notes_fname
, 'w') as f
:
306 if slave_utils
.GSUtilCopy(temp_notes_fname
, notes_url
, mimetype
='text/plain'):
307 util
.MarkBuildStepError()
317 cmd
= ['taskkill', '/F', '/IM']
319 cmd
= ['killall', '-9']
320 cmd
.append(chrome_map
[util
.GetPlatformName()])
325 tmp_dir
= tempfile
.gettempdir()
326 print 'cleaning temp directory:', tmp_dir
327 for file_name
in os
.listdir(tmp_dir
):
328 file_path
= os
.path
.join(tmp_dir
, file_name
)
329 if os
.path
.isdir(file_path
):
330 print 'deleting sub-directory', file_path
331 shutil
.rmtree(file_path
, True)
332 if file_name
.startswith('chromedriver_'):
333 print 'deleting file', file_path
337 def _WaitForLatestSnapshot(revision
):
338 util
.MarkBuildStepStart('wait_for_snapshot')
340 snapshot_revision
= archive
.GetLatestRevision(archive
.Site
.SNAPSHOT
)
341 if int(snapshot_revision
) >= int(revision
):
343 util
.PrintAndFlush('Waiting for snapshot >= %s, found %s' %
344 (revision
, snapshot_revision
))
346 util
.PrintAndFlush('Got snapshot revision %s' % snapshot_revision
)
349 def _AddToolsToPath(platform_name
):
350 """Add some tools like Ant and Java to PATH for testing steps to use."""
353 if platform_name
== 'win32':
355 # Path to Ant and Java, required for the java acceptance tests.
356 'C:\\Program Files (x86)\\Java\\ant\\bin',
357 'C:\\Program Files (x86)\\Java\\jre\\bin',
359 error_message
= ('Java test steps will fail as expected and '
360 'they can be ignored.\n'
361 'Ant, Java or others might not be installed on bot.\n'
362 'Please refer to page "WATERFALL" on site '
365 util
.MarkBuildStepStart('Add tools to PATH')
368 if not os
.path
.isdir(path
) or not os
.listdir(path
):
369 print 'Directory "%s" is not found or empty.' % path
373 util
.MarkBuildStepError()
375 os
.environ
['PATH'] += os
.pathsep
+ os
.pathsep
.join(paths
)
379 parser
= optparse
.OptionParser()
381 '', '--android-packages',
382 help='Comma separated list of application package names, '
383 'if running tests on Android.')
385 '-r', '--revision', type='int', help='Chromium revision')
386 parser
.add_option('', '--update-log', action
='store_true',
387 help='Update the test results log (only applicable to Android)')
388 options
, _
= parser
.parse_args()
391 if util
.IsLinux() and platform_module
.architecture()[0] == '64bit':
393 platform
= '%s%s' % (util
.GetPlatformName(), bitness
)
394 if options
.android_packages
:
397 if platform
!= 'android':
401 if platform
== 'android':
402 if not options
.revision
and options
.update_log
:
403 parser
.error('Must supply a --revision with --update-log')
406 if not options
.revision
:
407 parser
.error('Must supply a --revision')
408 if platform
== 'linux64':
409 _ArchivePrebuilts(options
.revision
)
410 _WaitForLatestSnapshot(options
.revision
)
412 _AddToolsToPath(platform
)
416 os
.path
.join(_THIS_DIR
, 'test', 'run_all_tests.py'),
418 if platform
== 'android':
419 cmd
.append('--android-packages=' + options
.android_packages
)
421 passed
= (util
.RunCommand(cmd
) == 0)
425 if platform
== 'android':
426 if options
.update_log
:
427 util
.MarkBuildStepStart('update test result log')
428 _UpdateTestResultsLog(platform
, options
.revision
, passed
)
430 _ArchiveGoodBuild(platform
, options
.revision
)
431 _MaybeRelease(platform
)
434 # Make sure the build is red if there is some uncaught exception during
435 # running run_all_tests.py.
436 util
.MarkBuildStepStart('run_all_tests.py')
437 util
.MarkBuildStepError()
439 # Add a "cleanup" step so that errors from runtest.py or bb_device_steps.py
440 # (which invoke this script) are kept in thier own build step.
441 util
.MarkBuildStepStart('cleanup')
444 if __name__
== '__main__':