コード例 #1
0
ファイル: code_coverage.py プロジェクト: xinghun61/infra
def _GetSameOrMostRecentReportForEachPlatform(host, project, ref, revision):
  """Find the matching report on other platforms, or the most recent.

  The intent of this function is to help the UI list the platforms that are
  available, and let the user switch. If a report with the same revision exists
  and is supposed to be visible to the public users, use it, otherwise use the
  most recent visible one.
  """
  result = {}
  platforms = _POSTSUBMIT_PLATFORM_INFO_MAP.keys()
  for platform in platforms:
    # Some 'platforms' are hidden from the selection to avoid confusion, as they
    # may be custom reports that do not make sense outside a certain team.
    # They should still be reachable via a url.
    if (_POSTSUBMIT_PLATFORM_INFO_MAP[platform].get('hidden') and
        not users.is_current_user_admin()):
      continue

    bucket = _POSTSUBMIT_PLATFORM_INFO_MAP[platform]['bucket']
    builder = _POSTSUBMIT_PLATFORM_INFO_MAP[platform]['builder']
    same_report = PostsubmitReport.Get(
        server_host=host,
        project=project,
        ref=ref,
        revision=revision,
        bucket=bucket,
        builder=builder)
    if same_report and same_report.visible:
      result[platform] = same_report
      continue

    query = PostsubmitReport.query(
        PostsubmitReport.gitiles_commit.server_host == host,
        PostsubmitReport.gitiles_commit.project == project,
        PostsubmitReport.bucket == bucket, PostsubmitReport.builder == builder,
        PostsubmitReport.visible == True).order(
            -PostsubmitReport.commit_timestamp)
    entities = query.fetch(limit=1)
    if entities:
      result[platform] = entities[0]

  return result
コード例 #2
0
ファイル: code_coverage.py プロジェクト: xinghun61/infra
def _IsReportSuspicious(report):
  """Returns True if the newly generated report is suspicious to be incorrect.

  A report is determined to be suspicious if and only if the absolute difference
  between its line coverage percentage and the most recent visible report is
  greater than 1.00%.

  Args:
    report (PostsubmitReport): The report to be evaluated.

  Returns:
    True if the report is suspicious, otherwise False.
  """

  def _GetLineCoveragePercentage(report):  # pragma: no cover
    line_coverage_percentage = None
    summary = report.summary_metrics
    for feature_summary in summary:
      if feature_summary['name'] == 'line':
        line_coverage_percentage = float(
            feature_summary['covered']) / feature_summary['total']
        break

    assert line_coverage_percentage is not None, (
        'Given report has invalid summary')
    return line_coverage_percentage

  target_server_host = report.gitiles_commit.server_host
  target_project = report.gitiles_commit.project
  target_bucket = report.bucket
  target_builder = report.builder
  most_recent_visible_reports = PostsubmitReport.query(
      PostsubmitReport.gitiles_commit.server_host == target_server_host,
      PostsubmitReport.gitiles_commit.project == target_project,
      PostsubmitReport.bucket == target_bucket,
      PostsubmitReport.builder == target_builder, PostsubmitReport.visible ==
      True).order(-PostsubmitReport.commit_timestamp).fetch(1)
  if not most_recent_visible_reports:
    logging.warn('No existing visible reports to use for reference, the new '
                 'report is determined as not suspicious by default')
    return False

  most_recent_visible_report = most_recent_visible_reports[0]
  if abs(
      _GetLineCoveragePercentage(report) -
      _GetLineCoveragePercentage(most_recent_visible_report)) > 0.01:
    return True

  return False
コード例 #3
0
def _CreateSamplePostsubmitReport(manifest=None):
  """Returns a sample PostsubmitReport for testing purpose.

  Note: only use this method if the exact values don't matter.
  """
  manifest = manifest or _CreateSampleManifest()
  return PostsubmitReport.Create(
      server_host='chromium.googlesource.com',
      project='chromium/src',
      ref='refs/heads/master',
      revision='aaaaa',
      bucket='coverage',
      builder='linux-code-coverage',
      commit_timestamp=datetime.datetime(2018, 1, 1),
      manifest=manifest,
      summary_metrics=_CreateSampleCoverageSummaryMetric(),
      build_id=123456789,
      visible=True)
