blob: a9a90eb83d823d9bfe485684b9c338905290457e [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 Wind1b09c562019-01-17 08:53:09 +010070 sys.stdout.flush()
Rico Wind1200f512018-09-26 08:48:37 +020071
Rico Wind800fd712018-09-24 11:29:33 +020072def ParseOptions():
73 result = optparse.OptionParser()
74 result.add_option('--continuous',
75 help='Continuously run internal tests and post results to GCS.',
76 default=False, action='store_true')
Rico Wind4fd2dda2018-09-26 17:41:45 +020077 result.add_option('--print_logs',
78 help='Fetch logs from gcs and print them, takes the commit to print for.',
79 default=None)
Rico Wind139eece2018-09-25 09:42:09 +020080 result.add_option('--bot',
81 help='Run in bot mode, i.e., scheduling runs.',
82 default=False, action='store_true')
Rico Wind800fd712018-09-24 11:29:33 +020083 result.add_option('--archive',
84 help='Post result to GCS, implied by --continuous',
85 default=False, action='store_true')
86 return result.parse_args()
87
88def get_own_file_content():
89 with open(sys.argv[0], 'r') as us:
90 return us.read()
91
92def restart_if_new_version(original_content):
93 new_content = get_own_file_content()
Rico Wind1b09c562019-01-17 08:53:09 +010094 log('Lengths %s %s' % (len(original_content), len(new_content)))
95 log('is master %s ' % utils.is_master())
96 # Restart if the script got updated.
Rico Wind800fd712018-09-24 11:29:33 +020097 if new_content != original_content:
Rico Wind1200f512018-09-26 08:48:37 +020098 log('Restarting tools/internal_test.py, content changed')
Rico Wind800fd712018-09-24 11:29:33 +020099 os.execv(sys.argv[0], sys.argv)
100
Rico Wind139eece2018-09-25 09:42:09 +0200101def ensure_git_clean():
Rico Wind800fd712018-09-24 11:29:33 +0200102 # Ensure clean git repo.
103 diff = subprocess.check_output(['git', 'diff'])
104 if len(diff) > 0:
Rico Wind1200f512018-09-26 08:48:37 +0200105 log('Local modifications to the git repo, exiting')
Rico Wind800fd712018-09-24 11:29:33 +0200106 sys.exit(1)
Rico Wind139eece2018-09-25 09:42:09 +0200107
108def git_pull():
109 ensure_git_clean()
Rico Wind2a19d932018-09-25 16:48:56 +0200110 subprocess.check_call(['git', 'checkout', 'master'])
Rico Wind800fd712018-09-24 11:29:33 +0200111 subprocess.check_call(['git', 'pull'])
112 return utils.get_HEAD_sha1()
113
Rico Wind139eece2018-09-25 09:42:09 +0200114def git_checkout(git_hash):
115 ensure_git_clean()
116 # Ensure that we are up to date to get the commit.
117 git_pull()
118 subprocess.check_call(['git', 'checkout', git_hash])
119 return utils.get_HEAD_sha1()
120
121def get_test_result_dir():
122 return os.path.join(BUCKET, TEST_RESULT_DIR)
123
Rico Wind800fd712018-09-24 11:29:33 +0200124def get_sha_destination(sha):
Rico Wind139eece2018-09-25 09:42:09 +0200125 return os.path.join(get_test_result_dir(), sha)
Rico Wind800fd712018-09-24 11:29:33 +0200126
127def archive_status(failed):
128 gs_destination = 'gs://%s' % get_sha_destination(utils.get_HEAD_sha1())
129 archive_value('status', gs_destination, failed)
130
Rico Wind139eece2018-09-25 09:42:09 +0200131def get_status(sha):
132 gs_destination = 'gs://%s/status' % get_sha_destination(sha)
133 return utils.cat_file_on_cloud_storage(gs_destination)
134
Rico Wind800fd712018-09-24 11:29:33 +0200135def archive_file(name, gs_dir, src_file):
136 gs_file = '%s/%s' % (gs_dir, name)
137 utils.upload_file_to_cloud_storage(src_file, gs_file, public_read=False)
138
139def archive_value(name, gs_dir, value):
140 with utils.TempDir() as temp:
141 tempfile = os.path.join(temp, name);
142 with open(tempfile, 'w') as f:
143 f.write(str(value))
144 archive_file(name, gs_dir, tempfile)
145
146def archive_log(stdout, stderr, exitcode, timed_out, cmd):
147 sha = utils.get_HEAD_sha1()
Rico Wind139eece2018-09-25 09:42:09 +0200148 cmd_dir = cmd.replace(' ', '_').replace('/', '_')
Rico Wind800fd712018-09-24 11:29:33 +0200149 destination = os.path.join(get_sha_destination(sha), cmd_dir)
150 gs_destination = 'gs://%s' % destination
151 url = 'https://storage.cloud.google.com/%s' % destination
Rico Wind1200f512018-09-26 08:48:37 +0200152 log('Archiving logs to: %s' % gs_destination)
Rico Wind139eece2018-09-25 09:42:09 +0200153 archive_value(EXITCODE, gs_destination, exitcode)
154 archive_value(TIMED_OUT, gs_destination, timed_out)
155 archive_file(STDOUT, gs_destination, stdout)
156 archive_file(STDERR, gs_destination, stderr)
Rico Wind1200f512018-09-26 08:48:37 +0200157 log('Logs available at: %s' % url)
Rico Wind800fd712018-09-24 11:29:33 +0200158
Rico Wind139eece2018-09-25 09:42:09 +0200159def get_magic_file_base_path():
160 return 'gs://%s/magic' % get_test_result_dir()
161
162def get_magic_file_gs_path(name):
163 return '%s/%s' % (get_magic_file_base_path(), name)
164
165def get_magic_file_exists(name):
166 return utils.file_exists_on_cloud_storage(get_magic_file_gs_path(name))
167
168def delete_magic_file(name):
169 utils.delete_file_from_cloud_storage(get_magic_file_gs_path(name))
170
171def put_magic_file(name, sha):
172 archive_value(name, get_magic_file_base_path(), sha)
173
174def get_magic_file_content(name, ignore_errors=False):
175 return utils.cat_file_on_cloud_storage(get_magic_file_gs_path(name),
176 ignore_errors=ignore_errors)
177
178def print_magic_file_state():
Rico Wind1200f512018-09-26 08:48:37 +0200179 log('Magic file status:')
Rico Wind139eece2018-09-25 09:42:09 +0200180 for magic in ALL_MAGIC:
181 if get_magic_file_exists(magic):
182 content = get_magic_file_content(magic, ignore_errors=True)
Rico Wind1200f512018-09-26 08:48:37 +0200183 log('%s content: %s' % (magic, content))
Rico Wind139eece2018-09-25 09:42:09 +0200184
Rico Wind4fd2dda2018-09-26 17:41:45 +0200185def fetch_and_print_logs(hash):
186 gs_base = 'gs://%s' % get_sha_destination(hash)
187 listing = utils.ls_files_on_cloud_storage(gs_base).strip().split('\n')
188 for entry in listing:
189 if not entry.endswith('/status'): # Ignore the overall status file
190 for to_print in [EXITCODE, TIMED_OUT, STDERR, STDOUT]:
191 gs_location = '%s%s' % (entry, to_print)
192 value = utils.cat_file_on_cloud_storage(gs_location)
193 print('\n\n%s had value:\n%s' % (to_print, value))
194
Rico Wind139eece2018-09-25 09:42:09 +0200195def run_bot():
196 print_magic_file_state()
197 # Ensure that there is nothing currently scheduled (broken/stopped run)
198 for magic in ALL_MAGIC:
199 if get_magic_file_exists(magic):
Rico Wind1200f512018-09-26 08:48:37 +0200200 log('ERROR: Synchronizing file %s exists, cleaning up' % magic)
Rico Wind139eece2018-09-25 09:42:09 +0200201 delete_magic_file(magic)
202 print_magic_file_state()
203 assert not get_magic_file_exists(READY_FOR_TESTING)
204 git_hash = utils.get_HEAD_sha1()
205 put_magic_file(READY_FOR_TESTING, git_hash)
206 begin = time.time()
207 while True:
208 if time.time() - begin > BOT_RUN_TIMEOUT:
Rico Wind1200f512018-09-26 08:48:37 +0200209 log('Timeout exceeded: http://go/internal-r8-doc')
Rico Wind139eece2018-09-25 09:42:09 +0200210 raise Exception('Bot timeout')
211 if get_magic_file_exists(TESTING_COMPLETE):
212 if get_magic_file_content(TESTING_COMPLETE) == git_hash:
213 break
214 else:
215 raise Exception('Non matching git hashes %s and %s' % (
216 get_magic_file_content(TESTING_COMPLETE), git_hash))
Rico Wind1200f512018-09-26 08:48:37 +0200217 log('Still waiting for test result')
Rico Wind139eece2018-09-25 09:42:09 +0200218 print_magic_file_state()
219 time.sleep(PULL_DELAY)
220 total_time = time.time()-begin
Rico Wind1200f512018-09-26 08:48:37 +0200221 log('Done running test for %s in %ss' % (git_hash, total_time))
Rico Wind139eece2018-09-25 09:42:09 +0200222 test_status = get_status(git_hash)
223 delete_magic_file(TESTING_COMPLETE)
Rico Wind1200f512018-09-26 08:48:37 +0200224 log('Test status is: %s' % test_status)
Rico Wind139eece2018-09-25 09:42:09 +0200225 if test_status != '0':
Rico Wind4fd2dda2018-09-26 17:41:45 +0200226 fetch_and_print_logs(git_hash)
Rico Wind139eece2018-09-25 09:42:09 +0200227 return 1
228
Rico Wind800fd712018-09-24 11:29:33 +0200229def run_continuously():
230 # If this script changes, we will restart ourselves
231 own_content = get_own_file_content()
Rico Wind800fd712018-09-24 11:29:33 +0200232 while True:
233 restart_if_new_version(own_content)
Rico Wind139eece2018-09-25 09:42:09 +0200234 print_magic_file_state()
235 if get_magic_file_exists(READY_FOR_TESTING):
236 git_hash = get_magic_file_content(READY_FOR_TESTING)
237 checked_out = git_checkout(git_hash)
Rico Wind9519a152019-01-23 13:34:20 +0100238 # If the script changed, we need to restart now to get correct commands
239 # Note that we have not removed the READY_FOR_TESTING yet, so if we
240 # execv we will pick up the same version.
241 restart_if_new_version(own_content)
Rico Wind139eece2018-09-25 09:42:09 +0200242 # Sanity check, if this does not succeed stop.
243 if checked_out != git_hash:
Rico Wind1200f512018-09-26 08:48:37 +0200244 log('Inconsistent state: %s %s' % (git_hash, checked_out))
Rico Wind139eece2018-09-25 09:42:09 +0200245 sys.exit(1)
246 put_magic_file(TESTING, git_hash)
247 delete_magic_file(READY_FOR_TESTING)
Rico Wind1200f512018-09-26 08:48:37 +0200248 log('Running with hash: %s' % git_hash)
Rico Wind139eece2018-09-25 09:42:09 +0200249 exitcode = run_once(archive=True)
Rico Wind1200f512018-09-26 08:48:37 +0200250 log('Running finished with exit code %s' % exitcode)
Rico Wind139eece2018-09-25 09:42:09 +0200251 put_magic_file(TESTING_COMPLETE, git_hash)
252 delete_magic_file(TESTING)
253 time.sleep(PULL_DELAY)
Rico Wind800fd712018-09-24 11:29:33 +0200254
255def handle_output(archive, stderr, stdout, exitcode, timed_out, cmd):
256 if archive:
257 archive_log(stdout, stderr, exitcode, timed_out, cmd)
258 else:
259 print 'Execution of %s resulted in:' % cmd
260 print 'exit code: %s ' % exitcode
261 print 'timeout: %s ' % timed_out
262 with open(stderr, 'r') as f:
263 print 'stderr: %s' % f.read()
264 with open(stdout, 'r') as f:
265 print 'stdout: %s' % f.read()
266
Rico Wind6e2205d2018-10-25 13:27:13 +0200267def execute(cmd, archive, env=None):
Rico Wind800fd712018-09-24 11:29:33 +0200268 utils.PrintCmd(cmd)
269 with utils.TempDir() as temp:
270 try:
271 stderr_fd = None
272 stdout_fd = None
273 exitcode = 0
274 stderr = os.path.join(temp, 'stderr')
275 stderr_fd = open(stderr, 'w')
276 stdout = os.path.join(temp, 'stdout')
277 stdout_fd = open(stdout, 'w')
278 popen = subprocess.Popen(cmd,
279 bufsize=1024*1024*10,
280 stdout=stdout_fd,
Rico Wind6e2205d2018-10-25 13:27:13 +0200281 stderr=stderr_fd,
282 env=env)
Rico Wind800fd712018-09-24 11:29:33 +0200283 begin = time.time()
284 timed_out = False
285 while popen.poll() == None:
286 if time.time() - begin > RUN_TIMEOUT:
287 popen.terminate()
288 timed_out = True
289 time.sleep(2)
290 exitcode = popen.returncode
291 finally:
292 if stderr_fd:
293 stderr_fd.close()
294 if stdout_fd:
295 stdout_fd.close()
296 if exitcode != 0:
297 handle_output(archive, stderr, stdout, popen.returncode,
298 timed_out, ' '.join(cmd))
299 return exitcode
300
301def run_once(archive):
302 failed = False
303 git_hash = utils.get_HEAD_sha1()
Rico Wind1200f512018-09-26 08:48:37 +0200304 log('Running once with hash %s' % git_hash)
Rico Wind6e2205d2018-10-25 13:27:13 +0200305 env = os.environ.copy()
306 # Bot does not have a lot of memory.
307 env['R8_GRADLE_CORES_PER_FORK'] = '8'
Morten Krogh-Jespersen2243b162019-01-14 08:40:53 +0100308 failed = any([execute(cmd, archive, env) for cmd in TEST_COMMANDS])
Rico Wind800fd712018-09-24 11:29:33 +0200309 archive_status(1 if failed else 0)
Rico Wind139eece2018-09-25 09:42:09 +0200310 return failed
Rico Wind800fd712018-09-24 11:29:33 +0200311
312def Main():
313 (options, args) = ParseOptions()
314 if options.continuous:
315 run_continuously()
Rico Wind139eece2018-09-25 09:42:09 +0200316 elif options.bot:
317 return run_bot()
Rico Wind4fd2dda2018-09-26 17:41:45 +0200318 elif options.print_logs:
319 return fetch_and_print_logs(options.print_logs)
Rico Wind800fd712018-09-24 11:29:33 +0200320 else:
Rico Wind139eece2018-09-25 09:42:09 +0200321 return run_once(options.archive)
Rico Wind800fd712018-09-24 11:29:33 +0200322
323if __name__ == '__main__':
324 sys.exit(Main())