# Copyright 2019 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.

import textwrap

from PB.go.chromium.org.luci.common.proto.findings import findings as findings_pb
from recipe_engine import post_process



DEPS = [
    'gclient',
    'presubmit',
    'recipe_engine/buildbucket',
    'recipe_engine/context',
    'recipe_engine/cq',
    'recipe_engine/json',
    'recipe_engine/path',
    'recipe_engine/properties',
    'recipe_engine/proto',
    'recipe_engine/runtime',
]


def RunSteps(api):
  api.gclient.set_config('infra')
  with api.context(cwd=api.path.cache_dir / 'builder'):
    bot_update_step = api.presubmit.prepare()
    skip_owners = api.properties.get('skip_owners', False)
    run_all = api.properties.get('run_all', False)
    return api.presubmit.execute(bot_update_step, skip_owners, run_all)


def GenTests(api):
  yield api.test(
      'success_ci',
      api.buildbucket.ci_build(
          git_repo='https://chromium.googlesource.com/infra/infra'),
      api.properties(run_all=True),
      api.step_data('presubmit', api.json.output({})),
      api.post_process(post_process.StatusSuccess),
      api.post_process(post_process.DropExpectation),
  )

  yield (api.test('success') + api.runtime(is_experimental=False) +
         api.buildbucket.try_build(project='infra') + api.step_data(
             'presubmit',
             api.json.output({
                 'errors': [],
                 'notifications': [],
                 'warnings': []
             }),
         ) + api.post_process(post_process.StatusSuccess) +
         api.post_process(post_process.DropExpectation))

  yield (api.test('cq_dry_run') + api.runtime(is_experimental=False) +
         api.buildbucket.try_build(project='infra') +
         api.cq(run_mode=api.cq.DRY_RUN) +
         api.post_process(post_process.StatusSuccess) + api.post_process(
             post_process.StepCommandContains, 'presubmit', ['--dry_run']) +
         api.post_process(post_process.DropExpectation))

  yield (api.test('module_scheme_experiment') +
         api.runtime(is_experimental=False) + api.buildbucket.try_build(
             experiments={'presubmit.resultdb_module': 100}) +
         api.cq(run_mode=api.cq.DRY_RUN) +
         api.post_process(post_process.StatusSuccess) +
         api.post_process(post_process.StepCommandContains, 'presubmit', [
             'rdb', 'stream', '-module-scheme', 'flat', '-module-name',
             '//:presubmit', '--'
         ]) + api.post_process(post_process.DropExpectation))

  yield (api.test('skip_owners') + api.runtime(is_experimental=False) +
         api.buildbucket.try_build(project='infra') +
         api.properties(skip_owners=True) +
         api.post_process(post_process.StatusSuccess) +
         api.post_process(post_process.StepCommandContains, 'presubmit',
                          ['--skip_canned', 'CheckOwners']) +
         api.post_process(post_process.DropExpectation))

  yield api.test(
      'timeout',
      api.runtime(is_experimental=False),
      api.buildbucket.try_build(project='infra'),
      api.presubmit(timeout_s=600),
      api.step_data(
          'presubmit',
          api.json.output({
              'errors': [],
              'notifications': [],
              'warnings': []
          }),
          times_out_after=1200,
      ),
      api.post_process(post_process.StatusFailure),
      api.post_process(
          post_process.SummaryMarkdown,
          (u'#### There are 0 error(s), 0 warning(s), and 0 notifications(s).'
           '\n\nTimeout occurred during presubmit step.')),
      api.post_process(post_process.DropExpectation),
      status="FAILURE")

  yield api.test(
      'failure',
      api.runtime(is_experimental=False),
      api.buildbucket.try_build(project='infra'),
      api.step_data(
          'presubmit',
          api.json.output(
              {
                  'errors': [{
                      'message': 'Missing LGTM',
                      'long_text': 'Here are some suggested OWNERS: fake@',
                      'items': [],
                      'fatal': True
                  }, {
                      'message': 'Syntax error in fake.py',
                      'long_text': 'Expected "," after item in list',
                      'items': [],
                      'fatal': True
                  }],
                  'notifications': [{
                      'message': 'If there is a bug associated please add it.',
                      'long_text': '',
                      'items': [],
                      'fatal': False
                  }],
                  'warnings': [{
                      'message': 'Line 100 has more than 80 characters',
                      'long_text': '',
                      'items': [],
                      'fatal': False
                  }]
              },
              retcode=1)),
      api.post_process(post_process.StatusFailure),
      api.post_process(
          post_process.SummaryMarkdown,
          textwrap.dedent(u'''
          #### There are 2 error(s), 1 warning(s), and 1 notifications(s).

          **ERROR**
          ```
          Missing LGTM
          Here are some suggested OWNERS: fake@
          ```

          **ERROR**
          ```
          Syntax error in fake.py
          Expected "," after item in list
          ```

          #### To see notifications and warnings, look at the stdout of the presubmit step.
          ''').strip()),
      api.post_process(post_process.DropExpectation),
      status="FAILURE")

  long_message = (u'Here are some suggested OWNERS:' +
    u'\nreallyLongFakeAccountNameEmail@chromium.org' * 10)
  yield api.test(
      'failure-long-message',
      api.runtime(is_experimental=False),
      api.buildbucket.try_build(project='infra'),
      api.step_data(
          'presubmit',
          api.json.output(
              {
                  'errors': [{
                      'message': 'Missing LGTM',
                      'long_text': long_message,
                      'items': [],
                      'fatal': True
                  }],
                  'notifications': [],
                  'warnings': []
              },
              retcode=1)),
      api.post_process(post_process.StatusFailure),
      api.post_process(
          post_process.SummaryMarkdown,
          textwrap.dedent('''
          #### There are 1 error(s), 0 warning(s), and 0 notifications(s).

          **ERROR**
          ```
          Missing LGTM
          Here are some suggested OWNERS:
          reallyLongFakeAccountNameEmail@chromium.org
          reallyLongFakeAccountNameEmail@chromium.org
          reallyLongFakeAccountNameEmail@chromium.org
          reallyLongFakeAccountNameEmail@chromium.org
          reallyLongFakeAccountNameEmail@chromium.org
          reallyLongFakeAccountNameEmail@chromium.org
          reallyLongFakeAccountNameEmail@chromium.org
          reallyLongFakeAccountNameEmail@chromium.org
          reallyLongFakeAccountNameEmail@chromium.org
          ```

          **Error size > 450 chars, there are 2 more error(s) (15 total)**
          **The complete output can be found at the bottom of the presubmit stdout.**
          ''').strip()),
      api.post_process(post_process.DropExpectation),
      status="FAILURE")

  yield api.test(
      'infra-failure',
      api.runtime(is_experimental=False),
      api.buildbucket.try_build(project='infra'),
      api.step_data(
          'presubmit',
          api.json.output(
              {
                  'errors': [{
                      'message': 'Infra Failure',
                      'long_text': '',
                      'items': [],
                      'fatal': True
                  }],
                  'notifications': [],
                  'warnings': []
              },
              retcode=2)),
      api.post_process(post_process.StatusException),
      api.post_process(
          post_process.SummaryMarkdown,
          textwrap.dedent(u'''
          #### There are 1 error(s), 0 warning(s), and 0 notifications(s).

          **ERROR**
          ```
          Infra Failure

          ```
          ''').strip()),
      api.post_process(post_process.DropExpectation),
      status="INFRA_FAILURE")

  bug_msg = ('Something unexpected occurred'
             ' while running presubmit checks.'
             ' Please [file a bug](https://issues.chromium.org'
             '/issues/new?component=1456211)')
  yield api.test(
      'failure-no-json',
      api.runtime(is_experimental=False),
      api.buildbucket.try_build(project='infra'),
      api.step_data('presubmit', api.json.output(None, retcode=1)),
      api.post_process(post_process.StatusException),
      api.post_process(post_process.SummaryMarkdown, bug_msg),
      api.post_process(post_process.DropExpectation),
      status="INFRA_FAILURE")

  yield api.test(
      'infra-failure-no-json',
      api.runtime(is_experimental=False),
      api.buildbucket.try_build(project='infra'),
      api.step_data('presubmit', api.json.output(None, retcode=2)),
      api.post_process(post_process.StatusException),
      api.post_process(post_process.SummaryMarkdown, bug_msg),
      api.post_process(post_process.DropExpectation),
      status="INFRA_FAILURE")

  def _has_uploaded_findings(check, steps, expected_findings):
    step_name = 'upload presubmit results as findings'
    check(step_name in steps)
    check('findings.json' in steps[step_name].logs)
    findings = api.proto.decode(steps[step_name].logs['findings.json'],
                                findings_pb.Findings, 'JSONPB').findings
    check(len(findings) == len(expected_findings))
    for (actual, expected) in zip(findings, expected_findings):
      check(actual == expected)

  gerrit_change_ref = findings_pb.Location.GerritChangeReference(
      host='chromium-review.googlesource.com',
      project='infra',
      change=123456,
      patchset=7)

  yield api.test(
      'upload_findings',
      api.runtime(is_experimental=False),
      api.buildbucket.try_build(project='infra'),
      api.step_data(
          'presubmit',
          api.json.output(
              {
                  'errors': [{
                      'message':
                      'bug bug bug',
                      'long_text':
                      '',
                      'items': [],
                      'locations': [{
                          'file_path': 'path/to/file1',
                      }, {
                          'file_path': 'path/to/file2',
                      }],
                      'fatal':
                      True
                  }],
                  'notifications':
                  [{
                      'message': 'cc abc@google.com',
                      'long_text': '',
                      'items': [],
                      'locations': [{
                          'file_path': '/COMMIT_MSG',
                      }],
                      'fatal': False
                  }],
                  'warnings': [{
                      'message':
                      'Change takes 20 min to take effect after landing',
                      'long_text': '',
                      'items': [],
                      'locations': [],
                      'fatal': False
                  }, {
                      'message':
                      'typo!!',
                      'long_text':
                      'replace foo with bar',
                      'items': [],
                      'locations': [{
                          'file_path': 'path/to/file',
                          'start_line': 1,
                          'start_col': 2,
                          'end_line': 1,
                          'end_col': 5,
                      }],
                      'fatal':
                      False
                  }]
              },
              retcode=1)),
      api.post_process(
          _has_uploaded_findings,
          [
              findings_pb.Finding(
                  category='chromium_presubmit',
                  location=findings_pb.Location(
                      gerrit_change_ref=gerrit_change_ref,
                      file_path='path/to/file1'),
                  message='bug bug bug',
                  severity_level=findings_pb.Finding.SEVERITY_LEVEL_ERROR),
              findings_pb.Finding(
                  category='chromium_presubmit',
                  location=findings_pb.Location(
                      gerrit_change_ref=gerrit_change_ref,
                      file_path='path/to/file2'),
                  message='bug bug bug',
                  severity_level=findings_pb.Finding.SEVERITY_LEVEL_ERROR),
              findings_pb.Finding(
                  category='chromium_presubmit',
                  location=findings_pb.Location(
                      gerrit_change_ref=gerrit_change_ref,
                      file_path='path/to/file',
                      range=findings_pb.Location.Range(
                          start_line=1,
                          start_column=2,
                          end_line=1,
                          end_column=5,
                      )),
                  message='typo!!',
                  severity_level=findings_pb.Finding.SEVERITY_LEVEL_WARNING),
              findings_pb.Finding(
                  category='chromium_presubmit',
                  location=findings_pb.Location(
                      gerrit_change_ref=gerrit_change_ref,
                      file_path='/COMMIT_MSG'),
                  message='cc abc@google.com',
                  severity_level=findings_pb.Finding.SEVERITY_LEVEL_INFO),
          ],
      ),
      api.post_process(post_process.StatusFailure),
      api.post_process(post_process.DropExpectation),
      status="FAILURE")

  yield api.test(
      'upload_findings_when_presubmit_succeeds',
      api.runtime(is_experimental=False),
      api.buildbucket.try_build(project='infra'),
      api.step_data(
          'presubmit',
          api.json.output({
              'errors': [],
              'notifications': [],
              'warnings': [{
                  'message': 'this is a message',
                  'long_text': '',
                  'items': [],
                  'locations': [{
                      'file_path': 'path/to/file',
                  }],
                  'fatal': False
              }]
          })),
      api.post_process(
          _has_uploaded_findings,
          [
              findings_pb.Finding(
                  category='chromium_presubmit',
                  location=findings_pb.Location(
                      gerrit_change_ref=gerrit_change_ref,
                      file_path='path/to/file'),
                  message='this is a message',
                  severity_level=findings_pb.Finding.SEVERITY_LEVEL_WARNING),
          ],
      ), api.post_process(post_process.StatusSuccess),
      api.post_process(post_process.DropExpectation))
