| #!/usr/bin/env python3 | |
| # | |
| # Copyright 2013 The Flutter Authors. All rights reserved. | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| import sys | |
| import subprocess | |
| import os | |
| import argparse | |
| import errno | |
| import shutil | |
| def get_llvm_bin_directory(): | |
| buildtool_dir = os.path.join( | |
| os.path.dirname(os.path.realpath(__file__)), '../../buildtools' | |
| ) | |
| platform_dir = '' | |
| if sys.platform.startswith('linux'): | |
| platform_dir = 'linux-x64' | |
| elif sys.platform == 'darwin': | |
| platform_dir = 'mac-x64' | |
| else: | |
| raise Exception('Unknown/Unsupported platform.') | |
| llvm_bin_dir = os.path.abspath( | |
| os.path.join(buildtool_dir, platform_dir, 'clang/bin') | |
| ) | |
| if not os.path.exists(llvm_bin_dir): | |
| raise Exception('LLVM directory %s double not be located.' % llvm_bin_dir) | |
| return llvm_bin_dir | |
| def make_dirs(new_dir): | |
| """A wrapper around os.makedirs() that emulates "mkdir -p".""" | |
| try: | |
| os.makedirs(new_dir) | |
| except OSError as err: | |
| if err.errno != errno.EEXIST: | |
| raise | |
| def remove_if_exists(path): | |
| if os.path.isdir(path) and not os.path.islink(path): | |
| shutil.rmtree(path) | |
| elif os.path.exists(path): | |
| os.remove(path) | |
| def collect_profiles(args): | |
| raw_profiles = [] | |
| binaries = [] | |
| # Run all unit tests and collect raw profiles. | |
| for test in args.tests: | |
| absolute_test_path = os.path.abspath(test) | |
| absolute_test_dir = os.path.dirname(absolute_test_path) | |
| test_name = os.path.basename(absolute_test_path) | |
| if not os.path.exists(absolute_test_path): | |
| print('Path %s does not exist.' % absolute_test_path) | |
| return -1 | |
| unstripped_test_path = os.path.join( | |
| absolute_test_dir, 'exe.unstripped', test_name | |
| ) | |
| if os.path.exists(unstripped_test_path): | |
| binaries.append(unstripped_test_path) | |
| else: | |
| binaries.append(absolute_test_path) | |
| raw_profile = absolute_test_path + '.rawprofile' | |
| remove_if_exists(raw_profile) | |
| print( | |
| 'Running test %s to gather profile.' % | |
| os.path.basename(absolute_test_path) | |
| ) | |
| test_command = [absolute_test_path] | |
| test_args = ' '.join(args.test_args).split() | |
| if test_args is not None: | |
| test_command += test_args | |
| subprocess.check_call(test_command, env={'LLVM_PROFILE_FILE': raw_profile}) | |
| if not os.path.exists(raw_profile): | |
| print('Could not find raw profile data for unit test run %s.' % test) | |
| print('Did you build with the --coverage flag?') | |
| return -1 | |
| raw_profiles.append(raw_profile) | |
| return (binaries, raw_profiles) | |
| def merge_profiles(llvm_bin_dir, raw_profiles, output): | |
| # Merge all raw profiles into a single profile. | |
| profdata_binary = os.path.join(llvm_bin_dir, 'llvm-profdata') | |
| print('Merging %d raw profile(s) into single profile.' % len(raw_profiles)) | |
| merged_profile_path = os.path.join(output, 'all.profile') | |
| remove_if_exists(merged_profile_path) | |
| merge_command = [profdata_binary, 'merge', '-sparse' | |
| ] + raw_profiles + ['-o', merged_profile_path] | |
| subprocess.check_call(merge_command) | |
| print('Done.') | |
| return merged_profile_path | |
| def main(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument( | |
| '-t', | |
| '--tests', | |
| nargs='+', | |
| dest='tests', | |
| required=True, | |
| help='The unit tests to run and gather coverage data on.' | |
| ) | |
| parser.add_argument( | |
| '-o', | |
| '--output', | |
| dest='output', | |
| required=True, | |
| help='The output directory for coverage results.' | |
| ) | |
| parser.add_argument( | |
| '-f', | |
| '--format', | |
| type=str, | |
| choices=['all', 'html', 'summary', 'lcov'], | |
| required=True, | |
| help='The type of coverage information to be displayed.' | |
| ) | |
| parser.add_argument( | |
| '-a', | |
| '--args', | |
| nargs='+', | |
| dest='test_args', | |
| required=False, | |
| help='The arguments to pass to the unit test executable being run.' | |
| ) | |
| args = parser.parse_args() | |
| output = os.path.abspath(args.output) | |
| make_dirs(output) | |
| generate_all_reports = args.format == 'all' | |
| binaries, raw_profiles = collect_profiles(args) | |
| if len(raw_profiles) == 0: | |
| print('No raw profiles could be generated.') | |
| return -1 | |
| binaries_flag = [] | |
| for binary in binaries: | |
| binaries_flag.append('-object') | |
| binaries_flag.append(binary) | |
| llvm_bin_dir = get_llvm_bin_directory() | |
| merged_profile_path = merge_profiles(llvm_bin_dir, raw_profiles, output) | |
| if not os.path.exists(merged_profile_path): | |
| print('Could not generate or find merged profile %s.' % merged_profile_path) | |
| return -1 | |
| llvm_cov_binary = os.path.join(llvm_bin_dir, 'llvm-cov') | |
| instr_profile_flag = '-instr-profile=%s' % merged_profile_path | |
| ignore_flags = '-ignore-filename-regex=third_party|unittest|fixture' | |
| # Generate the HTML report if specified. | |
| if generate_all_reports or args.format == 'html': | |
| print('Generating HTML report.') | |
| subprocess.check_call([llvm_cov_binary, 'show'] + binaries_flag + [ | |
| instr_profile_flag, | |
| '-format=html', | |
| '-output-dir=%s' % output, | |
| '-tab-size=2', | |
| ignore_flags, | |
| ]) | |
| print('Done.') | |
| # Generate a report summary if specified. | |
| if generate_all_reports or args.format == 'summary': | |
| print('Generating a summary report.') | |
| subprocess.check_call([llvm_cov_binary, 'report'] + binaries_flag + [ | |
| instr_profile_flag, | |
| ignore_flags, | |
| ]) | |
| print('Done.') | |
| # Generate a lcov summary if specified. | |
| if generate_all_reports or args.format == 'lcov': | |
| print('Generating LCOV report.') | |
| lcov_file = os.path.join(output, 'coverage.lcov') | |
| remove_if_exists(lcov_file) | |
| with open(lcov_file, 'w') as lcov_redirect: | |
| subprocess.check_call([llvm_cov_binary, 'export'] + binaries_flag + [ | |
| instr_profile_flag, | |
| ignore_flags, | |
| '-format=lcov', | |
| ], | |
| stdout=lcov_redirect) | |
| print('Done.') | |
| return 0 | |
| if __name__ == '__main__': | |
| sys.exit(main()) |