コード例 #4
0
  def testProcessFullRepoData(self, mocked_is_request_from_appself,
                              mocked_get_build, mocked_get_validated_data,
                              mocked_get_change_log, mocked_retrieve_manifest,
                              mocked_fetch_file):
    # Mock buildbucket v2 API.
    build = mock.Mock()
    build.builder.project = 'chrome'
    build.builder.bucket = 'coverage'
    build.builder.builder = 'linux-code-coverage'
    build.output.properties.items.return_value = [
        ('coverage_gs_bucket', 'code-coverage-data'),
        ('coverage_metadata_gs_path',
         ('postsubmit/chromium.googlesource.com/chromium/src/'
          'aaaaa/coverage/linux-code-coverage/123456789/metadata'))
    ]
    build.input.gitiles_commit = mock.Mock(
        host='chromium.googlesource.com',
        project='chromium/src',
        ref='refs/heads/master',
        id='aaaaa')
    mocked_get_build.return_value = build

    # Mock Gitiles API to get change log.
    change_log = mock.Mock()
    change_log.committer.time = datetime.datetime(2018, 1, 1)
    mocked_get_change_log.return_value = change_log

    # Mock retrieve manifest.
    manifest = _CreateSampleManifest()
    mocked_retrieve_manifest.return_value = manifest

    # Mock get validated data from cloud storage for both all.json and file
    # shard json.
    all_coverage_data = {
        'dirs': [{
            'path':
                '//dir/',
            'dirs': [],
            'files': [{
                'path': '//dir/test.cc',
                'name': 'test.cc',
                'summaries': _CreateSampleCoverageSummaryMetric()
            }],
            'summaries':
                _CreateSampleCoverageSummaryMetric()
        }],
        'file_shards': ['file_coverage/files1.json.gz'],
        'summaries':
            _CreateSampleCoverageSummaryMetric(),
        'components': [{
            'path':
                'Component>Test',
            'dirs': [{
                'path': '//dir/',
                'name': 'dir/',
                'summaries': _CreateSampleCoverageSummaryMetric()
            }],
            'summaries':
                _CreateSampleCoverageSummaryMetric()
        }],
    }

    file_shard_coverage_data = {
        'files': [{
            'path':
                '//dir/test.cc',
            'revision':
                'bbbbb',
            'lines': [{
                'count': 100,
                'last': 2,
                'first': 1
            }],
            'timestamp':
                '140000',
            'uncovered_blocks': [{
                'line': 1,
                'ranges': [{
                    'first': 1,
                    'last': 2
                }]
            }]
        }]
    }

    mocked_get_validated_data.side_effect = [
        all_coverage_data, file_shard_coverage_data
    ]

    request_url = '/coverage/task/process-data/build/123456789'
    response = self.test_app.post(request_url)
    self.assertEqual(200, response.status_int)
    mocked_is_request_from_appself.assert_called()

    fetched_reports = PostsubmitReport.query().fetch()
    self.assertEqual(1, len(fetched_reports))
    self.assertEqual(_CreateSamplePostsubmitReport(), fetched_reports[0])
    mocked_fetch_file.assert_called_with(_CreateSamplePostsubmitReport(),
                                         '//dir/test.cc', 'bbbbb')

    fetched_file_coverage_data = FileCoverageData.query().fetch()
    self.assertEqual(1, len(fetched_file_coverage_data))
    self.assertEqual(_CreateSampleFileCoverageData(),
                     fetched_file_coverage_data[0])

    fetched_summary_coverage_data = SummaryCoverageData.query().fetch()
    self.assertListEqual([
        _CreateSampleRootComponentCoverageData(),
        _CreateSampleComponentCoverageData(),
        _CreateSampleDirectoryCoverageData()
    ], fetched_summary_coverage_data)
