コード例 #1
0
    def get_base_commit_sha(self, pull_request: PullRequest) -> Optional[str]:
        if self._settings.pull_request_build == pull_request_build_mode_merge:
            if self._settings.event:
                # for pull request events we take the other parent of the merge commit (base)
                if self._settings.event_name == 'pull_request':
                    return self._settings.event.get('pull_request',
                                                    {}).get('base',
                                                            {}).get('sha')
                # for workflow run events we should take the same as for pull request events,
                # but we have no way to figure out the actual merge commit and its parents
                # we do not take the base sha from pull_request as it is not immutable
                if self._settings.event_name == 'workflow_run':
                    return None

        try:
            # we always fall back to where the branch merged off base ref
            logger.debug(
                f'comparing {pull_request.base.ref} with {self._settings.commit}'
            )
            compare = self._repo.compare(pull_request.base.ref,
                                         self._settings.commit)
            return compare.merge_base_commit.sha
        except:
            logger.warning(f'could not find best common ancestor '
                           f'between base {pull_request.base.sha} '
                           f'and commit {self._settings.commit}')

        return None
コード例 #2
0
    def get_check_run(self, commit_sha: str) -> Optional[CheckRun]:
        if commit_sha is None or commit_sha == '0000000000000000000000000000000000000000':
            return None

        commit = None
        try:
            commit = self._repo.get_commit(commit_sha)
        except GithubException as e:
            if e.status == 422:
                self._gha.warning(str(e.data))
            else:
                raise e

        if commit is None:
            self._gha.error(f'Could not find commit {commit_sha}')
            return None

        runs = commit.get_check_runs()
        logger.debug(
            f'found {runs.totalCount} check runs for commit {commit_sha}')
        runs = list(
            [run for run in runs if run.name == self._settings.check_name])
        logger.debug(
            f'found {len(runs)} check runs for commit {commit_sha} with title {self._settings.check_name}'
        )
        if len(runs) != 1:
            return None

        return runs[0]
コード例 #3
0
    def publish_comment(self,
                        title: str,
                        stats: UnitTestRunResults,
                        pull_request: PullRequest,
                        check_run: Optional[CheckRun] = None,
                        cases: Optional[UnitTestCaseResults] = None) -> PullRequest:
        # compare them with earlier stats
        base_check_run = None
        if self._settings.compare_earlier:
            base_commit_sha = self.get_base_commit_sha(pull_request)
            logger.debug(f'comparing against base={base_commit_sha}')
            base_check_run = self.get_check_run(base_commit_sha)
        base_stats = self.get_stats_from_check_run(base_check_run) if base_check_run is not None else None
        stats_with_delta = get_stats_delta(stats, base_stats, 'base') if base_stats is not None else stats
        logger.debug(f'stats with delta: {stats_with_delta}')

        # gather test lists from check run and cases
        before_all_tests, before_skipped_tests = self.get_test_lists_from_check_run(base_check_run)
        all_tests, skipped_tests = get_all_tests_list(cases), get_skipped_tests_list(cases)
        test_changes = SomeTestChanges(before_all_tests, all_tests, before_skipped_tests, skipped_tests)

        details_url = check_run.html_url if check_run else None
        summary = get_long_summary_md(stats_with_delta, details_url, test_changes, self._settings.test_changes_limit)
        body = f'## {title}\n{summary}'

        # reuse existing commend when comment_mode == comment_mode_update
        # if none exists or comment_mode != comment_mode_update, create new comment
        if self._settings.comment_mode != comment_mode_update or not self.reuse_comment(pull_request, body):
            logger.info('creating comment')
            pull_request.create_issue_comment(body)

        return pull_request
コード例 #4
0
    def reuse_comment(self, pull: PullRequest, body: str) -> bool:
        # get comments of this pull request
        comments = self.get_pull_request_comments(pull, order_by_updated=True)

        # get all comments that come from this action and are not hidden
        comments = self.get_action_comments(comments)

        # if there is no such comment, stop here
        if len(comments) == 0:
            return False

        # edit last comment
        comment_id = comments[-1].get("databaseId")
        logger.info(f'editing comment {comment_id}')
        if ':recycle:' not in body:
            body = f'{body}\n:recycle: This comment has been updated with latest results.'

        try:
            pull.get_issue_comment(comment_id).edit(body)
        except Exception as e:
            self._gha.warning(f'Failed to edit existing comment #{comment_id}')
            logger.debug('editing existing comment failed', exc_info=e)

        return True
