blob: 555259f9fd44fb505d4ec853c6cc49814446fdfc [file] [log] [blame]
Ian Zernydcb172e2022-02-22 15:36:45 +01001#!/usr/bin/env python3
Rico Wind800fd712018-09-24 11:29:33 +02002# 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
Morten Krogh-Jespersenf2412302019-10-22 10:18:04 +020039import run_on_app
Rico Wind800fd712018-09-24 11:29:33 +020040
Christoffer Quist Adamsen81d41502021-06-25 09:33:43 +020041import chrome_data
Christoffer Quist Adamsen4380de32021-04-23 06:24:31 +020042import youtube_data
43
Rico Wind139eece2018-09-25 09:42:09 +020044# How often the bot/tester should check state
45PULL_DELAY = 30
Rico Wind800fd712018-09-24 11:29:33 +020046TEST_RESULT_DIR = 'internal'
47
Rico Wind139eece2018-09-25 09:42:09 +020048# Magic files
49READY_FOR_TESTING = 'READY_FOR_TESTING'
50TESTING = 'TESTING'
51TESTING_COMPLETE = 'TESTING_COMPLETE'
52
53ALL_MAGIC = [READY_FOR_TESTING, TESTING, TESTING_COMPLETE]
54
55# Log file names
56STDERR = 'stderr'
57STDOUT = 'stdout'
58EXITCODE = 'exitcode'
59TIMED_OUT = 'timed_out'
60
Christoffer Quist Adamsenb8ab39d2023-08-23 12:22:10 +020061BENCHMARK_APPS = [chrome_data, youtube_data]
Morten Krogh-Jespersen6cd9f1d2019-10-09 14:01:04 +020062
Rico Winda7b8fff2023-07-03 07:49:17 +020063DEPENDENT_PYTHON_FILES = [gradle, utils, run_on_app]
64
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020065
Christoffer Quist Adamsen81d41502021-06-25 09:33:43 +020066def find_min_xmx_command(app_data):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020067 record = app_data.GetMemoryData(app_data.GetLatestVersion())
68 assert record['find-xmx-min'] < record['find-xmx-max']
69 assert record[
70 'find-xmx-range'] < record['find-xmx-max'] - record['find-xmx-min']
71 return [
72 'tools/run_on_app.py', '--compiler=r8', '--compiler-build=lib',
73 '--app=%s' % app_data.GetName(),
74 '--version=%s' % app_data.GetLatestVersion(), '--no-debug',
75 '--no-build', '--find-min-xmx',
76 '--find-min-xmx-min-memory=%s' % record['find-xmx-min'],
77 '--find-min-xmx-max-memory=%s' % record['find-xmx-max'],
78 '--find-min-xmx-range-size=%s' % record['find-xmx-range'],
79 '--find-min-xmx-archive'
80 ]
81
Morten Krogh-Jespersen6cd9f1d2019-10-09 14:01:04 +020082
Christoffer Quist Adamsen81d41502021-06-25 09:33:43 +020083def compile_with_memory_max_command(app_data):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020084 # TODO(b/152939233): Remove this special handling when fixed.
85 factor = 1.25 if app_data.GetName() == 'chrome' else 1.15
86 record = app_data.GetMemoryData(app_data.GetLatestVersion())
87 return [] if 'skip-find-xmx-max' in record else [
88 'tools/run_on_app.py', '--compiler=r8', '--compiler-build=lib',
89 '--app=%s' % app_data.GetName(),
90 '--version=%s' %
91 app_data.GetLatestVersion(), '--no-debug', '--no-build',
92 '--max-memory=%s' % int(record['oom-threshold'] * factor)
93 ]
94
Morten Krogh-Jespersen3793dc92019-10-09 14:48:48 +020095
Christoffer Quist Adamsen81d41502021-06-25 09:33:43 +020096def compile_with_memory_min_command(app_data):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020097 record = app_data.GetMemoryData(app_data.GetLatestVersion())
98 return [
99 'tools/run_on_app.py', '--compiler=r8', '--compiler-build=lib',
100 '--app=%s' % app_data.GetName(),
101 '--version=%s' % app_data.GetLatestVersion(), '--no-debug',
102 '--no-build', '--expect-oom',
103 '--max-memory=%s' % int(record['oom-threshold'] * 0.85)
104 ]
105
Morten Krogh-Jespersen3793dc92019-10-09 14:48:48 +0200106
Ian Zernydf1deca2023-10-12 10:38:46 +0200107CLEAN_COMMANDS = [
Rico Wind5a360e92022-03-31 08:49:25 +0200108 # Make sure we have a clean build to not be polluted by old test files
Rico Winde0c7cdf2023-10-31 08:16:03 +0100109 ['tools/gradle.py', 'clean'],
Ian Zernydf1deca2023-10-12 10:38:46 +0200110]
111
112# TODO(b/210982978): Enable testing of min xmx again
113TEST_COMMANDS = [
Morten Krogh-Jespersen2243b162019-01-14 08:40:53 +0100114 # Run test.py internal testing.
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200115 [
116 'tools/test.py', '--only_internal', '--slow_tests',
117 '--java_max_memory_size=8G'
118 ],
Christoffer Adamsen83607ad2024-09-17 12:21:05 +0200119 # Run internal benchmarks.
Christoffer Adamsen7a64eb52024-12-18 09:48:12 +0100120 [
121 'tools/perf.py', '--internal', '--iterations-inner', '3',
122 '--no-upload-benchmark-data-to-google-storage'
123 ],
Morten Krogh-Jespersen2243b162019-01-14 08:40:53 +0100124 # Ensure that all internal apps compile.
Rico Windbeeaded2024-04-11 09:49:37 +0200125 ['tools/run_on_app.py', '--run-all', '--out=out', '--workers', '3'],
Morten Krogh-Jespersenfcb06452021-12-16 15:19:49 +0100126]
Morten Krogh-Jespersen3793dc92019-10-09 14:48:48 +0200127
Rico Wind6847d132018-09-26 08:18:48 +0200128# Command timeout, in seconds.
Rico Wind39c73eb2023-08-17 08:59:01 +0200129RUN_TIMEOUT = 3600 * 7
Rico Windf021d832018-12-13 11:29:22 +0100130BOT_RUN_TIMEOUT = RUN_TIMEOUT * len(TEST_COMMANDS)
Rico Wind6847d132018-09-26 08:18:48 +0200131
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200132
Rico Wind1200f512018-09-26 08:48:37 +0200133def log(str):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200134 print("%s: %s" % (time.strftime("%c"), str))
135 sys.stdout.flush()
136
Rico Wind1200f512018-09-26 08:48:37 +0200137
Rico Wind800fd712018-09-24 11:29:33 +0200138def ParseOptions():
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200139 result = optparse.OptionParser()
140 result.add_option(
141 '--continuous',
142 help='Continuously run internal tests and post results to GCS.',
143 default=False,
144 action='store_true')
145 result.add_option(
146 '--print_logs',
147 help=
148 'Fetch logs from gcs and print them, takes the commit to print for.',
149 default=None)
150 result.add_option('--bot',
151 help='Run in bot mode, i.e., scheduling runs.',
152 default=False,
153 action='store_true')
154 result.add_option('--archive',
155 help='Post result to GCS, implied by --continuous',
156 default=False,
157 action='store_true')
158 return result.parse_args()
159
Rico Wind800fd712018-09-24 11:29:33 +0200160
Rico Wind139eece2018-09-25 09:42:09 +0200161def ensure_git_clean():
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200162 # Ensure clean git repo.
163 diff = subprocess.check_output(['git', 'diff']).decode('utf-8')
164 if len(diff) > 0:
165 log('Local modifications to the git repo, exiting')
166 sys.exit(1)
167
Rico Wind139eece2018-09-25 09:42:09 +0200168
169def git_pull():
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200170 ensure_git_clean()
171 subprocess.check_call(['git', 'checkout', 'main'])
172 subprocess.check_call(['git', 'pull'])
173 return utils.get_HEAD_sha1()
174
Rico Wind800fd712018-09-24 11:29:33 +0200175
Rico Wind139eece2018-09-25 09:42:09 +0200176def git_checkout(git_hash):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200177 ensure_git_clean()
178 # Ensure that we are up to date to get the commit.
179 git_pull()
180 exitcode = subprocess.call(['git', 'checkout', git_hash])
181 if exitcode != 0:
182 return None
183 return utils.get_HEAD_sha1()
184
Rico Wind139eece2018-09-25 09:42:09 +0200185
186def get_test_result_dir():
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200187 return os.path.join(utils.R8_INTERNAL_TEST_RESULTS_BUCKET, TEST_RESULT_DIR)
188
Rico Wind139eece2018-09-25 09:42:09 +0200189
Rico Wind800fd712018-09-24 11:29:33 +0200190def get_sha_destination(sha):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200191 return os.path.join(get_test_result_dir(), sha)
192
Rico Wind800fd712018-09-24 11:29:33 +0200193
194def archive_status(failed):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200195 gs_destination = 'gs://%s' % get_sha_destination(utils.get_HEAD_sha1())
196 utils.archive_value('status', gs_destination, failed)
197
Rico Wind800fd712018-09-24 11:29:33 +0200198
Rico Wind139eece2018-09-25 09:42:09 +0200199def get_status(sha):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200200 gs_destination = 'gs://%s/status' % get_sha_destination(sha)
201 return utils.cat_file_on_cloud_storage(gs_destination)
202
Rico Wind139eece2018-09-25 09:42:09 +0200203
Rico Wind800fd712018-09-24 11:29:33 +0200204def archive_log(stdout, stderr, exitcode, timed_out, cmd):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200205 sha = utils.get_HEAD_sha1()
206 cmd_dir = cmd.replace(' ', '_').replace('/', '_')
207 destination = os.path.join(get_sha_destination(sha), cmd_dir)
208 gs_destination = 'gs://%s' % destination
209 url = 'https://storage.cloud.google.com/%s' % destination
210 log('Archiving logs to: %s' % gs_destination)
211 utils.archive_value(EXITCODE, gs_destination, exitcode)
212 utils.archive_value(TIMED_OUT, gs_destination, timed_out)
213 utils.archive_file(STDOUT, gs_destination, stdout)
214 utils.archive_file(STDERR, gs_destination, stderr)
215 log('Logs available at: %s' % url)
216
Rico Wind800fd712018-09-24 11:29:33 +0200217
Rico Wind139eece2018-09-25 09:42:09 +0200218def get_magic_file_base_path():
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200219 return 'gs://%s/magic' % get_test_result_dir()
220
Rico Wind139eece2018-09-25 09:42:09 +0200221
222def get_magic_file_gs_path(name):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200223 return '%s/%s' % (get_magic_file_base_path(), name)
224
Rico Wind139eece2018-09-25 09:42:09 +0200225
226def get_magic_file_exists(name):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200227 return utils.file_exists_on_cloud_storage(get_magic_file_gs_path(name))
228
Rico Wind139eece2018-09-25 09:42:09 +0200229
230def delete_magic_file(name):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200231 utils.delete_file_from_cloud_storage(get_magic_file_gs_path(name))
232
Rico Wind139eece2018-09-25 09:42:09 +0200233
234def put_magic_file(name, sha):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200235 utils.archive_value(name, get_magic_file_base_path(), sha)
236
Rico Wind139eece2018-09-25 09:42:09 +0200237
238def get_magic_file_content(name, ignore_errors=False):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200239 return utils.cat_file_on_cloud_storage(get_magic_file_gs_path(name),
240 ignore_errors=ignore_errors)
241
Rico Wind139eece2018-09-25 09:42:09 +0200242
243def print_magic_file_state():
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200244 log('Magic file status:')
245 for magic in ALL_MAGIC:
246 if get_magic_file_exists(magic):
247 content = get_magic_file_content(magic, ignore_errors=True)
248 log('%s content: %s' % (magic, content))
249
Rico Wind139eece2018-09-25 09:42:09 +0200250
Rico Wind4fd2dda2018-09-26 17:41:45 +0200251def fetch_and_print_logs(hash):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200252 gs_base = 'gs://%s' % get_sha_destination(hash)
253 listing = utils.ls_files_on_cloud_storage(gs_base).strip().split('\n')
254 for entry in listing:
255 if not entry.endswith('/status'): # Ignore the overall status file
256 for to_print in [EXITCODE, TIMED_OUT, STDERR, STDOUT]:
257 gs_location = '%s%s' % (entry, to_print)
258 value = utils.cat_file_on_cloud_storage(gs_location)
259 print('\n\n%s had value:\n%s' % (to_print, value))
260 print("\n\nPrinting find-min-xmx ranges for apps")
261 run_on_app.print_min_xmx_ranges_for_hash(hash, 'r8', 'lib')
262
Rico Wind4fd2dda2018-09-26 17:41:45 +0200263
Rico Wind139eece2018-09-25 09:42:09 +0200264def run_bot():
Rico Wind139eece2018-09-25 09:42:09 +0200265 print_magic_file_state()
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200266 # Ensure that there is nothing currently scheduled (broken/stopped run)
267 for magic in ALL_MAGIC:
268 if get_magic_file_exists(magic):
269 log('ERROR: Synchronizing file %s exists, cleaning up' % magic)
270 delete_magic_file(magic)
271 print_magic_file_state()
272 assert not get_magic_file_exists(READY_FOR_TESTING)
273 git_hash = utils.get_HEAD_sha1()
274 put_magic_file(READY_FOR_TESTING, git_hash)
275 begin = time.time()
276 while True:
277 if time.time() - begin > BOT_RUN_TIMEOUT:
278 log('Timeout exceeded: http://go/internal-r8-doc')
279 raise Exception('Bot timeout')
280 if get_magic_file_exists(TESTING_COMPLETE):
281 if get_magic_file_content(TESTING_COMPLETE) == git_hash:
282 break
283 else:
284 raise Exception(
285 'Non matching git hashes %s and %s' %
286 (get_magic_file_content(TESTING_COMPLETE), git_hash))
287 log('Still waiting for test result')
288 print_magic_file_state()
289 time.sleep(PULL_DELAY)
290 total_time = time.time() - begin
291 log('Done running test for %s in %ss' % (git_hash, total_time))
292 test_status = get_status(git_hash)
293 delete_magic_file(TESTING_COMPLETE)
294 log('Test status is: %s' % test_status)
295 if test_status != '0':
296 print('Tests failed, you can print the logs by running(googlers only):')
297 print(' tools/internal_test.py --print_logs %s' % git_hash)
298 return 1
299
Rico Wind139eece2018-09-25 09:42:09 +0200300
Rico Wind800fd712018-09-24 11:29:33 +0200301def run_continuously():
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200302 while True:
303 print_magic_file_state()
304 if get_magic_file_exists(READY_FOR_TESTING):
305 git_hash = get_magic_file_content(READY_FOR_TESTING)
306 checked_out = git_checkout(git_hash)
307 if not checked_out:
308 # Gerrit change, we don't run these on internal.
309 archive_status(0)
310 put_magic_file(TESTING_COMPLETE, git_hash)
311 delete_magic_file(READY_FOR_TESTING)
312 continue
313 # Sanity check, if this does not succeed stop.
314 if checked_out != git_hash:
315 log('Inconsistent state: %s %s' % (git_hash, checked_out))
316 sys.exit(1)
317 put_magic_file(TESTING, git_hash)
318 delete_magic_file(READY_FOR_TESTING)
319 log('Running with hash: %s' % git_hash)
320 exitcode = run_external()
321 log('Running finished with exit code %s' % exitcode)
322 # If the bot timed out or something else triggered the bot to fail, don't
323 # put up the result (it will not be displayed anywhere, and we can't
324 # remove the magic file if the bot cleaned up).
325 if get_magic_file_exists(TESTING):
326 put_magic_file(TESTING_COMPLETE, git_hash)
327 # There is still a potential race here (we check, bot deletes, we try to
328 # delete) - this is unlikely and we ignore it (restart if it happens).
329 delete_magic_file(TESTING)
330 time.sleep(PULL_DELAY)
331
Rico Wind800fd712018-09-24 11:29:33 +0200332
Rico Wind62ed83c2023-08-25 11:01:53 +0200333def run_external():
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200334 return subprocess.call(
335 [sys.executable, "tools/internal_test.py", "--archive"])
336
Rico Wind62ed83c2023-08-25 11:01:53 +0200337
Rico Wind800fd712018-09-24 11:29:33 +0200338def handle_output(archive, stderr, stdout, exitcode, timed_out, cmd):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200339 if archive:
340 archive_log(stdout, stderr, exitcode, timed_out, cmd)
341 else:
342 print('Execution of %s resulted in:' % cmd)
343 print('exit code: %s ' % exitcode)
344 print('timeout: %s ' % timed_out)
345 with open(stderr, 'r') as f:
346 print('stderr: %s' % f.read())
347 with open(stdout, 'r') as f:
348 print('stdout: %s' % f.read())
349
Rico Wind800fd712018-09-24 11:29:33 +0200350
Rico Wind6e2205d2018-10-25 13:27:13 +0200351def execute(cmd, archive, env=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200352 if cmd == []:
353 return
Christoffer Quist Adamsen280fae02021-11-05 13:06:17 +0100354
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200355 assert (cmd[0].endswith('.py'))
356 cmd = [sys.executable] + cmd
Christoffer Quist Adamsene2788d22021-11-05 12:37:39 +0100357
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200358 utils.PrintCmd(cmd)
359 with utils.TempDir() as temp:
360 try:
361 stderr_fd = None
362 stdout_fd = None
363 exitcode = 0
364 stderr = os.path.join(temp, 'stderr')
365 stderr_fd = open(stderr, 'w')
366 stdout = os.path.join(temp, 'stdout')
367 stdout_fd = open(stdout, 'w')
368 popen = subprocess.Popen(cmd,
369 bufsize=1024 * 1024 * 10,
370 stdout=stdout_fd,
371 stderr=stderr_fd,
372 env=env)
373 begin = time.time()
374 timed_out = False
375 while popen.poll() == None:
376 if time.time() - begin > RUN_TIMEOUT:
377 popen.terminate()
378 timed_out = True
379 time.sleep(2)
380 exitcode = popen.returncode
381 finally:
382 if stderr_fd:
383 stderr_fd.close()
384 if stdout_fd:
385 stdout_fd.close()
386 if exitcode != 0:
387 handle_output(archive, stderr, stdout, popen.returncode,
388 timed_out, ' '.join(cmd))
389 return exitcode
Morten Krogh-Jespersenb042c972019-11-04 08:57:59 +0100390
Rico Wind800fd712018-09-24 11:29:33 +0200391
392def run_once(archive):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200393 git_hash = utils.get_HEAD_sha1()
394 log('Running once with hash %s' % git_hash)
395 env = os.environ.copy()
396 # Bot does not have a lot of memory.
397 env['R8_GRADLE_CORES_PER_FORK'] = '5'
398 if archive:
399 [execute(cmd, archive, env) for cmd in CLEAN_COMMANDS]
400 failed = any([execute(cmd, archive, env) for cmd in TEST_COMMANDS])
401 # Gradle daemon occasionally leaks memory, stop it.
402 gradle.RunGradle(['--stop'])
403 archive_status(1 if failed else 0)
404 return failed
405
Rico Wind800fd712018-09-24 11:29:33 +0200406
407def Main():
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200408 (options, args) = ParseOptions()
409 if options.continuous:
410 run_continuously()
411 elif options.bot:
412 return run_bot()
413 elif options.print_logs:
414 return fetch_and_print_logs(options.print_logs)
415 else:
416 return run_once(options.archive)
417
Rico Wind800fd712018-09-24 11:29:33 +0200418
419if __name__ == '__main__':
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200420 sys.exit(Main())