コード例 #5
0
ファイル: code_coverage.py プロジェクト: xinghun61/infra
  def _ProcessFullRepositoryData(self, commit, data, full_gs_metadata_dir,
                                 builder, build_id):

    # Load the commit log first so that we could fail fast before redo all.
    repo_url = 'https://%s/%s.git' % (commit.host, commit.project)
    change_log = CachedGitilesRepository(FinditHttpClient(),
                                         repo_url).GetChangeLog(commit.id)
    assert change_log is not None, 'Failed to retrieve the commit log'

    # Load the manifest based on the DEPS file.
    # TODO(crbug.com/921714): output the manifest as a build output property.
    manifest = _RetrieveManifest(repo_url, commit.id, 'unix')

    report = PostsubmitReport.Create(
        server_host=commit.host,
        project=commit.project,
        ref=commit.ref,
        revision=commit.id,
        bucket=builder.bucket,
        builder=builder.builder,
        commit_timestamp=change_log.committer.time,
        manifest=manifest,
        summary_metrics=data.get('summaries'),
        build_id=build_id,
        visible=False)
    report.put()

    # Save the file-level, directory-level and line-level coverage data.
    for data_type in ('dirs', 'components', 'files', 'file_shards'):
      sub_data = data.get(data_type)
      if not sub_data:
        continue

      logging.info('Processing %d entries for %s', len(sub_data), data_type)

      actual_data_type = data_type
      if data_type == 'file_shards':
        actual_data_type = 'files'

      def FlushEntries(entries, total, last=False):
        # Flush the data in a batch and release memory.
        if len(entries) < 100 and not (last and entries):
          return entries, total

        ndb.put_multi(entries)
        total += len(entries)
        logging.info('Dumped %d coverage data entries of type %s', total,
                     actual_data_type)

        return [], total

      def IterateOverFileShards(file_shards):
        for file_path in file_shards:
          url = '%s/%s' % (full_gs_metadata_dir, file_path)
          # Download data one by one.
          yield _GetValidatedData(url).get('files', [])

      if data_type == 'file_shards':
        data_iterator = IterateOverFileShards(sub_data)
      else:
        data_iterator = [sub_data]

      entities = []
      total = 0

      component_summaries = []
      for dataset in data_iterator:
        for group_data in dataset:
          if actual_data_type == 'components':
            component_summaries.append({
                'name': group_data['path'],
                'path': group_data['path'],
                'summaries': group_data['summaries'],
            })

          if actual_data_type == 'files' and 'revision' in group_data:
            self._FetchAndSaveFileIfNecessary(report, group_data['path'],
                                              group_data['revision'])

          if actual_data_type == 'files':
            coverage_data = FileCoverageData.Create(
                server_host=commit.host,
                project=commit.project,
                ref=commit.ref,
                revision=commit.id,
                path=group_data['path'],
                bucket=builder.bucket,
                builder=builder.builder,
                data=group_data)
          else:
            coverage_data = SummaryCoverageData.Create(
                server_host=commit.host,
                project=commit.project,
                ref=commit.ref,
                revision=commit.id,
                data_type=actual_data_type,
                path=group_data['path'],
                bucket=builder.bucket,
                builder=builder.builder,
                data=group_data)

          entities.append(coverage_data)
          entities, total = FlushEntries(entities, total, last=False)
        del dataset  # Explicitly release memory.
      FlushEntries(entities, total, last=True)

      if component_summaries:
        component_summaries.sort(key=lambda x: x['path'])
        SummaryCoverageData.Create(
            server_host=commit.host,
            project=commit.project,
            ref=commit.ref,
            revision=commit.id,
            data_type='components',
            path='>>',
            bucket=builder.bucket,
            builder=builder.builder,
            data={
                'dirs': component_summaries,
                'path': '>>'
            }).put()
        component_summaries = []
        logging.info('Summary of all components are saved to datastore.')

    if not _IsReportSuspicious(report):
      report.visible = True
      report.put()

      monitoring.code_coverage_full_reports.increment({
          'host':
              commit.host,
          'project':
              commit.project,
          'ref':
              commit.ref or 'refs/heads/master',
          'builder':
              '%s/%s/%s' % (builder.project, builder.bucket, builder.builder),
      })

    monitoring.code_coverage_report_timestamp.set(
        int(time.time()),
        fields={
            'host':
                commit.host,
            'project':
                commit.project,
            'ref':
                commit.ref or 'refs/heads/master',
            'builder':
                '%s/%s/%s' % (builder.project, builder.bucket, builder.builder),
            'is_success':
                report.visible,
        })
