blob: ddec57f399277e0b3f26d2c34bff8a39ef34cd1b [file] [log] [blame]
James E. Blairb0fcae42012-07-17 11:12:10 -07001#!/usr/bin/env python
2
3# Copyright 2012 Hewlett-Packard Development Company, L.P.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
17import unittest
18import ConfigParser
19import os
20import Queue
James E. Blair8cc15a82012-08-01 11:17:57 -070021import hashlib
James E. Blairb0fcae42012-07-17 11:12:10 -070022import logging
James E. Blair8cc15a82012-08-01 11:17:57 -070023import random
James E. Blairb0fcae42012-07-17 11:12:10 -070024import json
25import threading
26import time
27import pprint
28import re
James E. Blair8cc15a82012-08-01 11:17:57 -070029import urllib2
30import urlparse
James E. Blair4886cc12012-07-18 15:39:41 -070031import shutil
32import git
James E. Blairb0fcae42012-07-17 11:12:10 -070033
34import zuul
35import zuul.scheduler
36import zuul.launcher.jenkins
37import zuul.trigger.gerrit
38
39FIXTURE_DIR = os.path.join(os.path.dirname(__file__),
40 'fixtures')
41CONFIG = ConfigParser.ConfigParser()
42CONFIG.read(os.path.join(FIXTURE_DIR, "zuul.conf"))
43
44CONFIG.set('zuul', 'layout_config',
45 os.path.join(FIXTURE_DIR, "layout.yaml"))
46
James E. Blair1dbd5082012-08-23 15:12:15 -070047TMP_ROOT = os.environ.get("ZUUL_TEST_ROOT", "/tmp")
48TEST_ROOT = os.path.join(TMP_ROOT, "zuul-test")
49UPSTREAM_ROOT = os.path.join(TEST_ROOT, "upstream")
50GIT_ROOT = os.path.join(TEST_ROOT, "git")
51
52CONFIG.set('zuul', 'git_dir', GIT_ROOT)
53
James E. Blairb0fcae42012-07-17 11:12:10 -070054logging.basicConfig(level=logging.DEBUG)
55
56
James E. Blair8cc15a82012-08-01 11:17:57 -070057def random_sha1():
58 return hashlib.sha1(str(random.random())).hexdigest()
59
60
James E. Blair4886cc12012-07-18 15:39:41 -070061class ChangeReference(git.Reference):
62 _common_path_default = "refs/changes"
63 _points_to_commits_only = True
64
65
66def init_repo(project):
67 parts = project.split('/')
James E. Blair1dbd5082012-08-23 15:12:15 -070068 path = os.path.join(UPSTREAM_ROOT, *parts[:-1])
James E. Blair4886cc12012-07-18 15:39:41 -070069 if not os.path.exists(path):
70 os.makedirs(path)
James E. Blair1dbd5082012-08-23 15:12:15 -070071 path = os.path.join(UPSTREAM_ROOT, project)
James E. Blair4886cc12012-07-18 15:39:41 -070072 repo = git.Repo.init(path)
73
74 fn = os.path.join(path, 'README')
75 f = open(fn, 'w')
76 f.write("test\n")
77 f.close()
78 repo.index.add([fn])
79 repo.index.commit('initial commit')
James E. Blairc6294a52012-08-17 10:19:48 -070080 master = repo.create_head('master')
James E. Blair4886cc12012-07-18 15:39:41 -070081 repo.create_tag('init')
82
James E. Blairc6294a52012-08-17 10:19:48 -070083 mp = repo.create_head('mp')
84 repo.head.reference = mp
85 f = open(fn, 'a')
86 f.write("test mp\n")
87 f.close()
88 repo.index.add([fn])
89 repo.index.commit('mp commit')
90
91 repo.head.reference = master
92 repo.head.reset(index=True, working_tree=True)
93 repo.git.clean('-x', '-f', '-d')
94
James E. Blair4886cc12012-07-18 15:39:41 -070095
James E. Blair973721f2012-08-15 10:19:43 -070096def add_fake_change_to_repo(project, branch, change_num, patchset, msg, fn):
James E. Blair1dbd5082012-08-23 15:12:15 -070097 path = os.path.join(UPSTREAM_ROOT, project)
James E. Blair4886cc12012-07-18 15:39:41 -070098 repo = git.Repo(path)
99 ref = ChangeReference.create(repo, '1/%s/%s' % (change_num,
100 patchset),
101 'refs/tags/init')
102 repo.head.reference = ref
103 repo.head.reset(index=True, working_tree=True)
104 repo.git.clean('-x', '-f', '-d')
105
James E. Blair1dbd5082012-08-23 15:12:15 -0700106 path = os.path.join(UPSTREAM_ROOT, project)
James E. Blair973721f2012-08-15 10:19:43 -0700107 fn = os.path.join(path, fn)
James E. Blair4886cc12012-07-18 15:39:41 -0700108 f = open(fn, 'w')
James E. Blair973721f2012-08-15 10:19:43 -0700109 f.write("test %s %s %s\n" % (branch, change_num, patchset))
James E. Blair4886cc12012-07-18 15:39:41 -0700110 f.close()
111 repo.index.add([fn])
James E. Blairdaabed22012-08-15 15:38:57 -0700112 return repo.index.commit(msg)
James E. Blair4886cc12012-07-18 15:39:41 -0700113
114
115def ref_has_change(ref, change):
James E. Blair1dbd5082012-08-23 15:12:15 -0700116 path = os.path.join(GIT_ROOT, change.project)
James E. Blair4886cc12012-07-18 15:39:41 -0700117 repo = git.Repo(path)
118 for commit in repo.iter_commits(ref):
119 if commit.message.strip() == ('%s-1' % change.subject):
120 return True
121 return False
122
123
124def job_has_changes(*args):
125 job = args[0]
126 commits = args[1:]
127 project = job.parameters['ZUUL_PROJECT']
James E. Blair1dbd5082012-08-23 15:12:15 -0700128 path = os.path.join(GIT_ROOT, project)
James E. Blair4886cc12012-07-18 15:39:41 -0700129 repo = git.Repo(path)
130 ref = job.parameters['ZUUL_REF']
131 repo_messages = [c.message.strip() for c in repo.iter_commits(ref)]
132 commit_messages = ['%s-1' % commit.subject for commit in commits]
James E. Blair4886cc12012-07-18 15:39:41 -0700133 for msg in commit_messages:
134 if msg not in repo_messages:
135 return False
136 return True
137
138
James E. Blairb0fcae42012-07-17 11:12:10 -0700139class FakeChange(object):
James E. Blair8c803f82012-07-31 16:25:42 -0700140 categories = {'APRV': ('Approved', -1, 1),
141 'CRVW': ('Code-Review', -2, 2),
142 'VRFY': ('Verified', -2, 2)}
James E. Blairb0fcae42012-07-17 11:12:10 -0700143
James E. Blair8cc15a82012-08-01 11:17:57 -0700144 def __init__(self, gerrit, number, project, branch, subject, status='NEW'):
145 self.gerrit = gerrit
James E. Blaird466dc42012-07-31 10:42:56 -0700146 self.reported = 0
James E. Blair8c803f82012-07-31 16:25:42 -0700147 self.queried = 0
James E. Blairb0fcae42012-07-17 11:12:10 -0700148 self.patchsets = []
James E. Blairb0fcae42012-07-17 11:12:10 -0700149 self.number = number
150 self.project = project
151 self.branch = branch
152 self.subject = subject
153 self.latest_patchset = 0
James E. Blair8c803f82012-07-31 16:25:42 -0700154 self.depends_on_change = None
155 self.needed_by_changes = []
James E. Blairb0fcae42012-07-17 11:12:10 -0700156 self.data = {
157 'branch': branch,
158 'comments': [],
159 'commitMessage': subject,
160 'createdOn': time.time(),
James E. Blair8cc15a82012-08-01 11:17:57 -0700161 'id': 'I' + random_sha1(),
James E. Blairb0fcae42012-07-17 11:12:10 -0700162 'lastUpdated': time.time(),
163 'number': str(number),
164 'open': True,
165 'owner': {'email': 'user@example.com',
166 'name': 'User Name',
167 'username': 'username'},
168 'patchSets': self.patchsets,
169 'project': project,
170 'status': status,
171 'subject': subject,
James E. Blair8c803f82012-07-31 16:25:42 -0700172 'submitRecords': [],
James E. Blairb0fcae42012-07-17 11:12:10 -0700173 'url': 'https://hostname/%s' % number}
174
175 self.addPatchset()
James E. Blair8c803f82012-07-31 16:25:42 -0700176 self.data['submitRecords'] = self.getSubmitRecords()
James E. Blairb0fcae42012-07-17 11:12:10 -0700177
178 def addPatchset(self, files=None):
179 self.latest_patchset += 1
James E. Blairdaabed22012-08-15 15:38:57 -0700180 if files:
181 fn = files[0]
182 else:
183 fn = '%s-%s' % (self.branch, self.number)
184 msg = self.subject + '-' + str(self.latest_patchset)
185 c = add_fake_change_to_repo(self.project, self.branch,
186 self.number, self.latest_patchset,
187 msg, fn)
James E. Blairb0fcae42012-07-17 11:12:10 -0700188 d = {'approvals': [],
189 'createdOn': time.time(),
190 'files': [{'file': '/COMMIT_MSG',
191 'type': 'ADDED'},
192 {'file': 'README',
193 'type': 'MODIFIED'}],
James E. Blair8c803f82012-07-31 16:25:42 -0700194 'number': str(self.latest_patchset),
James E. Blairb0fcae42012-07-17 11:12:10 -0700195 'ref': 'refs/changes/1/%s/%s' % (self.number,
196 self.latest_patchset),
James E. Blairdaabed22012-08-15 15:38:57 -0700197 'revision': c.hexsha,
James E. Blairb0fcae42012-07-17 11:12:10 -0700198 'uploader': {'email': 'user@example.com',
199 'name': 'User name',
200 'username': 'user'}}
201 self.data['currentPatchSet'] = d
202 self.patchsets.append(d)
James E. Blair8c803f82012-07-31 16:25:42 -0700203 self.data['submitRecords'] = self.getSubmitRecords()
James E. Blairb0fcae42012-07-17 11:12:10 -0700204
205 def addApproval(self, category, value):
James E. Blair8c803f82012-07-31 16:25:42 -0700206 approval = {'description': self.categories[category][0],
207 'type': category,
208 'value': str(value)}
209 self.patchsets[-1]['approvals'].append(approval)
210 event = {'approvals': [approval],
James E. Blairb0fcae42012-07-17 11:12:10 -0700211 'author': {'email': 'user@example.com',
212 'name': 'User Name',
213 'username': 'username'},
214 'change': {'branch': self.branch,
215 'id': 'Iaa69c46accf97d0598111724a38250ae76a22c87',
216 'number': str(self.number),
217 'owner': {'email': 'user@example.com',
218 'name': 'User Name',
219 'username': 'username'},
220 'project': self.project,
221 'subject': self.subject,
222 'topic': 'master',
223 'url': 'https://hostname/459'},
224 'comment': '',
225 'patchSet': self.patchsets[-1],
226 'type': 'comment-added'}
James E. Blair8c803f82012-07-31 16:25:42 -0700227 self.data['submitRecords'] = self.getSubmitRecords()
James E. Blairb0fcae42012-07-17 11:12:10 -0700228 return json.loads(json.dumps(event))
229
James E. Blair8c803f82012-07-31 16:25:42 -0700230 def getSubmitRecords(self):
231 status = {}
232 for cat in self.categories.keys():
233 status[cat] = 0
234
235 for a in self.patchsets[-1]['approvals']:
236 cur = status[a['type']]
237 cat_min, cat_max = self.categories[a['type']][1:]
238 new = int(a['value'])
239 if new == cat_min:
240 cur = new
241 elif abs(new) > abs(cur):
242 cur = new
243 status[a['type']] = cur
244
245 labels = []
246 ok = True
247 for typ, cat in self.categories.items():
248 cur = status[typ]
249 cat_min, cat_max = cat[1:]
250 if cur == cat_min:
251 value = 'REJECT'
252 ok = False
253 elif cur == cat_max:
254 value = 'OK'
255 else:
256 value = 'NEED'
257 ok = False
258 labels.append({'label': cat[0], 'status': value})
259 if ok:
260 return [{'status': 'OK'}]
261 return [{'status': 'NOT_READY',
262 'labels': labels}]
263
264 def setDependsOn(self, other, patchset):
265 self.depends_on_change = other
266 d = {'id': other.data['id'],
267 'number': other.data['number'],
268 'ref': other.patchsets[patchset - 1]['ref']
269 }
270 self.data['dependsOn'] = [d]
271
272 other.needed_by_changes.append(self)
273 needed = other.data.get('neededBy', [])
274 d = {'id': self.data['id'],
275 'number': self.data['number'],
276 'ref': self.patchsets[patchset - 1]['ref'],
277 'revision': self.patchsets[patchset - 1]['revision']
278 }
279 needed.append(d)
280 other.data['neededBy'] = needed
281
James E. Blairb0fcae42012-07-17 11:12:10 -0700282 def query(self):
James E. Blair8c803f82012-07-31 16:25:42 -0700283 self.queried += 1
284 d = self.data.get('dependsOn')
285 if d:
286 d = d[0]
287 if (self.depends_on_change.patchsets[-1]['ref'] == d['ref']):
288 d['isCurrentPatchSet'] = True
289 else:
290 d['isCurrentPatchSet'] = False
James E. Blairb0fcae42012-07-17 11:12:10 -0700291 return json.loads(json.dumps(self.data))
292
293 def setMerged(self):
James E. Blaircaec0c52012-08-22 14:52:22 -0700294 if (self.depends_on_change
295 and self.depends_on_change.data['status'] != 'MERGED'):
296 return
James E. Blairb0fcae42012-07-17 11:12:10 -0700297 self.data['status'] = 'MERGED'
298 self.open = False
James E. Blairdaabed22012-08-15 15:38:57 -0700299
James E. Blair1dbd5082012-08-23 15:12:15 -0700300 path = os.path.join(UPSTREAM_ROOT, self.project)
James E. Blairdaabed22012-08-15 15:38:57 -0700301 repo = git.Repo(path)
302 repo.heads[self.branch].commit = \
303 repo.commit(self.patchsets[-1]['revision'])
James E. Blairb0fcae42012-07-17 11:12:10 -0700304
James E. Blaird466dc42012-07-31 10:42:56 -0700305 def setReported(self):
306 self.reported += 1
307
James E. Blairb0fcae42012-07-17 11:12:10 -0700308
309class FakeGerrit(object):
310 def __init__(self, *args, **kw):
311 self.event_queue = Queue.Queue()
312 self.fixture_dir = os.path.join(FIXTURE_DIR, 'gerrit')
313 self.change_number = 0
314 self.changes = {}
315
316 def addFakeChange(self, project, branch, subject):
317 self.change_number += 1
James E. Blair8cc15a82012-08-01 11:17:57 -0700318 c = FakeChange(self, self.change_number, project, branch, subject)
James E. Blairb0fcae42012-07-17 11:12:10 -0700319 self.changes[self.change_number] = c
320 return c
321
322 def addEvent(self, data):
323 return self.event_queue.put(data)
324
325 def getEvent(self):
326 return self.event_queue.get()
327
328 def eventDone(self):
329 self.event_queue.task_done()
330
331 def review(self, project, changeid, message, action):
James E. Blaird466dc42012-07-31 10:42:56 -0700332 number, ps = changeid.split(',')
333 change = self.changes[int(number)]
James E. Blairb0fcae42012-07-17 11:12:10 -0700334 if 'submit' in action:
James E. Blairb0fcae42012-07-17 11:12:10 -0700335 change.setMerged()
James E. Blaird466dc42012-07-31 10:42:56 -0700336 if message:
337 change.setReported()
James E. Blairb0fcae42012-07-17 11:12:10 -0700338
339 def query(self, number):
340 change = self.changes[int(number)]
341 return change.query()
342
343 def startWatching(self, *args, **kw):
344 pass
345
346
347class FakeJenkinsEvent(object):
348 def __init__(self, name, number, parameters, phase, status=None):
349 data = {'build':
350 {'full_url': 'https://server/job/%s/%s/' % (name, number),
351 'number': number,
352 'parameters': parameters,
353 'phase': phase,
354 'url': 'job/%s/%s/' % (name, number)},
355 'name': name,
356 'url': 'job/%s/' % name}
357 if status:
358 data['build']['status'] = status
359 self.body = json.dumps(data)
360
361
362class FakeJenkinsJob(threading.Thread):
363 log = logging.getLogger("zuul.test")
364
365 def __init__(self, jenkins, callback, name, number, parameters):
366 threading.Thread.__init__(self)
367 self.jenkins = jenkins
368 self.callback = callback
369 self.name = name
370 self.number = number
371 self.parameters = parameters
372 self.wait_condition = threading.Condition()
373 self.waiting = False
James E. Blaird466dc42012-07-31 10:42:56 -0700374 self.aborted = False
375 self.canceled = False
376 self.created = time.time()
James E. Blairb0fcae42012-07-17 11:12:10 -0700377
378 def release(self):
379 self.wait_condition.acquire()
380 self.wait_condition.notify()
381 self.waiting = False
382 self.log.debug("Job %s released" % (self.parameters['UUID']))
383 self.wait_condition.release()
384
385 def isWaiting(self):
386 self.wait_condition.acquire()
387 if self.waiting:
388 ret = True
389 else:
390 ret = False
391 self.wait_condition.release()
392 return ret
393
394 def _wait(self):
395 self.wait_condition.acquire()
396 self.waiting = True
397 self.log.debug("Job %s waiting" % (self.parameters['UUID']))
398 self.wait_condition.wait()
399 self.wait_condition.release()
400
401 def run(self):
402 self.jenkins.fakeEnqueue(self)
403 if self.jenkins.hold_jobs_in_queue:
404 self._wait()
405 self.jenkins.fakeDequeue(self)
James E. Blaird466dc42012-07-31 10:42:56 -0700406 if self.canceled:
407 self.jenkins.all_jobs.remove(self)
408 return
James E. Blairb0fcae42012-07-17 11:12:10 -0700409 self.callback.jenkins_endpoint(FakeJenkinsEvent(
410 self.name, self.number, self.parameters,
411 'STARTED'))
412 if self.jenkins.hold_jobs_in_build:
413 self._wait()
414 self.log.debug("Job %s continuing" % (self.parameters['UUID']))
James E. Blairb02a3bb2012-07-30 17:49:55 -0700415
416 result = 'SUCCESS'
James E. Blairdaabed22012-08-15 15:38:57 -0700417 if ('ZUUL_REF' in self.parameters) and self.jenkins.fakeShouldFailTest(
James E. Blairb02a3bb2012-07-30 17:49:55 -0700418 self.name,
James E. Blair4886cc12012-07-18 15:39:41 -0700419 self.parameters['ZUUL_REF']):
James E. Blairb02a3bb2012-07-30 17:49:55 -0700420 result = 'FAILURE'
James E. Blaird466dc42012-07-31 10:42:56 -0700421 if self.aborted:
422 result = 'ABORTED'
James E. Blairb02a3bb2012-07-30 17:49:55 -0700423
James E. Blairb0fcae42012-07-17 11:12:10 -0700424 self.jenkins.fakeAddHistory(name=self.name, number=self.number,
James E. Blairb02a3bb2012-07-30 17:49:55 -0700425 result=result)
James E. Blairb0fcae42012-07-17 11:12:10 -0700426 self.callback.jenkins_endpoint(FakeJenkinsEvent(
427 self.name, self.number, self.parameters,
James E. Blairb02a3bb2012-07-30 17:49:55 -0700428 'COMPLETED', result))
James E. Blairb0fcae42012-07-17 11:12:10 -0700429 self.callback.jenkins_endpoint(FakeJenkinsEvent(
430 self.name, self.number, self.parameters,
James E. Blairb02a3bb2012-07-30 17:49:55 -0700431 'FINISHED', result))
James E. Blairb0fcae42012-07-17 11:12:10 -0700432 self.jenkins.all_jobs.remove(self)
433
434
435class FakeJenkins(object):
436 log = logging.getLogger("zuul.test")
437
438 def __init__(self, *args, **kw):
439 self.queue = []
440 self.all_jobs = []
441 self.job_counter = {}
James E. Blaird466dc42012-07-31 10:42:56 -0700442 self.queue_counter = 0
James E. Blairb0fcae42012-07-17 11:12:10 -0700443 self.job_history = []
444 self.hold_jobs_in_queue = False
445 self.hold_jobs_in_build = False
James E. Blairb02a3bb2012-07-30 17:49:55 -0700446 self.fail_tests = {}
James E. Blairb0fcae42012-07-17 11:12:10 -0700447
448 def fakeEnqueue(self, job):
449 self.queue.append(job)
450
451 def fakeDequeue(self, job):
452 self.queue.remove(job)
453
454 def fakeAddHistory(self, **kw):
455 self.job_history.append(kw)
456
457 def fakeRelease(self, regex=None):
458 all_jobs = self.all_jobs[:]
459 self.log.debug("releasing jobs %s (%s)" % (regex, len(self.all_jobs)))
460 for job in all_jobs:
461 if not regex or re.match(regex, job.name):
462 self.log.debug("releasing job %s" % (job.parameters['UUID']))
463 job.release()
464 else:
465 self.log.debug("not releasing job %s" % (
466 job.parameters['UUID']))
467 self.log.debug("done releasing jobs %s (%s)" % (regex,
468 len(self.all_jobs)))
469
470 def fakeAllWaiting(self, regex=None):
471 all_jobs = self.all_jobs[:]
472 for job in all_jobs:
473 self.log.debug("job %s %s" % (job.parameters['UUID'],
474 job.isWaiting()))
475 if not job.isWaiting():
476 return False
477 return True
478
James E. Blairb02a3bb2012-07-30 17:49:55 -0700479 def fakeAddFailTest(self, name, change):
480 l = self.fail_tests.get(name, [])
481 l.append(change)
482 self.fail_tests[name] = l
483
James E. Blair4886cc12012-07-18 15:39:41 -0700484 def fakeShouldFailTest(self, name, ref):
James E. Blairb02a3bb2012-07-30 17:49:55 -0700485 l = self.fail_tests.get(name, [])
486 for change in l:
James E. Blair4886cc12012-07-18 15:39:41 -0700487 if ref_has_change(ref, change):
James E. Blairb02a3bb2012-07-30 17:49:55 -0700488 return True
489 return False
490
James E. Blairb0fcae42012-07-17 11:12:10 -0700491 def build_job(self, name, parameters):
492 count = self.job_counter.get(name, 0)
493 count += 1
494 self.job_counter[name] = count
James E. Blaird466dc42012-07-31 10:42:56 -0700495
496 queue_count = self.queue_counter
497 self.queue_counter += 1
James E. Blairb0fcae42012-07-17 11:12:10 -0700498 job = FakeJenkinsJob(self, self.callback, name, count, parameters)
James E. Blaird466dc42012-07-31 10:42:56 -0700499 job.queue_id = queue_count
500
James E. Blairb0fcae42012-07-17 11:12:10 -0700501 self.all_jobs.append(job)
502 job.start()
503
James E. Blaird466dc42012-07-31 10:42:56 -0700504 def stop_build(self, name, number):
505 for job in self.all_jobs:
506 if job.name == name and job.number == number:
507 job.aborted = True
508 job.release()
509 return
510
511 def cancel_queue(self, id):
512 for job in self.queue:
513 if job.queue_id == id:
514 job.canceled = True
515 job.release()
516 return
517
518 def get_queue_info(self):
519 items = []
520 for job in self.queue:
521 paramstr = ''
522 paramlst = []
523 d = {'actions': [{'parameters': paramlst},
524 {'causes': [{'shortDescription':
525 'Started by user Jenkins',
526 'userId': 'jenkins',
527 'userName': 'Jenkins'}]}],
528 'blocked': False,
529 'buildable': True,
530 'buildableStartMilliseconds': (job.created * 1000) + 5,
531 'id': job.queue_id,
532 'inQueueSince': (job.created * 1000),
533 'params': paramstr,
534 'stuck': False,
535 'task': {'color': 'blue',
536 'name': job.name,
537 'url': 'https://server/job/%s/' % job.name},
538 'why': 'Waiting for next available executor'}
539 for k, v in job.parameters.items():
540 paramstr += "\n(StringParameterValue) %s='%s'" % (k, v)
541 pd = {'name': k, 'value': v}
542 paramlst.append(pd)
543 items.append(d)
544 return items
545
James E. Blairb0fcae42012-07-17 11:12:10 -0700546 def set_build_description(self, *args, **kw):
547 pass
548
549
550class FakeJenkinsCallback(zuul.launcher.jenkins.JenkinsCallback):
551 def start(self):
552 pass
553
554
James E. Blair8cc15a82012-08-01 11:17:57 -0700555class FakeURLOpener(object):
556 def __init__(self, fake_gerrit, url):
557 self.fake_gerrit = fake_gerrit
558 self.url = url
559
560 def read(self):
561 res = urlparse.urlparse(self.url)
562 path = res.path
563 project = '/'.join(path.split('/')[2:-2])
James E. Blair8cc15a82012-08-01 11:17:57 -0700564 ret = ''
James E. Blair1dbd5082012-08-23 15:12:15 -0700565 path = os.path.join(UPSTREAM_ROOT, project)
James E. Blairdaabed22012-08-15 15:38:57 -0700566 repo = git.Repo(path)
567 for ref in repo.refs:
568 ret += ref.object.hexsha + '\t' + ref.path + '\n'
James E. Blair8cc15a82012-08-01 11:17:57 -0700569 return ret
570
571
James E. Blair4886cc12012-07-18 15:39:41 -0700572class FakeGerritTrigger(zuul.trigger.gerrit.Gerrit):
573 def getGitUrl(self, project):
James E. Blair1dbd5082012-08-23 15:12:15 -0700574 return os.path.join(UPSTREAM_ROOT, project.name)
James E. Blair4886cc12012-07-18 15:39:41 -0700575
576
James E. Blairb0fcae42012-07-17 11:12:10 -0700577class testScheduler(unittest.TestCase):
578 log = logging.getLogger("zuul.test")
579
580 def setUp(self):
James E. Blair1dbd5082012-08-23 15:12:15 -0700581 if os.path.exists(TEST_ROOT):
582 shutil.rmtree(TEST_ROOT)
583 os.makedirs(TEST_ROOT)
584 os.makedirs(UPSTREAM_ROOT)
585 os.makedirs(GIT_ROOT)
James E. Blair4886cc12012-07-18 15:39:41 -0700586
587 # For each project in config:
588 init_repo("org/project")
589 init_repo("org/project1")
590 init_repo("org/project2")
James E. Blair7f71c802012-08-22 13:04:32 -0700591 init_repo("org/one-job-project")
James E. Blair4ec821f2012-08-23 15:28:28 -0700592 init_repo("org/nonvoting-project")
James E. Blairb0fcae42012-07-17 11:12:10 -0700593 self.config = CONFIG
594 self.sched = zuul.scheduler.Scheduler()
595
596 def jenkinsFactory(*args, **kw):
597 self.fake_jenkins = FakeJenkins()
598 return self.fake_jenkins
599
600 def jenkinsCallbackFactory(*args, **kw):
601 self.fake_jenkins_callback = FakeJenkinsCallback(*args, **kw)
602 return self.fake_jenkins_callback
603
James E. Blair8cc15a82012-08-01 11:17:57 -0700604 def URLOpenerFactory(*args, **kw):
605 args = [self.fake_gerrit] + list(args)
606 return FakeURLOpener(*args, **kw)
607
James E. Blairb0fcae42012-07-17 11:12:10 -0700608 zuul.launcher.jenkins.ExtendedJenkins = jenkinsFactory
609 zuul.launcher.jenkins.JenkinsCallback = jenkinsCallbackFactory
James E. Blair8cc15a82012-08-01 11:17:57 -0700610 urllib2.urlopen = URLOpenerFactory
James E. Blairb0fcae42012-07-17 11:12:10 -0700611 self.jenkins = zuul.launcher.jenkins.Jenkins(self.config, self.sched)
612 self.fake_jenkins.callback = self.fake_jenkins_callback
613
614 zuul.lib.gerrit.Gerrit = FakeGerrit
615
James E. Blair4886cc12012-07-18 15:39:41 -0700616 self.gerrit = FakeGerritTrigger(self.config, self.sched)
James E. Blair8cc15a82012-08-01 11:17:57 -0700617 self.gerrit.replication_timeout = 1.5
618 self.gerrit.replication_retry_interval = 0.5
James E. Blairb0fcae42012-07-17 11:12:10 -0700619 self.fake_gerrit = self.gerrit.gerrit
620
621 self.sched.setLauncher(self.jenkins)
622 self.sched.setTrigger(self.gerrit)
623
624 self.sched.start()
625 self.sched.reconfigure(self.config)
626 self.sched.resume()
627
628 def tearDown(self):
629 self.jenkins.stop()
630 self.gerrit.stop()
631 self.sched.stop()
632 self.sched.join()
James E. Blair1dbd5082012-08-23 15:12:15 -0700633 #shutil.rmtree(TEST_ROOT)
James E. Blairb0fcae42012-07-17 11:12:10 -0700634
635 def waitUntilSettled(self):
636 self.log.debug("Waiting until settled...")
637 start = time.time()
638 while True:
639 if time.time() - start > 10:
640 print 'queue status:',
641 print self.sched.trigger_event_queue.empty(),
642 print self.sched.result_event_queue.empty(),
643 print self.fake_gerrit.event_queue.empty(),
644 raise Exception("Timeout waiting for Zuul to settle")
645 self.fake_gerrit.event_queue.join()
646 self.sched.queue_lock.acquire()
647 if (self.sched.trigger_event_queue.empty() and
648 self.sched.result_event_queue.empty() and
649 self.fake_gerrit.event_queue.empty() and
650 self.fake_jenkins.fakeAllWaiting()):
651 self.sched.queue_lock.release()
652 self.log.debug("...settled.")
653 return
654 self.sched.queue_lock.release()
655 self.sched.wake_event.wait(0.1)
656
James E. Blaird466dc42012-07-31 10:42:56 -0700657 def countJobResults(self, jobs, result):
658 jobs = filter(lambda x: x['result'] == result, jobs)
659 return len(jobs)
660
James E. Blairb0fcae42012-07-17 11:12:10 -0700661 def test_jobs_launched(self):
662 "Test that jobs are launched and a change is merged"
663 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
James E. Blair8c803f82012-07-31 16:25:42 -0700664 A.addApproval('CRVW', 2)
James E. Blairb0fcae42012-07-17 11:12:10 -0700665 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
666 self.waitUntilSettled()
667 jobs = self.fake_jenkins.job_history
668 job_names = [x['name'] for x in jobs]
669 assert 'project-merge' in job_names
670 assert 'project-test1' in job_names
671 assert 'project-test2' in job_names
672 assert jobs[0]['result'] == 'SUCCESS'
673 assert jobs[1]['result'] == 'SUCCESS'
674 assert jobs[2]['result'] == 'SUCCESS'
675 assert A.data['status'] == 'MERGED'
James E. Blaird466dc42012-07-31 10:42:56 -0700676 assert A.reported == 2
James E. Blairb0fcae42012-07-17 11:12:10 -0700677
678 def test_parallel_changes(self):
679 "Test that changes are tested in parallel and merged in series"
680 self.fake_jenkins.hold_jobs_in_build = True
681 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
682 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
683 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
James E. Blair8c803f82012-07-31 16:25:42 -0700684 A.addApproval('CRVW', 2)
685 B.addApproval('CRVW', 2)
686 C.addApproval('CRVW', 2)
James E. Blairb0fcae42012-07-17 11:12:10 -0700687
688 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
689 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
690 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
691
692 self.waitUntilSettled()
693 jobs = self.fake_jenkins.all_jobs
694 assert len(jobs) == 1
695 assert jobs[0].name == 'project-merge'
James E. Blair4886cc12012-07-18 15:39:41 -0700696 assert job_has_changes(jobs[0], A)
James E. Blairb0fcae42012-07-17 11:12:10 -0700697
698 self.fake_jenkins.fakeRelease('.*-merge')
699 self.waitUntilSettled()
700 assert len(jobs) == 3
701 assert jobs[0].name == 'project-test1'
James E. Blair4886cc12012-07-18 15:39:41 -0700702 assert job_has_changes(jobs[0], A)
James E. Blairb0fcae42012-07-17 11:12:10 -0700703 assert jobs[1].name == 'project-test2'
James E. Blair4886cc12012-07-18 15:39:41 -0700704 assert job_has_changes(jobs[1], A)
James E. Blairb0fcae42012-07-17 11:12:10 -0700705 assert jobs[2].name == 'project-merge'
James E. Blair4886cc12012-07-18 15:39:41 -0700706 assert job_has_changes(jobs[2], A, B)
James E. Blairb0fcae42012-07-17 11:12:10 -0700707
708 self.fake_jenkins.fakeRelease('.*-merge')
709 self.waitUntilSettled()
710 assert len(jobs) == 5
711 assert jobs[0].name == 'project-test1'
James E. Blair4886cc12012-07-18 15:39:41 -0700712 assert job_has_changes(jobs[0], A)
James E. Blairb0fcae42012-07-17 11:12:10 -0700713 assert jobs[1].name == 'project-test2'
James E. Blair4886cc12012-07-18 15:39:41 -0700714 assert job_has_changes(jobs[1], A)
James E. Blairb0fcae42012-07-17 11:12:10 -0700715
716 assert jobs[2].name == 'project-test1'
James E. Blair4886cc12012-07-18 15:39:41 -0700717 assert job_has_changes(jobs[2], A, B)
James E. Blairb0fcae42012-07-17 11:12:10 -0700718 assert jobs[3].name == 'project-test2'
James E. Blair4886cc12012-07-18 15:39:41 -0700719 assert job_has_changes(jobs[3], A, B)
James E. Blairb0fcae42012-07-17 11:12:10 -0700720
721 assert jobs[4].name == 'project-merge'
James E. Blair4886cc12012-07-18 15:39:41 -0700722 assert job_has_changes(jobs[4], A, B, C)
James E. Blairb0fcae42012-07-17 11:12:10 -0700723
724 self.fake_jenkins.fakeRelease('.*-merge')
725 self.waitUntilSettled()
726 assert len(jobs) == 6
727 assert jobs[0].name == 'project-test1'
James E. Blair4886cc12012-07-18 15:39:41 -0700728 assert job_has_changes(jobs[0], A)
James E. Blairb0fcae42012-07-17 11:12:10 -0700729 assert jobs[1].name == 'project-test2'
James E. Blair4886cc12012-07-18 15:39:41 -0700730 assert job_has_changes(jobs[1], A)
James E. Blairb0fcae42012-07-17 11:12:10 -0700731
732 assert jobs[2].name == 'project-test1'
James E. Blair4886cc12012-07-18 15:39:41 -0700733 assert job_has_changes(jobs[2], A, B)
James E. Blairb0fcae42012-07-17 11:12:10 -0700734 assert jobs[3].name == 'project-test2'
James E. Blair4886cc12012-07-18 15:39:41 -0700735 assert job_has_changes(jobs[3], A, B)
James E. Blairb0fcae42012-07-17 11:12:10 -0700736
737 assert jobs[4].name == 'project-test1'
James E. Blair4886cc12012-07-18 15:39:41 -0700738 assert job_has_changes(jobs[4], A, B, C)
James E. Blairb0fcae42012-07-17 11:12:10 -0700739 assert jobs[5].name == 'project-test2'
James E. Blair4886cc12012-07-18 15:39:41 -0700740 assert job_has_changes(jobs[5], A, B, C)
James E. Blairb0fcae42012-07-17 11:12:10 -0700741
742 self.fake_jenkins.hold_jobs_in_build = False
743 self.fake_jenkins.fakeRelease()
744 self.waitUntilSettled()
745 assert len(jobs) == 0
746
747 jobs = self.fake_jenkins.job_history
748 assert len(jobs) == 9
749 assert A.data['status'] == 'MERGED'
750 assert B.data['status'] == 'MERGED'
751 assert C.data['status'] == 'MERGED'
James E. Blaird466dc42012-07-31 10:42:56 -0700752 assert A.reported == 2
753 assert B.reported == 2
754 assert C.reported == 2
James E. Blairb02a3bb2012-07-30 17:49:55 -0700755
756 def test_failed_changes(self):
757 "Test that a change behind a failed change is retested"
758 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
759 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
James E. Blair8c803f82012-07-31 16:25:42 -0700760 A.addApproval('CRVW', 2)
761 B.addApproval('CRVW', 2)
James E. Blairb02a3bb2012-07-30 17:49:55 -0700762
763 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
764 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
765
James E. Blair4886cc12012-07-18 15:39:41 -0700766 self.fake_jenkins.fakeAddFailTest('project-test1', A)
James E. Blairb02a3bb2012-07-30 17:49:55 -0700767
768 self.waitUntilSettled()
769 jobs = self.fake_jenkins.job_history
770 assert len(jobs) > 6
771 assert A.data['status'] == 'NEW'
772 assert B.data['status'] == 'MERGED'
James E. Blaird466dc42012-07-31 10:42:56 -0700773 assert A.reported == 2
774 assert B.reported == 2
James E. Blairb02a3bb2012-07-30 17:49:55 -0700775
776 def test_independent_queues(self):
777 "Test that changes end up in the right queues"
778 self.fake_jenkins.hold_jobs_in_build = True
779 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
780 B = self.fake_gerrit.addFakeChange('org/project1', 'master', 'B')
781 C = self.fake_gerrit.addFakeChange('org/project2', 'master', 'C')
James E. Blair8c803f82012-07-31 16:25:42 -0700782 A.addApproval('CRVW', 2)
783 B.addApproval('CRVW', 2)
784 C.addApproval('CRVW', 2)
James E. Blairb02a3bb2012-07-30 17:49:55 -0700785
786 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
787 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
788 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
789
790 jobs = self.fake_jenkins.all_jobs
791 self.waitUntilSettled()
792
793 # There should be one merge job at the head of each queue running
794 assert len(jobs) == 2
795 assert jobs[0].name == 'project-merge'
James E. Blair4886cc12012-07-18 15:39:41 -0700796 assert job_has_changes(jobs[0], A)
James E. Blairb02a3bb2012-07-30 17:49:55 -0700797 assert jobs[1].name == 'project1-merge'
James E. Blair4886cc12012-07-18 15:39:41 -0700798 assert job_has_changes(jobs[1], B)
James E. Blairb02a3bb2012-07-30 17:49:55 -0700799
800 # Release the current merge jobs
801 self.fake_jenkins.fakeRelease('.*-merge')
802 self.waitUntilSettled()
803 # Release the merge job for project2 which is behind project1
804 self.fake_jenkins.fakeRelease('.*-merge')
805 self.waitUntilSettled()
806
807 # All the test jobs should be running:
808 # project1 (3) + project2 (3) + project (2) = 8
809 assert len(jobs) == 8
810
811 self.fake_jenkins.fakeRelease()
812 self.waitUntilSettled()
813 assert len(jobs) == 0
814
815 jobs = self.fake_jenkins.job_history
816 assert len(jobs) == 11
817 assert A.data['status'] == 'MERGED'
818 assert B.data['status'] == 'MERGED'
819 assert C.data['status'] == 'MERGED'
James E. Blaird466dc42012-07-31 10:42:56 -0700820 assert A.reported == 2
821 assert B.reported == 2
822 assert C.reported == 2
823
824 def test_failed_change_at_head(self):
825 "Test that if a change at the head fails, jobs behind it are canceled"
826 self.fake_jenkins.hold_jobs_in_build = True
827
828 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
829 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
830 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
James E. Blair8c803f82012-07-31 16:25:42 -0700831 A.addApproval('CRVW', 2)
832 B.addApproval('CRVW', 2)
833 C.addApproval('CRVW', 2)
James E. Blaird466dc42012-07-31 10:42:56 -0700834
James E. Blair4886cc12012-07-18 15:39:41 -0700835 self.fake_jenkins.fakeAddFailTest('project-test1', A)
James E. Blaird466dc42012-07-31 10:42:56 -0700836
837 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
838 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
839 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
840
841 self.waitUntilSettled()
842 jobs = self.fake_jenkins.all_jobs
843 finished_jobs = self.fake_jenkins.job_history
844
845 assert len(jobs) == 1
846 assert jobs[0].name == 'project-merge'
James E. Blair4886cc12012-07-18 15:39:41 -0700847 assert job_has_changes(jobs[0], A)
James E. Blaird466dc42012-07-31 10:42:56 -0700848
849 self.fake_jenkins.fakeRelease('.*-merge')
850 self.waitUntilSettled()
851 self.fake_jenkins.fakeRelease('.*-merge')
852 self.waitUntilSettled()
853 self.fake_jenkins.fakeRelease('.*-merge')
854 self.waitUntilSettled()
855
856 assert len(jobs) == 6
857 assert jobs[0].name == 'project-test1'
858 assert jobs[1].name == 'project-test2'
859 assert jobs[2].name == 'project-test1'
860 assert jobs[3].name == 'project-test2'
861 assert jobs[4].name == 'project-test1'
862 assert jobs[5].name == 'project-test2'
863
864 jobs[0].release()
865 self.waitUntilSettled()
866
James E. Blairec590122012-08-22 15:19:31 -0700867 assert len(jobs) == 2 # project-test2, project-merge for B
James E. Blaird466dc42012-07-31 10:42:56 -0700868 assert self.countJobResults(finished_jobs, 'ABORTED') == 4
869
870 self.fake_jenkins.hold_jobs_in_build = False
871 self.fake_jenkins.fakeRelease()
872 self.waitUntilSettled()
873
874 assert len(jobs) == 0
875 assert len(finished_jobs) == 15
876 assert A.data['status'] == 'NEW'
877 assert B.data['status'] == 'MERGED'
878 assert C.data['status'] == 'MERGED'
879 assert A.reported == 2
880 assert B.reported == 2
881 assert C.reported == 2
882
883 def test_failed_change_at_head_with_queue(self):
884 "Test that if a change at the head fails, queued jobs are canceled"
885 self.fake_jenkins.hold_jobs_in_queue = True
886
887 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
888 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
889 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
James E. Blair8c803f82012-07-31 16:25:42 -0700890 A.addApproval('CRVW', 2)
891 B.addApproval('CRVW', 2)
892 C.addApproval('CRVW', 2)
James E. Blaird466dc42012-07-31 10:42:56 -0700893
James E. Blair4886cc12012-07-18 15:39:41 -0700894 self.fake_jenkins.fakeAddFailTest('project-test1', A)
James E. Blaird466dc42012-07-31 10:42:56 -0700895
896 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
897 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
898 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
899
900 self.waitUntilSettled()
901 jobs = self.fake_jenkins.all_jobs
902 finished_jobs = self.fake_jenkins.job_history
903 queue = self.fake_jenkins.queue
904
905 assert len(jobs) == 1
906 assert len(queue) == 1
907 assert jobs[0].name == 'project-merge'
James E. Blair4886cc12012-07-18 15:39:41 -0700908 assert job_has_changes(jobs[0], A)
James E. Blaird466dc42012-07-31 10:42:56 -0700909
910 self.fake_jenkins.fakeRelease('.*-merge')
911 self.waitUntilSettled()
912 self.fake_jenkins.fakeRelease('.*-merge')
913 self.waitUntilSettled()
914 self.fake_jenkins.fakeRelease('.*-merge')
915 self.waitUntilSettled()
916
917 assert len(jobs) == 6
918 assert len(queue) == 6
919 assert jobs[0].name == 'project-test1'
920 assert jobs[1].name == 'project-test2'
921 assert jobs[2].name == 'project-test1'
922 assert jobs[3].name == 'project-test2'
923 assert jobs[4].name == 'project-test1'
924 assert jobs[5].name == 'project-test2'
925
926 jobs[0].release()
927 self.waitUntilSettled()
928
James E. Blairec590122012-08-22 15:19:31 -0700929 assert len(jobs) == 2 # project-test2, project-merge for B
930 assert len(queue) == 2
James E. Blaird466dc42012-07-31 10:42:56 -0700931 assert self.countJobResults(finished_jobs, 'ABORTED') == 0
932
933 self.fake_jenkins.hold_jobs_in_queue = False
934 self.fake_jenkins.fakeRelease()
935 self.waitUntilSettled()
936
937 assert len(jobs) == 0
938 assert len(finished_jobs) == 11
939 assert A.data['status'] == 'NEW'
940 assert B.data['status'] == 'MERGED'
941 assert C.data['status'] == 'MERGED'
942 assert A.reported == 2
943 assert B.reported == 2
944 assert C.reported == 2
James E. Blair8c803f82012-07-31 16:25:42 -0700945
946 def test_patch_order(self):
947 "Test that dependent patches are tested in the right order"
948 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
949 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
950 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
951 A.addApproval('CRVW', 2)
952 B.addApproval('CRVW', 2)
953 C.addApproval('CRVW', 2)
954
955 M2 = self.fake_gerrit.addFakeChange('org/project', 'master', 'M2')
956 M1 = self.fake_gerrit.addFakeChange('org/project', 'master', 'M1')
957 M2.setMerged()
958 M1.setMerged()
959
960 # C -> B -> A -> M1 -> M2
961 # M2 is here to make sure it is never queried. If it is, it
962 # means zuul is walking down the entire history of merged
963 # changes.
964
965 C.setDependsOn(B, 1)
966 B.setDependsOn(A, 1)
967 A.setDependsOn(M1, 1)
968 M1.setDependsOn(M2, 1)
969
970 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
971
972 self.waitUntilSettled()
973
974 assert A.data['status'] == 'NEW'
975 assert B.data['status'] == 'NEW'
976 assert C.data['status'] == 'NEW'
977
978 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
979 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
980
981 self.waitUntilSettled()
982 assert M2.queried == 0
983 assert A.data['status'] == 'MERGED'
984 assert B.data['status'] == 'MERGED'
985 assert C.data['status'] == 'MERGED'
986 assert A.reported == 2
987 assert B.reported == 2
988 assert C.reported == 2
989
990 def test_can_merge(self):
James E. Blair4886cc12012-07-18 15:39:41 -0700991 "Test whether a change is ready to merge"
James E. Blair8c803f82012-07-31 16:25:42 -0700992 # TODO: move to test_gerrit (this is a unit test!)
993 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
James E. Blair4aea70c2012-07-26 14:23:24 -0700994 a = self.sched.trigger.getChange(1, 2)
995 mgr = self.sched.pipelines['gate'].manager
996 assert not self.sched.trigger.canMerge(a, mgr.getSubmitAllowNeeds())
James E. Blair8c803f82012-07-31 16:25:42 -0700997
998 A.addApproval('CRVW', 2)
James E. Blair4aea70c2012-07-26 14:23:24 -0700999 a = self.sched.trigger.getChange(1, 2)
1000 assert not self.sched.trigger.canMerge(a, mgr.getSubmitAllowNeeds())
James E. Blair8c803f82012-07-31 16:25:42 -07001001
1002 A.addApproval('APRV', 1)
James E. Blair4aea70c2012-07-26 14:23:24 -07001003 a = self.sched.trigger.getChange(1, 2)
1004 assert self.sched.trigger.canMerge(a, mgr.getSubmitAllowNeeds())
James E. Blair8c803f82012-07-31 16:25:42 -07001005
1006 return True
James E. Blair4886cc12012-07-18 15:39:41 -07001007
1008 def test_build_configuration(self):
1009 "Test that zuul merges the right commits for testing"
1010 self.fake_jenkins.hold_jobs_in_queue = True
1011 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
1012 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
1013 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
1014 A.addApproval('CRVW', 2)
1015 B.addApproval('CRVW', 2)
1016 C.addApproval('CRVW', 2)
1017 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
1018 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
1019 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
1020 self.waitUntilSettled()
1021
1022 jobs = self.fake_jenkins.all_jobs
1023
1024 self.fake_jenkins.fakeRelease('.*-merge')
1025 self.waitUntilSettled()
1026 self.fake_jenkins.fakeRelease('.*-merge')
1027 self.waitUntilSettled()
1028 self.fake_jenkins.fakeRelease('.*-merge')
1029 self.waitUntilSettled()
James E. Blair1dbd5082012-08-23 15:12:15 -07001030
James E. Blair4886cc12012-07-18 15:39:41 -07001031 ref = jobs[-1].parameters['ZUUL_REF']
1032 self.fake_jenkins.hold_jobs_in_queue = False
1033 self.fake_jenkins.fakeRelease()
James E. Blair973721f2012-08-15 10:19:43 -07001034 self.waitUntilSettled()
James E. Blair4886cc12012-07-18 15:39:41 -07001035
James E. Blair1dbd5082012-08-23 15:12:15 -07001036 path = os.path.join(GIT_ROOT, "org/project")
James E. Blair4886cc12012-07-18 15:39:41 -07001037 repo = git.Repo(path)
1038 repo_messages = [c.message.strip() for c in repo.iter_commits(ref)]
1039 repo_messages.reverse()
James E. Blair4886cc12012-07-18 15:39:41 -07001040 correct_messages = ['initial commit', 'A-1', 'B-1', 'C-1']
1041 assert repo_messages == correct_messages
James E. Blair973721f2012-08-15 10:19:43 -07001042
1043 def test_build_configuration_conflict(self):
1044 "Test that merge conflicts are handled"
1045 self.fake_jenkins.hold_jobs_in_queue = True
1046 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
1047 A.addPatchset(['conflict'])
1048 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
1049 B.addPatchset(['conflict'])
1050 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
1051 A.addApproval('CRVW', 2)
1052 B.addApproval('CRVW', 2)
1053 C.addApproval('CRVW', 2)
1054 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
1055 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
1056 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
1057 self.waitUntilSettled()
1058
1059 jobs = self.fake_jenkins.all_jobs
1060
1061 self.fake_jenkins.fakeRelease('.*-merge')
1062 self.waitUntilSettled()
1063 self.fake_jenkins.fakeRelease('.*-merge')
1064 self.waitUntilSettled()
1065 self.fake_jenkins.fakeRelease('.*-merge')
1066 self.waitUntilSettled()
1067 ref = jobs[-1].parameters['ZUUL_REF']
1068 self.fake_jenkins.hold_jobs_in_queue = False
1069 self.fake_jenkins.fakeRelease()
1070 self.waitUntilSettled()
1071
1072 assert A.data['status'] == 'MERGED'
1073 assert B.data['status'] == 'NEW'
1074 assert C.data['status'] == 'MERGED'
1075 assert A.reported == 2
1076 assert B.reported == 2
1077 assert C.reported == 2
James E. Blairdaabed22012-08-15 15:38:57 -07001078
1079 def test_post(self):
1080 "Test that post jobs run"
1081 e = {"type": "ref-updated",
1082 "submitter": {"name": "User Name"},
1083 "refUpdate": {"oldRev":
1084 "90f173846e3af9154517b88543ffbd1691f31366",
1085 "newRev":
1086 "d479a0bfcb34da57a31adb2a595c0cf687812543",
1087 "refName": "master", "project": "org/project"}}
1088 self.fake_gerrit.addEvent(e)
1089 self.waitUntilSettled()
1090
1091 jobs = self.fake_jenkins.job_history
1092 job_names = [x['name'] for x in jobs]
1093 assert len(jobs) == 1
1094 assert 'project-post' in job_names
James E. Blairc6294a52012-08-17 10:19:48 -07001095
1096 def test_build_configuration_branch(self):
1097 "Test that the right commits are on alternate branches"
1098 self.fake_jenkins.hold_jobs_in_queue = True
1099 A = self.fake_gerrit.addFakeChange('org/project', 'mp', 'A')
1100 B = self.fake_gerrit.addFakeChange('org/project', 'mp', 'B')
1101 C = self.fake_gerrit.addFakeChange('org/project', 'mp', 'C')
1102 A.addApproval('CRVW', 2)
1103 B.addApproval('CRVW', 2)
1104 C.addApproval('CRVW', 2)
1105 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
1106 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
1107 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
1108 self.waitUntilSettled()
1109
1110 jobs = self.fake_jenkins.all_jobs
1111
1112 self.fake_jenkins.fakeRelease('.*-merge')
1113 self.waitUntilSettled()
1114 self.fake_jenkins.fakeRelease('.*-merge')
1115 self.waitUntilSettled()
1116 self.fake_jenkins.fakeRelease('.*-merge')
1117 self.waitUntilSettled()
1118 ref = jobs[-1].parameters['ZUUL_REF']
1119 self.fake_jenkins.hold_jobs_in_queue = False
1120 self.fake_jenkins.fakeRelease()
1121 self.waitUntilSettled()
1122
James E. Blair1dbd5082012-08-23 15:12:15 -07001123 path = os.path.join(GIT_ROOT, "org/project")
James E. Blairc6294a52012-08-17 10:19:48 -07001124 repo = git.Repo(path)
1125 repo_messages = [c.message.strip() for c in repo.iter_commits(ref)]
1126 repo_messages.reverse()
James E. Blairc6294a52012-08-17 10:19:48 -07001127 correct_messages = ['initial commit', 'mp commit', 'A-1', 'B-1', 'C-1']
1128 assert repo_messages == correct_messages
1129
1130 def test_build_configuration_branch_interaction(self):
1131 "Test that switching between branches works"
1132 self.test_build_configuration()
1133 self.test_build_configuration_branch()
1134 # C has been merged, undo that
James E. Blair1dbd5082012-08-23 15:12:15 -07001135 path = os.path.join(UPSTREAM_ROOT, "org/project")
James E. Blairc6294a52012-08-17 10:19:48 -07001136 repo = git.Repo(path)
1137 repo.heads.master.commit = repo.commit('init')
1138 self.test_build_configuration()
1139
1140 def test_build_configuration_multi_branch(self):
1141 "Test that dependent changes on multiple branches are merged"
1142 self.fake_jenkins.hold_jobs_in_queue = True
1143 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
1144 B = self.fake_gerrit.addFakeChange('org/project', 'mp', 'B')
1145 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
1146 A.addApproval('CRVW', 2)
1147 B.addApproval('CRVW', 2)
1148 C.addApproval('CRVW', 2)
1149 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
1150 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
1151 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
1152 self.waitUntilSettled()
1153
1154 jobs = self.fake_jenkins.all_jobs
1155
1156 self.fake_jenkins.fakeRelease('.*-merge')
1157 self.waitUntilSettled()
1158 ref_mp = jobs[-1].parameters['ZUUL_REF']
1159 self.fake_jenkins.fakeRelease('.*-merge')
1160 self.waitUntilSettled()
1161 self.fake_jenkins.fakeRelease('.*-merge')
1162 self.waitUntilSettled()
1163 ref_master = jobs[-1].parameters['ZUUL_REF']
1164 self.fake_jenkins.hold_jobs_in_queue = False
1165 self.fake_jenkins.fakeRelease()
1166 self.waitUntilSettled()
1167
James E. Blair1dbd5082012-08-23 15:12:15 -07001168 path = os.path.join(GIT_ROOT, "org/project")
James E. Blairc6294a52012-08-17 10:19:48 -07001169 repo = git.Repo(path)
1170
1171 repo_messages = [c.message.strip()
1172 for c in repo.iter_commits(ref_master)]
1173 repo_messages.reverse()
James E. Blairc6294a52012-08-17 10:19:48 -07001174 correct_messages = ['initial commit', 'A-1', 'C-1']
1175 assert repo_messages == correct_messages
1176
1177 repo_messages = [c.message.strip()
1178 for c in repo.iter_commits(ref_mp)]
1179 repo_messages.reverse()
James E. Blairc6294a52012-08-17 10:19:48 -07001180 correct_messages = ['initial commit', 'mp commit', 'B-1']
1181 assert repo_messages == correct_messages
James E. Blair7f71c802012-08-22 13:04:32 -07001182
1183 def test_one_job_project(self):
1184 "Test that queueing works with one job"
1185 A = self.fake_gerrit.addFakeChange('org/one-job-project',
1186 'master', 'A')
1187 B = self.fake_gerrit.addFakeChange('org/one-job-project',
1188 'master', 'B')
1189 A.addApproval('CRVW', 2)
1190 B.addApproval('CRVW', 2)
1191 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
1192 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
1193 self.waitUntilSettled()
1194
1195 jobs = self.fake_jenkins.all_jobs
1196 finished_jobs = self.fake_jenkins.job_history
James E. Blair7f71c802012-08-22 13:04:32 -07001197
1198 assert A.data['status'] == 'MERGED'
1199 assert A.reported == 2
1200 assert B.data['status'] == 'MERGED'
1201 assert B.reported == 2
James E. Blaircaec0c52012-08-22 14:52:22 -07001202
1203 def test_dependent_changes_dequeue(self):
1204 "Test that dependent patches are not needlessly tested"
1205 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
1206 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
1207 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
1208 A.addApproval('CRVW', 2)
1209 B.addApproval('CRVW', 2)
1210 C.addApproval('CRVW', 2)
1211
1212 M1 = self.fake_gerrit.addFakeChange('org/project', 'master', 'M1')
1213 M1.setMerged()
1214
1215 # C -> B -> A -> M1
1216
1217 C.setDependsOn(B, 1)
1218 B.setDependsOn(A, 1)
1219 A.setDependsOn(M1, 1)
1220
1221 self.fake_jenkins.fakeAddFailTest('project-merge', A)
1222
1223 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
1224 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
1225 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
1226
1227 self.waitUntilSettled()
1228
1229 jobs = self.fake_jenkins.all_jobs
1230 finished_jobs = self.fake_jenkins.job_history
1231
James E. Blairec590122012-08-22 15:19:31 -07001232 assert A.data['status'] == 'NEW'
1233 assert A.reported == 2
1234 assert B.data['status'] == 'NEW'
1235 assert B.reported == 2
1236 assert C.data['status'] == 'NEW'
1237 assert C.reported == 2
1238 assert len(finished_jobs) == 1
1239
1240 def test_head_is_dequeued_once(self):
1241 "Test that if a change at the head fails it is dequeud only once"
1242 # If it's dequeued more than once, we should see extra
1243 # aborted jobs.
1244 self.fake_jenkins.hold_jobs_in_build = True
1245
1246 A = self.fake_gerrit.addFakeChange('org/project1', 'master', 'A')
1247 B = self.fake_gerrit.addFakeChange('org/project1', 'master', 'B')
1248 C = self.fake_gerrit.addFakeChange('org/project1', 'master', 'C')
1249 A.addApproval('CRVW', 2)
1250 B.addApproval('CRVW', 2)
1251 C.addApproval('CRVW', 2)
1252
1253 self.fake_jenkins.fakeAddFailTest('project1-test1', A)
1254 self.fake_jenkins.fakeAddFailTest('project1-test2', A)
1255 self.fake_jenkins.fakeAddFailTest('project1-project2-integration', A)
1256
1257 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
1258 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
1259 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
1260
1261 self.waitUntilSettled()
1262 jobs = self.fake_jenkins.all_jobs
1263 finished_jobs = self.fake_jenkins.job_history
1264
1265 assert len(jobs) == 1
1266 assert jobs[0].name == 'project1-merge'
1267 assert job_has_changes(jobs[0], A)
1268
1269 self.fake_jenkins.fakeRelease('.*-merge')
1270 self.waitUntilSettled()
1271 self.fake_jenkins.fakeRelease('.*-merge')
1272 self.waitUntilSettled()
1273 self.fake_jenkins.fakeRelease('.*-merge')
1274 self.waitUntilSettled()
1275
1276 assert len(jobs) == 9
1277 assert jobs[0].name == 'project1-test1'
1278 assert jobs[1].name == 'project1-test2'
1279 assert jobs[2].name == 'project1-project2-integration'
1280 assert jobs[3].name == 'project1-test1'
1281 assert jobs[4].name == 'project1-test2'
1282 assert jobs[5].name == 'project1-project2-integration'
1283 assert jobs[6].name == 'project1-test1'
1284 assert jobs[7].name == 'project1-test2'
1285 assert jobs[8].name == 'project1-project2-integration'
1286
1287 jobs[0].release()
1288 self.waitUntilSettled()
1289
1290 assert len(jobs) == 3 # test2, integration, merge for B
1291 assert self.countJobResults(finished_jobs, 'ABORTED') == 6
1292
1293 self.fake_jenkins.hold_jobs_in_build = False
1294 self.fake_jenkins.fakeRelease()
1295 self.waitUntilSettled()
1296
1297 assert len(jobs) == 0
1298 assert len(finished_jobs) == 20
James E. Blaircaec0c52012-08-22 14:52:22 -07001299
1300 assert A.data['status'] == 'NEW'
James E. Blairec590122012-08-22 15:19:31 -07001301 assert B.data['status'] == 'MERGED'
1302 assert C.data['status'] == 'MERGED'
1303 assert A.reported == 2
1304 assert B.reported == 2
1305 assert C.reported == 2
James E. Blair4ec821f2012-08-23 15:28:28 -07001306
1307 def test_nonvoting_job(self):
1308 "Test that non-voting jobs don't vote."
1309 A = self.fake_gerrit.addFakeChange('org/nonvoting-project',
1310 'master', 'A')
1311 A.addApproval('CRVW', 2)
1312 self.fake_jenkins.fakeAddFailTest('nonvoting-project-test2', A)
1313 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
1314
1315 self.waitUntilSettled()
1316 jobs = self.fake_jenkins.all_jobs
1317 finished_jobs = self.fake_jenkins.job_history
1318
1319 assert A.data['status'] == 'MERGED'
1320 assert A.reported == 2
1321 assert finished_jobs[0]['result'] == 'SUCCESS'
1322 assert finished_jobs[1]['result'] == 'SUCCESS'
1323 assert finished_jobs[2]['result'] == 'FAILURE'