Home | History | Annotate | Download | only in recipes
      1 # Copyright 2017 The Chromium 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 
      6 # Recipe for uploading Coverage results.
      7 
      8 
      9 import calendar
     10 
     11 
     12 DEPS = [
     13   'gsutil',
     14   'recipe_engine/file',
     15   'recipe_engine/json',
     16   'recipe_engine/path',
     17   'recipe_engine/properties',
     18   'recipe_engine/python',
     19   'recipe_engine/raw_io',
     20   'recipe_engine/step',
     21   'recipe_engine/time',
     22 ]
     23 
     24 
     25 TRY_JOB_FOLDER = 'trybot/%s/%s/' # % (issue_number, patchset_number)
     26 COMMIT_FOLDER = 'commit/%s/'      # % (git_revision)
     27 
     28 RAW_FILE = '*.profraw'
     29 PARSED_FILE = '%s.profdata'
     30 SUMMARY_FILE = '%s.summary'
     31 
     32 COVERAGE_RAW_ARCHIVE = '%s.profraw.tar.gz'
     33 # Text is an easier format to read with machines (e.g. for Gerrit).
     34 COVERAGE_TEXT_FILE = '%s.text.tar'
     35 # HTML is a quick and dirty browsable format. (e.g. for coverage.skia.org)
     36 COVERAGE_HTML_FILE = '%s.html.tar'
     37 
     38 def RunSteps(api):
     39   # See https://clang.llvm.org/docs/SourceBasedCodeCoverage.html for a
     40   # detailed explanation of getting code coverage from LLVM.
     41   # Since we have already compiled the binary with the special flags
     42   # and run the executable to generate an output.profraw, we
     43   # need to merge and index the data, create the coverage output,
     44   # and then upload the results to GCS. We also upload the intermediate
     45   # results to GCS so we can regenerate reports if needed.
     46   builder_name = api.properties['buildername']
     47   bucket = api.properties['gs_bucket']
     48 
     49   # The raw data files are brought in as isolated inputs. It is possible
     50   # for there to be 1 if the coverage task wasn't broken up.
     51   raw_inputs = api.file.glob_paths('find raw inputs', api.path['start_dir'],
     52                                    RAW_FILE,
     53                                    test_data=['a.raw', 'b.raw', 'c.raw'])
     54 
     55 
     56   # The instrumented executable is brought in as an isolated input.
     57   executable = api.path['start_dir'].join('out','Debug','dm')
     58   # clang_dir is brought in via CIPD.
     59   clang_dir = api.path['start_dir'].join('clang_linux', 'bin')
     60 
     61   revision = api.properties['revision']
     62   path = COMMIT_FOLDER % revision
     63 
     64   issue = api.properties.get('patch_issue')
     65   patchset = api.properties.get('patch_set')
     66   if issue and patchset:
     67     path = TRY_JOB_FOLDER % (issue, patchset)
     68 
     69   # Upload the raw files, tarred together to decrease upload time and
     70   # improve compression.
     71   tar_file = api.path['start_dir'].join('raw_data.profraw.tar.gz')
     72   cmd = ['tar', '-zcvf', tar_file]
     73   cmd.extend(raw_inputs)
     74   api.step('create raw data archive', cmd=cmd)
     75 
     76   gcs_file = COVERAGE_RAW_ARCHIVE % builder_name
     77   api.gsutil.cp('raw data archive', tar_file,
     78                 'gs://%s/%s%s' % (bucket, path, gcs_file))
     79 
     80   # Merge all the raw data files together, then index the data.
     81   # This creates one cohesive
     82   indexed_data = api.path['start_dir'].join('output.profdata')
     83   cmd = [clang_dir.join('llvm-profdata'),
     84          'merge',
     85          '-sparse',
     86          '-o',
     87          indexed_data]
     88   cmd.extend(raw_inputs)
     89   api.step('merge and index',
     90            cmd=cmd)
     91 
     92   gcs_file = PARSED_FILE % builder_name
     93   api.gsutil.cp('parsed data', indexed_data,
     94                    'gs://%s/%s%s' % (bucket, path, gcs_file), extra_args=['-Z'])
     95 
     96   # Create text coverage output
     97   output_data = api.path['start_dir'].join('coverage_text')
     98   api.step('create text summary',
     99            cmd=[clang_dir.join('llvm-cov'),
    100                'show',
    101                executable,
    102                '-instr-profile=' + str(indexed_data),
    103                '-use-color=0',
    104                '-format=text',
    105                '-output-dir=' + str(output_data)])
    106 
    107   # Upload the summary by itself so we can get easier access to it (instead of
    108   # downloading and untarring all the coverage data.
    109   gcs_file = SUMMARY_FILE % builder_name
    110   api.gsutil.cp('coverage summary', output_data.join('index.txt'),
    111                    'gs://%s/%s%s' % (bucket, path, gcs_file), extra_args=['-Z'])
    112 
    113   tar_file = api.path['start_dir'].join('coverage.text.tar')
    114 
    115   # Tar and upload the coverage data. We tar it to ease downloading/ingestion,
    116   # otherwise, there is a 1:1 mapping of source code files -> coverage files.
    117   api.step('create text coverage archive', cmd=['tar', '-cvf',
    118                                            tar_file, output_data])
    119 
    120   gcs_file = COVERAGE_TEXT_FILE % builder_name
    121   api.gsutil.cp('text coverage data', tar_file,
    122                    'gs://%s/%s%s' % (bucket, path, gcs_file), extra_args=['-Z'])
    123 
    124   # Create html coverage output
    125   output_data = api.path['start_dir'].join('coverage_html')
    126   api.step('create html summary',
    127            cmd=[clang_dir.join('llvm-cov'),
    128                'show',
    129                executable,
    130                '-instr-profile=' + str(indexed_data),
    131                '-use-color=1',
    132                '-format=html',
    133                '-output-dir=' + str(output_data)])
    134 
    135   tar_file = api.path['start_dir'].join('coverage.html.tar')
    136 
    137   # Tar and upload the coverage data. We tar it to ease downloading/ingestion,
    138   # otherwise, there is a 1:1 mapping of source code files -> coverage files.
    139   api.step('create html coverage archive',
    140            cmd=['tar', '-cvf', tar_file, output_data])
    141 
    142   gcs_file = COVERAGE_HTML_FILE % builder_name
    143   api.gsutil.cp('html coverage data', tar_file,
    144                    'gs://%s/%s%s' % (bucket, path, gcs_file), extra_args=['-Z'])
    145 
    146 
    147 def GenTests(api):
    148   builder = 'Test-Debian9-GCC-GCE-CPU-AVX2-x86_64-Debug-All'
    149   yield (
    150     api.test('normal_bot') +
    151     api.properties(buildername=builder,
    152                    gs_bucket='skia-coverage',
    153                    revision='abc123',
    154                    path_config='kitchen')
    155   )
    156 
    157   yield (
    158     api.test('alternate_bucket') +
    159     api.properties(buildername=builder,
    160                    gs_bucket='skia-coverage-alt',
    161                    revision='abc123',
    162                    path_config='kitchen')
    163   )
    164 
    165   yield (
    166     api.test('failed_once') +
    167     api.properties(buildername=builder,
    168                    gs_bucket='skia-coverage',
    169                    revision='abc123',
    170                    path_config='kitchen') +
    171     api.step_data('upload parsed data', retcode=1)
    172   )
    173 
    174   yield (
    175     api.test('failed_all') +
    176     api.properties(buildername=builder,
    177                    gs_bucket='skia-coverage',
    178                    revision='abc123',
    179                    path_config='kitchen') +
    180     api.step_data('upload parsed data', retcode=1) +
    181     api.step_data('upload parsed data (attempt 2)', retcode=1) +
    182     api.step_data('upload parsed data (attempt 3)', retcode=1) +
    183     api.step_data('upload parsed data (attempt 4)', retcode=1) +
    184     api.step_data('upload parsed data (attempt 5)', retcode=1)
    185   )
    186 
    187   yield (
    188       api.test('trybot') +
    189       api.properties(
    190           buildername=builder,
    191           gs_bucket='skia-coverage',
    192           revision='abc123',
    193           path_config='kitchen',
    194           patch_storage='gerrit') +
    195       api.properties.tryserver(
    196           buildername=builder,
    197           gerrit_project='skia',
    198           gerrit_url='https://skia-review.googlesource.com/',
    199       )
    200   )
    201