コード例 #6
0
ファイル: code_coverage.py プロジェクト: xinghun61/infra
  def HandleGet(self):
    if self.request.path == '/coverage/api/coverage-data':
      return self._ServePerCLCoverageData()

    match = _LUCI_PROJECT_REGEX.match(self.request.path)
    if not match:
      return BaseHandler.CreateError('Invalid url path %s' % self.request.path,
                                     400)
    luci_project = match.group(1)

    host = self.request.get('host', 'chromium.googlesource.com')
    project = self.request.get('project', 'chromium/src')
    ref = self.request.get('ref', 'refs/heads/master')

    revision = self.request.get('revision')
    path = self.request.get('path')
    data_type = self.request.get('data_type')
    platform = self.request.get('platform', 'linux')
    list_reports = self.request.get('list_reports', False)
    if isinstance(list_reports, str):
      list_reports = (list_reports.lower() == 'true')

    if not data_type and path:
      if path.endswith('/'):
        data_type = 'dirs'
      elif path and '>' in path:
        data_type = 'components'
      else:
        data_type = 'files'

    logging.info('host=%s', host)
    logging.info('project=%s', project)
    logging.info('ref=%s', ref)
    logging.info('revision=%s', revision)
    logging.info('data_type=%s', data_type)
    logging.info('path=%s', path)
    logging.info('platform=%s', platform)

    if not project:
      return BaseHandler.CreateError('Invalid request', 400)

    logging.info('Servicing coverage data for postsubmit')
    if platform not in _POSTSUBMIT_PLATFORM_INFO_MAP:
      return BaseHandler.CreateError('Platform: %s is not supported' % platform,
                                     404)
    bucket = _POSTSUBMIT_PLATFORM_INFO_MAP[platform]['bucket']
    builder = _POSTSUBMIT_PLATFORM_INFO_MAP[platform]['builder']

    if list_reports:
      return self._ServeProjectViewCoverageData(
          luci_project, host, project, ref, revision, platform, bucket, builder)

    template = None
    warning = None
    if not data_type:
      data_type = 'dirs'
    if not revision:
      query = PostsubmitReport.query(
          PostsubmitReport.gitiles_commit.server_host == host,
          PostsubmitReport.gitiles_commit.project == project,
          PostsubmitReport.bucket == bucket,
          PostsubmitReport.builder == builder, PostsubmitReport.visible ==
          True).order(-PostsubmitReport.commit_timestamp)
      entities = query.fetch(limit=1)
      report = entities[0]
      revision = report.gitiles_commit.revision

    else:
      report = PostsubmitReport.Get(
          server_host=host,
          project=project,
          ref=ref,
          revision=revision,
          bucket=bucket,
          builder=builder)
      if not report:
        return BaseHandler.CreateError('Report record not found', 404)

    template = 'coverage/summary_view.html'
    if data_type == 'dirs':
      default_path = '//'
    elif data_type == 'components':
      default_path = '>>'
    else:
      if data_type != 'files':
        return BaseHandler.CreateError(
            'Expected data_type to be "files", but got "%s"' % data_type, 400)

      template = 'coverage/file_view.html'

    path = path or default_path

    if data_type == 'files':
      entity = FileCoverageData.Get(
          server_host=host,
          project=project,
          ref=ref,
          revision=revision,
          path=path,
          bucket=bucket,
          builder=builder)
      if not entity:
        warning = ('File "%s" does not exist in this report, defaulting to root'
                   % path)
        logging.warning(warning)
        path = '//'
        data_type = 'dirs'
        template = 'coverage/summary_view.html'
    if data_type != 'files':
      entity = SummaryCoverageData.Get(
          server_host=host,
          project=project,
          ref=ref,
          revision=revision,
          data_type=data_type,
          path=path,
          bucket=bucket,
          builder=builder)
      if not entity:
        warning = ('Path "%s" does not exist in this report, defaulting to root'
                   % path)
        logging.warning(warning)
        path = default_path
        entity = SummaryCoverageData.Get(
            server_host=host,
            project=project,
            ref=ref,
            revision=revision,
            data_type=data_type,
            path=path,
            bucket=bucket,
            builder=builder)

    metadata = entity.data
    data = {
        'metadata': metadata,
    }

    line_to_data = None
    if data_type == 'files':
      line_to_data = collections.defaultdict(dict)

      if 'revision' in metadata:
        gs_path = _ComposeSourceFileGsPath(report, path, metadata['revision'])
        file_content = _GetFileContentFromGs(gs_path)
        if not file_content:
          # Fetching files from Gitiles is slow, only use it as a backup.
          file_content = _GetFileContentFromGitiles(report, path,
                                                    metadata['revision'])
      else:
        # If metadata['revision'] is empty, it means that the file is not
        # a source file.
        file_content = None

      if not file_content:
        line_to_data[1]['line'] = '!!!!No source code available!!!!'
        line_to_data[1]['count'] = 0
      else:
        file_lines = file_content.splitlines()
        for i, line in enumerate(file_lines):
          # According to http://jinja.pocoo.org/docs/2.10/api/#unicode,
          # Jinja requires passing unicode objects or ASCII-only bytestring,
          # and given that it is possible for source files to have non-ASCII
          # chars, thus converting lines to unicode.
          line_to_data[i + 1]['line'] = unicode(line, 'utf8')
          line_to_data[i + 1]['count'] = -1

        uncovered_blocks = {}
        if 'uncovered_blocks' in metadata:
          for line_data in metadata['uncovered_blocks']:
            uncovered_blocks[line_data['line']] = line_data['ranges']

        for line in metadata['lines']:
          for line_num in range(line['first'], line['last'] + 1):
            line_to_data[line_num]['count'] = line['count']
            if line_num in uncovered_blocks:
              text = line_to_data[line_num]['line']
              regions = _SplitLineIntoRegions(text, uncovered_blocks[line_num])
              line_to_data[line_num]['regions'] = regions
              line_to_data[line_num]['is_partially_covered'] = True
            else:
              line_to_data[line_num]['is_partially_covered'] = False

      line_to_data = list(line_to_data.iteritems())
      line_to_data.sort(key=lambda x: x[0])
      data['line_to_data'] = line_to_data

    # Compute the mapping of the name->path mappings in order.
    path_parts = _GetNameToPathSeparator(path, data_type)
    path_root, _ = _GetPathRootAndSeparatorFromDataType(data_type)
    return {
        'data': {
            'luci_project':
                luci_project,
            'gitiles_commit': {
                'host': host,
                'project': project,
                'ref': ref,
                'revision': revision,
            },
            'path':
                path,
            'platform':
                platform,
            'platform_ui_name':
                _POSTSUBMIT_PLATFORM_INFO_MAP[platform]['ui_name'],
            'path_root':
                path_root,
            'metrics':
                code_coverage_util.GetMetricsBasedOnCoverageTool(
                    _POSTSUBMIT_PLATFORM_INFO_MAP[platform]['coverage_tool']),
            'data':
                data,
            'data_type':
                data_type,
            'path_parts':
                path_parts,
            'platform_select':
                _MakePlatformSelect(host, project, ref, revision, path,
                                    platform),
            'banner':
                _GetBanner(project),
            'warning':
                warning,
        },
        'template': template,
    }
