Path: blob/main/tools/maint/rebaseline_tests.py
6174 views
#!/usr/bin/env python31# Copyright 2024 The Emscripten Authors. All rights reserved.2# Emscripten is available under two separate licenses, the MIT license and the3# University of Illinois/NCSA Open Source License. Both these licenses can be4# found in the LICENSE file.56"""Automatically rebaseline tests that have codesize expectations and create7a git commit containing the resulting changes along with readable details of8the generated changes.9"""1011import argparse12import json13import os14import statistics15import subprocess16import sys1718script_dir = os.path.dirname(os.path.abspath(__file__))19root_dir = os.path.dirname(os.path.dirname(script_dir))2021sys.path.insert(0, root_dir)22from tools import utils232425def run(cmd, **args):26return subprocess.check_output(cmd, text=True, cwd=root_dir, **args)272829all_deltas = []303132def read_size_from_json(content):33json_data = json.loads(content)34if 'total' in json_data:35return json_data['total']36# If `total` if not in the json dict then just use the first key. This happens when only one37# file size is reported (in this case we don't calculate or store the `total`).38first_key = list(json_data.keys())[0]39return json_data[first_key]404142def process_changed_file(filename):43content = open(filename).read()44old_content = run(['git', 'show', f'HEAD:{filename}'])45print(f'processing {filename}')4647ext = os.path.splitext(filename)[1]48if ext == '.size':49size = int(content.strip())50old_size = int(old_content.strip())51elif ext == '.json':52size = read_size_from_json(content)53old_size = read_size_from_json(old_content)54else:55# Unhandled file type56return f'{filename} updated\n'5758filename = filename.removeprefix('test/')59delta = size - old_size60percent_delta = delta * 100 / old_size61all_deltas.append(percent_delta)62return f'{filename}: {old_size} => {size} [{delta:+} bytes / {percent_delta:+.2f}%]\n'636465def main():66parser = argparse.ArgumentParser()67parser.add_argument('-s', '--skip-tests', action='store_true', help="Don't actually run the tests, just analyze the existing results")68parser.add_argument('-b', '--new-branch', action='store_true', help='Create a new branch containing the updates')69parser.add_argument('-c', '--clear-cache', action='store_true', help='Clear the cache before rebaselining (useful when working with llvm changes)')70parser.add_argument('-n', '--check-only', dest='check_only', action='store_true', help='Return non-zero if test expectations are out of date, and skip creating a git commit')71args = parser.parse_args()7273if args.clear_cache:74run(['./emcc', '--clear-cache'])7576if not args.skip_tests:77if not args.check_only and run(['git', 'status', '-uno', '--porcelain']).strip():78print('tree is not clean')79return 18081subprocess.check_call([utils.exe_path_from_root('test/runner'), '--rebaseline', 'codesize'], cwd=root_dir)8283output = run(['git', 'status', '-uno', '--porcelain'])84filenames = []85for line in output.splitlines():86filename = line.strip().rsplit(' ', 1)[1]87if filename.startswith('test') and os.path.isfile(filename):88filenames.append(filename)8990if not filenames:91print('test expectations are up-to-date')92return 09394if args.check_only:95message = f'''Test expectations are out-of-date9697The following ({len(filenames)}) test expectation files were updated by98running the tests with `--rebaseline`:99100```101'''102else:103message = f'''104Automatic rebaseline of codesize expectations. NFC105106This is an automatic change generated by tools/maint/rebaseline_tests.py.107108The following ({len(filenames)}) test expectation files were updated by109running the tests with `--rebaseline`:110111```112'''113114for file in filenames:115message += process_changed_file(file)116117if all_deltas:118message += f'\nAverage change: {statistics.mean(all_deltas):+.2f}% ({min(all_deltas):+.2f}% - {max(all_deltas):+.2f}%)\n'119120message += '```\n'121122print(message)123if args.check_only:124return 1125126if args.new_branch:127run(['git', 'checkout', '-b', 'rebaseline_tests'])128run(['git', 'add', '-u', '.'])129run(['git', 'commit', '-F', '-'], input=message)130131return 2132133134if __name__ == '__main__':135sys.exit(main())136137138