Example #1
0
def test_unicode_truncate(
    length: int,
    placeholder: str,
    expected: typing.Optional[str],
) -> None:
    s = "hé ho! how are you√2?"
    if expected is None:
        with pytest.raises(ValueError):
            utils.unicode_truncate(s, length, placeholder)
    else:
        result = utils.unicode_truncate(s, length, placeholder)
        assert len(result.encode()) <= length
        assert result == expected
Example #2
0
async def set_check_run(
    ctxt: "context.Context",
    name: str,
    result: Result,
    external_id: typing.Optional[str] = None,
) -> github_types.GitHubCheckRun:
    if result.conclusion is Conclusion.PENDING:
        status = Status.IN_PROGRESS
    else:
        status = Status.COMPLETED

    post_parameters = GitHubCheckRunParameters({
        "name":
        name,
        "head_sha":
        ctxt.pull["head"]["sha"],
        "status":
        typing.cast(github_types.GitHubCheckRunStatus, status.value),
        "started_at":
        utils.utcnow().isoformat(),
        "details_url":
        f"{ctxt.pull['html_url']}/checks",
        "output": {
            "title": result.title,
            "summary": result.summary,
        },
    })

    if result.annotations is not None:
        post_parameters["output"]["annotations"] = result.annotations

    # Maximum output/summary length for Check API is 65535
    summary = post_parameters["output"]["summary"]
    if summary and len(summary) > 65535:
        post_parameters["output"]["summary"] = utils.unicode_truncate(
            summary, 65532)
        post_parameters["output"]["summary"] += "…"  # this is 3 bytes long

    if external_id:
        post_parameters["external_id"] = external_id

    if status is Status.COMPLETED:
        post_parameters["conclusion"] = result.conclusion.value
        post_parameters["completed_at"] = utils.utcnow().isoformat()

    checks = sorted(
        (c for c in await ctxt.pull_engine_check_runs if c["name"] == name),
        key=lambda c: c["id"],
        reverse=True,
    )

    # Only keep the newer checks, cancelled others
    for check_to_cancelled in checks[1:]:
        if Status(check_to_cancelled["status"]) != Status.COMPLETED:
            await ctxt.client.patch(
                f"{ctxt.base_url}/check-runs/{check_to_cancelled['id']}",
                json={
                    "conclusion": Conclusion.CANCELLED.value,
                    "status": Status.COMPLETED.value,
                },
            )

    if not checks or (Status(checks[0]["status"]) == Status.COMPLETED
                      and status == Status.IN_PROGRESS):
        # NOTE(sileht): First time we see it, or the previous one have been completed and
        # now go back to in_progress. Since GitHub doesn't allow to change status of
        # completed check-runs, we have to create a new one.
        new_check = typing.cast(
            github_types.GitHubCheckRun,
            (await ctxt.client.post(
                f"{ctxt.base_url}/check-runs",
                json=post_parameters,
            )).json(),
        )
    else:
        post_parameters["details_url"] += f"?check_run_id={checks[0]['id']}"

        # Don't do useless update
        if check_need_update(checks[0], post_parameters):
            new_check = typing.cast(
                github_types.GitHubCheckRun,
                (await ctxt.client.patch(
                    f"{ctxt.base_url}/check-runs/{checks[0]['id']}",
                    json=post_parameters,
                )).json(),
            )
        else:
            new_check = checks[0]

    await ctxt.update_pull_check_runs(new_check)
    return new_check
