Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / test / chromedriver / run_buildbot_steps.py
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.
5
6 """Runs all the buildbot steps for ChromeDriver except for update/compile."""
7
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
23
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'
34
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)
42
43 import archive
44 import chrome_paths
45 from slave import gsutil_download
46 from slave import slave_utils
47 import util
48
49
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()
59
60
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')
72
73
74 def _DownloadPrebuilts():
75   """Downloads the most recent prebuilts from google storage."""
76   util.MarkBuildStepStart('Download latest chromedriver')
77
78   zip_path = os.path.join(util.MakeTempDir(), 'build.zip')
79   if gsutil_download.DownloadLatestFile(GS_PREBUILTS_URL, 'r', zip_path):
80     util.MarkBuildStepError()
81
82   util.Unzip(zip_path, chrome_paths.GetBuildDir(['host_forwarder']))
83
84
85 def _GetTestResultsLog(platform):
86   """Gets the test results log for the given platform.
87
88   Args:
89     platform: The platform that the test results log is for.
90
91   Returns:
92     A dictionary where the keys are SVN revisions and the values are booleans
93     indicating whether the tests passed.
94   """
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)
99   if result:
100     return {}
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()])
105
106
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')
116
117
118 def _UpdateTestResultsLog(platform, revision, passed):
119   """Updates the test results log for the given platform.
120
121   Args:
122     platform: The platform name.
123     revision: The SVN revision number.
124     passed: Boolean indicating whether the tests passed at this revision.
125   """
126   assert isinstance(revision, int), 'The revision must be an integer'
127   log = _GetTestResultsLog(platform)
128   if len(log) > 500:
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)
133
134
135 def _GetVersion():
136   """Get the current chromedriver version."""
137   with open(os.path.join(_THIS_DIR, 'VERSION'), 'r') as f:
138     return f.read().strip()
139
140
141 def _GetSupportedChromeVersions():
142   """Get the minimum and maximum supported Chrome versions.
143
144   Returns:
145     A tuple of the form (min_version, max_version).
146   """
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)
157
158
159 def _RevisionState(test_results_log, revision):
160   """Check the state of tests at a given SVN revision.
161
162   Considers tests as having passed at a revision if they passed at revisons both
163   before and after.
164
165   Args:
166     test_results_log: A test results log dictionary from _GetTestResultsLog().
167     revision: The revision to check at.
168
169   Returns:
170     'passed', 'failed', or 'unknown'
171   """
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):
181     return 'unknown'
182   # No log exists for any prior revision, assume it failed.
183   if index == 0:
184     return '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]]:
187     return 'passed'
188   return 'failed'
189
190
191 def _ArchiveGoodBuild(platform, revision):
192   """Archive chromedriver binary if the build is green."""
193   assert platform != 'android'
194   util.MarkBuildStepStart('archive build')
195
196   server_name = 'chromedriver'
197   if util.IsWindows():
198     server_name += '.exe'
199   zip_path = util.Zip(os.path.join(chrome_paths.GetBuildDir([server_name]),
200                                    server_name))
201
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()
207
208   (latest_fd, latest_file) = tempfile.mkstemp()
209   os.write(latest_fd, build_name)
210   os.close(latest_fd)
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)
215
216
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),
221       [])
222   return result == 0
223
224
225 def _MaybeRelease(platform):
226   """Releases a release candidate if conditions are right."""
227   assert platform != 'android'
228
229   version = _GetVersion()
230
231   # Check if the current version has already been released.
232   if _WasReleased(version, platform):
233     return
234
235   # Fetch Android test results.
236   android_test_results = _GetTestResultsLog('android')
237
238   # Fetch release candidates.
239   result, output = slave_utils.GSUtilListBucket(
240       '%s/chromedriver_%s_%s*' % (
241           GS_CONTINUOUS_URL, platform, version),
242       [])
243   assert result == 0 and output, 'No release candidates found'
244   candidate_pattern = re.compile(
245       r'.*/chromedriver_%s_%s\.(\d+)\.zip$' % (platform, version))
246   candidates = []
247   for line in output.strip().split('\n'):
248     result = candidate_pattern.match(line)
249     if not result:
250       print 'Ignored line "%s"' % line
251       continue
252     candidates.append(int(result.group(1)))
253
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)
266       break
267     else:
268       print 'Android tests have not run at a revision as recent as', revision
269
270
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))
277
278   _MaybeUploadReleaseNotes(version)
279   _MaybeUpdateLatestRelease(version)
280
281
282 def _GetWebPageContent(url):
283   """Return the content of the web page specified by the given url."""
284   return urllib2.urlopen(url).read()
285
286
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)
296
297   result, _ = slave_utils.GSUtilListBucket(notes_url, [])
298   if result == 0:
299     return
300
301   fixed_issues = []
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):
306     if not issue:
307       continue
308     issue_id = issue[0]
309     desc = issue[1]
310     labels = issue[2]
311     fixed_issues += ['Resolved issue %s: %s [%s]' % (issue_id, desc, labels)]
312
313   old_notes = ''
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:
317       old_notes = f.read()
318
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),
323       old_notes)
324   with open(temp_notes_fname, 'w') as f:
325     f.write(new_notes)
326
327   if slave_utils.GSUtilCopy(temp_notes_fname, notes_url, mimetype='text/plain'):
328     util.MarkBuildStepError()
329
330
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)
335
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:
340     return
341
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):
346       return
347
348   util.MarkBuildStepStart('updating LATEST_RELEASE to %s' % version)
349
350   temp_latest_release_fname = tempfile.mkstemp()[1]
351   with open(temp_latest_release_fname, 'w') as f:
352     f.write(version)
353   if slave_utils.GSUtilCopy(temp_latest_release_fname, latest_release_url,
354                             mimetype='text/plain'):
355     util.MarkBuildStepError()
356
357
358 def _CleanTmpDir():
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
368       os.remove(file_path)
369
370
371 def _WaitForLatestSnapshot(revision):
372   util.MarkBuildStepStart('wait_for_snapshot')
373   while True:
374     snapshot_revision = archive.GetLatestRevision(archive.Site.SNAPSHOT)
375     if int(snapshot_revision) >= int(revision):
376       break
377     util.PrintAndFlush('Waiting for snapshot >= %s, found %s' %
378                        (revision, snapshot_revision))
379     time.sleep(60)
380   util.PrintAndFlush('Got snapshot revision %s' % snapshot_revision)
381
382
383 def _AddToolsToPath(platform_name):
384   """Add some tools like Ant and Java to PATH for testing steps to use."""
385   paths = []
386   error_message = ''
387   if platform_name == 'win32':
388     paths = [
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',
392     ]
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 '
397                      'go/chromedriver.')
398   if paths:
399     util.MarkBuildStepStart('Add tools to PATH')
400     path_missing = False
401     for path in paths:
402       if not os.path.isdir(path) or not os.listdir(path):
403         print 'Directory "%s" is not found or empty.' % path
404         path_missing = True
405     if path_missing:
406       print error_message
407       util.MarkBuildStepError()
408       return
409     os.environ['PATH'] += os.pathsep + os.pathsep.join(paths)
410
411
412 def main():
413   parser = optparse.OptionParser()
414   parser.add_option(
415       '', '--android-packages',
416       help=('Comma separated list of application package names, '
417             'if running tests on Android.'))
418   parser.add_option(
419       '-r', '--revision', type='int', help='Chromium revision')
420   parser.add_option(
421       '', '--update-log', action='store_true',
422       help='Update the test results log (only applicable to Android)')
423   options, _ = parser.parse_args()
424
425   bitness = '32'
426   if util.IsLinux() and platform_module.architecture()[0] == '64bit':
427     bitness = '64'
428   platform = '%s%s' % (util.GetPlatformName(), bitness)
429   if options.android_packages:
430     platform = 'android'
431
432   _CleanTmpDir()
433
434   if platform == 'android':
435     if not options.revision and options.update_log:
436       parser.error('Must supply a --revision with --update-log')
437     _DownloadPrebuilts()
438   else:
439     if not options.revision:
440       parser.error('Must supply a --revision')
441     if platform == 'linux64':
442       _ArchivePrebuilts(options.revision)
443     _WaitForLatestSnapshot(options.revision)
444
445   _AddToolsToPath(platform)
446
447   cmd = [
448       sys.executable,
449       os.path.join(_THIS_DIR, 'test', 'run_all_tests.py'),
450   ]
451   if platform == 'android':
452     cmd.append('--android-packages=' + options.android_packages)
453
454   passed = (util.RunCommand(cmd) == 0)
455
456   _ArchiveServerLogs()
457
458   if platform == 'android':
459     if options.update_log:
460       util.MarkBuildStepStart('update test result log')
461       _UpdateTestResultsLog(platform, options.revision, passed)
462   elif passed:
463     _ArchiveGoodBuild(platform, options.revision)
464     _MaybeRelease(platform)
465
466   if not passed:
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()
471
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')
475
476
477 if __name__ == '__main__':
478   main()