Upstream version 7.36.149.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,
80                                         GS_PREBUILTS_URL + '/r',
81                                         zip_path):
82     util.MarkBuildStepError()
83
84   util.Unzip(zip_path, chrome_paths.GetBuildDir(['host_forwarder']))
85
86
87 def _GetTestResultsLog(platform):
88   """Gets the test results log for the given platform.
89
90   Args:
91     platform: The platform that the test results log is for.
92
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()])
107
108
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')
118
119
120 def _UpdateTestResultsLog(platform, revision, passed):
121   """Updates the test results log for the given platform.
122
123   Args:
124     platform: The platform name.
125     revision: The SVN revision number.
126     passed: Boolean indicating whether the tests passed at this revision.
127   """
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)
135
136
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()
141
142
143 def _GetSupportedChromeVersions():
144   """Get the minimum and maximum supported Chrome versions.
145
146   Returns:
147     A tuple of the form (min_version, max_version).
148   """
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)
159
160
161 def _RevisionState(test_results_log, revision):
162   """Check the state of tests at a given SVN revision.
163
164   Considers tests as having passed at a revision if they passed at revisons both
165   before and after.
166
167   Args:
168     test_results_log: A test results log dictionary from _GetTestResultsLog().
169     revision: The revision to check at.
170
171   Returns:
172     'passed', 'failed', or 'unknown'
173   """
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'
191
192
193 def _ArchiveGoodBuild(platform, revision):
194   """Archive chromedriver binary if the build is green."""
195   assert platform != 'android'
196   util.MarkBuildStepStart('archive build')
197
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))
203
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()
209
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)
217
218
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),
223       [])
224   return result == 0
225
226
227 def _MaybeRelease(platform):
228   """Releases a release candidate if conditions are right."""
229   assert platform != 'android'
230
231   version = _GetVersion()
232
233   # Check if the current version has already been released.
234   if _WasReleased(version, platform):
235     return
236
237   # Fetch Android test results.
238   android_test_results = _GetTestResultsLog('android')
239
240   # Fetch release candidates.
241   result, output = slave_utils.GSUtilListBucket(
242       '%s/chromedriver_%s_%s*' % (
243           GS_CONTINUOUS_URL, platform, version),
244       [])
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)))
255
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
271
272
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))
280
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)
286
287   slave_utils.GSUtilCopy(
288       zip_path, '%s/%s/%s' % (GS_CHROMEDRIVER_BUCKET, version, release_name))
289
290   _MaybeUploadReleaseNotes(version)
291   _MaybeUpdateLatestRelease(version)
292
293
294 def _GetWebPageContent(url):
295   """Return the content of the web page specified by the given url."""
296   return urllib2.urlopen(url).read()
297
298
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)
308
309   result, _ = slave_utils.GSUtilListBucket(notes_url, [])
310   if result == 0:
311     return
312
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)]
324
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()
330
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)
338
339   if slave_utils.GSUtilCopy(temp_notes_fname, notes_url, mimetype='text/plain'):
340     util.MarkBuildStepError()
341
342
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)
347
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
353
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
359
360   util.MarkBuildStepStart('updating LATEST_RELEASE to %s' % version)
361
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()
368
369
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)
381
382
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)
393
394
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',
404     ]
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)
422
423
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()
436
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'
443
444   _CleanTmpDir()
445
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)
456
457   _AddToolsToPath(platform)
458
459   cmd = [
460       sys.executable,
461       os.path.join(_THIS_DIR, 'test', 'run_all_tests.py'),
462   ]
463   if platform == 'android':
464     cmd.append('--android-packages=' + options.android_packages)
465
466   passed = (util.RunCommand(cmd) == 0)
467
468   _ArchiveServerLogs()
469
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)
477
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()
483
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')
487
488
489 if __name__ == '__main__':
490   main()