Example #3
0
async def set_check_run(
    ctxt: "context.Context",
    name: str,
    result: Result,
    external_id: typing.Optional[str] = None,
    skip_cache: bool = False,
) -> github_types.CachedGitHubCheckRun:
    if result.conclusion is Conclusion.PENDING:
        status = Status.IN_PROGRESS
    else:
        status = Status.COMPLETED

    started_at = (result.started_at or date.utcnow()).isoformat()

    post_parameters = GitHubCheckRunParameters({
        "name":
        name,
        "head_sha":
        ctxt.pull["head"]["sha"],
        "status":
        typing.cast(github_types.GitHubCheckRunStatus, status.value),
        "started_at":
        typing.cast(github_types.ISODateTimeType, started_at),
        "details_url":
        f"{ctxt.pull['html_url']}/checks",
        "output": {
            "title": result.title,
            "summary": result.summary,
        },
    })

    if result.annotations is not None:
        post_parameters["output"]["annotations"] = result.annotations

    # Maximum output/summary length for Check API is 65535
    summary = post_parameters["output"]["summary"]
    if summary:
        post_parameters["output"]["summary"] = utils.unicode_truncate(
            summary, 65535, "…")

    if external_id:
        post_parameters["external_id"] = external_id

    if status is Status.COMPLETED:
        ended_at = (result.ended_at or date.utcnow()).isoformat()
        post_parameters["conclusion"] = result.conclusion.value
        post_parameters["completed_at"] = typing.cast(
            github_types.ISODateTimeType, ended_at)

    if skip_cache:
        checks = sorted(
            await get_checks_for_ref(
                ctxt,
                ctxt.pull["head"]["sha"],
                check_name=name,
                app_id=config.INTEGRATION_ID,
            ),
            key=lambda c: c["id"],
            reverse=True,
        )
    else:
        checks = sorted(
            (c
             for c in await ctxt.pull_engine_check_runs if c["name"] == name),
            key=lambda c: c["id"],
            reverse=True,
        )

    if len(checks) >= 2:
        ctxt.log.warning(
            "pull requests with duplicate checks",
            checks=checks,
            skip_cache=skip_cache,
            all_checks=await ctxt.pull_engine_check_runs,
            fresh_checks=await
            get_checks_for_ref(ctxt,
                               ctxt.pull["head"]["sha"],
                               app_id=config.INTEGRATION_ID),
        )

    if not checks or (Status(checks[0]["status"]) == Status.COMPLETED
                      and status == Status.IN_PROGRESS):
        # NOTE(sileht): First time we see it, or the previous one have been completed and
        # now go back to in_progress. Since GitHub doesn't allow to change status of
        # completed check-runs, we have to create a new one.
        new_check = to_check_run_light(
            typing.cast(
                github_types.GitHubCheckRun,
                (await ctxt.client.post(
                    f"{ctxt.base_url}/check-runs",
                    api_version="antiope",
                    json=post_parameters,
                )).json(),
            ))
    else:
        post_parameters["details_url"] += f"?check_run_id={checks[0]['id']}"

        # Don't do useless update
        if check_need_update(checks[0], post_parameters):
            new_check = to_check_run_light(
                typing.cast(
                    github_types.GitHubCheckRun,
                    (await ctxt.client.patch(
                        f"{ctxt.base_url}/check-runs/{checks[0]['id']}",
                        api_version="antiope",
                        json=post_parameters,
                    )).json(),
                ))
        else:
            new_check = checks[0]

    if not skip_cache:
        await ctxt.update_cached_check_runs(new_check)
    return new_check
Example #4
0
def test_unicode_truncate():
    s = "hé ho! how are you√2?"
    assert utils.unicode_truncate(s, 0) == ""
    assert utils.unicode_truncate(s, 1) == "h"
    assert utils.unicode_truncate(s, 2) == "h"
    assert utils.unicode_truncate(s, 3) == "hé"
    assert utils.unicode_truncate(s, 4) == "hé "
    assert utils.unicode_truncate(s, 10) == "hé ho! ho"
    assert utils.unicode_truncate(s, 18) == "hé ho! how are yo"
    assert utils.unicode_truncate(s, 19) == "hé ho! how are you"
    assert utils.unicode_truncate(s, 20) == "hé ho! how are you"
    assert utils.unicode_truncate(s, 21) == "hé ho! how are you"
    assert utils.unicode_truncate(s, 22) == "hé ho! how are you√"
    assert utils.unicode_truncate(s, 23) == "hé ho! how are you√2"
    assert utils.unicode_truncate(s, 50) == s
Example #5
0
def set_check_run(pull, name, status, conclusion=None, output=None):
    post_parameters = {
        "name": name,
        "head_sha": pull.head.sha,
        "status": status
    }
    if conclusion:
        post_parameters["conclusion"] = conclusion
    if output:
        # Maximum output/summary length for Check API is 65535
        summary = output.get("summary")
        if summary and len(summary) > 65535:
            output["summary"] = utils.unicode_truncate(summary, 65532)
            output["summary"] += "…"  # this is 3 bytes long
        post_parameters["output"] = output

    post_parameters["started_at"] = utils.utcnow().isoformat()
    post_parameters["details_url"] = "%s/checks" % pull.html_url

    if status == "completed":
        post_parameters["completed_at"] = utils.utcnow().isoformat()

    checks = list(c for c in get_checks(pull, {"check_name": name})
                  if c._rawData["app"]["id"] == config.INTEGRATION_ID)

    if not checks:
        headers, data = pull._requester.requestJsonAndCheck(
            "POST",
            "%s/check-runs" % (pull.base.repo.url),
            input=post_parameters,
            headers={"Accept": "application/vnd.github.antiope-preview+json"},
        )
        checks = [Check(pull._requester, headers, data, completed=True)]

    if len(checks) > 1:
        LOG.warning(
            "Multiple mergify checks have been created, "
            "we got the known race.",
            pull_request=pull,
        )

    post_parameters["details_url"] += "?check_run_id=%s" % checks[0].id

    # FIXME(sileht): We have no (simple) way to ensure we don't have multiple
    # worker doing POST at the same time. It's unlike to happen, but it has
    # happen once, so to ensure Mergify continue to work, we update all
    # checks. User will see the check twice for a while, but it's better than
    # having Mergify stuck
    for check in checks:
        # Don't do useless update
        if compare_dict(
                post_parameters,
                check.raw_data,
            ("name", "head_sha", "status", "conclusion", "details_url"),
        ):
            if check.output == output:
                continue
            elif (check.output is not None and output is not None
                  and compare_dict(output, check.output,
                                   ("title", "summary"))):
                continue

        headers, data = pull._requester.requestJsonAndCheck(
            "PATCH",
            "%s/check-runs/%s" % (pull.base.repo.url, check.id),
            input=post_parameters,
            headers={"Accept": "application/vnd.github.antiope-preview+json"},
        )
        check = Check(pull._requester, headers, data, completed=True)

    return check
