#!/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())