|  | # Copyright 2016 the V8 project authors. All rights reserved. | 
|  | # Use of this source code is governed by a BSD-style license that can be | 
|  | # found in the LICENSE file. | 
|  |  | 
|  | # Requires python-coverage. Native python coverage version >= 3.7.1 should | 
|  | # be installed to get the best speed. | 
|  |  | 
|  | import copy | 
|  | import coverage | 
|  | import logging | 
|  | import json | 
|  | import os | 
|  | import shutil | 
|  | import sys | 
|  | import tempfile | 
|  | import unittest | 
|  |  | 
|  |  | 
|  | # Directory of this file. | 
|  | LOCATION = os.path.dirname(os.path.abspath(__file__)) | 
|  |  | 
|  | # V8 checkout directory. | 
|  | BASE_DIR = os.path.dirname(os.path.dirname(LOCATION)) | 
|  |  | 
|  | # Executable location. | 
|  | BUILD_DIR = os.path.join(BASE_DIR, 'out', 'Release') | 
|  |  | 
|  | def abs_line(line): | 
|  | """Absolute paths as output by the llvm symbolizer.""" | 
|  | return '%s/%s' % (BUILD_DIR, line) | 
|  |  | 
|  |  | 
|  | #------------------------------------------------------------------------------ | 
|  |  | 
|  | # Data for test_process_symbolizer_output. This simulates output from the | 
|  | # llvm symbolizer. The paths are not normlized. | 
|  | SYMBOLIZER_OUTPUT = ( | 
|  | abs_line('../../src/foo.cc:87:7\n') + | 
|  | abs_line('../../src/foo.cc:92:0\n') + # Test sorting. | 
|  | abs_line('../../src/baz/bar.h:1234567:0\n') + # Test large line numbers. | 
|  | abs_line('../../src/foo.cc:92:0\n') + # Test duplicates. | 
|  | abs_line('../../src/baz/bar.h:0:0\n') + # Test subdirs. | 
|  | '/usr/include/cool_stuff.h:14:2\n' + # Test dropping absolute paths. | 
|  | abs_line('../../src/foo.cc:87:10\n') + # Test dropping character indexes. | 
|  | abs_line('../../third_party/icu.cc:0:0\n') + # Test dropping excluded dirs. | 
|  | abs_line('../../src/baz/bar.h:11:0\n') | 
|  | ) | 
|  |  | 
|  | # The expected post-processed output maps relative file names to line numbers. | 
|  | # The numbers are sorted and unique. | 
|  | EXPECTED_PROCESSED_OUTPUT = { | 
|  | 'src/baz/bar.h': [0, 11, 1234567], | 
|  | 'src/foo.cc': [87, 92], | 
|  | } | 
|  |  | 
|  |  | 
|  | #------------------------------------------------------------------------------ | 
|  |  | 
|  | # Data for test_merge_instrumented_line_results. A list of absolute paths to | 
|  | # all executables. | 
|  | EXE_LIST = [ | 
|  | '/path/to/d8', | 
|  | '/path/to/cctest', | 
|  | '/path/to/unittests', | 
|  | ] | 
|  |  | 
|  | # Post-processed llvm symbolizer output as returned by | 
|  | # process_symbolizer_output. These are lists of this output for merging. | 
|  | INSTRUMENTED_LINE_RESULTS = [ | 
|  | { | 
|  | 'src/baz/bar.h': [0, 3, 7], | 
|  | 'src/foo.cc': [11], | 
|  | }, | 
|  | { | 
|  | 'src/baz/bar.h': [3, 7, 8], | 
|  | 'src/baz.cc': [2], | 
|  | 'src/foo.cc': [1, 92], | 
|  | }, | 
|  | { | 
|  | 'src/baz.cc': [1], | 
|  | 'src/foo.cc': [92, 93], | 
|  | }, | 
|  | ] | 
|  |  | 
|  | # This shows initial instrumentation. No lines are covered, hence, | 
|  | # the coverage mask is 0 for all lines. The line tuples remain sorted by | 
|  | # line number and contain no duplicates. | 
|  | EXPECTED_INSTRUMENTED_LINES_DATA = { | 
|  | 'version': 1, | 
|  | 'tests': ['cctest', 'd8', 'unittests'], | 
|  | 'files': { | 
|  | 'src/baz/bar.h': [[0, 0], [3, 0], [7, 0], [8, 0]], | 
|  | 'src/baz.cc': [[1, 0], [2, 0]], | 
|  | 'src/foo.cc': [[1, 0], [11, 0], [92, 0], [93, 0]], | 
|  | }, | 
|  | } | 
|  |  | 
|  |  | 
|  | #------------------------------------------------------------------------------ | 
|  |  | 
|  | # Data for test_merge_covered_line_results. List of post-processed | 
|  | # llvm-symbolizer output as a tuple including the executable name of each data | 
|  | # set. | 
|  | COVERED_LINE_RESULTS = [ | 
|  | ({ | 
|  | 'src/baz/bar.h': [3, 7], | 
|  | 'src/foo.cc': [11], | 
|  | }, 'd8'), | 
|  | ({ | 
|  | 'src/baz/bar.h': [3, 7], | 
|  | 'src/baz.cc': [2], | 
|  | 'src/foo.cc': [1], | 
|  | }, 'cctest'), | 
|  | ({ | 
|  | 'src/foo.cc': [92], | 
|  | 'src/baz.cc': [2], | 
|  | }, 'unittests'), | 
|  | ] | 
|  |  | 
|  | # This shows initial instrumentation + coverage. The mask bits are: | 
|  | # cctest: 1, d8: 2, unittests:4. So a line covered by cctest and unittests | 
|  | # has a coverage mask of 0b101, e.g. line 2 in src/baz.cc. | 
|  | EXPECTED_COVERED_LINES_DATA = { | 
|  | 'version': 1, | 
|  | 'tests': ['cctest', 'd8', 'unittests'], | 
|  | 'files': { | 
|  | 'src/baz/bar.h': [[0, 0b0], [3, 0b11], [7, 0b11], [8, 0b0]], | 
|  | 'src/baz.cc': [[1, 0b0], [2, 0b101]], | 
|  | 'src/foo.cc': [[1, 0b1], [11, 0b10], [92, 0b100], [93, 0b0]], | 
|  | }, | 
|  | } | 
|  |  | 
|  |  | 
|  | #------------------------------------------------------------------------------ | 
|  |  | 
|  | # Data for test_split. | 
|  |  | 
|  | EXPECTED_SPLIT_FILES = [ | 
|  | ( | 
|  | os.path.join('src', 'baz', 'bar.h.json'), | 
|  | { | 
|  | 'version': 1, | 
|  | 'tests': ['cctest', 'd8', 'unittests'], | 
|  | 'files': { | 
|  | 'src/baz/bar.h': [[0, 0b0], [3, 0b11], [7, 0b11], [8, 0b0]], | 
|  | }, | 
|  | }, | 
|  | ), | 
|  | ( | 
|  | os.path.join('src', 'baz.cc.json'), | 
|  | { | 
|  | 'version': 1, | 
|  | 'tests': ['cctest', 'd8', 'unittests'], | 
|  | 'files': { | 
|  | 'src/baz.cc': [[1, 0b0], [2, 0b101]], | 
|  | }, | 
|  | }, | 
|  | ), | 
|  | ( | 
|  | os.path.join('src', 'foo.cc.json'), | 
|  | { | 
|  | 'version': 1, | 
|  | 'tests': ['cctest', 'd8', 'unittests'], | 
|  | 'files': { | 
|  | 'src/foo.cc': [[1, 0b1], [11, 0b10], [92, 0b100], [93, 0b0]], | 
|  | }, | 
|  | }, | 
|  | ), | 
|  | ] | 
|  |  | 
|  |  | 
|  | class FormatterTests(unittest.TestCase): | 
|  | @classmethod | 
|  | def setUpClass(cls): | 
|  | sys.path.append(LOCATION) | 
|  | cls._cov = coverage.coverage( | 
|  | include=([os.path.join(LOCATION, 'sancov_formatter.py')])) | 
|  | cls._cov.start() | 
|  | import sancov_formatter | 
|  | global sancov_formatter | 
|  |  | 
|  | @classmethod | 
|  | def tearDownClass(cls): | 
|  | cls._cov.stop() | 
|  | cls._cov.report() | 
|  |  | 
|  | def test_process_symbolizer_output(self): | 
|  | result = sancov_formatter.process_symbolizer_output( | 
|  | SYMBOLIZER_OUTPUT, BUILD_DIR) | 
|  | self.assertEquals(EXPECTED_PROCESSED_OUTPUT, result) | 
|  |  | 
|  | def test_merge_instrumented_line_results(self): | 
|  | result = sancov_formatter.merge_instrumented_line_results( | 
|  | EXE_LIST, INSTRUMENTED_LINE_RESULTS) | 
|  | self.assertEquals(EXPECTED_INSTRUMENTED_LINES_DATA, result) | 
|  |  | 
|  | def test_merge_covered_line_results(self): | 
|  | data = copy.deepcopy(EXPECTED_INSTRUMENTED_LINES_DATA) | 
|  | sancov_formatter.merge_covered_line_results( | 
|  | data, COVERED_LINE_RESULTS) | 
|  | self.assertEquals(EXPECTED_COVERED_LINES_DATA, data) | 
|  |  | 
|  | def test_split(self): | 
|  | _, json_input = tempfile.mkstemp(prefix='tmp_coverage_test_split') | 
|  | with open(json_input, 'w') as f: | 
|  | json.dump(EXPECTED_COVERED_LINES_DATA, f) | 
|  | output_dir = tempfile.mkdtemp(prefix='tmp_coverage_test_split') | 
|  |  | 
|  | try: | 
|  | sancov_formatter.main([ | 
|  | 'split', | 
|  | '--json-input', json_input, | 
|  | '--output-dir', output_dir, | 
|  | ]) | 
|  |  | 
|  | for file_name, expected_data in EXPECTED_SPLIT_FILES: | 
|  | full_path = os.path.join(output_dir, file_name) | 
|  | self.assertTrue(os.path.exists(full_path)) | 
|  | with open(full_path) as f: | 
|  | self.assertEquals(expected_data, json.load(f)) | 
|  | finally: | 
|  | os.remove(json_input) | 
|  | shutil.rmtree(output_dir) |