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
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
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
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
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
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
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