diff --git a/tests/fakegithub.py b/tests/fakegithub.py index 6b951ef514..df34c694b6 100644 --- a/tests/fakegithub.py +++ b/tests/fakegithub.py @@ -14,6 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. +import github3.exceptions import re FAKE_BASE_URL = 'https://example.com/api/v3/' @@ -82,6 +83,9 @@ class FakeRepository(object): self.data = data self.name = name + # fail the next commit requests with 404 + self.fail_not_found = 0 + def branches(self, protected=False): if protected: # simulate there is no protected branch @@ -121,6 +125,25 @@ class FakeRepository(object): commit.set_status(state, url, description, context, user) def commit(self, sha): + + if self.fail_not_found > 0: + self.fail_not_found -= 1 + + class Response: + status_code = 0 + message = '' + + def json(self): + return { + 'message': self.message + } + + resp = Response() + resp.status_code = 404 + resp.message = 'Not Found' + + raise github3.exceptions.NotFoundError(resp) + commit = self._commits.get(sha, None) if commit is None: commit = FakeCommit(sha) diff --git a/tests/unit/test_github_driver.py b/tests/unit/test_github_driver.py index 14b9979ada..2b2f9bd3da 100644 --- a/tests/unit/test_github_driver.py +++ b/tests/unit/test_github_driver.py @@ -1000,6 +1000,37 @@ class TestGithubDriver(ZuulTestCase): self.assertEquals('https://github.com/org/project/pull/1', job2_params['zuul']['items'][0]['change_url']) + @simple_layout('layouts/basic-github.yaml', driver='github') + def test_pull_commit_race(self): + """Test graceful handling of delayed availability of commits""" + + github = self.fake_github.getGithubClient('org/project') + repo = github.repo_from_project('org/project') + repo.fail_not_found = 1 + + A = self.fake_github.openFakePullRequest('org/project', 'master', 'A') + self.fake_github.emitEvent(A.getPullRequestOpenedEvent()) + self.waitUntilSettled() + + self.assertEqual('SUCCESS', + self.getJobFromHistory('project-test1').result) + self.assertEqual('SUCCESS', + self.getJobFromHistory('project-test2').result) + + job = self.getJobFromHistory('project-test2') + zuulvars = job.parameters['zuul'] + self.assertEqual(str(A.number), zuulvars['change']) + self.assertEqual(str(A.head_sha), zuulvars['patchset']) + self.assertEqual('master', zuulvars['branch']) + self.assertEqual(1, len(A.comments)) + self.assertThat( + A.comments[0], + MatchesRegex(r'.*\[project-test1 \]\(.*\).*', re.DOTALL)) + self.assertThat( + A.comments[0], + MatchesRegex(r'.*\[project-test2 \]\(.*\).*', re.DOTALL)) + self.assertEqual(2, len(self.history)) + class TestGithubUnprotectedBranches(ZuulTestCase): config_file = 'zuul-github-driver.conf' diff --git a/zuul/driver/github/githubconnection.py b/zuul/driver/github/githubconnection.py index 84419d3ba9..58c38183b0 100644 --- a/zuul/driver/github/githubconnection.py +++ b/zuul/driver/github/githubconnection.py @@ -1322,11 +1322,24 @@ class GithubConnection(BaseConnection): if not result: raise Exception('Pull request was not merged') + def _getCommit(self, repository, sha, retries=5): + try: + return repository.commit(sha) + except github3.exceptions.NotFoundError: + self.log.warning("Commit %s of project %s returned None", + sha, repository.name) + if retries <= 0: + raise + time.sleep(1) + return self._getCommit(repository, sha, retries - 1) + def getCommitStatuses(self, project, sha): github = self.getGithubClient(project) owner, proj = project.split('/') repository = github.repository(owner, proj) - commit = repository.commit(sha) + + commit = self._getCommit(repository, sha, 5) + # make a list out of the statuses so that we complete our # API transaction statuses = [status.as_dict() for status in commit.statuses()]