Home | History | Annotate | Download | only in sanitizers
      1 # Copyright 2016 the V8 project authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 # Requires python-coverage. Native python coverage version >= 3.7.1 should
      6 # be installed to get the best speed.
      7 
      8 import copy
      9 import coverage
     10 import logging
     11 import json
     12 import os
     13 import shutil
     14 import sys
     15 import tempfile
     16 import unittest
     17 
     18 
     19 # Directory of this file.
     20 LOCATION = os.path.dirname(os.path.abspath(__file__))
     21 
     22 # V8 checkout directory.
     23 BASE_DIR = os.path.dirname(os.path.dirname(LOCATION))
     24 
     25 # Executable location.
     26 BUILD_DIR = os.path.join(BASE_DIR, 'out', 'Release')
     27 
     28 def abs_line(line):
     29   """Absolute paths as output by the llvm symbolizer."""
     30   return '%s/%s' % (BUILD_DIR, line)
     31 
     32 
     33 #------------------------------------------------------------------------------
     34 
     35 # Data for test_process_symbolizer_output. This simulates output from the
     36 # llvm symbolizer. The paths are not normlized.
     37 SYMBOLIZER_OUTPUT = (
     38   abs_line('../../src/foo.cc:87:7\n') +
     39   abs_line('../../src/foo.cc:92:0\n') + # Test sorting.
     40   abs_line('../../src/baz/bar.h:1234567:0\n') + # Test large line numbers.
     41   abs_line('../../src/foo.cc:92:0\n') + # Test duplicates.
     42   abs_line('../../src/baz/bar.h:0:0\n') + # Test subdirs.
     43   '/usr/include/cool_stuff.h:14:2\n' + # Test dropping absolute paths.
     44   abs_line('../../src/foo.cc:87:10\n') + # Test dropping character indexes.
     45   abs_line('../../third_party/icu.cc:0:0\n') + # Test dropping excluded dirs.
     46   abs_line('../../src/baz/bar.h:11:0\n')
     47 )
     48 
     49 # The expected post-processed output maps relative file names to line numbers.
     50 # The numbers are sorted and unique.
     51 EXPECTED_PROCESSED_OUTPUT = {
     52   'src/baz/bar.h': [0, 11, 1234567],
     53   'src/foo.cc': [87, 92],
     54 }
     55 
     56 
     57 #------------------------------------------------------------------------------
     58 
     59 # Data for test_merge_instrumented_line_results. A list of absolute paths to
     60 # all executables.
     61 EXE_LIST = [
     62   '/path/to/d8',
     63   '/path/to/cctest',
     64   '/path/to/unittests',
     65 ]
     66 
     67 # Post-processed llvm symbolizer output as returned by
     68 # process_symbolizer_output. These are lists of this output for merging.
     69 INSTRUMENTED_LINE_RESULTS = [
     70   {
     71     'src/baz/bar.h': [0, 3, 7],
     72     'src/foo.cc': [11],
     73   },
     74   {
     75     'src/baz/bar.h': [3, 7, 8],
     76     'src/baz.cc': [2],
     77     'src/foo.cc': [1, 92],
     78   },
     79   {
     80     'src/baz.cc': [1],
     81     'src/foo.cc': [92, 93],
     82   },
     83 ]
     84 
     85 # This shows initial instrumentation. No lines are covered, hence,
     86 # the coverage mask is 0 for all lines. The line tuples remain sorted by
     87 # line number and contain no duplicates.
     88 EXPECTED_INSTRUMENTED_LINES_DATA = {
     89   'version': 1,
     90   'tests': ['cctest', 'd8', 'unittests'],
     91   'files': {
     92     'src/baz/bar.h': [[0, 0], [3, 0], [7, 0], [8, 0]],
     93     'src/baz.cc': [[1, 0], [2, 0]],
     94     'src/foo.cc': [[1, 0], [11, 0], [92, 0], [93, 0]],
     95   },
     96 }
     97 
     98 
     99 #------------------------------------------------------------------------------
    100 
    101 # Data for test_merge_covered_line_results. List of post-processed
    102 # llvm-symbolizer output as a tuple including the executable name of each data
    103 # set.
    104 COVERED_LINE_RESULTS = [
    105   ({
    106      'src/baz/bar.h': [3, 7],
    107      'src/foo.cc': [11],
    108    }, 'd8'),
    109   ({
    110      'src/baz/bar.h': [3, 7],
    111      'src/baz.cc': [2],
    112      'src/foo.cc': [1],
    113    }, 'cctest'),
    114   ({
    115      'src/foo.cc': [92],
    116      'src/baz.cc': [2],
    117    }, 'unittests'),
    118 ]
    119 
    120 # This shows initial instrumentation + coverage. The mask bits are:
    121 # cctest: 1, d8: 2, unittests:4. So a line covered by cctest and unittests
    122 # has a coverage mask of 0b101, e.g. line 2 in src/baz.cc.
    123 EXPECTED_COVERED_LINES_DATA = {
    124   'version': 1,
    125   'tests': ['cctest', 'd8', 'unittests'],
    126   'files': {
    127     'src/baz/bar.h': [[0, 0b0], [3, 0b11], [7, 0b11], [8, 0b0]],
    128     'src/baz.cc': [[1, 0b0], [2, 0b101]],
    129     'src/foo.cc': [[1, 0b1], [11, 0b10], [92, 0b100], [93, 0b0]],
    130   },
    131 }
    132 
    133 
    134 #------------------------------------------------------------------------------
    135 
    136 # Data for test_split.
    137 
    138 EXPECTED_SPLIT_FILES = [
    139   (
    140     os.path.join('src', 'baz', 'bar.h.json'),
    141     {
    142       'version': 1,
    143       'tests': ['cctest', 'd8', 'unittests'],
    144       'files': {
    145         'src/baz/bar.h': [[0, 0b0], [3, 0b11], [7, 0b11], [8, 0b0]],
    146       },
    147     },
    148   ),
    149   (
    150     os.path.join('src', 'baz.cc.json'),
    151     {
    152       'version': 1,
    153       'tests': ['cctest', 'd8', 'unittests'],
    154       'files': {
    155         'src/baz.cc': [[1, 0b0], [2, 0b101]],
    156       },
    157     },
    158   ),
    159   (
    160     os.path.join('src', 'foo.cc.json'),
    161     {
    162       'version': 1,
    163       'tests': ['cctest', 'd8', 'unittests'],
    164       'files': {
    165         'src/foo.cc': [[1, 0b1], [11, 0b10], [92, 0b100], [93, 0b0]],
    166       },
    167     },
    168   ),
    169 ]
    170 
    171 
    172 class FormatterTests(unittest.TestCase):
    173   @classmethod
    174   def setUpClass(cls):
    175     sys.path.append(LOCATION)
    176     cls._cov = coverage.coverage(
    177         include=([os.path.join(LOCATION, 'sancov_formatter.py')]))
    178     cls._cov.start()
    179     import sancov_formatter
    180     global sancov_formatter
    181 
    182   @classmethod
    183   def tearDownClass(cls):
    184     cls._cov.stop()
    185     cls._cov.report()
    186 
    187   def test_process_symbolizer_output(self):
    188     result = sancov_formatter.process_symbolizer_output(SYMBOLIZER_OUTPUT)
    189     self.assertEquals(EXPECTED_PROCESSED_OUTPUT, result)
    190 
    191   def test_merge_instrumented_line_results(self):
    192     result = sancov_formatter.merge_instrumented_line_results(
    193       EXE_LIST, INSTRUMENTED_LINE_RESULTS)
    194     self.assertEquals(EXPECTED_INSTRUMENTED_LINES_DATA, result)
    195 
    196   def test_merge_covered_line_results(self):
    197     data = copy.deepcopy(EXPECTED_INSTRUMENTED_LINES_DATA)
    198     sancov_formatter.merge_covered_line_results(
    199       data, COVERED_LINE_RESULTS)
    200     self.assertEquals(EXPECTED_COVERED_LINES_DATA, data)
    201 
    202   def test_split(self):
    203     _, json_input = tempfile.mkstemp(prefix='tmp_coverage_test_split')
    204     with open(json_input, 'w') as f:
    205       json.dump(EXPECTED_COVERED_LINES_DATA, f)
    206     output_dir = tempfile.mkdtemp(prefix='tmp_coverage_test_split')
    207 
    208     try:
    209       sancov_formatter.main([
    210         'split',
    211         '--json-input', json_input,
    212         '--output-dir', output_dir,
    213       ])
    214 
    215       for file_name, expected_data in EXPECTED_SPLIT_FILES:
    216         full_path = os.path.join(output_dir, file_name)
    217         self.assertTrue(os.path.exists(full_path))
    218         with open(full_path) as f:
    219           self.assertEquals(expected_data, json.load(f))
    220     finally:
    221       os.remove(json_input)
    222       shutil.rmtree(output_dir)
    223