blob: be9779a3e3e6e6d46dd128b89ffeaf408bc1ab7e [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 = [
Morten Krogh-Jespersen2243b162019-01-14 08:40:53 +010058 # Run test.py internal testing.
Rico Wind6847d132018-09-26 08:18:48 +020059 ['tools/test.py', '--only_internal'],
Morten Krogh-Jespersen2243b162019-01-14 08:40:53 +010060 # Ensure that all internal apps compile.
Rico Wind6847d132018-09-26 08:18:48 +020061 ['tools/run_on_app.py', '--ignore-java-version','--run-all', '--out=out']
62]
63
64# Command timeout, in seconds.
Rico Wind47b01762019-01-14 10:40:50 +010065RUN_TIMEOUT = 3600 * 3
Rico Windf021d832018-12-13 11:29:22 +010066BOT_RUN_TIMEOUT = RUN_TIMEOUT * len(TEST_COMMANDS)
Rico Wind6847d132018-09-26 08:18:48 +020067
Rico Wind1200f512018-09-26 08:48:37 +020068def log(str):
Rico Windffccab12018-09-26 12:39:42 +020069 print("%s: %s" % (time.strftime("%c"), str))
Rico Wind1200f512018-09-26 08:48:37 +020070
Rico Wind800fd712018-09-24 11:29:33 +020071def ParseOptions():
72 result = optparse.OptionParser()
73 result.add_option('--continuous',
74 help='Continuously run internal tests and post results to GCS.',
75 default=False, action='store_true')
Rico Wind4fd2dda2018-09-26 17:41:45 +020076 result.add_option('--print_logs',
77 help='Fetch logs from gcs and print them, takes the commit to print for.',
78 default=None)
Rico Wind139eece2018-09-25 09:42:09 +020079 result.add_option('--bot',
80 help='Run in bot mode, i.e., scheduling runs.',
81 default=False, action='store_true')
Rico Wind800fd712018-09-24 11:29:33 +020082 result.add_option('--archive',
83 help='Post result to GCS, implied by --continuous',
84 default=False, action='store_true')
85 return result.parse_args()
86
87def get_own_file_content():
88 with open(sys.argv[0], 'r') as us:
89 return us.read()
90
91def restart_if_new_version(original_content):
92 new_content = get_own_file_content()
93 if new_content != original_content:
Rico Wind1200f512018-09-26 08:48:37 +020094 log('Restarting tools/internal_test.py, content changed')
Rico Wind800fd712018-09-24 11:29:33 +020095 os.execv(sys.argv[0], sys.argv)
96
Rico Wind139eece2018-09-25 09:42:09 +020097def ensure_git_clean():
Rico Wind800fd712018-09-24 11:29:33 +020098 # Ensure clean git repo.
99 diff = subprocess.check_output(['git', 'diff'])
100 if len(diff) > 0:
Rico Wind1200f512018-09-26 08:48:37 +0200101 log('Local modifications to the git repo, exiting')
Rico Wind800fd712018-09-24 11:29:33 +0200102 sys.exit(1)
Rico Wind139eece2018-09-25 09:42:09 +0200103
104def git_pull():
105 ensure_git_clean()
Rico Wind2a19d932018-09-25 16:48:56 +0200106 subprocess.check_call(['git', 'checkout', 'master'])
Rico Wind800fd712018-09-24 11:29:33 +0200107 subprocess.check_call(['git', 'pull'])
108 return utils.get_HEAD_sha1()
109
Rico Wind139eece2018-09-25 09:42:09 +0200110def git_checkout(git_hash):
111 ensure_git_clean()
112 # Ensure that we are up to date to get the commit.
113 git_pull()
114 subprocess.check_call(['git', 'checkout', git_hash])
115 return utils.get_HEAD_sha1()
116
117def get_test_result_dir():
118 return os.path.join(BUCKET, TEST_RESULT_DIR)
119
Rico Wind800fd712018-09-24 11:29:33 +0200120def get_sha_destination(sha):
Rico Wind139eece2018-09-25 09:42:09 +0200121 return os.path.join(get_test_result_dir(), sha)
Rico Wind800fd712018-09-24 11:29:33 +0200122
123def archive_status(failed):
124 gs_destination = 'gs://%s' % get_sha_destination(utils.get_HEAD_sha1())
125 archive_value('status', gs_destination, failed)
126
Rico Wind139eece2018-09-25 09:42:09 +0200127def get_status(sha):
128 gs_destination = 'gs://%s/status' % get_sha_destination(sha)
129 return utils.cat_file_on_cloud_storage(gs_destination)
130
Rico Wind800fd712018-09-24 11:29:33 +0200131def archive_file(name, gs_dir, src_file):
132 gs_file = '%s/%s' % (gs_dir, name)
133 utils.upload_file_to_cloud_storage(src_file, gs_file, public_read=False)
134
135def archive_value(name, gs_dir, value):
136 with utils.TempDir() as temp:
137 tempfile = os.path.join(temp, name);
138 with open(tempfile, 'w') as f:
139 f.write(str(value))
140 archive_file(name, gs_dir, tempfile)
141
142def archive_log(stdout, stderr, exitcode, timed_out, cmd):
143 sha = utils.get_HEAD_sha1()
Rico Wind139eece2018-09-25 09:42:09 +0200144 cmd_dir = cmd.replace(' ', '_').replace('/', '_')
Rico Wind800fd712018-09-24 11:29:33 +0200145 destination = os.path.join(get_sha_destination(sha), cmd_dir)
146 gs_destination = 'gs://%s' % destination
147 url = 'https://storage.cloud.google.com/%s' % destination
Rico Wind1200f512018-09-26 08:48:37 +0200148 log('Archiving logs to: %s' % gs_destination)
Rico Wind139eece2018-09-25 09:42:09 +0200149 archive_value(EXITCODE, gs_destination, exitcode)
150 archive_value(TIMED_OUT, gs_destination, timed_out)
151 archive_file(STDOUT, gs_destination, stdout)
152 archive_file(STDERR, gs_destination, stderr)
Rico Wind1200f512018-09-26 08:48:37 +0200153 log('Logs available at: %s' % url)
Rico Wind800fd712018-09-24 11:29:33 +0200154
Rico Wind139eece2018-09-25 09:42:09 +0200155def get_magic_file_base_path():
156 return 'gs://%s/magic' % get_test_result_dir()
157
158def get_magic_file_gs_path(name):
159 return '%s/%s' % (get_magic_file_base_path(), name)
160
161def get_magic_file_exists(name):
162 return utils.file_exists_on_cloud_storage(get_magic_file_gs_path(name))
163
164def delete_magic_file(name):
165 utils.delete_file_from_cloud_storage(get_magic_file_gs_path(name))
166
167def put_magic_file(name, sha):
168 archive_value(name, get_magic_file_base_path(), sha)
169
170def get_magic_file_content(name, ignore_errors=False):
171 return utils.cat_file_on_cloud_storage(get_magic_file_gs_path(name),
172 ignore_errors=ignore_errors)
173
174def print_magic_file_state():
Rico Wind1200f512018-09-26 08:48:37 +0200175 log('Magic file status:')
Rico Wind139eece2018-09-25 09:42:09 +0200176 for magic in ALL_MAGIC:
177 if get_magic_file_exists(magic):
178 content = get_magic_file_content(magic, ignore_errors=True)
Rico Wind1200f512018-09-26 08:48:37 +0200179 log('%s content: %s' % (magic, content))
Rico Wind139eece2018-09-25 09:42:09 +0200180
Rico Wind4fd2dda2018-09-26 17:41:45 +0200181def fetch_and_print_logs(hash):
182 gs_base = 'gs://%s' % get_sha_destination(hash)
183 listing = utils.ls_files_on_cloud_storage(gs_base).strip().split('\n')
184 for entry in listing:
185 if not entry.endswith('/status'): # Ignore the overall status file
186 for to_print in [EXITCODE, TIMED_OUT, STDERR, STDOUT]:
187 gs_location = '%s%s' % (entry, to_print)
188 value = utils.cat_file_on_cloud_storage(gs_location)
189 print('\n\n%s had value:\n%s' % (to_print, value))
190
Rico Wind139eece2018-09-25 09:42:09 +0200191def run_bot():
192 print_magic_file_state()
193 # Ensure that there is nothing currently scheduled (broken/stopped run)
194 for magic in ALL_MAGIC:
195 if get_magic_file_exists(magic):
Rico Wind1200f512018-09-26 08:48:37 +0200196 log('ERROR: Synchronizing file %s exists, cleaning up' % magic)
Rico Wind139eece2018-09-25 09:42:09 +0200197 delete_magic_file(magic)
198 print_magic_file_state()
199 assert not get_magic_file_exists(READY_FOR_TESTING)
200 git_hash = utils.get_HEAD_sha1()
201 put_magic_file(READY_FOR_TESTING, git_hash)
202 begin = time.time()
203 while True:
204 if time.time() - begin > BOT_RUN_TIMEOUT:
Rico Wind1200f512018-09-26 08:48:37 +0200205 log('Timeout exceeded: http://go/internal-r8-doc')
Rico Wind139eece2018-09-25 09:42:09 +0200206 raise Exception('Bot timeout')
207 if get_magic_file_exists(TESTING_COMPLETE):
208 if get_magic_file_content(TESTING_COMPLETE) == git_hash:
209 break
210 else:
211 raise Exception('Non matching git hashes %s and %s' % (
212 get_magic_file_content(TESTING_COMPLETE), git_hash))
Rico Wind1200f512018-09-26 08:48:37 +0200213 log('Still waiting for test result')
Rico Wind139eece2018-09-25 09:42:09 +0200214 print_magic_file_state()
215 time.sleep(PULL_DELAY)
216 total_time = time.time()-begin
Rico Wind1200f512018-09-26 08:48:37 +0200217 log('Done running test for %s in %ss' % (git_hash, total_time))
Rico Wind139eece2018-09-25 09:42:09 +0200218 test_status = get_status(git_hash)
219 delete_magic_file(TESTING_COMPLETE)
Rico Wind1200f512018-09-26 08:48:37 +0200220 log('Test status is: %s' % test_status)
Rico Wind139eece2018-09-25 09:42:09 +0200221 if test_status != '0':
Rico Wind4fd2dda2018-09-26 17:41:45 +0200222 fetch_and_print_logs(git_hash)
Rico Wind139eece2018-09-25 09:42:09 +0200223 return 1
224
Rico Wind800fd712018-09-24 11:29:33 +0200225def run_continuously():
226 # If this script changes, we will restart ourselves
227 own_content = get_own_file_content()
Rico Wind800fd712018-09-24 11:29:33 +0200228 while True:
229 restart_if_new_version(own_content)
Rico Wind139eece2018-09-25 09:42:09 +0200230 print_magic_file_state()
231 if get_magic_file_exists(READY_FOR_TESTING):
232 git_hash = get_magic_file_content(READY_FOR_TESTING)
233 checked_out = git_checkout(git_hash)
234 # Sanity check, if this does not succeed stop.
235 if checked_out != git_hash:
Rico Wind1200f512018-09-26 08:48:37 +0200236 log('Inconsistent state: %s %s' % (git_hash, checked_out))
Rico Wind139eece2018-09-25 09:42:09 +0200237 sys.exit(1)
238 put_magic_file(TESTING, git_hash)
239 delete_magic_file(READY_FOR_TESTING)
Rico Wind1200f512018-09-26 08:48:37 +0200240 log('Running with hash: %s' % git_hash)
Rico Wind139eece2018-09-25 09:42:09 +0200241 exitcode = run_once(archive=True)
Rico Wind1200f512018-09-26 08:48:37 +0200242 log('Running finished with exit code %s' % exitcode)
Rico Wind139eece2018-09-25 09:42:09 +0200243 put_magic_file(TESTING_COMPLETE, git_hash)
244 delete_magic_file(TESTING)
245 time.sleep(PULL_DELAY)
Rico Wind800fd712018-09-24 11:29:33 +0200246
247def handle_output(archive, stderr, stdout, exitcode, timed_out, cmd):
248 if archive:
249 archive_log(stdout, stderr, exitcode, timed_out, cmd)
250 else:
251 print 'Execution of %s resulted in:' % cmd
252 print 'exit code: %s ' % exitcode
253 print 'timeout: %s ' % timed_out
254 with open(stderr, 'r') as f:
255 print 'stderr: %s' % f.read()
256 with open(stdout, 'r') as f:
257 print 'stdout: %s' % f.read()
258
Rico Wind6e2205d2018-10-25 13:27:13 +0200259def execute(cmd, archive, env=None):
Rico Wind800fd712018-09-24 11:29:33 +0200260 utils.PrintCmd(cmd)
261 with utils.TempDir() as temp:
262 try:
263 stderr_fd = None
264 stdout_fd = None
265 exitcode = 0
266 stderr = os.path.join(temp, 'stderr')
267 stderr_fd = open(stderr, 'w')
268 stdout = os.path.join(temp, 'stdout')
269 stdout_fd = open(stdout, 'w')
270 popen = subprocess.Popen(cmd,
271 bufsize=1024*1024*10,
272 stdout=stdout_fd,
Rico Wind6e2205d2018-10-25 13:27:13 +0200273 stderr=stderr_fd,
274 env=env)
Rico Wind800fd712018-09-24 11:29:33 +0200275 begin = time.time()
276 timed_out = False
277 while popen.poll() == None:
278 if time.time() - begin > RUN_TIMEOUT:
279 popen.terminate()
280 timed_out = True
281 time.sleep(2)
282 exitcode = popen.returncode
283 finally:
284 if stderr_fd:
285 stderr_fd.close()
286 if stdout_fd:
287 stdout_fd.close()
288 if exitcode != 0:
289 handle_output(archive, stderr, stdout, popen.returncode,
290 timed_out, ' '.join(cmd))
291 return exitcode
292
293def run_once(archive):
294 failed = False
295 git_hash = utils.get_HEAD_sha1()
Rico Wind1200f512018-09-26 08:48:37 +0200296 log('Running once with hash %s' % git_hash)
Rico Wind6e2205d2018-10-25 13:27:13 +0200297 env = os.environ.copy()
298 # Bot does not have a lot of memory.
299 env['R8_GRADLE_CORES_PER_FORK'] = '8'
Morten Krogh-Jespersen2243b162019-01-14 08:40:53 +0100300 failed = any([execute(cmd, archive, env) for cmd in TEST_COMMANDS])
Rico Wind800fd712018-09-24 11:29:33 +0200301 archive_status(1 if failed else 0)
Rico Wind139eece2018-09-25 09:42:09 +0200302 return failed
Rico Wind800fd712018-09-24 11:29:33 +0200303
304def Main():
305 (options, args) = ParseOptions()
306 if options.continuous:
307 run_continuously()
Rico Wind139eece2018-09-25 09:42:09 +0200308 elif options.bot:
309 return run_bot()
Rico Wind4fd2dda2018-09-26 17:41:45 +0200310 elif options.print_logs:
311 return fetch_and_print_logs(options.print_logs)
Rico Wind800fd712018-09-24 11:29:33 +0200312 else:
Rico Wind139eece2018-09-25 09:42:09 +0200313 return run_once(options.archive)
Rico Wind800fd712018-09-24 11:29:33 +0200314
315if __name__ == '__main__':
316 sys.exit(Main())