blob: 4713daf5c710a37f4e68284061141b4686b8383c [file] [log] [blame]
Rico Wind800fd712018-09-24 11:29:33 +02001#!/usr/bin/env python
2# Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
3# for details. All rights reserved. Use of this source code is governed by a
4# BSD-style license that can be found in the LICENSE file.
5
6# Run all internal tests, archive result to cloud storage.
Rico Wind139eece2018-09-25 09:42:09 +02007# In the continuous operation flow we have a tester continuously checking
8# a specific cloud storage location for a file with a git hash.
9# If the file is there, the tester will remove the file, and add another
10# file stating that this is now being run. After successfully running,
11# the tester will add yet another file, and remove the last one.
12# Complete flow with states:
13# 1:
14# BOT:
15# Add file READY_FOR_TESTING (contains git hash)
16# Wait until file TESTING_COMPLETE exists (contains git hash)
17# Timeout if no progress for RUN_TIMEOUT
18# Cleanup READY_FOR_TESTING and TESTING
19# 2:
20# TESTER:
21# Replace file READY_FOR_TESTING by TESTING (contains git hash)
22# Run tests for git hash
23# Upload commit specific logs if failures
24# Upload git specific overall status file (failed or succeeded)
25# Replace file TESTING by TESTING_COMPLETE (contains git hash)
26# 3:
27# BOT:
28# Read overall status
29# Delete TESTING_COMPLETE
30# Exit based on status
Rico Wind800fd712018-09-24 11:29:33 +020031
32import optparse
33import os
34import subprocess
35import sys
36import time
37import utils
38
Rico Wind139eece2018-09-25 09:42:09 +020039# How often the bot/tester should check state
40PULL_DELAY = 30
Rico Wind800fd712018-09-24 11:29:33 +020041BUCKET = 'r8-test-results'
42TEST_RESULT_DIR = 'internal'
43
Rico Wind139eece2018-09-25 09:42:09 +020044# Magic files
45READY_FOR_TESTING = 'READY_FOR_TESTING'
46TESTING = 'TESTING'
47TESTING_COMPLETE = 'TESTING_COMPLETE'
48
49ALL_MAGIC = [READY_FOR_TESTING, TESTING, TESTING_COMPLETE]
50
51# Log file names
52STDERR = 'stderr'
53STDOUT = 'stdout'
54EXITCODE = 'exitcode'
55TIMED_OUT = 'timed_out'
56
Rico Wind6847d132018-09-26 08:18:48 +020057TEST_COMMANDS = [
58 ['tools/test.py', '--only_internal'],
59 ['tools/run_on_app.py', '--ignore-java-version','--run-all', '--out=out']
60]
61
62# Command timeout, in seconds.
Rico Windf021d832018-12-13 11:29:22 +010063RUN_TIMEOUT = 7200
64BOT_RUN_TIMEOUT = RUN_TIMEOUT * len(TEST_COMMANDS)
Rico Wind6847d132018-09-26 08:18:48 +020065
Rico Wind1200f512018-09-26 08:48:37 +020066def log(str):
Rico Windffccab12018-09-26 12:39:42 +020067 print("%s: %s" % (time.strftime("%c"), str))
Rico Wind1200f512018-09-26 08:48:37 +020068
Rico Wind800fd712018-09-24 11:29:33 +020069def ParseOptions():
70 result = optparse.OptionParser()
71 result.add_option('--continuous',
72 help='Continuously run internal tests and post results to GCS.',
73 default=False, action='store_true')
Rico Wind4fd2dda2018-09-26 17:41:45 +020074 result.add_option('--print_logs',
75 help='Fetch logs from gcs and print them, takes the commit to print for.',
76 default=None)
Rico Wind139eece2018-09-25 09:42:09 +020077 result.add_option('--bot',
78 help='Run in bot mode, i.e., scheduling runs.',
79 default=False, action='store_true')
Rico Wind800fd712018-09-24 11:29:33 +020080 result.add_option('--archive',
81 help='Post result to GCS, implied by --continuous',
82 default=False, action='store_true')
83 return result.parse_args()
84
85def get_own_file_content():
86 with open(sys.argv[0], 'r') as us:
87 return us.read()
88
89def restart_if_new_version(original_content):
90 new_content = get_own_file_content()
91 if new_content != original_content:
Rico Wind1200f512018-09-26 08:48:37 +020092 log('Restarting tools/internal_test.py, content changed')
Rico Wind800fd712018-09-24 11:29:33 +020093 os.execv(sys.argv[0], sys.argv)
94
Rico Wind139eece2018-09-25 09:42:09 +020095def ensure_git_clean():
Rico Wind800fd712018-09-24 11:29:33 +020096 # Ensure clean git repo.
97 diff = subprocess.check_output(['git', 'diff'])
98 if len(diff) > 0:
Rico Wind1200f512018-09-26 08:48:37 +020099 log('Local modifications to the git repo, exiting')
Rico Wind800fd712018-09-24 11:29:33 +0200100 sys.exit(1)
Rico Wind139eece2018-09-25 09:42:09 +0200101
102def git_pull():
103 ensure_git_clean()
Rico Wind2a19d932018-09-25 16:48:56 +0200104 subprocess.check_call(['git', 'checkout', 'master'])
Rico Wind800fd712018-09-24 11:29:33 +0200105 subprocess.check_call(['git', 'pull'])
106 return utils.get_HEAD_sha1()
107
Rico Wind139eece2018-09-25 09:42:09 +0200108def git_checkout(git_hash):
109 ensure_git_clean()
110 # Ensure that we are up to date to get the commit.
111 git_pull()
112 subprocess.check_call(['git', 'checkout', git_hash])
113 return utils.get_HEAD_sha1()
114
115def get_test_result_dir():
116 return os.path.join(BUCKET, TEST_RESULT_DIR)
117
Rico Wind800fd712018-09-24 11:29:33 +0200118def get_sha_destination(sha):
Rico Wind139eece2018-09-25 09:42:09 +0200119 return os.path.join(get_test_result_dir(), sha)
Rico Wind800fd712018-09-24 11:29:33 +0200120
121def archive_status(failed):
122 gs_destination = 'gs://%s' % get_sha_destination(utils.get_HEAD_sha1())
123 archive_value('status', gs_destination, failed)
124
Rico Wind139eece2018-09-25 09:42:09 +0200125def get_status(sha):
126 gs_destination = 'gs://%s/status' % get_sha_destination(sha)
127 return utils.cat_file_on_cloud_storage(gs_destination)
128
Rico Wind800fd712018-09-24 11:29:33 +0200129def archive_file(name, gs_dir, src_file):
130 gs_file = '%s/%s' % (gs_dir, name)
131 utils.upload_file_to_cloud_storage(src_file, gs_file, public_read=False)
132
133def archive_value(name, gs_dir, value):
134 with utils.TempDir() as temp:
135 tempfile = os.path.join(temp, name);
136 with open(tempfile, 'w') as f:
137 f.write(str(value))
138 archive_file(name, gs_dir, tempfile)
139
140def archive_log(stdout, stderr, exitcode, timed_out, cmd):
141 sha = utils.get_HEAD_sha1()
Rico Wind139eece2018-09-25 09:42:09 +0200142 cmd_dir = cmd.replace(' ', '_').replace('/', '_')
Rico Wind800fd712018-09-24 11:29:33 +0200143 destination = os.path.join(get_sha_destination(sha), cmd_dir)
144 gs_destination = 'gs://%s' % destination
145 url = 'https://storage.cloud.google.com/%s' % destination
Rico Wind1200f512018-09-26 08:48:37 +0200146 log('Archiving logs to: %s' % gs_destination)
Rico Wind139eece2018-09-25 09:42:09 +0200147 archive_value(EXITCODE, gs_destination, exitcode)
148 archive_value(TIMED_OUT, gs_destination, timed_out)
149 archive_file(STDOUT, gs_destination, stdout)
150 archive_file(STDERR, gs_destination, stderr)
Rico Wind1200f512018-09-26 08:48:37 +0200151 log('Logs available at: %s' % url)
Rico Wind800fd712018-09-24 11:29:33 +0200152
Rico Wind139eece2018-09-25 09:42:09 +0200153def get_magic_file_base_path():
154 return 'gs://%s/magic' % get_test_result_dir()
155
156def get_magic_file_gs_path(name):
157 return '%s/%s' % (get_magic_file_base_path(), name)
158
159def get_magic_file_exists(name):
160 return utils.file_exists_on_cloud_storage(get_magic_file_gs_path(name))
161
162def delete_magic_file(name):
163 utils.delete_file_from_cloud_storage(get_magic_file_gs_path(name))
164
165def put_magic_file(name, sha):
166 archive_value(name, get_magic_file_base_path(), sha)
167
168def get_magic_file_content(name, ignore_errors=False):
169 return utils.cat_file_on_cloud_storage(get_magic_file_gs_path(name),
170 ignore_errors=ignore_errors)
171
172def print_magic_file_state():
Rico Wind1200f512018-09-26 08:48:37 +0200173 log('Magic file status:')
Rico Wind139eece2018-09-25 09:42:09 +0200174 for magic in ALL_MAGIC:
175 if get_magic_file_exists(magic):
176 content = get_magic_file_content(magic, ignore_errors=True)
Rico Wind1200f512018-09-26 08:48:37 +0200177 log('%s content: %s' % (magic, content))
Rico Wind139eece2018-09-25 09:42:09 +0200178
Rico Wind4fd2dda2018-09-26 17:41:45 +0200179def fetch_and_print_logs(hash):
180 gs_base = 'gs://%s' % get_sha_destination(hash)
181 listing = utils.ls_files_on_cloud_storage(gs_base).strip().split('\n')
182 for entry in listing:
183 if not entry.endswith('/status'): # Ignore the overall status file
184 for to_print in [EXITCODE, TIMED_OUT, STDERR, STDOUT]:
185 gs_location = '%s%s' % (entry, to_print)
186 value = utils.cat_file_on_cloud_storage(gs_location)
187 print('\n\n%s had value:\n%s' % (to_print, value))
188
Rico Wind139eece2018-09-25 09:42:09 +0200189def run_bot():
190 print_magic_file_state()
191 # Ensure that there is nothing currently scheduled (broken/stopped run)
192 for magic in ALL_MAGIC:
193 if get_magic_file_exists(magic):
Rico Wind1200f512018-09-26 08:48:37 +0200194 log('ERROR: Synchronizing file %s exists, cleaning up' % magic)
Rico Wind139eece2018-09-25 09:42:09 +0200195 delete_magic_file(magic)
196 print_magic_file_state()
197 assert not get_magic_file_exists(READY_FOR_TESTING)
198 git_hash = utils.get_HEAD_sha1()
199 put_magic_file(READY_FOR_TESTING, git_hash)
200 begin = time.time()
201 while True:
202 if time.time() - begin > BOT_RUN_TIMEOUT:
Rico Wind1200f512018-09-26 08:48:37 +0200203 log('Timeout exceeded: http://go/internal-r8-doc')
Rico Wind139eece2018-09-25 09:42:09 +0200204 raise Exception('Bot timeout')
205 if get_magic_file_exists(TESTING_COMPLETE):
206 if get_magic_file_content(TESTING_COMPLETE) == git_hash:
207 break
208 else:
209 raise Exception('Non matching git hashes %s and %s' % (
210 get_magic_file_content(TESTING_COMPLETE), git_hash))
Rico Wind1200f512018-09-26 08:48:37 +0200211 log('Still waiting for test result')
Rico Wind139eece2018-09-25 09:42:09 +0200212 print_magic_file_state()
213 time.sleep(PULL_DELAY)
214 total_time = time.time()-begin
Rico Wind1200f512018-09-26 08:48:37 +0200215 log('Done running test for %s in %ss' % (git_hash, total_time))
Rico Wind139eece2018-09-25 09:42:09 +0200216 test_status = get_status(git_hash)
217 delete_magic_file(TESTING_COMPLETE)
Rico Wind1200f512018-09-26 08:48:37 +0200218 log('Test status is: %s' % test_status)
Rico Wind139eece2018-09-25 09:42:09 +0200219 if test_status != '0':
Rico Wind4fd2dda2018-09-26 17:41:45 +0200220 fetch_and_print_logs(git_hash)
Rico Wind139eece2018-09-25 09:42:09 +0200221 return 1
222
Rico Wind800fd712018-09-24 11:29:33 +0200223def run_continuously():
224 # If this script changes, we will restart ourselves
225 own_content = get_own_file_content()
Rico Wind800fd712018-09-24 11:29:33 +0200226 while True:
227 restart_if_new_version(own_content)
Rico Wind139eece2018-09-25 09:42:09 +0200228 print_magic_file_state()
229 if get_magic_file_exists(READY_FOR_TESTING):
230 git_hash = get_magic_file_content(READY_FOR_TESTING)
231 checked_out = git_checkout(git_hash)
232 # Sanity check, if this does not succeed stop.
233 if checked_out != git_hash:
Rico Wind1200f512018-09-26 08:48:37 +0200234 log('Inconsistent state: %s %s' % (git_hash, checked_out))
Rico Wind139eece2018-09-25 09:42:09 +0200235 sys.exit(1)
236 put_magic_file(TESTING, git_hash)
237 delete_magic_file(READY_FOR_TESTING)
Rico Wind1200f512018-09-26 08:48:37 +0200238 log('Running with hash: %s' % git_hash)
Rico Wind139eece2018-09-25 09:42:09 +0200239 exitcode = run_once(archive=True)
Rico Wind1200f512018-09-26 08:48:37 +0200240 log('Running finished with exit code %s' % exitcode)
Rico Wind139eece2018-09-25 09:42:09 +0200241 put_magic_file(TESTING_COMPLETE, git_hash)
242 delete_magic_file(TESTING)
243 time.sleep(PULL_DELAY)
Rico Wind800fd712018-09-24 11:29:33 +0200244
245def handle_output(archive, stderr, stdout, exitcode, timed_out, cmd):
246 if archive:
247 archive_log(stdout, stderr, exitcode, timed_out, cmd)
248 else:
249 print 'Execution of %s resulted in:' % cmd
250 print 'exit code: %s ' % exitcode
251 print 'timeout: %s ' % timed_out
252 with open(stderr, 'r') as f:
253 print 'stderr: %s' % f.read()
254 with open(stdout, 'r') as f:
255 print 'stdout: %s' % f.read()
256
Rico Wind6e2205d2018-10-25 13:27:13 +0200257def execute(cmd, archive, env=None):
Rico Wind800fd712018-09-24 11:29:33 +0200258 utils.PrintCmd(cmd)
259 with utils.TempDir() as temp:
260 try:
261 stderr_fd = None
262 stdout_fd = None
263 exitcode = 0
264 stderr = os.path.join(temp, 'stderr')
265 stderr_fd = open(stderr, 'w')
266 stdout = os.path.join(temp, 'stdout')
267 stdout_fd = open(stdout, 'w')
268 popen = subprocess.Popen(cmd,
269 bufsize=1024*1024*10,
270 stdout=stdout_fd,
Rico Wind6e2205d2018-10-25 13:27:13 +0200271 stderr=stderr_fd,
272 env=env)
Rico Wind800fd712018-09-24 11:29:33 +0200273 begin = time.time()
274 timed_out = False
275 while popen.poll() == None:
276 if time.time() - begin > RUN_TIMEOUT:
277 popen.terminate()
278 timed_out = True
279 time.sleep(2)
280 exitcode = popen.returncode
281 finally:
282 if stderr_fd:
283 stderr_fd.close()
284 if stdout_fd:
285 stdout_fd.close()
286 if exitcode != 0:
287 handle_output(archive, stderr, stdout, popen.returncode,
288 timed_out, ' '.join(cmd))
289 return exitcode
290
291def run_once(archive):
292 failed = False
293 git_hash = utils.get_HEAD_sha1()
Rico Wind1200f512018-09-26 08:48:37 +0200294 log('Running once with hash %s' % git_hash)
Rico Wind800fd712018-09-24 11:29:33 +0200295 # Run test.py internal testing.
Morten Krogh-Jespersenb4f8e162018-12-21 13:43:43 +0100296 # TODO(mkrogh) Change this to --r8lib when we have it working with dependencies relocated.
297 cmd = ['tools/test.py', '--only_internal', '--r8lib_no_deps']
Rico Wind6e2205d2018-10-25 13:27:13 +0200298 env = os.environ.copy()
299 # Bot does not have a lot of memory.
300 env['R8_GRADLE_CORES_PER_FORK'] = '8'
301 if execute(cmd, archive, env):
Rico Wind800fd712018-09-24 11:29:33 +0200302 failed = True
303 # Ensure that all internal apps compile.
Rico Wind139eece2018-09-25 09:42:09 +0200304 cmd = ['tools/run_on_app.py', '--ignore-java-version','--run-all',
305 '--out=out']
Rico Wind800fd712018-09-24 11:29:33 +0200306 if execute(cmd, archive):
307 failed = True
308 archive_status(1 if failed else 0)
Rico Wind139eece2018-09-25 09:42:09 +0200309 return failed
Rico Wind800fd712018-09-24 11:29:33 +0200310
311def Main():
312 (options, args) = ParseOptions()
313 if options.continuous:
314 run_continuously()
Rico Wind139eece2018-09-25 09:42:09 +0200315 elif options.bot:
316 return run_bot()
Rico Wind4fd2dda2018-09-26 17:41:45 +0200317 elif options.print_logs:
318 return fetch_and_print_logs(options.print_logs)
Rico Wind800fd712018-09-24 11:29:33 +0200319 else:
Rico Wind139eece2018-09-25 09:42:09 +0200320 return run_once(options.archive)
Rico Wind800fd712018-09-24 11:29:33 +0200321
322if __name__ == '__main__':
323 sys.exit(Main())