- add sources.
[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 subprocess
20 import sys
21 import tempfile
22 import time
23 import urllib2
24
25 import archive
26 import chrome_paths
27 import util
28
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'
35 SERVER_LOGS_LINK = (
36   'http://chromedriver-data.storage.googleapis.com/server_logs')
37 TEST_LOG_FORMAT = '%s_log.json'
38
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,
43                                'site_config')
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
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   Returns:
89     A dictionary where the keys are SVN revisions and the values are booleans
90     indicating whether the tests passed.
91   """
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)
96   if result:
97     return {}
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()])
102
103
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')
113
114
115 def _UpdateTestResultsLog(platform, revision, passed):
116   """Updates the test results log for the given platform.
117
118   Args:
119     platform: The platform name.
120     revision: The SVN revision number.
121     passed: Boolean indicating whether the tests passed at this revision.
122   """
123   assert isinstance(revision, int), 'The revision must be an integer'
124   log = _GetTestResultsLog(platform)
125   if len(log) > 500:
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)
130
131
132 def _GetVersion():
133   """Get the current chromedriver version."""
134   with open(os.path.join(_THIS_DIR, 'VERSION'), 'r') as f:
135     return f.read().strip()
136
137
138 def _GetSupportedChromeVersions():
139   """Get the minimum and maximum supported Chrome versions.
140
141   Returns:
142     A tuple of the form (min_version, max_version).
143   """
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)
154
155
156 def _RevisionState(test_results_log, revision):
157   """Check the state of tests at a given SVN revision.
158
159   Considers tests as having passed at a revision if they passed at revisons both
160   before and after.
161
162   Args:
163     test_results_log: A test results log dictionary from _GetTestResultsLog().
164     revision: The revision to check at.
165
166   Returns:
167     'passed', 'failed', or 'unknown'
168   """
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):
178     return 'unknown'
179   # No log exists for any prior revision, assume it failed.
180   if index == 0:
181     return '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]]:
184     return 'passed'
185   return 'failed'
186
187
188 def _ArchiveGoodBuild(platform, revision):
189   assert platform != 'android'
190   util.MarkBuildStepStart('archive build')
191
192   server_name = 'chromedriver'
193   if util.IsWindows():
194     server_name += '.exe'
195   zip_path = util.Zip(os.path.join(chrome_paths.GetBuildDir([server_name]),
196                                    server_name))
197
198   build_url = '%s/chromedriver_%s_%s.%s.zip' % (
199       GS_CONTINUOUS_URL, platform, _GetVersion(), revision)
200   if slave_utils.GSUtilCopy(zip_path, build_url):
201     util.MarkBuildStepError()
202
203
204 def _MaybeRelease(platform):
205   """Releases a release candidate if conditions are right."""
206   assert platform != 'android'
207
208   # Check if the current version has already been released.
209   result, _ = slave_utils.GSUtilListBucket(
210       '%s/%s/chromedriver_%s*' % (
211           GS_CHROMEDRIVER_BUCKET, _GetVersion(), platform),
212       [])
213   if result == 0:
214     return
215
216   # Fetch Android test results.
217   android_test_results = _GetTestResultsLog('android')
218
219   # Fetch release candidates.
220   result, output = slave_utils.GSUtilListBucket(
221       '%s/chromedriver_%s_%s*' % (
222           GS_CONTINUOUS_URL, platform, _GetVersion()),
223       [])
224   assert result == 0 and output, 'No release candidates found'
225   candidates = [b.split('/')[-1] for b in output.strip().split('\n')]
226
227   # Release the first candidate build that passed Android, if any.
228   for candidate in candidates:
229     if not candidate.startswith('chromedriver_%s' % platform):
230       continue
231     revision = candidate.split('.')[2]
232     android_result = _RevisionState(android_test_results, int(revision))
233     if android_result == 'failed':
234       print 'Android tests did not pass at revision', revision
235     elif android_result == 'passed':
236       print 'Android tests passed at revision', revision
237       _Release('%s/%s' % (GS_CONTINUOUS_URL, candidate), platform)
238       break
239     else:
240       print 'Android tests have not run at a revision as recent as', revision
241
242
243 def _Release(build, platform):
244   """Releases the given candidate build."""
245   release_name = 'chromedriver_%s.zip' % platform
246   util.MarkBuildStepStart('releasing %s' % release_name)
247   slave_utils.GSUtilCopy(
248       build, '%s/%s/%s' % (GS_CHROMEDRIVER_BUCKET, _GetVersion(), release_name))
249
250   _MaybeUploadReleaseNotes()
251
252
253 def _MaybeUploadReleaseNotes():
254   """Upload release notes if conditions are right."""
255   # Check if the current version has already been released.
256   version = _GetVersion()
257   notes_name = 'notes.txt'
258   notes_url = '%s/%s/%s' % (GS_CHROMEDRIVER_BUCKET, version, notes_name)
259   prev_version = '.'.join([version.split('.')[0],
260                           str(int(version.split('.')[1]) - 1)])
261   prev_notes_url = '%s/%s/%s' % (
262       GS_CHROMEDRIVER_BUCKET, prev_version, notes_name)
263
264   result, _ = slave_utils.GSUtilListBucket(notes_url, [])
265   if result == 0:
266     return
267
268   fixed_issues = []
269   query = ('https://code.google.com/p/chromedriver/issues/csv?'
270            'q=status%3AToBeReleased&colspec=ID%20Summary')
271   issues = StringIO.StringIO(urllib2.urlopen(query).read().split('\n', 1)[1])
272   for issue in csv.reader(issues):
273     if not issue:
274       continue
275     id = issue[0]
276     desc = issue[1]
277     labels = issue[2]
278     fixed_issues += ['Resolved issue %s: %s [%s]' % (id, desc, labels)]
279
280   old_notes = ''
281   temp_notes_fname = tempfile.mkstemp()[1]
282   if not slave_utils.GSUtilDownloadFile(prev_notes_url, temp_notes_fname):
283     with open(temp_notes_fname, 'rb') as f:
284       old_notes = f.read()
285
286   new_notes = '----------ChromeDriver v%s (%s)----------\n%s\n%s\n\n%s' % (
287       version, datetime.date.today().isoformat(),
288       'Supports Chrome v%s-%s' % _GetSupportedChromeVersions(),
289       '\n'.join(fixed_issues),
290       old_notes)
291   with open(temp_notes_fname, 'w') as f:
292     f.write(new_notes)
293
294   if slave_utils.GSUtilCopy(temp_notes_fname, notes_url, mimetype='text/plain'):
295     util.MarkBuildStepError()
296
297
298 def _KillChromes():
299   chrome_map = {
300       'win': 'chrome.exe',
301       'mac': 'Chromium',
302       'linux': 'chrome',
303   }
304   if util.IsWindows():
305     cmd = ['taskkill', '/F', '/IM']
306   else:
307     cmd = ['killall', '-9']
308   cmd.append(chrome_map[util.GetPlatformName()])
309   util.RunCommand(cmd)
310
311
312 def _CleanTmpDir():
313   tmp_dir = tempfile.gettempdir()
314   print 'cleaning temp directory:', tmp_dir
315   for file_name in os.listdir(tmp_dir):
316     file_path = os.path.join(tmp_dir, file_name)
317     if os.path.isdir(file_path):
318       print 'deleting sub-directory', file_path
319       shutil.rmtree(file_path, True)
320     if file_name.startswith('chromedriver_'):
321       print 'deleting file', file_path
322       os.remove(file_path)
323
324
325 def _WaitForLatestSnapshot(revision):
326   util.MarkBuildStepStart('wait_for_snapshot')
327   while True:
328     snapshot_revision = archive.GetLatestRevision(archive.Site.SNAPSHOT)
329     if int(snapshot_revision) >= int(revision):
330       break
331     util.PrintAndFlush('Waiting for snapshot >= %s, found %s' %
332                        (revision, snapshot_revision))
333     time.sleep(60)
334   util.PrintAndFlush('Got snapshot revision %s' % snapshot_revision)
335
336
337 def main():
338   parser = optparse.OptionParser()
339   parser.add_option(
340       '', '--android-packages',
341       help='Comma separated list of application package names, '
342            'if running tests on Android.')
343   parser.add_option(
344       '-r', '--revision', type='int', help='Chromium revision')
345   parser.add_option('', '--update-log', action='store_true',
346       help='Update the test results log (only applicable to Android)')
347   options, _ = parser.parse_args()
348
349   bitness = '32'
350   if util.IsLinux() and platform_module.architecture()[0] == '64bit':
351     bitness = '64'
352   platform = '%s%s' % (util.GetPlatformName(), bitness)
353   if options.android_packages:
354     platform = 'android'
355
356   if platform != 'android':
357     _KillChromes()
358   _CleanTmpDir()
359
360   if platform == 'android':
361     if not options.revision and options.update_log:
362       parser.error('Must supply a --revision with --update-log')
363     _DownloadPrebuilts()
364   else:
365     if not options.revision:
366       parser.error('Must supply a --revision')
367     if platform == 'linux64':
368       _ArchivePrebuilts(options.revision)
369     _WaitForLatestSnapshot(options.revision)
370
371   cmd = [
372       sys.executable,
373       os.path.join(_THIS_DIR, 'test', 'run_all_tests.py'),
374   ]
375   if platform == 'android':
376     cmd.append('--android-packages=' + options.android_packages)
377
378   passed = (util.RunCommand(cmd) == 0)
379
380   _ArchiveServerLogs()
381
382   if platform == 'android':
383     if options.update_log:
384       util.MarkBuildStepStart('update test result log')
385       _UpdateTestResultsLog(platform, options.revision, passed)
386   elif passed:
387     _ArchiveGoodBuild(platform, options.revision)
388     _MaybeRelease(platform)
389
390
391 if __name__ == '__main__':
392   main()