コード例 #5
0
    def get_stats_from_check_run(self, check_run: CheckRun) -> Optional[UnitTestRunResults]:
        summary = check_run.output.summary
        if summary is None:
            return None
        for line in summary.split('\n'):
            logger.debug(f'summary: {line}')

        pos = summary.index(digest_prefix) if digest_prefix in summary else None
        if pos:
            digest = summary[pos + len(digest_prefix):]
            logger.debug(f'digest: {digest}')
            stats = get_stats_from_digest(digest)
            logger.debug(f'stats: {stats}')
            return stats
コード例 #6
0
    def publish_check(self, stats: UnitTestRunResults,
                      cases: UnitTestCaseResults, compare_earlier: bool,
                      conclusion: str) -> CheckRun:
        # get stats from earlier commits
        before_stats = None
        if compare_earlier:
            before_commit_sha = self._settings.event.get('before')
            logger.debug(f'comparing against before={before_commit_sha}')
            before_stats = self.get_stats_from_commit(before_commit_sha)
        stats_with_delta = get_stats_delta(
            stats, before_stats,
            'earlier') if before_stats is not None else stats
        logger.debug(f'stats with delta: {stats_with_delta}')

        error_annotations = get_error_annotations(stats.errors)
        case_annotations = get_case_annotations(
            cases, self._settings.report_individual_runs)
        file_list_annotations = self.get_test_list_annotations(cases)
        all_annotations = error_annotations + case_annotations + file_list_annotations

        # we can send only 50 annotations at once, so we split them into chunks of 50
        check_run = None
        all_annotations = [
            all_annotations[x:x + 50]
            for x in range(0, len(all_annotations), 50)
        ] or [[]]
        for annotations in all_annotations:
            output = dict(title=get_short_summary(stats),
                          summary=get_long_summary_with_digest_md(
                              stats_with_delta, stats),
                          annotations=[
                              annotation.to_dict()
                              for annotation in annotations
                          ])

            logger.info('creating check')
            check_run = self._repo.create_check_run(
                name=self._settings.check_name,
                head_sha=self._settings.commit,
                status='completed',
                conclusion=conclusion,
                output=output)
            logger.debug(f'created check {check_run}')
        return check_run
コード例 #7
0
 def debug(self, message: str) -> str:
     logger.debug(message)
     return self._command(self._file, 'debug', message)
コード例 #8
0
    def get_pull(self, commit: str) -> Optional[PullRequest]:
        issues = self._gh.search_issues(
            f'type:pr repo:"{self._settings.repo}" {commit}')
        logger.debug(
            f'found {issues.totalCount} pull requests in repo {self._settings.repo} for commit {commit}'
        )

        if issues.totalCount == 0:
            return None

        for issue in issues:
            pr = issue.as_pull_request()
            logger.debug(pr)
            logger.debug(pr.raw_data)
            logger.debug(
                f'PR {pr.html_url}: {pr.head.repo.full_name} -> {pr.base.repo.full_name}'
            )

        # we can only publish the comment to PRs that are in the same repository as this action is executed in
        # so pr.base.repo.full_name must be same as GITHUB_REPOSITORY / self._settings.repo
        # we won't have permission otherwise
        pulls = list([
            pr for issue in issues for pr in [issue.as_pull_request()]
            if pr.base.repo.full_name == self._settings.repo
        ])

        if len(pulls) == 0:
            logger.debug(
                f'found no pull requests in repo {self._settings.repo} for commit {commit}'
            )
            return None
        if len(pulls) > 1:
            pulls = [pull for pull in pulls if pull.state == 'open']
        if len(pulls) == 0:
            logger.debug(
                f'found no open pull request in repo {self._settings.repo} for commit {commit}'
            )
            return None
        if len(pulls) > 1:
            self._gha.error(
                f'Found multiple open pull requests for commit {commit}')
            return None

        pull = pulls[0]
        logger.debug(f'found pull request #{pull.number} for commit {commit}')
        return pull