blob: 51ddfb626a378e000e7f68254341005a85bde277 [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
Rico Windba63dc82019-03-29 14:33:47 +010032import gradle
Rico Wind800fd712018-09-24 11:29:33 +020033import optparse
34import os
35import subprocess
36import sys
37import time
38import utils
39
Rico Wind139eece2018-09-25 09:42:09 +020040# How often the bot/tester should check state
41PULL_DELAY = 30
Rico Wind800fd712018-09-24 11:29:33 +020042BUCKET = 'r8-test-results'
43TEST_RESULT_DIR = 'internal'
44
Rico Wind139eece2018-09-25 09:42:09 +020045# Magic files
46READY_FOR_TESTING = 'READY_FOR_TESTING'
47TESTING = 'TESTING'
48TESTING_COMPLETE = 'TESTING_COMPLETE'
49
50ALL_MAGIC = [READY_FOR_TESTING, TESTING, TESTING_COMPLETE]
51
52# Log file names
53STDERR = 'stderr'
54STDOUT = 'stdout'
55EXITCODE = 'exitcode'
56TIMED_OUT = 'timed_out'
57
Rico Wind6847d132018-09-26 08:18:48 +020058TEST_COMMANDS = [
Morten Krogh-Jespersen2243b162019-01-14 08:40:53 +010059 # Run test.py internal testing.
Rico Wind6847d132018-09-26 08:18:48 +020060 ['tools/test.py', '--only_internal'],
Morten Krogh-Jespersen2243b162019-01-14 08:40:53 +010061 # Ensure that all internal apps compile.
Rico Wind6847d132018-09-26 08:18:48 +020062 ['tools/run_on_app.py', '--ignore-java-version','--run-all', '--out=out']
63]
64
65# Command timeout, in seconds.
Rico Wind47b01762019-01-14 10:40:50 +010066RUN_TIMEOUT = 3600 * 3
Rico Windf021d832018-12-13 11:29:22 +010067BOT_RUN_TIMEOUT = RUN_TIMEOUT * len(TEST_COMMANDS)
Rico Wind6847d132018-09-26 08:18:48 +020068
Rico Wind1200f512018-09-26 08:48:37 +020069def log(str):
Rico Windffccab12018-09-26 12:39:42 +020070 print("%s: %s" % (time.strftime("%c"), str))
Rico Wind1b09c562019-01-17 08:53:09 +010071 sys.stdout.flush()
Rico Wind1200f512018-09-26 08:48:37 +020072
Rico Wind800fd712018-09-24 11:29:33 +020073def ParseOptions():
74 result = optparse.OptionParser()
75 result.add_option('--continuous',
76 help='Continuously run internal tests and post results to GCS.',
77 default=False, action='store_true')
Rico Wind4fd2dda2018-09-26 17:41:45 +020078 result.add_option('--print_logs',
79 help='Fetch logs from gcs and print them, takes the commit to print for.',
80 default=None)
Rico Wind139eece2018-09-25 09:42:09 +020081 result.add_option('--bot',
82 help='Run in bot mode, i.e., scheduling runs.',
83 default=False, action='store_true')
Rico Wind800fd712018-09-24 11:29:33 +020084 result.add_option('--archive',
85 help='Post result to GCS, implied by --continuous',
86 default=False, action='store_true')
87 return result.parse_args()
88
89def get_own_file_content():
90 with open(sys.argv[0], 'r') as us:
91 return us.read()
92
93def restart_if_new_version(original_content):
94 new_content = get_own_file_content()
Rico Wind1b09c562019-01-17 08:53:09 +010095 log('Lengths %s %s' % (len(original_content), len(new_content)))
96 log('is master %s ' % utils.is_master())
97 # Restart if the script got updated.
Rico Wind800fd712018-09-24 11:29:33 +020098 if new_content != original_content:
Rico Wind1200f512018-09-26 08:48:37 +020099 log('Restarting tools/internal_test.py, content changed')
Rico Wind800fd712018-09-24 11:29:33 +0200100 os.execv(sys.argv[0], sys.argv)
101
Rico Wind139eece2018-09-25 09:42:09 +0200102def ensure_git_clean():
Rico Wind800fd712018-09-24 11:29:33 +0200103 # Ensure clean git repo.
104 diff = subprocess.check_output(['git', 'diff'])
105 if len(diff) > 0:
Rico Wind1200f512018-09-26 08:48:37 +0200106 log('Local modifications to the git repo, exiting')
Rico Wind800fd712018-09-24 11:29:33 +0200107 sys.exit(1)
Rico Wind139eece2018-09-25 09:42:09 +0200108
109def git_pull():
110 ensure_git_clean()
Rico Wind2a19d932018-09-25 16:48:56 +0200111 subprocess.check_call(['git', 'checkout', 'master'])
Rico Wind800fd712018-09-24 11:29:33 +0200112 subprocess.check_call(['git', 'pull'])
113 return utils.get_HEAD_sha1()
114
Rico Wind139eece2018-09-25 09:42:09 +0200115def git_checkout(git_hash):
116 ensure_git_clean()
117 # Ensure that we are up to date to get the commit.
118 git_pull()
Rico Windd7d91062019-04-29 09:24:10 +0200119 exitcode = subprocess.call(['git', 'checkout', git_hash])
120 if exitcode != 0:
121 return None
Rico Wind139eece2018-09-25 09:42:09 +0200122 return utils.get_HEAD_sha1()
123
124def get_test_result_dir():
125 return os.path.join(BUCKET, TEST_RESULT_DIR)
126
Rico Wind800fd712018-09-24 11:29:33 +0200127def get_sha_destination(sha):
Rico Wind139eece2018-09-25 09:42:09 +0200128 return os.path.join(get_test_result_dir(), sha)
Rico Wind800fd712018-09-24 11:29:33 +0200129
130def archive_status(failed):
131 gs_destination = 'gs://%s' % get_sha_destination(utils.get_HEAD_sha1())
132 archive_value('status', gs_destination, failed)
133
Rico Wind139eece2018-09-25 09:42:09 +0200134def get_status(sha):
135 gs_destination = 'gs://%s/status' % get_sha_destination(sha)
136 return utils.cat_file_on_cloud_storage(gs_destination)
137
Rico Wind800fd712018-09-24 11:29:33 +0200138def archive_file(name, gs_dir, src_file):
139 gs_file = '%s/%s' % (gs_dir, name)
140 utils.upload_file_to_cloud_storage(src_file, gs_file, public_read=False)
141
142def archive_value(name, gs_dir, value):
143 with utils.TempDir() as temp:
144 tempfile = os.path.join(temp, name);
145 with open(tempfile, 'w') as f:
146 f.write(str(value))
147 archive_file(name, gs_dir, tempfile)
148
149def archive_log(stdout, stderr, exitcode, timed_out, cmd):
150 sha = utils.get_HEAD_sha1()
Rico Wind139eece2018-09-25 09:42:09 +0200151 cmd_dir = cmd.replace(' ', '_').replace('/', '_')
Rico Wind800fd712018-09-24 11:29:33 +0200152 destination = os.path.join(get_sha_destination(sha), cmd_dir)
153 gs_destination = 'gs://%s' % destination
154 url = 'https://storage.cloud.google.com/%s' % destination
Rico Wind1200f512018-09-26 08:48:37 +0200155 log('Archiving logs to: %s' % gs_destination)
Rico Wind139eece2018-09-25 09:42:09 +0200156 archive_value(EXITCODE, gs_destination, exitcode)
157 archive_value(TIMED_OUT, gs_destination, timed_out)
158 archive_file(STDOUT, gs_destination, stdout)
159 archive_file(STDERR, gs_destination, stderr)
Rico Wind1200f512018-09-26 08:48:37 +0200160 log('Logs available at: %s' % url)
Rico Wind800fd712018-09-24 11:29:33 +0200161
Rico Wind139eece2018-09-25 09:42:09 +0200162def get_magic_file_base_path():
163 return 'gs://%s/magic' % get_test_result_dir()
164
165def get_magic_file_gs_path(name):
166 return '%s/%s' % (get_magic_file_base_path(), name)
167
168def get_magic_file_exists(name):
169 return utils.file_exists_on_cloud_storage(get_magic_file_gs_path(name))
170
171def delete_magic_file(name):
172 utils.delete_file_from_cloud_storage(get_magic_file_gs_path(name))
173
174def put_magic_file(name, sha):
175 archive_value(name, get_magic_file_base_path(), sha)
176
177def get_magic_file_content(name, ignore_errors=False):
178 return utils.cat_file_on_cloud_storage(get_magic_file_gs_path(name),
179 ignore_errors=ignore_errors)
180
181def print_magic_file_state():
Rico Wind1200f512018-09-26 08:48:37 +0200182 log('Magic file status:')
Rico Wind139eece2018-09-25 09:42:09 +0200183 for magic in ALL_MAGIC:
184 if get_magic_file_exists(magic):
185 content = get_magic_file_content(magic, ignore_errors=True)
Rico Wind1200f512018-09-26 08:48:37 +0200186 log('%s content: %s' % (magic, content))
Rico Wind139eece2018-09-25 09:42:09 +0200187
Rico Wind4fd2dda2018-09-26 17:41:45 +0200188def fetch_and_print_logs(hash):
189 gs_base = 'gs://%s' % get_sha_destination(hash)
190 listing = utils.ls_files_on_cloud_storage(gs_base).strip().split('\n')
191 for entry in listing:
192 if not entry.endswith('/status'): # Ignore the overall status file
193 for to_print in [EXITCODE, TIMED_OUT, STDERR, STDOUT]:
194 gs_location = '%s%s' % (entry, to_print)
195 value = utils.cat_file_on_cloud_storage(gs_location)
196 print('\n\n%s had value:\n%s' % (to_print, value))
197
Rico Wind139eece2018-09-25 09:42:09 +0200198def run_bot():
199 print_magic_file_state()
200 # Ensure that there is nothing currently scheduled (broken/stopped run)
201 for magic in ALL_MAGIC:
202 if get_magic_file_exists(magic):
Rico Wind1200f512018-09-26 08:48:37 +0200203 log('ERROR: Synchronizing file %s exists, cleaning up' % magic)
Rico Wind139eece2018-09-25 09:42:09 +0200204 delete_magic_file(magic)
205 print_magic_file_state()
206 assert not get_magic_file_exists(READY_FOR_TESTING)
207 git_hash = utils.get_HEAD_sha1()
208 put_magic_file(READY_FOR_TESTING, git_hash)
209 begin = time.time()
210 while True:
211 if time.time() - begin > BOT_RUN_TIMEOUT:
Rico Wind1200f512018-09-26 08:48:37 +0200212 log('Timeout exceeded: http://go/internal-r8-doc')
Rico Wind139eece2018-09-25 09:42:09 +0200213 raise Exception('Bot timeout')
214 if get_magic_file_exists(TESTING_COMPLETE):
215 if get_magic_file_content(TESTING_COMPLETE) == git_hash:
216 break
217 else:
218 raise Exception('Non matching git hashes %s and %s' % (
219 get_magic_file_content(TESTING_COMPLETE), git_hash))
Rico Wind1200f512018-09-26 08:48:37 +0200220 log('Still waiting for test result')
Rico Wind139eece2018-09-25 09:42:09 +0200221 print_magic_file_state()
222 time.sleep(PULL_DELAY)
223 total_time = time.time()-begin
Rico Wind1200f512018-09-26 08:48:37 +0200224 log('Done running test for %s in %ss' % (git_hash, total_time))
Rico Wind139eece2018-09-25 09:42:09 +0200225 test_status = get_status(git_hash)
226 delete_magic_file(TESTING_COMPLETE)
Rico Wind1200f512018-09-26 08:48:37 +0200227 log('Test status is: %s' % test_status)
Rico Wind139eece2018-09-25 09:42:09 +0200228 if test_status != '0':
Rico Wind9e9449e2019-04-04 14:42:29 +0200229 print('Tests failed, you can print the logs by running(googlers only):')
Rico Wind1cb65e12019-04-26 08:54:17 +0200230 print(' tools/internal_test.py --print_logs %s' % git_hash)
Rico Wind139eece2018-09-25 09:42:09 +0200231 return 1
232
Rico Wind800fd712018-09-24 11:29:33 +0200233def run_continuously():
234 # If this script changes, we will restart ourselves
235 own_content = get_own_file_content()
Rico Wind800fd712018-09-24 11:29:33 +0200236 while True:
237 restart_if_new_version(own_content)
Rico Wind139eece2018-09-25 09:42:09 +0200238 print_magic_file_state()
239 if get_magic_file_exists(READY_FOR_TESTING):
240 git_hash = get_magic_file_content(READY_FOR_TESTING)
241 checked_out = git_checkout(git_hash)
Rico Windd7d91062019-04-29 09:24:10 +0200242 if not checked_out:
243 # Gerrit change, we don't run these on internal.
244 archive_status(0)
245 put_magic_file(TESTING_COMPLETE, git_hash)
246 delete_magic_file(READY_FOR_TESTING)
247 continue
Rico Wind9519a152019-01-23 13:34:20 +0100248 # If the script changed, we need to restart now to get correct commands
249 # Note that we have not removed the READY_FOR_TESTING yet, so if we
250 # execv we will pick up the same version.
251 restart_if_new_version(own_content)
Rico Wind139eece2018-09-25 09:42:09 +0200252 # Sanity check, if this does not succeed stop.
253 if checked_out != git_hash:
Rico Wind1200f512018-09-26 08:48:37 +0200254 log('Inconsistent state: %s %s' % (git_hash, checked_out))
Rico Wind139eece2018-09-25 09:42:09 +0200255 sys.exit(1)
256 put_magic_file(TESTING, git_hash)
257 delete_magic_file(READY_FOR_TESTING)
Rico Wind1200f512018-09-26 08:48:37 +0200258 log('Running with hash: %s' % git_hash)
Rico Wind139eece2018-09-25 09:42:09 +0200259 exitcode = run_once(archive=True)
Rico Wind1200f512018-09-26 08:48:37 +0200260 log('Running finished with exit code %s' % exitcode)
Rico Wind139eece2018-09-25 09:42:09 +0200261 put_magic_file(TESTING_COMPLETE, git_hash)
262 delete_magic_file(TESTING)
263 time.sleep(PULL_DELAY)
Rico Wind800fd712018-09-24 11:29:33 +0200264
265def handle_output(archive, stderr, stdout, exitcode, timed_out, cmd):
266 if archive:
267 archive_log(stdout, stderr, exitcode, timed_out, cmd)
268 else:
269 print 'Execution of %s resulted in:' % cmd
270 print 'exit code: %s ' % exitcode
271 print 'timeout: %s ' % timed_out
272 with open(stderr, 'r') as f:
273 print 'stderr: %s' % f.read()
274 with open(stdout, 'r') as f:
275 print 'stdout: %s' % f.read()
276
Rico Wind6e2205d2018-10-25 13:27:13 +0200277def execute(cmd, archive, env=None):
Rico Wind800fd712018-09-24 11:29:33 +0200278 utils.PrintCmd(cmd)
279 with utils.TempDir() as temp:
280 try:
281 stderr_fd = None
282 stdout_fd = None
283 exitcode = 0
284 stderr = os.path.join(temp, 'stderr')
285 stderr_fd = open(stderr, 'w')
286 stdout = os.path.join(temp, 'stdout')
287 stdout_fd = open(stdout, 'w')
288 popen = subprocess.Popen(cmd,
289 bufsize=1024*1024*10,
290 stdout=stdout_fd,
Rico Wind6e2205d2018-10-25 13:27:13 +0200291 stderr=stderr_fd,
292 env=env)
Rico Wind800fd712018-09-24 11:29:33 +0200293 begin = time.time()
294 timed_out = False
295 while popen.poll() == None:
296 if time.time() - begin > RUN_TIMEOUT:
297 popen.terminate()
298 timed_out = True
299 time.sleep(2)
300 exitcode = popen.returncode
301 finally:
302 if stderr_fd:
303 stderr_fd.close()
304 if stdout_fd:
305 stdout_fd.close()
306 if exitcode != 0:
307 handle_output(archive, stderr, stdout, popen.returncode,
308 timed_out, ' '.join(cmd))
309 return exitcode
310
311def run_once(archive):
312 failed = False
313 git_hash = utils.get_HEAD_sha1()
Rico Wind1200f512018-09-26 08:48:37 +0200314 log('Running once with hash %s' % git_hash)
Rico Wind6e2205d2018-10-25 13:27:13 +0200315 env = os.environ.copy()
316 # Bot does not have a lot of memory.
Rico Wind3defc8d2019-03-27 08:07:31 +0100317 env['R8_GRADLE_CORES_PER_FORK'] = '16'
Morten Krogh-Jespersen2243b162019-01-14 08:40:53 +0100318 failed = any([execute(cmd, archive, env) for cmd in TEST_COMMANDS])
Rico Windba63dc82019-03-29 14:33:47 +0100319 # Gradle daemon occasionally leaks memory, stop it.
Rico Wind74233002019-04-04 08:28:58 +0200320 gradle.RunGradle(['--stop'])
Rico Wind800fd712018-09-24 11:29:33 +0200321 archive_status(1 if failed else 0)
Rico Wind139eece2018-09-25 09:42:09 +0200322 return failed
Rico Wind800fd712018-09-24 11:29:33 +0200323
324def Main():
325 (options, args) = ParseOptions()
326 if options.continuous:
327 run_continuously()
Rico Wind139eece2018-09-25 09:42:09 +0200328 elif options.bot:
329 return run_bot()
Rico Wind4fd2dda2018-09-26 17:41:45 +0200330 elif options.print_logs:
331 return fetch_and_print_logs(options.print_logs)
Rico Wind800fd712018-09-24 11:29:33 +0200332 else:
Rico Wind139eece2018-09-25 09:42:09 +0200333 return run_once(options.archive)
Rico Wind800fd712018-09-24 11:29:33 +0200334
335if __name__ == '__main__':
336 sys.exit(Main())