Path: blob/main/tools/maint/rebaseline_tests.py
4150 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 subprocess15import statistics16import 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 utils, shared2324TESTS = [25'other.test_small_js_flags',26'other.*code_size*',27'other.*codesize*',28'skip:other.test_jspi_code_size',29]303132def run(cmd, **args):33return subprocess.check_output(cmd, text=True, cwd=root_dir, **args)343536all_deltas = []373839def read_size_from_json(content):40json_data = json.loads(content)41if 'total' in json_data:42return json_data['total']43# If `total` if not in the json dict then just use the first key. This happens when only one44# file size is reported (in this case we don't calculate or store the `total`).45first_key = list(json_data.keys())[0]46return json_data[first_key]474849def process_changed_file(filename):50content = open(filename).read()51old_content = run(['git', 'show', f'HEAD:{filename}'])52print(f'processing {filename}')5354ext = os.path.splitext(filename)[1]55if ext == '.size':56size = int(content.strip())57old_size = int(old_content.strip())58elif ext == '.json':59size = read_size_from_json(content)60old_size = read_size_from_json(old_content)61else:62# Unhandled file type63return f'{filename} updated\n'6465filename = utils.removeprefix(filename, 'test/')66delta = size - old_size67percent_delta = delta * 100 / old_size68all_deltas.append(percent_delta)69return f'{filename}: {old_size} => {size} [{delta:+} bytes / {percent_delta:+.2f}%]\n'707172def main():73parser = argparse.ArgumentParser()74parser.add_argument('-s', '--skip-tests', action='store_true', help="Don't actually run the tests, just analyze the existing results")75parser.add_argument('-b', '--new-branch', action='store_true', help='Create a new branch containing the updates')76parser.add_argument('-c', '--clear-cache', action='store_true', help='Clear the cache before rebaselining (useful when working with llvm changes)')77parser.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')78args = parser.parse_args()7980if args.clear_cache:81run(['./emcc', '--clear-cache'])8283if not args.skip_tests:84if not args.check_only and run(['git', 'status', '-uno', '--porcelain']).strip():85print('tree is not clean')86return 18788subprocess.check_call([shared.bat_suffix(os.path.join('test', 'runner')), '--rebaseline', '--browser=0'] + TESTS, cwd=root_dir)8990output = run(['git', 'status', '-uno', '--porcelain'])91filenames = []92for line in output.splitlines():93filename = line.strip().rsplit(' ', 1)[1]94if filename.startswith('test') and os.path.isfile(filename):95filenames.append(filename)9697if not filenames:98print('test expectations are up-to-date')99return 0100101if args.check_only:102message = f'''Test expectations are out-of-date103104The following ({len(filenames)}) test expectation files were updated by105running the tests with `--rebaseline`:106107```108'''109else:110message = f'''111Automatic rebaseline of codesize expectations. NFC112113This is an automatic change generated by tools/maint/rebaseline_tests.py.114115The following ({len(filenames)}) test expectation files were updated by116running the tests with `--rebaseline`:117118```119'''120121for file in filenames:122message += process_changed_file(file)123124if all_deltas:125message += f'\nAverage change: {statistics.mean(all_deltas):+.2f}% ({min(all_deltas):+.2f}% - {max(all_deltas):+.2f}%)\n'126127message += '```\n'128129print(message)130if args.check_only:131return 1132133if args.new_branch:134run(['git', 'checkout', '-b', 'rebaseline_tests'])135run(['git', 'add', '-u', '.'])136run(['git', 'commit', '-F', '-'], input=message)137138return 2139140141if __name__ == '__main__':142sys.exit(main())143144145