# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.


# Recipe for uploading Coverage results.


import calendar


DEPS = [
  'gsutil',
  'recipe_engine/file',
  'recipe_engine/json',
  'recipe_engine/path',
  'recipe_engine/properties',
  'recipe_engine/python',
  'recipe_engine/raw_io',
  'recipe_engine/step',
  'recipe_engine/time',
]


TRY_JOB_FOLDER = 'trybot/%s/%s/' # % (issue_number, patchset_number)
COMMIT_FOLDER = 'commit/%s/'      # % (git_revision)

RAW_FILE = '*.profraw'
PARSED_FILE = '%s.profdata'
SUMMARY_FILE = '%s.summary'

COVERAGE_RAW_ARCHIVE = '%s.profraw.tar.gz'
# Text is an easier format to read with machines (e.g. for Gerrit).
COVERAGE_TEXT_FILE = '%s.text.tar'
# HTML is a quick and dirty browsable format. (e.g. for coverage.skia.org)
COVERAGE_HTML_FILE = '%s.html.tar'

def RunSteps(api):
  # See https://clang.llvm.org/docs/SourceBasedCodeCoverage.html for a
  # detailed explanation of getting code coverage from LLVM.
  # Since we have already compiled the binary with the special flags
  # and run the executable to generate an output.profraw, we
  # need to merge and index the data, create the coverage output,
  # and then upload the results to GCS. We also upload the intermediate
  # results to GCS so we can regenerate reports if needed.
  builder_name = api.properties['buildername']
  bucket = api.properties['gs_bucket']

  # The raw data files are brought in as isolated inputs. It is possible
  # for there to be 1 if the coverage task wasn't broken up.
  raw_inputs = api.file.glob_paths('find raw inputs', api.path['start_dir'],
                                   RAW_FILE,
                                   test_data=['a.raw', 'b.raw', 'c.raw'])


  # The instrumented executable is brought in as an isolated input.
  executable = api.path['start_dir'].join('out','Debug','dm')
  # clang_dir is brought in via CIPD.
  clang_dir = api.path['start_dir'].join('clang_linux', 'bin')

  revision = api.properties['revision']
  path = COMMIT_FOLDER % revision

  issue = api.properties.get('patch_issue')
  patchset = api.properties.get('patch_set')
  if issue and patchset:
    path = TRY_JOB_FOLDER % (issue, patchset)

  # Upload the raw files, tarred together to decrease upload time and
  # improve compression.
  tar_file = api.path['start_dir'].join('raw_data.profraw.tar.gz')
  cmd = ['tar', '-zcvf', tar_file]
  cmd.extend(raw_inputs)
  api.step('create raw data archive', cmd=cmd)

  gcs_file = COVERAGE_RAW_ARCHIVE % builder_name
  api.gsutil.cp('raw data archive', tar_file,
                'gs://%s/%s%s' % (bucket, path, gcs_file))

  # Merge all the raw data files together, then index the data.
  # This creates one cohesive
  indexed_data = api.path['start_dir'].join('output.profdata')
  cmd = [clang_dir.join('llvm-profdata'),
         'merge',
         '-sparse',
         '-o',
         indexed_data]
  cmd.extend(raw_inputs)
  api.step('merge and index',
           cmd=cmd)

  gcs_file = PARSED_FILE % builder_name
  api.gsutil.cp('parsed data', indexed_data,
                   'gs://%s/%s%s' % (bucket, path, gcs_file), extra_args=['-Z'])

  # Create text coverage output
  output_data = api.path['start_dir'].join('coverage_text')
  api.step('create text summary',
           cmd=[clang_dir.join('llvm-cov'),
               'show',
               executable,
               '-instr-profile=' + str(indexed_data),
               '-use-color=0',
               '-format=text',
               '-output-dir=' + str(output_data)])

  # Upload the summary by itself so we can get easier access to it (instead of
  # downloading and untarring all the coverage data.
  gcs_file = SUMMARY_FILE % builder_name
  api.gsutil.cp('coverage summary', output_data.join('index.txt'),
                   'gs://%s/%s%s' % (bucket, path, gcs_file), extra_args=['-Z'])

  tar_file = api.path['start_dir'].join('coverage.text.tar')

  # Tar and upload the coverage data. We tar it to ease downloading/ingestion,
  # otherwise, there is a 1:1 mapping of source code files -> coverage files.
  api.step('create text coverage archive', cmd=['tar', '-cvf',
                                           tar_file, output_data])

  gcs_file = COVERAGE_TEXT_FILE % builder_name
  api.gsutil.cp('text coverage data', tar_file,
                   'gs://%s/%s%s' % (bucket, path, gcs_file), extra_args=['-Z'])

  # Create html coverage output
  output_data = api.path['start_dir'].join('coverage_html')
  api.step('create html summary',
           cmd=[clang_dir.join('llvm-cov'),
               'show',
               executable,
               '-instr-profile=' + str(indexed_data),
               '-use-color=1',
               '-format=html',
               '-output-dir=' + str(output_data)])

  tar_file = api.path['start_dir'].join('coverage.html.tar')

  # Tar and upload the coverage data. We tar it to ease downloading/ingestion,
  # otherwise, there is a 1:1 mapping of source code files -> coverage files.
  api.step('create html coverage archive',
           cmd=['tar', '-cvf', tar_file, output_data])

  gcs_file = COVERAGE_HTML_FILE % builder_name
  api.gsutil.cp('html coverage data', tar_file,
                   'gs://%s/%s%s' % (bucket, path, gcs_file), extra_args=['-Z'])


def GenTests(api):
  builder = 'Test-Debian9-GCC-GCE-CPU-AVX2-x86_64-Debug-All'
  yield (
    api.test('normal_bot') +
    api.properties(buildername=builder,
                   gs_bucket='skia-coverage',
                   revision='abc123',
                   path_config='kitchen')
  )

  yield (
    api.test('alternate_bucket') +
    api.properties(buildername=builder,
                   gs_bucket='skia-coverage-alt',
                   revision='abc123',
                   path_config='kitchen')
  )

  yield (
    api.test('failed_once') +
    api.properties(buildername=builder,
                   gs_bucket='skia-coverage',
                   revision='abc123',
                   path_config='kitchen') +
    api.step_data('upload parsed data', retcode=1)
  )

  yield (
    api.test('failed_all') +
    api.properties(buildername=builder,
                   gs_bucket='skia-coverage',
                   revision='abc123',
                   path_config='kitchen') +
    api.step_data('upload parsed data', retcode=1) +
    api.step_data('upload parsed data (attempt 2)', retcode=1) +
    api.step_data('upload parsed data (attempt 3)', retcode=1) +
    api.step_data('upload parsed data (attempt 4)', retcode=1) +
    api.step_data('upload parsed data (attempt 5)', retcode=1)
  )

  yield (
      api.test('trybot') +
      api.properties(
          buildername=builder,
          gs_bucket='skia-coverage',
          revision='abc123',
          path_config='kitchen',
          patch_storage='gerrit') +
      api.properties.tryserver(
          buildername=builder,
          gerrit_project='skia',
          gerrit_url='https://skia-review.googlesource.com/',
      )
  )