blob: 89784155a7bd6a5f1ebcc607c0ee0270cab86c35 [file] [log] [blame]
Gregory Haynes4fc12542015-04-22 20:38:06 -07001# Copyright 2015 GoodData
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
Jesse Keating80730e62017-09-14 15:35:11 -060015import asyncio
16import threading
James E. Blair21037782017-07-19 11:56:55 -070017import os
Jan Hrubane252a732017-01-03 15:03:09 +010018import re
Jamie Lennox3f16de52017-05-09 14:24:11 +100019from testtools.matchers import MatchesRegex, StartsWith
Tristan Cacqueray2bafb1f2017-06-12 07:10:26 +000020import urllib
Jesse Keating80730e62017-09-14 15:35:11 -060021import socket
Jan Hruban324ca5b2015-11-05 19:28:54 +010022import time
Jesse Keating64d29012017-09-06 12:27:49 -070023from unittest import skip
Gregory Haynes4fc12542015-04-22 20:38:06 -070024
James E. Blair21037782017-07-19 11:56:55 -070025import git
26
Jesse Keating80730e62017-09-14 15:35:11 -060027import zuul.web
28
Wayne1a78c612015-06-11 17:14:13 -070029from tests.base import ZuulTestCase, simple_layout, random_sha1
Gregory Haynes4fc12542015-04-22 20:38:06 -070030
Gregory Haynes4fc12542015-04-22 20:38:06 -070031
32class TestGithubDriver(ZuulTestCase):
33 config_file = 'zuul-github-driver.conf'
34
35 @simple_layout('layouts/basic-github.yaml', driver='github')
36 def test_pull_event(self):
37 self.executor_server.hold_jobs_in_build = True
38
Jan Hruban37615e52015-11-19 14:30:49 +010039 A = self.fake_github.openFakePullRequest('org/project', 'master', 'A')
40 self.fake_github.emitEvent(A.getPullRequestOpenedEvent())
Gregory Haynes4fc12542015-04-22 20:38:06 -070041 self.waitUntilSettled()
42
Gregory Haynes4fc12542015-04-22 20:38:06 -070043 self.executor_server.hold_jobs_in_build = False
44 self.executor_server.release()
45 self.waitUntilSettled()
46
47 self.assertEqual('SUCCESS',
48 self.getJobFromHistory('project-test1').result)
49 self.assertEqual('SUCCESS',
50 self.getJobFromHistory('project-test2').result)
51
52 job = self.getJobFromHistory('project-test2')
Monty Taylord13bc362017-06-30 13:11:37 -050053 zuulvars = job.parameters['zuul']
James E. Blaire3db2952017-07-21 15:03:36 -070054 self.assertEqual(str(A.number), zuulvars['change'])
55 self.assertEqual(str(A.head_sha), zuulvars['patchset'])
James E. Blair3b222492017-07-21 15:17:37 -070056 self.assertEqual('master', zuulvars['branch'])
Jan Hruban37615e52015-11-19 14:30:49 +010057 self.assertEqual(1, len(A.comments))
liushengd3419e82018-01-06 19:52:03 +080058 self.assertThat(
59 A.comments[0],
60 MatchesRegex('.*\[project-test1 \]\(.*\).*', re.DOTALL))
61 self.assertThat(
62 A.comments[0],
63 MatchesRegex('.*\[project-test2 \]\(.*\).*', re.DOTALL))
Jan Hruband4edee82015-12-16 12:49:51 +010064 self.assertEqual(2, len(self.history))
65
66 # test_pull_unmatched_branch_event(self):
67 self.create_branch('org/project', 'unmatched_branch')
68 B = self.fake_github.openFakePullRequest(
69 'org/project', 'unmatched_branch', 'B')
70 self.fake_github.emitEvent(B.getPullRequestOpenedEvent())
71 self.waitUntilSettled()
72
73 self.assertEqual(2, len(self.history))
Wayne1a78c612015-06-11 17:14:13 -070074
Jan Hruban570d01c2016-03-10 21:51:32 +010075 @simple_layout('layouts/files-github.yaml', driver='github')
76 def test_pull_matched_file_event(self):
77 A = self.fake_github.openFakePullRequest(
78 'org/project', 'master', 'A',
79 files=['random.txt', 'build-requires'])
80 self.fake_github.emitEvent(A.getPullRequestOpenedEvent())
81 self.waitUntilSettled()
82 self.assertEqual(1, len(self.history))
83
84 # test_pull_unmatched_file_event
85 B = self.fake_github.openFakePullRequest('org/project', 'master', 'B',
86 files=['random.txt'])
87 self.fake_github.emitEvent(B.getPullRequestOpenedEvent())
88 self.waitUntilSettled()
89 self.assertEqual(1, len(self.history))
90
Jan Hrubanc7ab1602015-10-14 15:29:33 +020091 @simple_layout('layouts/basic-github.yaml', driver='github')
92 def test_comment_event(self):
Jan Hruban37615e52015-11-19 14:30:49 +010093 A = self.fake_github.openFakePullRequest('org/project', 'master', 'A')
94 self.fake_github.emitEvent(A.getCommentAddedEvent('test me'))
Jan Hrubanc7ab1602015-10-14 15:29:33 +020095 self.waitUntilSettled()
96 self.assertEqual(2, len(self.history))
97
98 # Test an unmatched comment, history should remain the same
Jan Hruban37615e52015-11-19 14:30:49 +010099 B = self.fake_github.openFakePullRequest('org/project', 'master', 'B')
100 self.fake_github.emitEvent(B.getCommentAddedEvent('casual comment'))
Jan Hrubanc7ab1602015-10-14 15:29:33 +0200101 self.waitUntilSettled()
102 self.assertEqual(2, len(self.history))
103
Wayne1a78c612015-06-11 17:14:13 -0700104 @simple_layout('layouts/push-tag-github.yaml', driver='github')
105 def test_tag_event(self):
106 self.executor_server.hold_jobs_in_build = True
107
James E. Blair21037782017-07-19 11:56:55 -0700108 self.create_branch('org/project', 'tagbranch')
109 files = {'README.txt': 'test'}
110 self.addCommitToRepo('org/project', 'test tag',
111 files, branch='tagbranch', tag='newtag')
112 path = os.path.join(self.upstream_root, 'org/project')
113 repo = git.Repo(path)
114 tag = repo.tags['newtag']
115 sha = tag.commit.hexsha
116 del repo
117
Wayne1a78c612015-06-11 17:14:13 -0700118 self.fake_github.emitEvent(
119 self.fake_github.getPushEvent('org/project', 'refs/tags/newtag',
120 new_rev=sha))
121 self.waitUntilSettled()
122
123 build_params = self.builds[0].parameters
James E. Blaira438c172017-07-21 14:54:42 -0700124 self.assertEqual('refs/tags/newtag', build_params['zuul']['ref'])
James E. Blairb8203e42017-08-02 17:00:14 -0700125 self.assertFalse('oldrev' in build_params['zuul'])
James E. Blaired8b0012017-07-21 14:49:29 -0700126 self.assertEqual(sha, build_params['zuul']['newrev'])
Wayne1a78c612015-06-11 17:14:13 -0700127 self.executor_server.hold_jobs_in_build = False
128 self.executor_server.release()
129 self.waitUntilSettled()
130
131 self.assertEqual('SUCCESS',
132 self.getJobFromHistory('project-tag').result)
133
134 @simple_layout('layouts/push-tag-github.yaml', driver='github')
135 def test_push_event(self):
136 self.executor_server.hold_jobs_in_build = True
137
James E. Blair289f5932017-07-27 15:02:29 -0700138 A = self.fake_github.openFakePullRequest('org/project', 'master', 'A')
139 old_sha = '0' * 40
140 new_sha = A.head_sha
141 A.setMerged("merging A")
142 pevent = self.fake_github.getPushEvent(project='org/project',
143 ref='refs/heads/master',
144 old_rev=old_sha,
145 new_rev=new_sha)
146 self.fake_github.emitEvent(pevent)
Wayne1a78c612015-06-11 17:14:13 -0700147 self.waitUntilSettled()
148
149 build_params = self.builds[0].parameters
James E. Blaira438c172017-07-21 14:54:42 -0700150 self.assertEqual('refs/heads/master', build_params['zuul']['ref'])
James E. Blair289f5932017-07-27 15:02:29 -0700151 self.assertFalse('oldrev' in build_params['zuul'])
James E. Blaired8b0012017-07-21 14:49:29 -0700152 self.assertEqual(new_sha, build_params['zuul']['newrev'])
Wayne1a78c612015-06-11 17:14:13 -0700153
154 self.executor_server.hold_jobs_in_build = False
155 self.executor_server.release()
156 self.waitUntilSettled()
157
158 self.assertEqual('SUCCESS',
159 self.getJobFromHistory('project-post').result)
Jan Hruband4edee82015-12-16 12:49:51 +0100160 self.assertEqual(1, len(self.history))
161
162 # test unmatched push event
163 old_sha = random_sha1()
164 new_sha = random_sha1()
165 self.fake_github.emitEvent(
166 self.fake_github.getPushEvent('org/project',
167 'refs/heads/unmatched_branch',
168 old_sha, new_sha))
169 self.waitUntilSettled()
170
171 self.assertEqual(1, len(self.history))
Jan Hruban6d53c5e2015-10-24 03:03:34 +0200172
Jan Hruban16ad31f2015-11-07 14:39:07 +0100173 @simple_layout('layouts/labeling-github.yaml', driver='github')
174 def test_labels(self):
Jan Hruban37615e52015-11-19 14:30:49 +0100175 A = self.fake_github.openFakePullRequest('org/project', 'master', 'A')
Jan Hruban16ad31f2015-11-07 14:39:07 +0100176 self.fake_github.emitEvent(A.addLabel('test'))
177 self.waitUntilSettled()
178 self.assertEqual(1, len(self.history))
179 self.assertEqual('project-labels', self.history[0].name)
180 self.assertEqual(['tests passed'], A.labels)
181
182 # test label removed
Jan Hruban37615e52015-11-19 14:30:49 +0100183 B = self.fake_github.openFakePullRequest('org/project', 'master', 'B')
Jan Hruban16ad31f2015-11-07 14:39:07 +0100184 B.addLabel('do not test')
185 self.fake_github.emitEvent(B.removeLabel('do not test'))
186 self.waitUntilSettled()
187 self.assertEqual(2, len(self.history))
188 self.assertEqual('project-labels', self.history[1].name)
189 self.assertEqual(['tests passed'], B.labels)
190
191 # test unmatched label
Jan Hruban37615e52015-11-19 14:30:49 +0100192 C = self.fake_github.openFakePullRequest('org/project', 'master', 'C')
Jan Hruban16ad31f2015-11-07 14:39:07 +0100193 self.fake_github.emitEvent(C.addLabel('other label'))
194 self.waitUntilSettled()
195 self.assertEqual(2, len(self.history))
196 self.assertEqual(['other label'], C.labels)
197
Jesse Keating5c05a9f2017-01-12 14:44:58 -0800198 @simple_layout('layouts/reviews-github.yaml', driver='github')
199 def test_review_event(self):
200 A = self.fake_github.openFakePullRequest('org/project', 'master', 'A')
201 self.fake_github.emitEvent(A.getReviewAddedEvent('approve'))
202 self.waitUntilSettled()
203 self.assertEqual(1, len(self.history))
204 self.assertEqual('project-reviews', self.history[0].name)
205 self.assertEqual(['tests passed'], A.labels)
206
207 # test_review_unmatched_event
208 B = self.fake_github.openFakePullRequest('org/project', 'master', 'B')
209 self.fake_github.emitEvent(B.getReviewAddedEvent('comment'))
210 self.waitUntilSettled()
211 self.assertEqual(1, len(self.history))
212
liusheng09642782018-01-23 09:49:12 +0800213 @simple_layout('layouts/basic-github.yaml', driver='github')
214 def test_timer_event(self):
215 self.executor_server.hold_jobs_in_build = True
216 self.commitConfigUpdate('org/common-config',
217 'layouts/timer-github.yaml')
218 self.sched.reconfigure(self.config)
219 time.sleep(2)
220 self.waitUntilSettled()
221 self.assertEqual(len(self.builds), 1)
222 self.executor_server.hold_jobs_in_build = False
223 # Stop queuing timer triggered jobs so that the assertions
224 # below don't race against more jobs being queued.
225 self.commitConfigUpdate('org/common-config',
226 'layouts/basic-github.yaml')
227 self.sched.reconfigure(self.config)
228 self.waitUntilSettled()
229 # If APScheduler is in mid-event when we remove the job, we
230 # can end up with one more event firing, so give it an extra
231 # second to settle.
232 time.sleep(1)
233 self.waitUntilSettled()
234 self.executor_server.release()
235 self.waitUntilSettled()
236 self.assertHistory([
237 dict(name='project-bitrot', result='SUCCESS',
238 ref='refs/heads/master'),
239 ], ordered=False)
240
Jan Hruban324ca5b2015-11-05 19:28:54 +0100241 @simple_layout('layouts/dequeue-github.yaml', driver='github')
242 def test_dequeue_pull_synchronized(self):
243 self.executor_server.hold_jobs_in_build = True
244
Jan Hruban37615e52015-11-19 14:30:49 +0100245 A = self.fake_github.openFakePullRequest(
246 'org/one-job-project', 'master', 'A')
247 self.fake_github.emitEvent(A.getPullRequestOpenedEvent())
Jan Hruban324ca5b2015-11-05 19:28:54 +0100248 self.waitUntilSettled()
249
250 # event update stamp has resolution one second, wait so the latter
251 # one has newer timestamp
252 time.sleep(1)
Jan Hruban37615e52015-11-19 14:30:49 +0100253 A.addCommit()
254 self.fake_github.emitEvent(A.getPullRequestSynchronizeEvent())
Jan Hruban324ca5b2015-11-05 19:28:54 +0100255 self.waitUntilSettled()
256
257 self.executor_server.hold_jobs_in_build = False
258 self.executor_server.release()
259 self.waitUntilSettled()
260
261 self.assertEqual(2, len(self.history))
262 self.assertEqual(1, self.countJobResults(self.history, 'ABORTED'))
263
264 @simple_layout('layouts/dequeue-github.yaml', driver='github')
265 def test_dequeue_pull_abandoned(self):
266 self.executor_server.hold_jobs_in_build = True
267
Jan Hruban37615e52015-11-19 14:30:49 +0100268 A = self.fake_github.openFakePullRequest(
269 'org/one-job-project', 'master', 'A')
270 self.fake_github.emitEvent(A.getPullRequestOpenedEvent())
Jan Hruban324ca5b2015-11-05 19:28:54 +0100271 self.waitUntilSettled()
Jan Hruban37615e52015-11-19 14:30:49 +0100272 self.fake_github.emitEvent(A.getPullRequestClosedEvent())
Jan Hruban324ca5b2015-11-05 19:28:54 +0100273 self.waitUntilSettled()
274
275 self.executor_server.hold_jobs_in_build = False
276 self.executor_server.release()
277 self.waitUntilSettled()
278
279 self.assertEqual(1, len(self.history))
280 self.assertEqual(1, self.countJobResults(self.history, 'ABORTED'))
281
Jan Hruban6d53c5e2015-10-24 03:03:34 +0200282 @simple_layout('layouts/basic-github.yaml', driver='github')
283 def test_git_https_url(self):
284 """Test that git_ssh option gives git url with ssh"""
Tobias Henkel42f7d6f2017-12-15 16:09:43 +0100285 tenant = self.sched.abide.tenants.get('tenant-one')
286 _, project = tenant.getProject('org/project')
287
288 url = self.fake_github.real_getGitUrl(project)
Jan Hruban6d53c5e2015-10-24 03:03:34 +0200289 self.assertEqual('https://github.com/org/project', url)
290
291 @simple_layout('layouts/basic-github.yaml', driver='github')
292 def test_git_ssh_url(self):
293 """Test that git_ssh option gives git url with ssh"""
Tobias Henkel42f7d6f2017-12-15 16:09:43 +0100294 tenant = self.sched.abide.tenants.get('tenant-one')
295 _, project = tenant.getProject('org/project')
296
297 url = self.fake_github_ssh.real_getGitUrl(project)
Jan Hruban6d53c5e2015-10-24 03:03:34 +0200298 self.assertEqual('ssh://git@github.com/org/project.git', url)
Jan Hrubane252a732017-01-03 15:03:09 +0100299
Jesse Keatingbe4ef8a2016-12-06 11:29:13 -0800300 @simple_layout('layouts/basic-github.yaml', driver='github')
301 def test_git_enterprise_url(self):
302 """Test that git_url option gives git url with proper host"""
Tobias Henkel42f7d6f2017-12-15 16:09:43 +0100303 tenant = self.sched.abide.tenants.get('tenant-one')
304 _, project = tenant.getProject('org/project')
305
306 url = self.fake_github_ent.real_getGitUrl(project)
Jesse Keatingbe4ef8a2016-12-06 11:29:13 -0800307 self.assertEqual('ssh://git@github.enterprise.io/org/project.git', url)
308
Jan Hrubane252a732017-01-03 15:03:09 +0100309 @simple_layout('layouts/reporting-github.yaml', driver='github')
310 def test_reporting(self):
Jesse Keating1f7ebe92017-06-12 17:21:00 -0700311 project = 'org/project'
Tobias Henkel3c17d5f2017-08-03 11:46:54 +0200312 github = self.fake_github.github_client
313
Jan Hrubane252a732017-01-03 15:03:09 +0100314 # pipeline reports pull status both on start and success
315 self.executor_server.hold_jobs_in_build = True
Jesse Keating1f7ebe92017-06-12 17:21:00 -0700316 A = self.fake_github.openFakePullRequest(project, 'master', 'A')
Jan Hruban37615e52015-11-19 14:30:49 +0100317 self.fake_github.emitEvent(A.getPullRequestOpenedEvent())
Jan Hrubane252a732017-01-03 15:03:09 +0100318 self.waitUntilSettled()
Tobias Henkel3c17d5f2017-08-03 11:46:54 +0200319
Jesse Keatingd96e5882017-01-19 13:55:50 -0800320 # We should have a status container for the head sha
Tobias Henkel3c17d5f2017-08-03 11:46:54 +0200321 self.assertIn(
322 A.head_sha, github.repo_from_project(project)._commits.keys())
323 statuses = self.fake_github.getCommitStatuses(project, A.head_sha)
324
Jesse Keatingd96e5882017-01-19 13:55:50 -0800325 # We should only have one status for the head sha
Jesse Keating1f7ebe92017-06-12 17:21:00 -0700326 self.assertEqual(1, len(statuses))
327 check_status = statuses[0]
Jan Hrubanddeb95a2017-01-03 15:12:41 +0100328 check_url = ('http://zuul.example.com/status/#%s,%s' %
329 (A.number, A.head_sha))
Jamie Lennox18bc7ed2017-05-10 10:37:55 +1000330 self.assertEqual('tenant-one/check', check_status['context'])
Jesse Keatingfb6cc992017-08-01 14:18:13 -0700331 self.assertEqual('check status: pending',
332 check_status['description'])
Jan Hrubane252a732017-01-03 15:03:09 +0100333 self.assertEqual('pending', check_status['state'])
Jan Hrubanddeb95a2017-01-03 15:12:41 +0100334 self.assertEqual(check_url, check_status['url'])
Jan Hruban37615e52015-11-19 14:30:49 +0100335 self.assertEqual(0, len(A.comments))
Jan Hrubane252a732017-01-03 15:03:09 +0100336
337 self.executor_server.hold_jobs_in_build = False
338 self.executor_server.release()
339 self.waitUntilSettled()
Jesse Keatingd96e5882017-01-19 13:55:50 -0800340 # We should only have two statuses for the head sha
Tobias Henkel3c17d5f2017-08-03 11:46:54 +0200341 statuses = self.fake_github.getCommitStatuses(project, A.head_sha)
Jesse Keating1f7ebe92017-06-12 17:21:00 -0700342 self.assertEqual(2, len(statuses))
343 check_status = statuses[0]
Jesse Keatingd96e5882017-01-19 13:55:50 -0800344 check_url = ('http://zuul.example.com/status/#%s,%s' %
345 (A.number, A.head_sha))
Jamie Lennox18bc7ed2017-05-10 10:37:55 +1000346 self.assertEqual('tenant-one/check', check_status['context'])
Jesse Keatingfb6cc992017-08-01 14:18:13 -0700347 self.assertEqual('check status: success',
348 check_status['description'])
Jan Hrubane252a732017-01-03 15:03:09 +0100349 self.assertEqual('success', check_status['state'])
Jan Hrubanddeb95a2017-01-03 15:12:41 +0100350 self.assertEqual(check_url, check_status['url'])
Jan Hruban37615e52015-11-19 14:30:49 +0100351 self.assertEqual(1, len(A.comments))
352 self.assertThat(A.comments[0],
Jan Hrubane252a732017-01-03 15:03:09 +0100353 MatchesRegex('.*Build succeeded.*', re.DOTALL))
354
355 # pipeline does not report any status but does comment
356 self.executor_server.hold_jobs_in_build = True
357 self.fake_github.emitEvent(
Jan Hruban37615e52015-11-19 14:30:49 +0100358 A.getCommentAddedEvent('reporting check'))
Jan Hrubane252a732017-01-03 15:03:09 +0100359 self.waitUntilSettled()
Tobias Henkel3c17d5f2017-08-03 11:46:54 +0200360 statuses = self.fake_github.getCommitStatuses(project, A.head_sha)
Jesse Keating1f7ebe92017-06-12 17:21:00 -0700361 self.assertEqual(2, len(statuses))
Jan Hrubane252a732017-01-03 15:03:09 +0100362 # comments increased by one for the start message
Jan Hruban37615e52015-11-19 14:30:49 +0100363 self.assertEqual(2, len(A.comments))
364 self.assertThat(A.comments[1],
Jan Hrubane252a732017-01-03 15:03:09 +0100365 MatchesRegex('.*Starting reporting jobs.*', re.DOTALL))
366 self.executor_server.hold_jobs_in_build = False
367 self.executor_server.release()
368 self.waitUntilSettled()
Jesse Keatingd96e5882017-01-19 13:55:50 -0800369 # pipeline reports success status
Tobias Henkel3c17d5f2017-08-03 11:46:54 +0200370 statuses = self.fake_github.getCommitStatuses(project, A.head_sha)
Jesse Keating1f7ebe92017-06-12 17:21:00 -0700371 self.assertEqual(3, len(statuses))
372 report_status = statuses[0]
Jamie Lennox18bc7ed2017-05-10 10:37:55 +1000373 self.assertEqual('tenant-one/reporting', report_status['context'])
Jesse Keatingfb6cc992017-08-01 14:18:13 -0700374 self.assertEqual('reporting status: success',
375 report_status['description'])
Jesse Keatingd96e5882017-01-19 13:55:50 -0800376 self.assertEqual('success', report_status['state'])
Jan Hruban37615e52015-11-19 14:30:49 +0100377 self.assertEqual(2, len(A.comments))
Jamie Lennox3f16de52017-05-09 14:24:11 +1000378
379 base = 'http://logs.example.com/tenant-one/reporting/%s/%s/' % (
380 A.project, A.number)
381
382 # Deconstructing the URL because we don't save the BuildSet UUID
383 # anywhere to do a direct comparison and doing regexp matches on a full
384 # URL is painful.
385
386 # The first part of the URL matches the easy base string
387 self.assertThat(report_status['url'], StartsWith(base))
388
389 # The rest of the URL is a UUID and a trailing slash.
390 self.assertThat(report_status['url'][len(base):],
391 MatchesRegex('^[a-fA-F0-9]{32}\/$'))
Jan Hruban49bff072015-11-03 11:45:46 +0100392
Jesse Keating08dab8f2017-06-21 12:59:23 +0100393 @simple_layout('layouts/reporting-github.yaml', driver='github')
Jesse Keatingfb6cc992017-08-01 14:18:13 -0700394 def test_truncated_status_description(self):
395 project = 'org/project'
396 # pipeline reports pull status both on start and success
397 self.executor_server.hold_jobs_in_build = True
398 A = self.fake_github.openFakePullRequest(project, 'master', 'A')
399 self.fake_github.emitEvent(
400 A.getCommentAddedEvent('long pipeline'))
401 self.waitUntilSettled()
Tobias Henkel3c17d5f2017-08-03 11:46:54 +0200402 statuses = self.fake_github.getCommitStatuses(project, A.head_sha)
Jesse Keatingfb6cc992017-08-01 14:18:13 -0700403 self.assertEqual(1, len(statuses))
404 check_status = statuses[0]
405 # Status is truncated due to long pipeline name
406 self.assertEqual('status: pending',
407 check_status['description'])
408
409 self.executor_server.hold_jobs_in_build = False
410 self.executor_server.release()
411 self.waitUntilSettled()
412 # We should only have two statuses for the head sha
Tobias Henkel3c17d5f2017-08-03 11:46:54 +0200413 statuses = self.fake_github.getCommitStatuses(project, A.head_sha)
Jesse Keatingfb6cc992017-08-01 14:18:13 -0700414 self.assertEqual(2, len(statuses))
415 check_status = statuses[0]
416 # Status is truncated due to long pipeline name
417 self.assertEqual('status: success',
418 check_status['description'])
419
420 @simple_layout('layouts/reporting-github.yaml', driver='github')
Jesse Keating08dab8f2017-06-21 12:59:23 +0100421 def test_push_reporting(self):
422 project = 'org/project2'
423 # pipeline reports pull status both on start and success
424 self.executor_server.hold_jobs_in_build = True
Jesse Keating08dab8f2017-06-21 12:59:23 +0100425
James E. Blair289f5932017-07-27 15:02:29 -0700426 A = self.fake_github.openFakePullRequest(project, 'master', 'A')
427 old_sha = '0' * 40
428 new_sha = A.head_sha
429 A.setMerged("merging A")
430 pevent = self.fake_github.getPushEvent(project=project,
431 ref='refs/heads/master',
432 old_rev=old_sha,
433 new_rev=new_sha)
Jesse Keating08dab8f2017-06-21 12:59:23 +0100434 self.fake_github.emitEvent(pevent)
435 self.waitUntilSettled()
436
437 # there should only be one report, a status
438 self.assertEqual(1, len(self.fake_github.reports))
439 # Verify the user/context/state of the status
440 status = ('zuul', 'tenant-one/push-reporting', 'pending')
441 self.assertEqual(status, self.fake_github.reports[0][-1])
442
443 # free the executor, allow the build to finish
444 self.executor_server.hold_jobs_in_build = False
445 self.executor_server.release()
446 self.waitUntilSettled()
447
448 # Now there should be a second report, the success of the build
449 self.assertEqual(2, len(self.fake_github.reports))
450 # Verify the user/context/state of the status
451 status = ('zuul', 'tenant-one/push-reporting', 'success')
452 self.assertEqual(status, self.fake_github.reports[-1][-1])
453
454 # now make a PR which should also comment
455 self.executor_server.hold_jobs_in_build = True
456 A = self.fake_github.openFakePullRequest(project, 'master', 'A')
457 self.fake_github.emitEvent(A.getPullRequestOpenedEvent())
458 self.waitUntilSettled()
459
460 # Now there should be a four reports, a new comment
461 # and status
462 self.assertEqual(4, len(self.fake_github.reports))
463 self.executor_server.release()
464 self.waitUntilSettled()
465
Jan Hruban49bff072015-11-03 11:45:46 +0100466 @simple_layout('layouts/merging-github.yaml', driver='github')
467 def test_report_pull_merge(self):
468 # pipeline merges the pull request on success
Jan Hruban3b415922016-02-03 13:10:22 +0100469 A = self.fake_github.openFakePullRequest('org/project', 'master',
470 'PR title')
Jan Hruban49bff072015-11-03 11:45:46 +0100471 self.fake_github.emitEvent(A.getCommentAddedEvent('merge me'))
472 self.waitUntilSettled()
473 self.assertTrue(A.is_merged)
Jan Hruban3b415922016-02-03 13:10:22 +0100474 self.assertThat(A.merge_message,
475 MatchesRegex('.*PR title.*Reviewed-by.*', re.DOTALL))
Jan Hruban49bff072015-11-03 11:45:46 +0100476
477 # pipeline merges the pull request on success after failure
478 self.fake_github.merge_failure = True
Jan Hruban37615e52015-11-19 14:30:49 +0100479 B = self.fake_github.openFakePullRequest('org/project', 'master', 'B')
Jan Hruban49bff072015-11-03 11:45:46 +0100480 self.fake_github.emitEvent(B.getCommentAddedEvent('merge me'))
481 self.waitUntilSettled()
482 self.assertFalse(B.is_merged)
483 self.fake_github.merge_failure = False
484
485 # pipeline merges the pull request on second run of merge
486 # first merge failed on 405 Method Not Allowed error
487 self.fake_github.merge_not_allowed_count = 1
Jan Hruban37615e52015-11-19 14:30:49 +0100488 C = self.fake_github.openFakePullRequest('org/project', 'master', 'C')
Jan Hruban49bff072015-11-03 11:45:46 +0100489 self.fake_github.emitEvent(C.getCommentAddedEvent('merge me'))
490 self.waitUntilSettled()
491 self.assertTrue(C.is_merged)
492
493 # pipeline does not merge the pull request
494 # merge failed on 405 Method Not Allowed error - twice
495 self.fake_github.merge_not_allowed_count = 2
Jan Hruban37615e52015-11-19 14:30:49 +0100496 D = self.fake_github.openFakePullRequest('org/project', 'master', 'D')
Jan Hruban49bff072015-11-03 11:45:46 +0100497 self.fake_github.emitEvent(D.getCommentAddedEvent('merge me'))
498 self.waitUntilSettled()
499 self.assertFalse(D.is_merged)
Adam Gandelman62198cb2017-02-14 16:11:02 -0800500 self.assertEqual(len(D.comments), 1)
501 self.assertEqual(D.comments[0], 'Merge failed')
Jan Hruban37615e52015-11-19 14:30:49 +0100502
Jesse Keating28434312017-07-31 11:32:48 -0700503 @simple_layout('layouts/reporting-multiple-github.yaml', driver='github')
504 def test_reporting_multiple_github(self):
505 project = 'org/project1'
Tobias Henkel3c17d5f2017-08-03 11:46:54 +0200506 github = self.fake_github.github_client
507
Jesse Keating28434312017-07-31 11:32:48 -0700508 # pipeline reports pull status both on start and success
509 self.executor_server.hold_jobs_in_build = True
510 A = self.fake_github.openFakePullRequest(project, 'master', 'A')
511 self.fake_github.emitEvent(A.getPullRequestOpenedEvent())
512 # open one on B as well, which should not effect A reporting
513 B = self.fake_github.openFakePullRequest('org/project2', 'master',
514 'B')
515 self.fake_github.emitEvent(B.getPullRequestOpenedEvent())
516 self.waitUntilSettled()
517 # We should have a status container for the head sha
Tobias Henkel3c17d5f2017-08-03 11:46:54 +0200518 statuses = self.fake_github.getCommitStatuses(project, A.head_sha)
519 self.assertIn(
520 A.head_sha, github.repo_from_project(project)._commits.keys())
Jesse Keating28434312017-07-31 11:32:48 -0700521 # We should only have one status for the head sha
522 self.assertEqual(1, len(statuses))
523 check_status = statuses[0]
524 check_url = ('http://zuul.example.com/status/#%s,%s' %
525 (A.number, A.head_sha))
526 self.assertEqual('tenant-one/check', check_status['context'])
Jesse Keatingfb6cc992017-08-01 14:18:13 -0700527 self.assertEqual('check status: pending', check_status['description'])
Jesse Keating28434312017-07-31 11:32:48 -0700528 self.assertEqual('pending', check_status['state'])
529 self.assertEqual(check_url, check_status['url'])
530 self.assertEqual(0, len(A.comments))
531
532 self.executor_server.hold_jobs_in_build = False
533 self.executor_server.release()
534 self.waitUntilSettled()
535 # We should only have two statuses for the head sha
Tobias Henkel3c17d5f2017-08-03 11:46:54 +0200536 statuses = self.fake_github.getCommitStatuses(project, A.head_sha)
Jesse Keating28434312017-07-31 11:32:48 -0700537 self.assertEqual(2, len(statuses))
538 check_status = statuses[0]
539 check_url = ('http://zuul.example.com/status/#%s,%s' %
540 (A.number, A.head_sha))
541 self.assertEqual('tenant-one/check', check_status['context'])
542 self.assertEqual('success', check_status['state'])
Jesse Keatingfb6cc992017-08-01 14:18:13 -0700543 self.assertEqual('check status: success', check_status['description'])
Jesse Keating28434312017-07-31 11:32:48 -0700544 self.assertEqual(check_url, check_status['url'])
545 self.assertEqual(1, len(A.comments))
546 self.assertThat(A.comments[0],
547 MatchesRegex('.*Build succeeded.*', re.DOTALL))
548
Jan Hruban37615e52015-11-19 14:30:49 +0100549 @simple_layout('layouts/dependent-github.yaml', driver='github')
550 def test_parallel_changes(self):
551 "Test that changes are tested in parallel and merged in series"
552
553 self.executor_server.hold_jobs_in_build = True
554 A = self.fake_github.openFakePullRequest('org/project', 'master', 'A')
555 B = self.fake_github.openFakePullRequest('org/project', 'master', 'B')
556 C = self.fake_github.openFakePullRequest('org/project', 'master', 'C')
557
558 self.fake_github.emitEvent(A.addLabel('merge'))
559 self.fake_github.emitEvent(B.addLabel('merge'))
560 self.fake_github.emitEvent(C.addLabel('merge'))
561
562 self.waitUntilSettled()
563 self.assertEqual(len(self.builds), 1)
564 self.assertEqual(self.builds[0].name, 'project-merge')
565 self.assertTrue(self.builds[0].hasChanges(A))
566
567 self.executor_server.release('.*-merge')
568 self.waitUntilSettled()
569 self.assertEqual(len(self.builds), 3)
570 self.assertEqual(self.builds[0].name, 'project-test1')
571 self.assertTrue(self.builds[0].hasChanges(A))
572 self.assertEqual(self.builds[1].name, 'project-test2')
573 self.assertTrue(self.builds[1].hasChanges(A))
574 self.assertEqual(self.builds[2].name, 'project-merge')
575 self.assertTrue(self.builds[2].hasChanges(A, B))
576
577 self.executor_server.release('.*-merge')
578 self.waitUntilSettled()
579 self.assertEqual(len(self.builds), 5)
580 self.assertEqual(self.builds[0].name, 'project-test1')
581 self.assertTrue(self.builds[0].hasChanges(A))
582 self.assertEqual(self.builds[1].name, 'project-test2')
583 self.assertTrue(self.builds[1].hasChanges(A))
584
585 self.assertEqual(self.builds[2].name, 'project-test1')
586 self.assertTrue(self.builds[2].hasChanges(A))
587 self.assertEqual(self.builds[3].name, 'project-test2')
588 self.assertTrue(self.builds[3].hasChanges(A, B))
589
590 self.assertEqual(self.builds[4].name, 'project-merge')
591 self.assertTrue(self.builds[4].hasChanges(A, B, C))
592
593 self.executor_server.release('.*-merge')
594 self.waitUntilSettled()
595 self.assertEqual(len(self.builds), 6)
596 self.assertEqual(self.builds[0].name, 'project-test1')
597 self.assertTrue(self.builds[0].hasChanges(A))
598 self.assertEqual(self.builds[1].name, 'project-test2')
599 self.assertTrue(self.builds[1].hasChanges(A))
600
601 self.assertEqual(self.builds[2].name, 'project-test1')
602 self.assertTrue(self.builds[2].hasChanges(A, B))
603 self.assertEqual(self.builds[3].name, 'project-test2')
604 self.assertTrue(self.builds[3].hasChanges(A, B))
605
606 self.assertEqual(self.builds[4].name, 'project-test1')
607 self.assertTrue(self.builds[4].hasChanges(A, B, C))
608 self.assertEqual(self.builds[5].name, 'project-test2')
609 self.assertTrue(self.builds[5].hasChanges(A, B, C))
610
611 all_builds = self.builds[:]
612 self.release(all_builds[2])
613 self.release(all_builds[3])
614 self.waitUntilSettled()
615 self.assertFalse(A.is_merged)
616 self.assertFalse(B.is_merged)
617 self.assertFalse(C.is_merged)
618
619 self.release(all_builds[0])
620 self.release(all_builds[1])
621 self.waitUntilSettled()
622 self.assertTrue(A.is_merged)
623 self.assertTrue(B.is_merged)
624 self.assertFalse(C.is_merged)
625
626 self.executor_server.hold_jobs_in_build = False
627 self.executor_server.release()
628 self.waitUntilSettled()
629 self.assertEqual(len(self.builds), 0)
630 self.assertEqual(len(self.history), 9)
631 self.assertTrue(C.is_merged)
632
633 self.assertNotIn('merge', A.labels)
634 self.assertNotIn('merge', B.labels)
635 self.assertNotIn('merge', C.labels)
636
637 @simple_layout('layouts/dependent-github.yaml', driver='github')
638 def test_failed_changes(self):
639 "Test that a change behind a failed change is retested"
640 self.executor_server.hold_jobs_in_build = True
641
642 A = self.fake_github.openFakePullRequest('org/project', 'master', 'A')
643 B = self.fake_github.openFakePullRequest('org/project', 'master', 'B')
644
645 self.executor_server.failJob('project-test1', A)
646
647 self.fake_github.emitEvent(A.addLabel('merge'))
648 self.fake_github.emitEvent(B.addLabel('merge'))
649 self.waitUntilSettled()
650
651 self.executor_server.release('.*-merge')
652 self.waitUntilSettled()
653
654 self.executor_server.hold_jobs_in_build = False
655 self.executor_server.release()
656
657 self.waitUntilSettled()
658 # It's certain that the merge job for change 2 will run, but
659 # the test1 and test2 jobs may or may not run.
660 self.assertTrue(len(self.history) > 6)
661 self.assertFalse(A.is_merged)
662 self.assertTrue(B.is_merged)
663 self.assertNotIn('merge', A.labels)
664 self.assertNotIn('merge', B.labels)
665
666 @simple_layout('layouts/dependent-github.yaml', driver='github')
667 def test_failed_change_at_head(self):
668 "Test that if a change at the head fails, jobs behind it are canceled"
669
670 self.executor_server.hold_jobs_in_build = True
671 A = self.fake_github.openFakePullRequest('org/project', 'master', 'A')
672 B = self.fake_github.openFakePullRequest('org/project', 'master', 'B')
673 C = self.fake_github.openFakePullRequest('org/project', 'master', 'C')
674
675 self.executor_server.failJob('project-test1', A)
676
677 self.fake_github.emitEvent(A.addLabel('merge'))
678 self.fake_github.emitEvent(B.addLabel('merge'))
679 self.fake_github.emitEvent(C.addLabel('merge'))
680
681 self.waitUntilSettled()
682
683 self.assertEqual(len(self.builds), 1)
684 self.assertEqual(self.builds[0].name, 'project-merge')
685 self.assertTrue(self.builds[0].hasChanges(A))
686
687 self.executor_server.release('.*-merge')
688 self.waitUntilSettled()
689 self.executor_server.release('.*-merge')
690 self.waitUntilSettled()
691 self.executor_server.release('.*-merge')
692 self.waitUntilSettled()
693
694 self.assertEqual(len(self.builds), 6)
695 self.assertEqual(self.builds[0].name, 'project-test1')
696 self.assertEqual(self.builds[1].name, 'project-test2')
697 self.assertEqual(self.builds[2].name, 'project-test1')
698 self.assertEqual(self.builds[3].name, 'project-test2')
699 self.assertEqual(self.builds[4].name, 'project-test1')
700 self.assertEqual(self.builds[5].name, 'project-test2')
701
702 self.release(self.builds[0])
703 self.waitUntilSettled()
704
705 # project-test2, project-merge for B
706 self.assertEqual(len(self.builds), 2)
707 self.assertEqual(self.countJobResults(self.history, 'ABORTED'), 4)
708
709 self.executor_server.hold_jobs_in_build = False
710 self.executor_server.release()
711 self.waitUntilSettled()
712
713 self.assertEqual(len(self.builds), 0)
714 self.assertEqual(len(self.history), 15)
715 self.assertFalse(A.is_merged)
716 self.assertTrue(B.is_merged)
717 self.assertTrue(C.is_merged)
718 self.assertNotIn('merge', A.labels)
719 self.assertNotIn('merge', B.labels)
720 self.assertNotIn('merge', C.labels)
Jesse Keating71a47ff2017-06-06 11:36:43 -0700721
722 @simple_layout('layouts/basic-github.yaml', driver='github')
723 def test_push_event_reconfigure(self):
Tobias Henkel3c17d5f2017-08-03 11:46:54 +0200724 pevent = self.fake_github.getPushEvent(project='org/common-config',
Jesse Keating71a47ff2017-06-06 11:36:43 -0700725 ref='refs/heads/master',
726 modified_files=['zuul.yaml'])
727
728 # record previous tenant reconfiguration time, which may not be set
729 old = self.sched.tenant_last_reconfigured.get('tenant-one', 0)
730 time.sleep(1)
731 self.fake_github.emitEvent(pevent)
732 self.waitUntilSettled()
733 new = self.sched.tenant_last_reconfigured.get('tenant-one', 0)
734 # New timestamp should be greater than the old timestamp
735 self.assertLess(old, new)
Tristan Cacqueray2bafb1f2017-06-12 07:10:26 +0000736
Jesse Keating64d29012017-09-06 12:27:49 -0700737 # TODO(jlk): Make this a more generic test for unknown project
738 @skip("Skipped for rewrite of webhook handler")
Tristan Cacqueray2bafb1f2017-06-12 07:10:26 +0000739 @simple_layout('layouts/basic-github.yaml', driver='github')
740 def test_ping_event(self):
741 # Test valid ping
742 pevent = {'repository': {'full_name': 'org/project'}}
743 req = self.fake_github.emitEvent(('ping', pevent))
744 self.assertEqual(req.status, 200, "Ping event didn't succeed")
745
746 # Test invalid ping
747 pevent = {'repository': {'full_name': 'unknown-project'}}
748 self.assertRaises(
749 urllib.error.HTTPError,
750 self.fake_github.emitEvent,
751 ('ping', pevent),
752 )
Tobias Henkeleca46202017-08-02 20:27:10 +0200753
754
755class TestGithubUnprotectedBranches(ZuulTestCase):
756 config_file = 'zuul-github-driver.conf'
757 tenant_config_file = 'config/unprotected-branches/main.yaml'
758
759 def test_unprotected_branches(self):
760 tenant = self.sched.abide.tenants.get('tenant-one')
761
762 project1 = tenant.untrusted_projects[0]
763 project2 = tenant.untrusted_projects[1]
764
765 # project1 should have parsed master
766 self.assertIn('master', project1.unparsed_branch_config.keys())
767
768 # project2 should have no parsed branch
769 self.assertEqual(0, len(project2.unparsed_branch_config.keys()))
Jesse Keating80730e62017-09-14 15:35:11 -0600770
771
772class TestGithubWebhook(ZuulTestCase):
773 config_file = 'zuul-github-driver.conf'
774
775 def setUp(self):
776 super(TestGithubWebhook, self).setUp()
777
778 # Start the web server
779 self.web = zuul.web.ZuulWeb(
780 listen_address='127.0.0.1', listen_port=0,
781 gear_server='127.0.0.1', gear_port=self.gearman_server.port,
Monty Taylor64bf8e02018-01-23 16:39:30 -0600782 connections=[self.fake_github])
Jesse Keating80730e62017-09-14 15:35:11 -0600783 loop = asyncio.new_event_loop()
784 loop.set_debug(True)
785 ws_thread = threading.Thread(target=self.web.run, args=(loop,))
786 ws_thread.start()
787 self.addCleanup(loop.close)
788 self.addCleanup(ws_thread.join)
789 self.addCleanup(self.web.stop)
790
791 host = '127.0.0.1'
792 # Wait until web server is started
793 while True:
794 time.sleep(0.1)
795 if self.web.server is None:
796 continue
797 port = self.web.server.sockets[0].getsockname()[1]
798 try:
799 with socket.create_connection((host, port)):
800 break
801 except ConnectionRefusedError:
802 pass
803
804 self.fake_github.setZuulWebPort(port)
805
806 def tearDown(self):
807 super(TestGithubWebhook, self).tearDown()
808
809 @simple_layout('layouts/basic-github.yaml', driver='github')
810 def test_webhook(self):
811 """Test that we can get github events via zuul-web."""
812
813 self.executor_server.hold_jobs_in_build = True
814
815 A = self.fake_github.openFakePullRequest('org/project', 'master', 'A')
816 self.fake_github.emitEvent(A.getPullRequestOpenedEvent(),
817 use_zuulweb=True)
818 self.waitUntilSettled()
819
820 self.executor_server.hold_jobs_in_build = False
821 self.executor_server.release()
822 self.waitUntilSettled()
823
824 self.assertEqual('SUCCESS',
825 self.getJobFromHistory('project-test1').result)
826 self.assertEqual('SUCCESS',
827 self.getJobFromHistory('project-test2').result)
828
829 job = self.getJobFromHistory('project-test2')
830 zuulvars = job.parameters['zuul']
831 self.assertEqual(str(A.number), zuulvars['change'])
832 self.assertEqual(str(A.head_sha), zuulvars['patchset'])
833 self.assertEqual('master', zuulvars['branch'])
834 self.assertEqual(1, len(A.comments))
835 self.assertThat(
836 A.comments[0],
837 MatchesRegex('.*\[project-test1 \]\(.*\).*', re.DOTALL))
838 self.assertThat(
839 A.comments[0],
840 MatchesRegex('.*\[project-test2 \]\(.*\).*', re.DOTALL))
841 self.assertEqual(2, len(self.history))
842
843 # test_pull_unmatched_branch_event(self):
844 self.create_branch('org/project', 'unmatched_branch')
845 B = self.fake_github.openFakePullRequest(
846 'org/project', 'unmatched_branch', 'B')
847 self.fake_github.emitEvent(B.getPullRequestOpenedEvent(),
848 use_zuulweb=True)
849 self.waitUntilSettled()
850
851 self.assertEqual(2, len(self.history))