# Copyright 2019 The Chromium Authors
# 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,
      (
        "#### 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("""
          #### 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 = (
    "Here are some suggested OWNERS:"
    + "\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("""
          #### 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),
  )