コード例 #7
0
ファイル: code_coverage.py プロジェクト: xinghun61/infra
  def _ServeProjectViewCoverageData(self, luci_project, host, project, ref,
                                    revision, platform, bucket, builder):
    """Serves coverage data for the project view."""
    cursor = self.request.get('cursor', None)
    page_size = int(self.request.get('page_size', 100))
    direction = self.request.get('direction', 'next').lower()

    query = PostsubmitReport.query(
        PostsubmitReport.gitiles_commit.server_host == host,
        PostsubmitReport.gitiles_commit.project == project,
        PostsubmitReport.bucket == bucket,
        PostsubmitReport.builder == builder)
    order_props = [(PostsubmitReport.commit_timestamp, 'desc')]
    entities, prev_cursor, next_cursor = GetPagedResults(
        query, order_props, cursor, direction, page_size)

    # TODO(crbug.com/926237): Move the conversion to client side and use
    # local timezone.
    data = []
    for entity in entities:
      data.append({
          'gitiles_commit': entity.gitiles_commit.to_dict(),
          'commit_timestamp': ConvertUTCToPST(entity.commit_timestamp),
          'summary_metrics': entity.summary_metrics,
          'build_id': entity.build_id,
          'visible': entity.visible,
      })

    return {
        'data': {
            'luci_project':
                luci_project,
            'gitiles_commit': {
                'host': host,
                'project': project,
                'ref': ref,
                'revision': revision,
            },
            'platform':
                platform,
            'platform_ui_name':
                _POSTSUBMIT_PLATFORM_INFO_MAP[platform]['ui_name'],
            'metrics':
                code_coverage_util.GetMetricsBasedOnCoverageTool(
                    _POSTSUBMIT_PLATFORM_INFO_MAP[platform]['coverage_tool']),
            'data':
                data,
            'data_type':
                'project',
            'platform_select':
                _MakePlatformSelect(host, project, ref, revision, None,
                                    platform),
            'banner':
                _GetBanner(project),
            'next_cursor':
                next_cursor,
            'prev_cursor':
                prev_cursor,
        },
        'template': 'coverage/project_view.html',
    }