Example #6
0
def set_check_run(
    ctxt: "context.Context",
    name: str,
    result: Result,
    external_id: typing.Optional[str] = None,
) -> github_types.GitHubCheckRun:
    if result.conclusion is Conclusion.PENDING:
        status = Status.IN_PROGRESS
    else:
        status = Status.COMPLETED

    post_parameters = {
        "name": name,
        "head_sha": ctxt.pull["head"]["sha"],
        "status": status.value,
        "started_at": utils.utcnow().isoformat(),
        "details_url": f"{ctxt.pull['html_url']}/checks",
        "output": {
            "title": result.title,
            "summary": result.summary,
        },
    }

    if result.annotations is not None:
        post_parameters["output"]["annotations"] = result.annotations

    # Maximum output/summary length for Check API is 65535
    summary = post_parameters["output"]["summary"]
    if summary and len(summary) > 65535:
        post_parameters["output"]["summary"] = utils.unicode_truncate(
            summary, 65532)
        post_parameters["output"]["summary"] += "…"  # this is 3 bytes long

    if external_id:
        post_parameters["external_id"] = external_id

    if status is Status.COMPLETED:
        post_parameters["conclusion"] = result.conclusion.value
        post_parameters["completed_at"] = utils.utcnow().isoformat()

    checks = [c for c in ctxt.pull_engine_check_runs if c["name"] == name]

    if not checks:
        check = typing.cast(
            github_types.GitHubCheckRun,
            ctxt.client.post(
                f"{ctxt.base_url}/check-runs",
                api_version="antiope",  # type: ignore[call-arg]
                json=post_parameters,
            ).json(),
        )
        ctxt.update_pull_check_runs(check)
        return check

    elif len(checks) > 1:
        ctxt.log.warning(
            "Multiple mergify checks have been created, we got the known race.",
        )

    post_parameters["details_url"] += "?check_run_id=%s" % checks[0]["id"]

    # FIXME(sileht): We have no (simple) way to ensure we don't have multiple
    # worker doing POST at the same time. It's unlike to happen, but it has
    # happen once, so to ensure Mergify continue to work, we update all
    # checks. User will see the check twice for a while, but it's better than
    # having Mergify stuck
    for check in checks:
        # Don't do useless update
        if compare_dict(
                post_parameters,
                check,
            ("name", "head_sha", "status", "conclusion", "details_url"),
        ):
            if check["output"] == post_parameters["output"]:
                continue
            elif check["output"] is not None and compare_dict(
                    post_parameters["output"], check["output"],
                ("title", "summary")):
                continue

        check = typing.cast(
            github_types.GitHubCheckRun,
            ctxt.client.patch(
                f"{ctxt.base_url}/check-runs/{check['id']}",
                api_version="antiope",  # type: ignore[call-arg]
                json=post_parameters,
            ).json(),
        )

    ctxt.update_pull_check_runs(check)
    return check
Example #7
0
def set_check_run(ctxt, name, status, conclusion=None, output=None):
    post_parameters = {
        "name": name,
        "head_sha": ctxt.pull["head"]["sha"],
        "status": status,
    }
    if conclusion:
        post_parameters["conclusion"] = conclusion
    if output:
        # Maximum output/summary length for Check API is 65535
        summary = output.get("summary")
        if summary and len(summary) > 65535:
            output["summary"] = utils.unicode_truncate(summary, 65532)
            output["summary"] += "…"  # this is 3 bytes long
        post_parameters["output"] = output

    post_parameters["started_at"] = utils.utcnow().isoformat()
    post_parameters["details_url"] = "%s/checks" % ctxt.pull["html_url"]

    if status == "completed":
        post_parameters["completed_at"] = utils.utcnow().isoformat()

    checks = get_checks(ctxt, check_name=name, mergify_only=True)

    if not checks:
        checks = [
            ctxt.client.post(
                "check-runs",
                api_version="antiope",
                json=post_parameters,
            ).json()
        ]

    if len(checks) > 1:
        ctxt.log.warning(
            "Multiple mergify checks have been created, we got the known race.",
        )

    post_parameters["details_url"] += "?check_run_id=%s" % checks[0]["id"]

    # FIXME(sileht): We have no (simple) way to ensure we don't have multiple
    # worker doing POST at the same time. It's unlike to happen, but it has
    # happen once, so to ensure Mergify continue to work, we update all
    # checks. User will see the check twice for a while, but it's better than
    # having Mergify stuck
    for check in checks:
        # Don't do useless update
        if compare_dict(
                post_parameters,
                check,
            ("name", "head_sha", "status", "conclusion", "details_url"),
        ):
            if check["output"] == output:
                continue
            elif (check["output"] is not None and output is not None
                  and compare_dict(output, check["output"],
                                   ("title", "summary"))):
                continue

        check = ctxt.client.patch(
            f"check-runs/{check['id']}",
            api_version="antiope",
            json=post_parameters,
        ).json()

    return check