11cb0ef41Sopenharmony_ci#!/usr/bin/env python 21cb0ef41Sopenharmony_ci# Copyright 2016 the V8 project authors. All rights reserved. 31cb0ef41Sopenharmony_ci# Use of this source code is governed by a BSD-style license that can be 41cb0ef41Sopenharmony_ci# found in the LICENSE file. 51cb0ef41Sopenharmony_ci 61cb0ef41Sopenharmony_ci"""Script to transform and merge sancov files into human readable json-format. 71cb0ef41Sopenharmony_ci 81cb0ef41Sopenharmony_ciThe script supports three actions: 91cb0ef41Sopenharmony_ciall: Writes a json file with all instrumented lines of all executables. 101cb0ef41Sopenharmony_cimerge: Merges sancov files with coverage output into an existing json file. 111cb0ef41Sopenharmony_cisplit: Split json file into separate files per covered source file. 121cb0ef41Sopenharmony_ci 131cb0ef41Sopenharmony_ciThe json data is structured as follows: 141cb0ef41Sopenharmony_ci{ 151cb0ef41Sopenharmony_ci "version": 1, 161cb0ef41Sopenharmony_ci "tests": ["executable1", "executable2", ...], 171cb0ef41Sopenharmony_ci "files": { 181cb0ef41Sopenharmony_ci "file1": [[<instr line 1>, <bit_mask>], [<instr line 2>, <bit_mask>], ...], 191cb0ef41Sopenharmony_ci "file2": [...], 201cb0ef41Sopenharmony_ci ... 211cb0ef41Sopenharmony_ci } 221cb0ef41Sopenharmony_ci} 231cb0ef41Sopenharmony_ci 241cb0ef41Sopenharmony_ciThe executables are sorted and determine the test bit mask. Their index+1 is 251cb0ef41Sopenharmony_cithe bit, e.g. executable1 = 1, executable3 = 4, etc. Hence, a line covered by 261cb0ef41Sopenharmony_ciexecutable1 and executable3 will have bit_mask == 5 == 0b101. The number of 271cb0ef41Sopenharmony_citests is restricted to 52 in version 1, to allow javascript JSON parsing of 281cb0ef41Sopenharmony_cithe bitsets encoded as numbers. JS max safe int is (1 << 53) - 1. 291cb0ef41Sopenharmony_ci 301cb0ef41Sopenharmony_ciThe line-number-bit_mask pairs are sorted by line number and don't contain 311cb0ef41Sopenharmony_ciduplicates. 321cb0ef41Sopenharmony_ci 331cb0ef41Sopenharmony_ciSplit json data preserves the same format, but only contains one file per 341cb0ef41Sopenharmony_cijson file. 351cb0ef41Sopenharmony_ci 361cb0ef41Sopenharmony_ciThe sancov tool is expected to be in the llvm compiler-rt third-party 371cb0ef41Sopenharmony_cidirectory. It's not checked out by default and must be added as a custom deps: 381cb0ef41Sopenharmony_ci'v8/third_party/llvm/projects/compiler-rt': 391cb0ef41Sopenharmony_ci 'https://chromium.googlesource.com/external/llvm.org/compiler-rt.git' 401cb0ef41Sopenharmony_ci""" 411cb0ef41Sopenharmony_ci 421cb0ef41Sopenharmony_ci# for py2/py3 compatibility 431cb0ef41Sopenharmony_cifrom __future__ import print_function 441cb0ef41Sopenharmony_cifrom functools import reduce 451cb0ef41Sopenharmony_ci 461cb0ef41Sopenharmony_ciimport argparse 471cb0ef41Sopenharmony_ciimport json 481cb0ef41Sopenharmony_ciimport logging 491cb0ef41Sopenharmony_ciimport os 501cb0ef41Sopenharmony_ciimport re 511cb0ef41Sopenharmony_ciimport subprocess 521cb0ef41Sopenharmony_ciimport sys 531cb0ef41Sopenharmony_ci 541cb0ef41Sopenharmony_cifrom multiprocessing import Pool, cpu_count 551cb0ef41Sopenharmony_ci 561cb0ef41Sopenharmony_ci 571cb0ef41Sopenharmony_cilogging.basicConfig(level=logging.INFO) 581cb0ef41Sopenharmony_ci 591cb0ef41Sopenharmony_ci# Files to exclude from coverage. Dropping their data early adds more speed. 601cb0ef41Sopenharmony_ci# The contained cc files are already excluded from instrumentation, but inlined 611cb0ef41Sopenharmony_ci# data is referenced through v8's object files. 621cb0ef41Sopenharmony_ciEXCLUSIONS = [ 631cb0ef41Sopenharmony_ci 'buildtools', 641cb0ef41Sopenharmony_ci 'src/third_party', 651cb0ef41Sopenharmony_ci 'third_party', 661cb0ef41Sopenharmony_ci 'test', 671cb0ef41Sopenharmony_ci 'testing', 681cb0ef41Sopenharmony_ci] 691cb0ef41Sopenharmony_ci 701cb0ef41Sopenharmony_ci# Executables found in the build output for which no coverage is generated. 711cb0ef41Sopenharmony_ci# Exclude them from the coverage data file. 721cb0ef41Sopenharmony_ciEXE_EXCLUSIONS = [ 731cb0ef41Sopenharmony_ci 'generate-bytecode-expectations', 741cb0ef41Sopenharmony_ci 'hello-world', 751cb0ef41Sopenharmony_ci 'mksnapshot', 761cb0ef41Sopenharmony_ci 'parser-shell', 771cb0ef41Sopenharmony_ci 'process', 781cb0ef41Sopenharmony_ci 'shell', 791cb0ef41Sopenharmony_ci] 801cb0ef41Sopenharmony_ci 811cb0ef41Sopenharmony_ci# V8 checkout directory. 821cb0ef41Sopenharmony_ciBASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname( 831cb0ef41Sopenharmony_ci os.path.abspath(__file__)))) 841cb0ef41Sopenharmony_ci 851cb0ef41Sopenharmony_ci# The sancov tool location. 861cb0ef41Sopenharmony_ciSANCOV_TOOL = os.path.join( 871cb0ef41Sopenharmony_ci BASE_DIR, 'third_party', 'llvm', 'projects', 'compiler-rt', 881cb0ef41Sopenharmony_ci 'lib', 'sanitizer_common', 'scripts', 'sancov.py') 891cb0ef41Sopenharmony_ci 901cb0ef41Sopenharmony_ci# Simple script to sanitize the PCs from objdump. 911cb0ef41Sopenharmony_ciSANITIZE_PCS = os.path.join(BASE_DIR, 'tools', 'sanitizers', 'sanitize_pcs.py') 921cb0ef41Sopenharmony_ci 931cb0ef41Sopenharmony_ci# The llvm symbolizer location. 941cb0ef41Sopenharmony_ciSYMBOLIZER = os.path.join( 951cb0ef41Sopenharmony_ci BASE_DIR, 'third_party', 'llvm-build', 'Release+Asserts', 'bin', 961cb0ef41Sopenharmony_ci 'llvm-symbolizer') 971cb0ef41Sopenharmony_ci 981cb0ef41Sopenharmony_ci# Number of cpus. 991cb0ef41Sopenharmony_ciCPUS = cpu_count() 1001cb0ef41Sopenharmony_ci 1011cb0ef41Sopenharmony_ci# Regexp to find sancov files as output by sancov_merger.py. Also grabs the 1021cb0ef41Sopenharmony_ci# executable name in group 1. 1031cb0ef41Sopenharmony_ciSANCOV_FILE_RE = re.compile(r'^(.*)\.result.sancov$') 1041cb0ef41Sopenharmony_ci 1051cb0ef41Sopenharmony_ci 1061cb0ef41Sopenharmony_cidef executables(build_dir): 1071cb0ef41Sopenharmony_ci """Iterates over executable files in the build directory.""" 1081cb0ef41Sopenharmony_ci for f in os.listdir(build_dir): 1091cb0ef41Sopenharmony_ci file_path = os.path.join(build_dir, f) 1101cb0ef41Sopenharmony_ci if (os.path.isfile(file_path) and 1111cb0ef41Sopenharmony_ci os.access(file_path, os.X_OK) and 1121cb0ef41Sopenharmony_ci f not in EXE_EXCLUSIONS): 1131cb0ef41Sopenharmony_ci yield file_path 1141cb0ef41Sopenharmony_ci 1151cb0ef41Sopenharmony_ci 1161cb0ef41Sopenharmony_cidef process_symbolizer_output(output, build_dir): 1171cb0ef41Sopenharmony_ci """Post-process llvm symbolizer output. 1181cb0ef41Sopenharmony_ci 1191cb0ef41Sopenharmony_ci Excludes files outside the v8 checkout or given in exclusion list above 1201cb0ef41Sopenharmony_ci from further processing. Drops the character index in each line. 1211cb0ef41Sopenharmony_ci 1221cb0ef41Sopenharmony_ci Returns: A mapping of file names to lists of line numbers. The file names 1231cb0ef41Sopenharmony_ci have relative paths to the v8 base directory. The lists of line 1241cb0ef41Sopenharmony_ci numbers don't contain duplicate lines and are sorted. 1251cb0ef41Sopenharmony_ci """ 1261cb0ef41Sopenharmony_ci # Path prefix added by the llvm symbolizer including trailing slash. 1271cb0ef41Sopenharmony_ci output_path_prefix = os.path.join(build_dir, '..', '..', '') 1281cb0ef41Sopenharmony_ci 1291cb0ef41Sopenharmony_ci # Drop path prefix when iterating lines. The path is redundant and takes 1301cb0ef41Sopenharmony_ci # too much space. Drop files outside that path, e.g. generated files in 1311cb0ef41Sopenharmony_ci # the build dir and absolute paths to c++ library headers. 1321cb0ef41Sopenharmony_ci def iter_lines(): 1331cb0ef41Sopenharmony_ci for line in output.strip().splitlines(): 1341cb0ef41Sopenharmony_ci if line.startswith(output_path_prefix): 1351cb0ef41Sopenharmony_ci yield line[len(output_path_prefix):] 1361cb0ef41Sopenharmony_ci 1371cb0ef41Sopenharmony_ci # Map file names to sets of instrumented line numbers. 1381cb0ef41Sopenharmony_ci file_map = {} 1391cb0ef41Sopenharmony_ci for line in iter_lines(): 1401cb0ef41Sopenharmony_ci # Drop character number, we only care for line numbers. Each line has the 1411cb0ef41Sopenharmony_ci # form: <file name>:<line number>:<character number>. 1421cb0ef41Sopenharmony_ci file_name, number, _ = line.split(':') 1431cb0ef41Sopenharmony_ci file_map.setdefault(file_name, set([])).add(int(number)) 1441cb0ef41Sopenharmony_ci 1451cb0ef41Sopenharmony_ci # Remove exclusion patterns from file map. It's cheaper to do it after the 1461cb0ef41Sopenharmony_ci # mapping, as there are few excluded files and we don't want to do this 1471cb0ef41Sopenharmony_ci # check for numerous lines in ordinary files. 1481cb0ef41Sopenharmony_ci def keep(file_name): 1491cb0ef41Sopenharmony_ci for e in EXCLUSIONS: 1501cb0ef41Sopenharmony_ci if file_name.startswith(e): 1511cb0ef41Sopenharmony_ci return False 1521cb0ef41Sopenharmony_ci return True 1531cb0ef41Sopenharmony_ci 1541cb0ef41Sopenharmony_ci # Return in serializable form and filter. 1551cb0ef41Sopenharmony_ci return {k: sorted(file_map[k]) for k in file_map if keep(k)} 1561cb0ef41Sopenharmony_ci 1571cb0ef41Sopenharmony_ci 1581cb0ef41Sopenharmony_cidef get_instrumented_lines(executable): 1591cb0ef41Sopenharmony_ci """Return the instrumented lines of an executable. 1601cb0ef41Sopenharmony_ci 1611cb0ef41Sopenharmony_ci Called trough multiprocessing pool. 1621cb0ef41Sopenharmony_ci 1631cb0ef41Sopenharmony_ci Returns: Post-processed llvm output as returned by process_symbolizer_output. 1641cb0ef41Sopenharmony_ci """ 1651cb0ef41Sopenharmony_ci # The first two pipes are from llvm's tool sancov.py with 0x added to the hex 1661cb0ef41Sopenharmony_ci # numbers. The results are piped into the llvm symbolizer, which outputs for 1671cb0ef41Sopenharmony_ci # each PC: <file name with abs path>:<line number>:<character number>. 1681cb0ef41Sopenharmony_ci # We don't call the sancov tool to get more speed. 1691cb0ef41Sopenharmony_ci process = subprocess.Popen( 1701cb0ef41Sopenharmony_ci 'objdump -d %s | ' 1711cb0ef41Sopenharmony_ci 'grep \'^\s\+[0-9a-f]\+:.*\scall\(q\|\)\s\+[0-9a-f]\+ ' 1721cb0ef41Sopenharmony_ci '<__sanitizer_cov\(_with_check\|\|_trace_pc_guard\)\(@plt\|\)>\' | ' 1731cb0ef41Sopenharmony_ci 'grep \'^\s\+[0-9a-f]\+\' -o | ' 1741cb0ef41Sopenharmony_ci '%s | ' 1751cb0ef41Sopenharmony_ci '%s --obj %s -functions=none' % 1761cb0ef41Sopenharmony_ci (executable, SANITIZE_PCS, SYMBOLIZER, executable), 1771cb0ef41Sopenharmony_ci stdout=subprocess.PIPE, 1781cb0ef41Sopenharmony_ci stderr=subprocess.PIPE, 1791cb0ef41Sopenharmony_ci stdin=subprocess.PIPE, 1801cb0ef41Sopenharmony_ci cwd=BASE_DIR, 1811cb0ef41Sopenharmony_ci shell=True, 1821cb0ef41Sopenharmony_ci ) 1831cb0ef41Sopenharmony_ci output, _ = process.communicate() 1841cb0ef41Sopenharmony_ci assert process.returncode == 0 1851cb0ef41Sopenharmony_ci return process_symbolizer_output(output, os.path.dirname(executable)) 1861cb0ef41Sopenharmony_ci 1871cb0ef41Sopenharmony_ci 1881cb0ef41Sopenharmony_cidef merge_instrumented_line_results(exe_list, results): 1891cb0ef41Sopenharmony_ci """Merge multiprocessing results for all instrumented lines. 1901cb0ef41Sopenharmony_ci 1911cb0ef41Sopenharmony_ci Args: 1921cb0ef41Sopenharmony_ci exe_list: List of all executable names with absolute paths. 1931cb0ef41Sopenharmony_ci results: List of results as returned by get_instrumented_lines. 1941cb0ef41Sopenharmony_ci 1951cb0ef41Sopenharmony_ci Returns: Dict to be used as json data as specified on the top of this page. 1961cb0ef41Sopenharmony_ci The dictionary contains all instrumented lines of all files 1971cb0ef41Sopenharmony_ci referenced by all executables. 1981cb0ef41Sopenharmony_ci """ 1991cb0ef41Sopenharmony_ci def merge_files(x, y): 2001cb0ef41Sopenharmony_ci for file_name, lines in y.iteritems(): 2011cb0ef41Sopenharmony_ci x.setdefault(file_name, set([])).update(lines) 2021cb0ef41Sopenharmony_ci return x 2031cb0ef41Sopenharmony_ci result = reduce(merge_files, results, {}) 2041cb0ef41Sopenharmony_ci 2051cb0ef41Sopenharmony_ci # Return data as file->lines mapping. The lines are saved as lists 2061cb0ef41Sopenharmony_ci # with (line number, test bits (as int)). The test bits are initialized with 2071cb0ef41Sopenharmony_ci # 0, meaning instrumented, but no coverage. 2081cb0ef41Sopenharmony_ci # The order of the test bits is given with key 'tests'. For now, these are 2091cb0ef41Sopenharmony_ci # the executable names. We use a _list_ with two items instead of a tuple to 2101cb0ef41Sopenharmony_ci # ease merging by allowing mutation of the second item. 2111cb0ef41Sopenharmony_ci return { 2121cb0ef41Sopenharmony_ci 'version': 1, 2131cb0ef41Sopenharmony_ci 'tests': sorted(map(os.path.basename, exe_list)), 2141cb0ef41Sopenharmony_ci 'files': {f: map(lambda l: [l, 0], sorted(result[f])) for f in result}, 2151cb0ef41Sopenharmony_ci } 2161cb0ef41Sopenharmony_ci 2171cb0ef41Sopenharmony_ci 2181cb0ef41Sopenharmony_cidef write_instrumented(options): 2191cb0ef41Sopenharmony_ci """Implements the 'all' action of this tool.""" 2201cb0ef41Sopenharmony_ci exe_list = list(executables(options.build_dir)) 2211cb0ef41Sopenharmony_ci logging.info('Reading instrumented lines from %d executables.', 2221cb0ef41Sopenharmony_ci len(exe_list)) 2231cb0ef41Sopenharmony_ci pool = Pool(CPUS) 2241cb0ef41Sopenharmony_ci try: 2251cb0ef41Sopenharmony_ci results = pool.imap_unordered(get_instrumented_lines, exe_list) 2261cb0ef41Sopenharmony_ci finally: 2271cb0ef41Sopenharmony_ci pool.close() 2281cb0ef41Sopenharmony_ci 2291cb0ef41Sopenharmony_ci # Merge multiprocessing results and prepare output data. 2301cb0ef41Sopenharmony_ci data = merge_instrumented_line_results(exe_list, results) 2311cb0ef41Sopenharmony_ci 2321cb0ef41Sopenharmony_ci logging.info('Read data from %d executables, which covers %d files.', 2331cb0ef41Sopenharmony_ci len(data['tests']), len(data['files'])) 2341cb0ef41Sopenharmony_ci logging.info('Writing results to %s', options.json_output) 2351cb0ef41Sopenharmony_ci 2361cb0ef41Sopenharmony_ci # Write json output. 2371cb0ef41Sopenharmony_ci with open(options.json_output, 'w') as f: 2381cb0ef41Sopenharmony_ci json.dump(data, f, sort_keys=True) 2391cb0ef41Sopenharmony_ci 2401cb0ef41Sopenharmony_ci 2411cb0ef41Sopenharmony_cidef get_covered_lines(args): 2421cb0ef41Sopenharmony_ci """Return the covered lines of an executable. 2431cb0ef41Sopenharmony_ci 2441cb0ef41Sopenharmony_ci Called trough multiprocessing pool. The args are expected to unpack to: 2451cb0ef41Sopenharmony_ci cov_dir: Folder with sancov files merged by sancov_merger.py. 2461cb0ef41Sopenharmony_ci executable: Absolute path to the executable that was called to produce the 2471cb0ef41Sopenharmony_ci given coverage data. 2481cb0ef41Sopenharmony_ci sancov_file: The merged sancov file with coverage data. 2491cb0ef41Sopenharmony_ci 2501cb0ef41Sopenharmony_ci Returns: A tuple of post-processed llvm output as returned by 2511cb0ef41Sopenharmony_ci process_symbolizer_output and the executable name. 2521cb0ef41Sopenharmony_ci """ 2531cb0ef41Sopenharmony_ci cov_dir, executable, sancov_file = args 2541cb0ef41Sopenharmony_ci 2551cb0ef41Sopenharmony_ci # Let the sancov tool print the covered PCs and pipe them through the llvm 2561cb0ef41Sopenharmony_ci # symbolizer. 2571cb0ef41Sopenharmony_ci process = subprocess.Popen( 2581cb0ef41Sopenharmony_ci '%s print %s 2> /dev/null | ' 2591cb0ef41Sopenharmony_ci '%s --obj %s -functions=none' % 2601cb0ef41Sopenharmony_ci (SANCOV_TOOL, 2611cb0ef41Sopenharmony_ci os.path.join(cov_dir, sancov_file), 2621cb0ef41Sopenharmony_ci SYMBOLIZER, 2631cb0ef41Sopenharmony_ci executable), 2641cb0ef41Sopenharmony_ci stdout=subprocess.PIPE, 2651cb0ef41Sopenharmony_ci stderr=subprocess.PIPE, 2661cb0ef41Sopenharmony_ci stdin=subprocess.PIPE, 2671cb0ef41Sopenharmony_ci cwd=BASE_DIR, 2681cb0ef41Sopenharmony_ci shell=True, 2691cb0ef41Sopenharmony_ci ) 2701cb0ef41Sopenharmony_ci output, _ = process.communicate() 2711cb0ef41Sopenharmony_ci assert process.returncode == 0 2721cb0ef41Sopenharmony_ci return ( 2731cb0ef41Sopenharmony_ci process_symbolizer_output(output, os.path.dirname(executable)), 2741cb0ef41Sopenharmony_ci os.path.basename(executable), 2751cb0ef41Sopenharmony_ci ) 2761cb0ef41Sopenharmony_ci 2771cb0ef41Sopenharmony_ci 2781cb0ef41Sopenharmony_cidef merge_covered_line_results(data, results): 2791cb0ef41Sopenharmony_ci """Merge multiprocessing results for covered lines. 2801cb0ef41Sopenharmony_ci 2811cb0ef41Sopenharmony_ci The data is mutated, the results are merged into it in place. 2821cb0ef41Sopenharmony_ci 2831cb0ef41Sopenharmony_ci Args: 2841cb0ef41Sopenharmony_ci data: Existing coverage data from json file containing all instrumented 2851cb0ef41Sopenharmony_ci lines. 2861cb0ef41Sopenharmony_ci results: List of results as returned by get_covered_lines. 2871cb0ef41Sopenharmony_ci """ 2881cb0ef41Sopenharmony_ci 2891cb0ef41Sopenharmony_ci # List of executables and mapping to the test bit mask. The number of 2901cb0ef41Sopenharmony_ci # tests is restricted to 52, to allow javascript JSON parsing of 2911cb0ef41Sopenharmony_ci # the bitsets encoded as numbers. JS max safe int is (1 << 53) - 1. 2921cb0ef41Sopenharmony_ci exe_list = data['tests'] 2931cb0ef41Sopenharmony_ci assert len(exe_list) <= 52, 'Max 52 different tests are supported.' 2941cb0ef41Sopenharmony_ci test_bit_masks = {exe:1<<i for i, exe in enumerate(exe_list)} 2951cb0ef41Sopenharmony_ci 2961cb0ef41Sopenharmony_ci def merge_lines(old_lines, new_lines, mask): 2971cb0ef41Sopenharmony_ci """Merge the coverage data of a list of lines. 2981cb0ef41Sopenharmony_ci 2991cb0ef41Sopenharmony_ci Args: 3001cb0ef41Sopenharmony_ci old_lines: Lines as list of pairs with line number and test bit mask. 3011cb0ef41Sopenharmony_ci The new lines will be merged into the list in place. 3021cb0ef41Sopenharmony_ci new_lines: List of new (covered) lines (sorted). 3031cb0ef41Sopenharmony_ci mask: The bit to be set for covered lines. The bit index is the test 3041cb0ef41Sopenharmony_ci index of the executable that covered the line. 3051cb0ef41Sopenharmony_ci """ 3061cb0ef41Sopenharmony_ci i = 0 3071cb0ef41Sopenharmony_ci # Iterate over old and new lines, both are sorted. 3081cb0ef41Sopenharmony_ci for l in new_lines: 3091cb0ef41Sopenharmony_ci while old_lines[i][0] < l: 3101cb0ef41Sopenharmony_ci # Forward instrumented lines not present in this coverage data. 3111cb0ef41Sopenharmony_ci i += 1 3121cb0ef41Sopenharmony_ci # TODO: Add more context to the assert message. 3131cb0ef41Sopenharmony_ci assert i < len(old_lines), 'Covered line %d not in input file.' % l 3141cb0ef41Sopenharmony_ci assert old_lines[i][0] == l, 'Covered line %d not in input file.' % l 3151cb0ef41Sopenharmony_ci 3161cb0ef41Sopenharmony_ci # Add coverage information to the line. 3171cb0ef41Sopenharmony_ci old_lines[i][1] |= mask 3181cb0ef41Sopenharmony_ci 3191cb0ef41Sopenharmony_ci def merge_files(data, result): 3201cb0ef41Sopenharmony_ci """Merge result into data. 3211cb0ef41Sopenharmony_ci 3221cb0ef41Sopenharmony_ci The data is mutated in place. 3231cb0ef41Sopenharmony_ci 3241cb0ef41Sopenharmony_ci Args: 3251cb0ef41Sopenharmony_ci data: Merged coverage data from the previous reduce step. 3261cb0ef41Sopenharmony_ci result: New result to be merged in. The type is as returned by 3271cb0ef41Sopenharmony_ci get_covered_lines. 3281cb0ef41Sopenharmony_ci """ 3291cb0ef41Sopenharmony_ci file_map, executable = result 3301cb0ef41Sopenharmony_ci files = data['files'] 3311cb0ef41Sopenharmony_ci for file_name, lines in file_map.iteritems(): 3321cb0ef41Sopenharmony_ci merge_lines(files[file_name], lines, test_bit_masks[executable]) 3331cb0ef41Sopenharmony_ci return data 3341cb0ef41Sopenharmony_ci 3351cb0ef41Sopenharmony_ci reduce(merge_files, results, data) 3361cb0ef41Sopenharmony_ci 3371cb0ef41Sopenharmony_ci 3381cb0ef41Sopenharmony_cidef merge(options): 3391cb0ef41Sopenharmony_ci """Implements the 'merge' action of this tool.""" 3401cb0ef41Sopenharmony_ci 3411cb0ef41Sopenharmony_ci # Check if folder with coverage output exists. 3421cb0ef41Sopenharmony_ci assert (os.path.exists(options.coverage_dir) and 3431cb0ef41Sopenharmony_ci os.path.isdir(options.coverage_dir)) 3441cb0ef41Sopenharmony_ci 3451cb0ef41Sopenharmony_ci # Inputs for multiprocessing. List of tuples of: 3461cb0ef41Sopenharmony_ci # Coverage dir, absoluate path to executable, sancov file name. 3471cb0ef41Sopenharmony_ci inputs = [] 3481cb0ef41Sopenharmony_ci for sancov_file in os.listdir(options.coverage_dir): 3491cb0ef41Sopenharmony_ci match = SANCOV_FILE_RE.match(sancov_file) 3501cb0ef41Sopenharmony_ci if match: 3511cb0ef41Sopenharmony_ci inputs.append(( 3521cb0ef41Sopenharmony_ci options.coverage_dir, 3531cb0ef41Sopenharmony_ci os.path.join(options.build_dir, match.group(1)), 3541cb0ef41Sopenharmony_ci sancov_file, 3551cb0ef41Sopenharmony_ci )) 3561cb0ef41Sopenharmony_ci 3571cb0ef41Sopenharmony_ci logging.info('Merging %d sancov files into %s', 3581cb0ef41Sopenharmony_ci len(inputs), options.json_input) 3591cb0ef41Sopenharmony_ci 3601cb0ef41Sopenharmony_ci # Post-process covered lines in parallel. 3611cb0ef41Sopenharmony_ci pool = Pool(CPUS) 3621cb0ef41Sopenharmony_ci try: 3631cb0ef41Sopenharmony_ci results = pool.imap_unordered(get_covered_lines, inputs) 3641cb0ef41Sopenharmony_ci finally: 3651cb0ef41Sopenharmony_ci pool.close() 3661cb0ef41Sopenharmony_ci 3671cb0ef41Sopenharmony_ci # Load existing json data file for merging the results. 3681cb0ef41Sopenharmony_ci with open(options.json_input, 'r') as f: 3691cb0ef41Sopenharmony_ci data = json.load(f) 3701cb0ef41Sopenharmony_ci 3711cb0ef41Sopenharmony_ci # Merge muliprocessing results. Mutates data. 3721cb0ef41Sopenharmony_ci merge_covered_line_results(data, results) 3731cb0ef41Sopenharmony_ci 3741cb0ef41Sopenharmony_ci logging.info('Merged data from %d executables, which covers %d files.', 3751cb0ef41Sopenharmony_ci len(data['tests']), len(data['files'])) 3761cb0ef41Sopenharmony_ci logging.info('Writing results to %s', options.json_output) 3771cb0ef41Sopenharmony_ci 3781cb0ef41Sopenharmony_ci # Write merged results to file. 3791cb0ef41Sopenharmony_ci with open(options.json_output, 'w') as f: 3801cb0ef41Sopenharmony_ci json.dump(data, f, sort_keys=True) 3811cb0ef41Sopenharmony_ci 3821cb0ef41Sopenharmony_ci 3831cb0ef41Sopenharmony_cidef split(options): 3841cb0ef41Sopenharmony_ci """Implements the 'split' action of this tool.""" 3851cb0ef41Sopenharmony_ci # Load existing json data file for splitting. 3861cb0ef41Sopenharmony_ci with open(options.json_input, 'r') as f: 3871cb0ef41Sopenharmony_ci data = json.load(f) 3881cb0ef41Sopenharmony_ci 3891cb0ef41Sopenharmony_ci logging.info('Splitting off %d coverage files from %s', 3901cb0ef41Sopenharmony_ci len(data['files']), options.json_input) 3911cb0ef41Sopenharmony_ci 3921cb0ef41Sopenharmony_ci for file_name, coverage in data['files'].iteritems(): 3931cb0ef41Sopenharmony_ci # Preserve relative directories that are part of the file name. 3941cb0ef41Sopenharmony_ci file_path = os.path.join(options.output_dir, file_name + '.json') 3951cb0ef41Sopenharmony_ci try: 3961cb0ef41Sopenharmony_ci os.makedirs(os.path.dirname(file_path)) 3971cb0ef41Sopenharmony_ci except OSError: 3981cb0ef41Sopenharmony_ci # Ignore existing directories. 3991cb0ef41Sopenharmony_ci pass 4001cb0ef41Sopenharmony_ci 4011cb0ef41Sopenharmony_ci with open(file_path, 'w') as f: 4021cb0ef41Sopenharmony_ci # Flat-copy the old dict. 4031cb0ef41Sopenharmony_ci new_data = dict(data) 4041cb0ef41Sopenharmony_ci 4051cb0ef41Sopenharmony_ci # Update current file. 4061cb0ef41Sopenharmony_ci new_data['files'] = {file_name: coverage} 4071cb0ef41Sopenharmony_ci 4081cb0ef41Sopenharmony_ci # Write json data. 4091cb0ef41Sopenharmony_ci json.dump(new_data, f, sort_keys=True) 4101cb0ef41Sopenharmony_ci 4111cb0ef41Sopenharmony_ci 4121cb0ef41Sopenharmony_cidef main(args=None): 4131cb0ef41Sopenharmony_ci parser = argparse.ArgumentParser() 4141cb0ef41Sopenharmony_ci # TODO(machenbach): Make this required and deprecate the default. 4151cb0ef41Sopenharmony_ci parser.add_argument('--build-dir', 4161cb0ef41Sopenharmony_ci default=os.path.join(BASE_DIR, 'out', 'Release'), 4171cb0ef41Sopenharmony_ci help='Path to the build output directory.') 4181cb0ef41Sopenharmony_ci parser.add_argument('--coverage-dir', 4191cb0ef41Sopenharmony_ci help='Path to the sancov output files.') 4201cb0ef41Sopenharmony_ci parser.add_argument('--json-input', 4211cb0ef41Sopenharmony_ci help='Path to an existing json file with coverage data.') 4221cb0ef41Sopenharmony_ci parser.add_argument('--json-output', 4231cb0ef41Sopenharmony_ci help='Path to a file to write json output to.') 4241cb0ef41Sopenharmony_ci parser.add_argument('--output-dir', 4251cb0ef41Sopenharmony_ci help='Directory where to put split output files to.') 4261cb0ef41Sopenharmony_ci parser.add_argument('action', choices=['all', 'merge', 'split'], 4271cb0ef41Sopenharmony_ci help='Action to perform.') 4281cb0ef41Sopenharmony_ci 4291cb0ef41Sopenharmony_ci options = parser.parse_args(args) 4301cb0ef41Sopenharmony_ci options.build_dir = os.path.abspath(options.build_dir) 4311cb0ef41Sopenharmony_ci if options.action.lower() == 'all': 4321cb0ef41Sopenharmony_ci if not options.json_output: 4331cb0ef41Sopenharmony_ci print('--json-output is required') 4341cb0ef41Sopenharmony_ci return 1 4351cb0ef41Sopenharmony_ci write_instrumented(options) 4361cb0ef41Sopenharmony_ci elif options.action.lower() == 'merge': 4371cb0ef41Sopenharmony_ci if not options.coverage_dir: 4381cb0ef41Sopenharmony_ci print('--coverage-dir is required') 4391cb0ef41Sopenharmony_ci return 1 4401cb0ef41Sopenharmony_ci if not options.json_input: 4411cb0ef41Sopenharmony_ci print('--json-input is required') 4421cb0ef41Sopenharmony_ci return 1 4431cb0ef41Sopenharmony_ci if not options.json_output: 4441cb0ef41Sopenharmony_ci print('--json-output is required') 4451cb0ef41Sopenharmony_ci return 1 4461cb0ef41Sopenharmony_ci merge(options) 4471cb0ef41Sopenharmony_ci elif options.action.lower() == 'split': 4481cb0ef41Sopenharmony_ci if not options.json_input: 4491cb0ef41Sopenharmony_ci print('--json-input is required') 4501cb0ef41Sopenharmony_ci return 1 4511cb0ef41Sopenharmony_ci if not options.output_dir: 4521cb0ef41Sopenharmony_ci print('--output-dir is required') 4531cb0ef41Sopenharmony_ci return 1 4541cb0ef41Sopenharmony_ci split(options) 4551cb0ef41Sopenharmony_ci return 0 4561cb0ef41Sopenharmony_ci 4571cb0ef41Sopenharmony_ci 4581cb0ef41Sopenharmony_ciif __name__ == '__main__': 4591cb0ef41Sopenharmony_ci sys.exit(main()) 460