コード例 #8
0
  def testCreateAndGetPostsubmitReport(self):
    server_host = 'chromium.googlesource.com'
    project = 'chromium/src'
    ref = 'refs/heads/master'
    revision = '99999'
    bucket = 'coverage'
    builder = 'linux-code-coverage'
    commit_position = 100
    commit_timestamp = datetime.datetime(2018, 1, 1)

    manifest = [
        DependencyRepository(
            path='//src',
            server_host='chromium.googlesource.com',
            project='chromium/src.git',
            revision='88888')
    ]

    summary_metrics = [{
        'covered': 1,
        'total': 2,
        'name': 'region'
    }, {
        'covered': 1,
        'total': 2,
        'name': 'function'
    }, {
        'covered': 1,
        'total': 2,
        'name': 'line'
    }]

    build_id = 123456789
    visible = True

    report = PostsubmitReport.Create(
        server_host=server_host,
        project=project,
        ref=ref,
        revision=revision,
        bucket=bucket,
        builder=builder,
        commit_position=commit_position,
        commit_timestamp=commit_timestamp,
        manifest=manifest,
        summary_metrics=summary_metrics,
        build_id=build_id,
        visible=visible)
    report.put()

    # Test key.
    self.assertEqual(
        'chromium.googlesource.com$chromium/src$refs/heads/master$99999$'
        'coverage$linux-code-coverage', report.key.id())

    # Test Create.
    fetched_reports = PostsubmitReport.query().fetch()
    self.assertEqual(1, len(fetched_reports))
    self.assertEqual(report, fetched_reports[0])

    # Test Get.
    self.assertEqual(
        report,
        PostsubmitReport.Get(
            server_host=server_host,
            project=project,
            ref=ref,
            revision=revision,
            bucket=bucket,
            builder=builder))