blob: 862c3825da85d2dc2c690f84920b10ec4f149a16 [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
47logging.basicConfig(level=logging.DEBUG)
48
49
James E. Blair8cc15a82012-08-01 11:17:57 -070050def random_sha1():
51 return hashlib.sha1(str(random.random())).hexdigest()
52
53
James E. Blair4886cc12012-07-18 15:39:41 -070054class ChangeReference(git.Reference):
55 _common_path_default = "refs/changes"
56 _points_to_commits_only = True
57
58
59def init_repo(project):
60 parts = project.split('/')
61 path = os.path.join("/tmp/zuul-test/upstream", *parts[:-1])
62 if not os.path.exists(path):
63 os.makedirs(path)
64 path = os.path.join("/tmp/zuul-test/upstream", project)
65 repo = git.Repo.init(path)
66
67 fn = os.path.join(path, 'README')
68 f = open(fn, 'w')
69 f.write("test\n")
70 f.close()
71 repo.index.add([fn])
72 repo.index.commit('initial commit')
73 repo.create_head('master')
74 repo.create_tag('init')
75
76
James E. Blair973721f2012-08-15 10:19:43 -070077def add_fake_change_to_repo(project, branch, change_num, patchset, msg, fn):
James E. Blair4886cc12012-07-18 15:39:41 -070078 path = os.path.join("/tmp/zuul-test/upstream", project)
79 repo = git.Repo(path)
80 ref = ChangeReference.create(repo, '1/%s/%s' % (change_num,
81 patchset),
82 'refs/tags/init')
83 repo.head.reference = ref
84 repo.head.reset(index=True, working_tree=True)
85 repo.git.clean('-x', '-f', '-d')
86
87 path = os.path.join("/tmp/zuul-test/upstream", project)
James E. Blair973721f2012-08-15 10:19:43 -070088 fn = os.path.join(path, fn)
James E. Blair4886cc12012-07-18 15:39:41 -070089 f = open(fn, 'w')
James E. Blair973721f2012-08-15 10:19:43 -070090 f.write("test %s %s %s\n" % (branch, change_num, patchset))
James E. Blair4886cc12012-07-18 15:39:41 -070091 f.close()
92 repo.index.add([fn])
93 repo.index.commit(msg)
94
95
96def ref_has_change(ref, change):
97 path = os.path.join("/tmp/zuul-test/git", change.project)
98 repo = git.Repo(path)
99 for commit in repo.iter_commits(ref):
100 if commit.message.strip() == ('%s-1' % change.subject):
101 return True
102 return False
103
104
105def job_has_changes(*args):
106 job = args[0]
107 commits = args[1:]
108 project = job.parameters['ZUUL_PROJECT']
109 path = os.path.join("/tmp/zuul-test/git", project)
110 repo = git.Repo(path)
111 ref = job.parameters['ZUUL_REF']
112 repo_messages = [c.message.strip() for c in repo.iter_commits(ref)]
113 commit_messages = ['%s-1' % commit.subject for commit in commits]
114 print 'checking that job %s has changes:' % ref
115 print ' commit messages:', commit_messages
116 print ' repo messages :', repo_messages
117 for msg in commit_messages:
118 if msg not in repo_messages:
119 return False
120 return True
121
122
James E. Blairb0fcae42012-07-17 11:12:10 -0700123class FakeChange(object):
James E. Blair8c803f82012-07-31 16:25:42 -0700124 categories = {'APRV': ('Approved', -1, 1),
125 'CRVW': ('Code-Review', -2, 2),
126 'VRFY': ('Verified', -2, 2)}
James E. Blairb0fcae42012-07-17 11:12:10 -0700127
James E. Blair8cc15a82012-08-01 11:17:57 -0700128 def __init__(self, gerrit, number, project, branch, subject, status='NEW'):
129 self.gerrit = gerrit
James E. Blaird466dc42012-07-31 10:42:56 -0700130 self.reported = 0
James E. Blair8c803f82012-07-31 16:25:42 -0700131 self.queried = 0
James E. Blairb0fcae42012-07-17 11:12:10 -0700132 self.patchsets = []
James E. Blairb0fcae42012-07-17 11:12:10 -0700133 self.number = number
134 self.project = project
135 self.branch = branch
136 self.subject = subject
137 self.latest_patchset = 0
James E. Blair8c803f82012-07-31 16:25:42 -0700138 self.depends_on_change = None
139 self.needed_by_changes = []
James E. Blairb0fcae42012-07-17 11:12:10 -0700140 self.data = {
141 'branch': branch,
142 'comments': [],
143 'commitMessage': subject,
144 'createdOn': time.time(),
James E. Blair8cc15a82012-08-01 11:17:57 -0700145 'id': 'I' + random_sha1(),
James E. Blairb0fcae42012-07-17 11:12:10 -0700146 'lastUpdated': time.time(),
147 'number': str(number),
148 'open': True,
149 'owner': {'email': 'user@example.com',
150 'name': 'User Name',
151 'username': 'username'},
152 'patchSets': self.patchsets,
153 'project': project,
154 'status': status,
155 'subject': subject,
James E. Blair8c803f82012-07-31 16:25:42 -0700156 'submitRecords': [],
James E. Blairb0fcae42012-07-17 11:12:10 -0700157 'url': 'https://hostname/%s' % number}
158
159 self.addPatchset()
James E. Blair8c803f82012-07-31 16:25:42 -0700160 self.data['submitRecords'] = self.getSubmitRecords()
James E. Blairb0fcae42012-07-17 11:12:10 -0700161
162 def addPatchset(self, files=None):
163 self.latest_patchset += 1
164 d = {'approvals': [],
165 'createdOn': time.time(),
166 'files': [{'file': '/COMMIT_MSG',
167 'type': 'ADDED'},
168 {'file': 'README',
169 'type': 'MODIFIED'}],
James E. Blair8c803f82012-07-31 16:25:42 -0700170 'number': str(self.latest_patchset),
James E. Blairb0fcae42012-07-17 11:12:10 -0700171 'ref': 'refs/changes/1/%s/%s' % (self.number,
172 self.latest_patchset),
James E. Blair8cc15a82012-08-01 11:17:57 -0700173 'revision': random_sha1(),
James E. Blairb0fcae42012-07-17 11:12:10 -0700174 'uploader': {'email': 'user@example.com',
175 'name': 'User name',
176 'username': 'user'}}
177 self.data['currentPatchSet'] = d
178 self.patchsets.append(d)
James E. Blair8c803f82012-07-31 16:25:42 -0700179 self.data['submitRecords'] = self.getSubmitRecords()
James E. Blair973721f2012-08-15 10:19:43 -0700180 if files:
181 fn = files[0]
182 else:
183 fn = '%s-%s' % (self.branch, self.number)
James E. Blair4886cc12012-07-18 15:39:41 -0700184 add_fake_change_to_repo(self.project, self.branch,
185 self.number, self.latest_patchset,
James E. Blair973721f2012-08-15 10:19:43 -0700186 self.subject + '-' + str(self.latest_patchset),
187 fn)
James E. Blairb0fcae42012-07-17 11:12:10 -0700188
189 def addApproval(self, category, value):
James E. Blair8c803f82012-07-31 16:25:42 -0700190 approval = {'description': self.categories[category][0],
191 'type': category,
192 'value': str(value)}
193 self.patchsets[-1]['approvals'].append(approval)
194 event = {'approvals': [approval],
James E. Blairb0fcae42012-07-17 11:12:10 -0700195 'author': {'email': 'user@example.com',
196 'name': 'User Name',
197 'username': 'username'},
198 'change': {'branch': self.branch,
199 'id': 'Iaa69c46accf97d0598111724a38250ae76a22c87',
200 'number': str(self.number),
201 'owner': {'email': 'user@example.com',
202 'name': 'User Name',
203 'username': 'username'},
204 'project': self.project,
205 'subject': self.subject,
206 'topic': 'master',
207 'url': 'https://hostname/459'},
208 'comment': '',
209 'patchSet': self.patchsets[-1],
210 'type': 'comment-added'}
James E. Blair8c803f82012-07-31 16:25:42 -0700211 self.data['submitRecords'] = self.getSubmitRecords()
James E. Blairb0fcae42012-07-17 11:12:10 -0700212 return json.loads(json.dumps(event))
213
James E. Blair8c803f82012-07-31 16:25:42 -0700214 def getSubmitRecords(self):
215 status = {}
216 for cat in self.categories.keys():
217 status[cat] = 0
218
219 for a in self.patchsets[-1]['approvals']:
220 cur = status[a['type']]
221 cat_min, cat_max = self.categories[a['type']][1:]
222 new = int(a['value'])
223 if new == cat_min:
224 cur = new
225 elif abs(new) > abs(cur):
226 cur = new
227 status[a['type']] = cur
228
229 labels = []
230 ok = True
231 for typ, cat in self.categories.items():
232 cur = status[typ]
233 cat_min, cat_max = cat[1:]
234 if cur == cat_min:
235 value = 'REJECT'
236 ok = False
237 elif cur == cat_max:
238 value = 'OK'
239 else:
240 value = 'NEED'
241 ok = False
242 labels.append({'label': cat[0], 'status': value})
243 if ok:
244 return [{'status': 'OK'}]
245 return [{'status': 'NOT_READY',
246 'labels': labels}]
247
248 def setDependsOn(self, other, patchset):
249 self.depends_on_change = other
250 d = {'id': other.data['id'],
251 'number': other.data['number'],
252 'ref': other.patchsets[patchset - 1]['ref']
253 }
254 self.data['dependsOn'] = [d]
255
256 other.needed_by_changes.append(self)
257 needed = other.data.get('neededBy', [])
258 d = {'id': self.data['id'],
259 'number': self.data['number'],
260 'ref': self.patchsets[patchset - 1]['ref'],
261 'revision': self.patchsets[patchset - 1]['revision']
262 }
263 needed.append(d)
264 other.data['neededBy'] = needed
265
James E. Blairb0fcae42012-07-17 11:12:10 -0700266 def query(self):
James E. Blair8c803f82012-07-31 16:25:42 -0700267 self.queried += 1
268 d = self.data.get('dependsOn')
269 if d:
270 d = d[0]
271 if (self.depends_on_change.patchsets[-1]['ref'] == d['ref']):
272 d['isCurrentPatchSet'] = True
273 else:
274 d['isCurrentPatchSet'] = False
James E. Blairb0fcae42012-07-17 11:12:10 -0700275 return json.loads(json.dumps(self.data))
276
277 def setMerged(self):
278 self.data['status'] = 'MERGED'
279 self.open = False
James E. Blair8cc15a82012-08-01 11:17:57 -0700280 branch_heads = self.gerrit.heads[self.project]
281 branch_heads[self.branch] = self.patchsets[-1]['revision']
James E. Blairb0fcae42012-07-17 11:12:10 -0700282
James E. Blaird466dc42012-07-31 10:42:56 -0700283 def setReported(self):
284 self.reported += 1
285
James E. Blairb0fcae42012-07-17 11:12:10 -0700286
287class FakeGerrit(object):
288 def __init__(self, *args, **kw):
289 self.event_queue = Queue.Queue()
290 self.fixture_dir = os.path.join(FIXTURE_DIR, 'gerrit')
291 self.change_number = 0
292 self.changes = {}
James E. Blair8cc15a82012-08-01 11:17:57 -0700293 self.heads = {}
James E. Blairb0fcae42012-07-17 11:12:10 -0700294
295 def addFakeChange(self, project, branch, subject):
James E. Blair8cc15a82012-08-01 11:17:57 -0700296 branch_heads = self.heads.get(project, {})
297 if branch not in branch_heads:
298 branch_heads[branch] = random_sha1()
299 self.heads[project] = branch_heads
300
James E. Blairb0fcae42012-07-17 11:12:10 -0700301 self.change_number += 1
James E. Blair8cc15a82012-08-01 11:17:57 -0700302 c = FakeChange(self, self.change_number, project, branch, subject)
James E. Blairb0fcae42012-07-17 11:12:10 -0700303 self.changes[self.change_number] = c
304 return c
305
306 def addEvent(self, data):
307 return self.event_queue.put(data)
308
309 def getEvent(self):
310 return self.event_queue.get()
311
312 def eventDone(self):
313 self.event_queue.task_done()
314
315 def review(self, project, changeid, message, action):
James E. Blaird466dc42012-07-31 10:42:56 -0700316 number, ps = changeid.split(',')
317 change = self.changes[int(number)]
James E. Blairb0fcae42012-07-17 11:12:10 -0700318 if 'submit' in action:
James E. Blairb0fcae42012-07-17 11:12:10 -0700319 change.setMerged()
James E. Blaird466dc42012-07-31 10:42:56 -0700320 if message:
321 change.setReported()
James E. Blairb0fcae42012-07-17 11:12:10 -0700322
323 def query(self, number):
324 change = self.changes[int(number)]
325 return change.query()
326
327 def startWatching(self, *args, **kw):
328 pass
329
330
331class FakeJenkinsEvent(object):
332 def __init__(self, name, number, parameters, phase, status=None):
333 data = {'build':
334 {'full_url': 'https://server/job/%s/%s/' % (name, number),
335 'number': number,
336 'parameters': parameters,
337 'phase': phase,
338 'url': 'job/%s/%s/' % (name, number)},
339 'name': name,
340 'url': 'job/%s/' % name}
341 if status:
342 data['build']['status'] = status
343 self.body = json.dumps(data)
344
345
346class FakeJenkinsJob(threading.Thread):
347 log = logging.getLogger("zuul.test")
348
349 def __init__(self, jenkins, callback, name, number, parameters):
350 threading.Thread.__init__(self)
351 self.jenkins = jenkins
352 self.callback = callback
353 self.name = name
354 self.number = number
355 self.parameters = parameters
356 self.wait_condition = threading.Condition()
357 self.waiting = False
James E. Blaird466dc42012-07-31 10:42:56 -0700358 self.aborted = False
359 self.canceled = False
360 self.created = time.time()
James E. Blairb0fcae42012-07-17 11:12:10 -0700361
362 def release(self):
363 self.wait_condition.acquire()
364 self.wait_condition.notify()
365 self.waiting = False
366 self.log.debug("Job %s released" % (self.parameters['UUID']))
367 self.wait_condition.release()
368
369 def isWaiting(self):
370 self.wait_condition.acquire()
371 if self.waiting:
372 ret = True
373 else:
374 ret = False
375 self.wait_condition.release()
376 return ret
377
378 def _wait(self):
379 self.wait_condition.acquire()
380 self.waiting = True
381 self.log.debug("Job %s waiting" % (self.parameters['UUID']))
382 self.wait_condition.wait()
383 self.wait_condition.release()
384
385 def run(self):
386 self.jenkins.fakeEnqueue(self)
387 if self.jenkins.hold_jobs_in_queue:
388 self._wait()
389 self.jenkins.fakeDequeue(self)
James E. Blaird466dc42012-07-31 10:42:56 -0700390 if self.canceled:
391 self.jenkins.all_jobs.remove(self)
392 return
James E. Blairb0fcae42012-07-17 11:12:10 -0700393 self.callback.jenkins_endpoint(FakeJenkinsEvent(
394 self.name, self.number, self.parameters,
395 'STARTED'))
396 if self.jenkins.hold_jobs_in_build:
397 self._wait()
398 self.log.debug("Job %s continuing" % (self.parameters['UUID']))
James E. Blairb02a3bb2012-07-30 17:49:55 -0700399
400 result = 'SUCCESS'
401 if self.jenkins.fakeShouldFailTest(
402 self.name,
James E. Blair4886cc12012-07-18 15:39:41 -0700403 self.parameters['ZUUL_REF']):
James E. Blairb02a3bb2012-07-30 17:49:55 -0700404 result = 'FAILURE'
James E. Blaird466dc42012-07-31 10:42:56 -0700405 if self.aborted:
406 result = 'ABORTED'
James E. Blairb02a3bb2012-07-30 17:49:55 -0700407
James E. Blairb0fcae42012-07-17 11:12:10 -0700408 self.jenkins.fakeAddHistory(name=self.name, number=self.number,
James E. Blairb02a3bb2012-07-30 17:49:55 -0700409 result=result)
James E. Blairb0fcae42012-07-17 11:12:10 -0700410 self.callback.jenkins_endpoint(FakeJenkinsEvent(
411 self.name, self.number, self.parameters,
James E. Blairb02a3bb2012-07-30 17:49:55 -0700412 'COMPLETED', result))
James E. Blairb0fcae42012-07-17 11:12:10 -0700413 self.callback.jenkins_endpoint(FakeJenkinsEvent(
414 self.name, self.number, self.parameters,
James E. Blairb02a3bb2012-07-30 17:49:55 -0700415 'FINISHED', result))
James E. Blairb0fcae42012-07-17 11:12:10 -0700416 self.jenkins.all_jobs.remove(self)
417
418
419class FakeJenkins(object):
420 log = logging.getLogger("zuul.test")
421
422 def __init__(self, *args, **kw):
423 self.queue = []
424 self.all_jobs = []
425 self.job_counter = {}
James E. Blaird466dc42012-07-31 10:42:56 -0700426 self.queue_counter = 0
James E. Blairb0fcae42012-07-17 11:12:10 -0700427 self.job_history = []
428 self.hold_jobs_in_queue = False
429 self.hold_jobs_in_build = False
James E. Blairb02a3bb2012-07-30 17:49:55 -0700430 self.fail_tests = {}
James E. Blairb0fcae42012-07-17 11:12:10 -0700431
432 def fakeEnqueue(self, job):
433 self.queue.append(job)
434
435 def fakeDequeue(self, job):
436 self.queue.remove(job)
437
438 def fakeAddHistory(self, **kw):
439 self.job_history.append(kw)
440
441 def fakeRelease(self, regex=None):
442 all_jobs = self.all_jobs[:]
443 self.log.debug("releasing jobs %s (%s)" % (regex, len(self.all_jobs)))
444 for job in all_jobs:
445 if not regex or re.match(regex, job.name):
446 self.log.debug("releasing job %s" % (job.parameters['UUID']))
447 job.release()
448 else:
449 self.log.debug("not releasing job %s" % (
450 job.parameters['UUID']))
451 self.log.debug("done releasing jobs %s (%s)" % (regex,
452 len(self.all_jobs)))
453
454 def fakeAllWaiting(self, regex=None):
455 all_jobs = self.all_jobs[:]
456 for job in all_jobs:
457 self.log.debug("job %s %s" % (job.parameters['UUID'],
458 job.isWaiting()))
459 if not job.isWaiting():
460 return False
461 return True
462
James E. Blairb02a3bb2012-07-30 17:49:55 -0700463 def fakeAddFailTest(self, name, change):
464 l = self.fail_tests.get(name, [])
465 l.append(change)
466 self.fail_tests[name] = l
467
James E. Blair4886cc12012-07-18 15:39:41 -0700468 def fakeShouldFailTest(self, name, ref):
James E. Blairb02a3bb2012-07-30 17:49:55 -0700469 l = self.fail_tests.get(name, [])
470 for change in l:
James E. Blair4886cc12012-07-18 15:39:41 -0700471 if ref_has_change(ref, change):
James E. Blairb02a3bb2012-07-30 17:49:55 -0700472 return True
473 return False
474
James E. Blairb0fcae42012-07-17 11:12:10 -0700475 def build_job(self, name, parameters):
476 count = self.job_counter.get(name, 0)
477 count += 1
478 self.job_counter[name] = count
James E. Blaird466dc42012-07-31 10:42:56 -0700479
480 queue_count = self.queue_counter
481 self.queue_counter += 1
James E. Blairb0fcae42012-07-17 11:12:10 -0700482 job = FakeJenkinsJob(self, self.callback, name, count, parameters)
James E. Blaird466dc42012-07-31 10:42:56 -0700483 job.queue_id = queue_count
484
James E. Blairb0fcae42012-07-17 11:12:10 -0700485 self.all_jobs.append(job)
486 job.start()
487
James E. Blaird466dc42012-07-31 10:42:56 -0700488 def stop_build(self, name, number):
489 for job in self.all_jobs:
490 if job.name == name and job.number == number:
491 job.aborted = True
492 job.release()
493 return
494
495 def cancel_queue(self, id):
496 for job in self.queue:
497 if job.queue_id == id:
498 job.canceled = True
499 job.release()
500 return
501
502 def get_queue_info(self):
503 items = []
504 for job in self.queue:
505 paramstr = ''
506 paramlst = []
507 d = {'actions': [{'parameters': paramlst},
508 {'causes': [{'shortDescription':
509 'Started by user Jenkins',
510 'userId': 'jenkins',
511 'userName': 'Jenkins'}]}],
512 'blocked': False,
513 'buildable': True,
514 'buildableStartMilliseconds': (job.created * 1000) + 5,
515 'id': job.queue_id,
516 'inQueueSince': (job.created * 1000),
517 'params': paramstr,
518 'stuck': False,
519 'task': {'color': 'blue',
520 'name': job.name,
521 'url': 'https://server/job/%s/' % job.name},
522 'why': 'Waiting for next available executor'}
523 for k, v in job.parameters.items():
524 paramstr += "\n(StringParameterValue) %s='%s'" % (k, v)
525 pd = {'name': k, 'value': v}
526 paramlst.append(pd)
527 items.append(d)
528 return items
529
James E. Blairb0fcae42012-07-17 11:12:10 -0700530 def set_build_description(self, *args, **kw):
531 pass
532
533
534class FakeJenkinsCallback(zuul.launcher.jenkins.JenkinsCallback):
535 def start(self):
536 pass
537
538
James E. Blair8cc15a82012-08-01 11:17:57 -0700539class FakeURLOpener(object):
540 def __init__(self, fake_gerrit, url):
541 self.fake_gerrit = fake_gerrit
542 self.url = url
543
544 def read(self):
545 res = urlparse.urlparse(self.url)
546 path = res.path
547 project = '/'.join(path.split('/')[2:-2])
548 heads = self.fake_gerrit.heads[project]
549 ret = ''
550 for change in self.fake_gerrit.changes.values():
551 for ps in change.patchsets:
552 ret += ps['revision'] + '\t' + ps['ref'] + '\n'
553 for head, ref in heads.items():
554 ret += ref + '\trefs/heads/' + head + '\n'
555 return ret
556
557
James E. Blair4886cc12012-07-18 15:39:41 -0700558class FakeGerritTrigger(zuul.trigger.gerrit.Gerrit):
559 def getGitUrl(self, project):
560 return "/tmp/zuul-test/upstream/%s" % project
561
562
James E. Blairb0fcae42012-07-17 11:12:10 -0700563class testScheduler(unittest.TestCase):
564 log = logging.getLogger("zuul.test")
565
566 def setUp(self):
James E. Blair4886cc12012-07-18 15:39:41 -0700567 if os.path.exists("/tmp/zuul-test"):
568 shutil.rmtree("/tmp/zuul-test")
569 os.makedirs("/tmp/zuul-test")
570 os.makedirs("/tmp/zuul-test/upstream")
571 os.makedirs("/tmp/zuul-test/git")
572
573 # For each project in config:
574 init_repo("org/project")
575 init_repo("org/project1")
576 init_repo("org/project2")
James E. Blairb0fcae42012-07-17 11:12:10 -0700577 self.config = CONFIG
578 self.sched = zuul.scheduler.Scheduler()
579
580 def jenkinsFactory(*args, **kw):
581 self.fake_jenkins = FakeJenkins()
582 return self.fake_jenkins
583
584 def jenkinsCallbackFactory(*args, **kw):
585 self.fake_jenkins_callback = FakeJenkinsCallback(*args, **kw)
586 return self.fake_jenkins_callback
587
James E. Blair8cc15a82012-08-01 11:17:57 -0700588 def URLOpenerFactory(*args, **kw):
589 args = [self.fake_gerrit] + list(args)
590 return FakeURLOpener(*args, **kw)
591
James E. Blairb0fcae42012-07-17 11:12:10 -0700592 zuul.launcher.jenkins.ExtendedJenkins = jenkinsFactory
593 zuul.launcher.jenkins.JenkinsCallback = jenkinsCallbackFactory
James E. Blair8cc15a82012-08-01 11:17:57 -0700594 urllib2.urlopen = URLOpenerFactory
James E. Blairb0fcae42012-07-17 11:12:10 -0700595 self.jenkins = zuul.launcher.jenkins.Jenkins(self.config, self.sched)
596 self.fake_jenkins.callback = self.fake_jenkins_callback
597
598 zuul.lib.gerrit.Gerrit = FakeGerrit
599
James E. Blair4886cc12012-07-18 15:39:41 -0700600 self.gerrit = FakeGerritTrigger(self.config, self.sched)
James E. Blair8cc15a82012-08-01 11:17:57 -0700601 self.gerrit.replication_timeout = 1.5
602 self.gerrit.replication_retry_interval = 0.5
James E. Blairb0fcae42012-07-17 11:12:10 -0700603 self.fake_gerrit = self.gerrit.gerrit
604
605 self.sched.setLauncher(self.jenkins)
606 self.sched.setTrigger(self.gerrit)
607
608 self.sched.start()
609 self.sched.reconfigure(self.config)
610 self.sched.resume()
611
612 def tearDown(self):
613 self.jenkins.stop()
614 self.gerrit.stop()
615 self.sched.stop()
616 self.sched.join()
James E. Blair4886cc12012-07-18 15:39:41 -0700617 #shutil.rmtree("/tmp/zuul-test")
James E. Blairb0fcae42012-07-17 11:12:10 -0700618
619 def waitUntilSettled(self):
620 self.log.debug("Waiting until settled...")
621 start = time.time()
622 while True:
623 if time.time() - start > 10:
624 print 'queue status:',
625 print self.sched.trigger_event_queue.empty(),
626 print self.sched.result_event_queue.empty(),
627 print self.fake_gerrit.event_queue.empty(),
628 raise Exception("Timeout waiting for Zuul to settle")
629 self.fake_gerrit.event_queue.join()
630 self.sched.queue_lock.acquire()
631 if (self.sched.trigger_event_queue.empty() and
632 self.sched.result_event_queue.empty() and
633 self.fake_gerrit.event_queue.empty() and
634 self.fake_jenkins.fakeAllWaiting()):
635 self.sched.queue_lock.release()
636 self.log.debug("...settled.")
637 return
638 self.sched.queue_lock.release()
639 self.sched.wake_event.wait(0.1)
640
James E. Blaird466dc42012-07-31 10:42:56 -0700641 def countJobResults(self, jobs, result):
642 jobs = filter(lambda x: x['result'] == result, jobs)
643 return len(jobs)
644
James E. Blairb0fcae42012-07-17 11:12:10 -0700645 def test_jobs_launched(self):
646 "Test that jobs are launched and a change is merged"
647 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
James E. Blair8c803f82012-07-31 16:25:42 -0700648 A.addApproval('CRVW', 2)
James E. Blairb0fcae42012-07-17 11:12:10 -0700649 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
650 self.waitUntilSettled()
651 jobs = self.fake_jenkins.job_history
652 job_names = [x['name'] for x in jobs]
653 assert 'project-merge' in job_names
654 assert 'project-test1' in job_names
655 assert 'project-test2' in job_names
656 assert jobs[0]['result'] == 'SUCCESS'
657 assert jobs[1]['result'] == 'SUCCESS'
658 assert jobs[2]['result'] == 'SUCCESS'
659 assert A.data['status'] == 'MERGED'
James E. Blaird466dc42012-07-31 10:42:56 -0700660 assert A.reported == 2
James E. Blairb0fcae42012-07-17 11:12:10 -0700661
662 def test_parallel_changes(self):
663 "Test that changes are tested in parallel and merged in series"
664 self.fake_jenkins.hold_jobs_in_build = True
665 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
666 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
667 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
James E. Blair8c803f82012-07-31 16:25:42 -0700668 A.addApproval('CRVW', 2)
669 B.addApproval('CRVW', 2)
670 C.addApproval('CRVW', 2)
James E. Blairb0fcae42012-07-17 11:12:10 -0700671
672 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
673 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
674 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
675
676 self.waitUntilSettled()
677 jobs = self.fake_jenkins.all_jobs
678 assert len(jobs) == 1
679 assert jobs[0].name == 'project-merge'
James E. Blair4886cc12012-07-18 15:39:41 -0700680 assert job_has_changes(jobs[0], A)
James E. Blairb0fcae42012-07-17 11:12:10 -0700681
682 self.fake_jenkins.fakeRelease('.*-merge')
683 self.waitUntilSettled()
684 assert len(jobs) == 3
685 assert jobs[0].name == 'project-test1'
James E. Blair4886cc12012-07-18 15:39:41 -0700686 assert job_has_changes(jobs[0], A)
James E. Blairb0fcae42012-07-17 11:12:10 -0700687 assert jobs[1].name == 'project-test2'
James E. Blair4886cc12012-07-18 15:39:41 -0700688 assert job_has_changes(jobs[1], A)
James E. Blairb0fcae42012-07-17 11:12:10 -0700689 assert jobs[2].name == 'project-merge'
James E. Blair4886cc12012-07-18 15:39:41 -0700690 assert job_has_changes(jobs[2], A, B)
James E. Blairb0fcae42012-07-17 11:12:10 -0700691
692 self.fake_jenkins.fakeRelease('.*-merge')
693 self.waitUntilSettled()
694 assert len(jobs) == 5
695 assert jobs[0].name == 'project-test1'
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 assert jobs[1].name == 'project-test2'
James E. Blair4886cc12012-07-18 15:39:41 -0700698 assert job_has_changes(jobs[1], A)
James E. Blairb0fcae42012-07-17 11:12:10 -0700699
700 assert jobs[2].name == 'project-test1'
James E. Blair4886cc12012-07-18 15:39:41 -0700701 assert job_has_changes(jobs[2], A, B)
James E. Blairb0fcae42012-07-17 11:12:10 -0700702 assert jobs[3].name == 'project-test2'
James E. Blair4886cc12012-07-18 15:39:41 -0700703 assert job_has_changes(jobs[3], A, B)
James E. Blairb0fcae42012-07-17 11:12:10 -0700704
705 assert jobs[4].name == 'project-merge'
James E. Blair4886cc12012-07-18 15:39:41 -0700706 assert job_has_changes(jobs[4], A, B, C)
James E. Blairb0fcae42012-07-17 11:12:10 -0700707
708 self.fake_jenkins.fakeRelease('.*-merge')
709 self.waitUntilSettled()
710 assert len(jobs) == 6
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-test1'
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 assert jobs[5].name == 'project-test2'
James E. Blair4886cc12012-07-18 15:39:41 -0700724 assert job_has_changes(jobs[5], A, B, C)
James E. Blairb0fcae42012-07-17 11:12:10 -0700725
726 self.fake_jenkins.hold_jobs_in_build = False
727 self.fake_jenkins.fakeRelease()
728 self.waitUntilSettled()
729 assert len(jobs) == 0
730
731 jobs = self.fake_jenkins.job_history
732 assert len(jobs) == 9
733 assert A.data['status'] == 'MERGED'
734 assert B.data['status'] == 'MERGED'
735 assert C.data['status'] == 'MERGED'
James E. Blaird466dc42012-07-31 10:42:56 -0700736 assert A.reported == 2
737 assert B.reported == 2
738 assert C.reported == 2
James E. Blairb02a3bb2012-07-30 17:49:55 -0700739
740 def test_failed_changes(self):
741 "Test that a change behind a failed change is retested"
742 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
743 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
James E. Blair8c803f82012-07-31 16:25:42 -0700744 A.addApproval('CRVW', 2)
745 B.addApproval('CRVW', 2)
James E. Blairb02a3bb2012-07-30 17:49:55 -0700746
747 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
748 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
749
James E. Blair4886cc12012-07-18 15:39:41 -0700750 self.fake_jenkins.fakeAddFailTest('project-test1', A)
James E. Blairb02a3bb2012-07-30 17:49:55 -0700751
752 self.waitUntilSettled()
753 jobs = self.fake_jenkins.job_history
754 assert len(jobs) > 6
755 assert A.data['status'] == 'NEW'
756 assert B.data['status'] == 'MERGED'
James E. Blaird466dc42012-07-31 10:42:56 -0700757 assert A.reported == 2
758 assert B.reported == 2
James E. Blairb02a3bb2012-07-30 17:49:55 -0700759
760 def test_independent_queues(self):
761 "Test that changes end up in the right queues"
762 self.fake_jenkins.hold_jobs_in_build = True
763 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
764 B = self.fake_gerrit.addFakeChange('org/project1', 'master', 'B')
765 C = self.fake_gerrit.addFakeChange('org/project2', 'master', 'C')
James E. Blair8c803f82012-07-31 16:25:42 -0700766 A.addApproval('CRVW', 2)
767 B.addApproval('CRVW', 2)
768 C.addApproval('CRVW', 2)
James E. Blairb02a3bb2012-07-30 17:49:55 -0700769
770 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
771 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
772 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
773
774 jobs = self.fake_jenkins.all_jobs
775 self.waitUntilSettled()
776
777 # There should be one merge job at the head of each queue running
778 assert len(jobs) == 2
779 assert jobs[0].name == 'project-merge'
James E. Blair4886cc12012-07-18 15:39:41 -0700780 assert job_has_changes(jobs[0], A)
James E. Blairb02a3bb2012-07-30 17:49:55 -0700781 assert jobs[1].name == 'project1-merge'
James E. Blair4886cc12012-07-18 15:39:41 -0700782 assert job_has_changes(jobs[1], B)
James E. Blairb02a3bb2012-07-30 17:49:55 -0700783
784 # Release the current merge jobs
785 self.fake_jenkins.fakeRelease('.*-merge')
786 self.waitUntilSettled()
787 # Release the merge job for project2 which is behind project1
788 self.fake_jenkins.fakeRelease('.*-merge')
789 self.waitUntilSettled()
790
791 # All the test jobs should be running:
792 # project1 (3) + project2 (3) + project (2) = 8
793 assert len(jobs) == 8
794
795 self.fake_jenkins.fakeRelease()
796 self.waitUntilSettled()
797 assert len(jobs) == 0
798
799 jobs = self.fake_jenkins.job_history
800 assert len(jobs) == 11
801 assert A.data['status'] == 'MERGED'
802 assert B.data['status'] == 'MERGED'
803 assert C.data['status'] == 'MERGED'
James E. Blaird466dc42012-07-31 10:42:56 -0700804 assert A.reported == 2
805 assert B.reported == 2
806 assert C.reported == 2
807
808 def test_failed_change_at_head(self):
809 "Test that if a change at the head fails, jobs behind it are canceled"
810 self.fake_jenkins.hold_jobs_in_build = True
811
812 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
813 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
814 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
James E. Blair8c803f82012-07-31 16:25:42 -0700815 A.addApproval('CRVW', 2)
816 B.addApproval('CRVW', 2)
817 C.addApproval('CRVW', 2)
James E. Blaird466dc42012-07-31 10:42:56 -0700818
James E. Blair4886cc12012-07-18 15:39:41 -0700819 self.fake_jenkins.fakeAddFailTest('project-test1', A)
James E. Blaird466dc42012-07-31 10:42:56 -0700820
821 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
822 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
823 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
824
825 self.waitUntilSettled()
826 jobs = self.fake_jenkins.all_jobs
827 finished_jobs = self.fake_jenkins.job_history
828
829 assert len(jobs) == 1
830 assert jobs[0].name == 'project-merge'
James E. Blair4886cc12012-07-18 15:39:41 -0700831 assert job_has_changes(jobs[0], A)
James E. Blaird466dc42012-07-31 10:42:56 -0700832
833 self.fake_jenkins.fakeRelease('.*-merge')
834 self.waitUntilSettled()
835 self.fake_jenkins.fakeRelease('.*-merge')
836 self.waitUntilSettled()
837 self.fake_jenkins.fakeRelease('.*-merge')
838 self.waitUntilSettled()
839
840 assert len(jobs) == 6
841 assert jobs[0].name == 'project-test1'
842 assert jobs[1].name == 'project-test2'
843 assert jobs[2].name == 'project-test1'
844 assert jobs[3].name == 'project-test2'
845 assert jobs[4].name == 'project-test1'
846 assert jobs[5].name == 'project-test2'
847
848 jobs[0].release()
849 self.waitUntilSettled()
850
851 assert len(jobs) == 1 # project-test2
852 assert self.countJobResults(finished_jobs, 'ABORTED') == 4
853
854 self.fake_jenkins.hold_jobs_in_build = False
855 self.fake_jenkins.fakeRelease()
856 self.waitUntilSettled()
857
858 assert len(jobs) == 0
859 assert len(finished_jobs) == 15
860 assert A.data['status'] == 'NEW'
861 assert B.data['status'] == 'MERGED'
862 assert C.data['status'] == 'MERGED'
863 assert A.reported == 2
864 assert B.reported == 2
865 assert C.reported == 2
866
867 def test_failed_change_at_head_with_queue(self):
868 "Test that if a change at the head fails, queued jobs are canceled"
869 self.fake_jenkins.hold_jobs_in_queue = True
870
871 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
872 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
873 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
James E. Blair8c803f82012-07-31 16:25:42 -0700874 A.addApproval('CRVW', 2)
875 B.addApproval('CRVW', 2)
876 C.addApproval('CRVW', 2)
James E. Blaird466dc42012-07-31 10:42:56 -0700877
James E. Blair4886cc12012-07-18 15:39:41 -0700878 self.fake_jenkins.fakeAddFailTest('project-test1', A)
James E. Blaird466dc42012-07-31 10:42:56 -0700879
880 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
881 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
882 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
883
884 self.waitUntilSettled()
885 jobs = self.fake_jenkins.all_jobs
886 finished_jobs = self.fake_jenkins.job_history
887 queue = self.fake_jenkins.queue
888
889 assert len(jobs) == 1
890 assert len(queue) == 1
891 assert jobs[0].name == 'project-merge'
James E. Blair4886cc12012-07-18 15:39:41 -0700892 assert job_has_changes(jobs[0], A)
James E. Blaird466dc42012-07-31 10:42:56 -0700893
894 self.fake_jenkins.fakeRelease('.*-merge')
895 self.waitUntilSettled()
896 self.fake_jenkins.fakeRelease('.*-merge')
897 self.waitUntilSettled()
898 self.fake_jenkins.fakeRelease('.*-merge')
899 self.waitUntilSettled()
900
901 assert len(jobs) == 6
902 assert len(queue) == 6
903 assert jobs[0].name == 'project-test1'
904 assert jobs[1].name == 'project-test2'
905 assert jobs[2].name == 'project-test1'
906 assert jobs[3].name == 'project-test2'
907 assert jobs[4].name == 'project-test1'
908 assert jobs[5].name == 'project-test2'
909
910 jobs[0].release()
911 self.waitUntilSettled()
912
913 assert len(jobs) == 1 # project-test2
914 assert len(queue) == 1
915 assert self.countJobResults(finished_jobs, 'ABORTED') == 0
916
917 self.fake_jenkins.hold_jobs_in_queue = False
918 self.fake_jenkins.fakeRelease()
919 self.waitUntilSettled()
920
921 assert len(jobs) == 0
922 assert len(finished_jobs) == 11
923 assert A.data['status'] == 'NEW'
924 assert B.data['status'] == 'MERGED'
925 assert C.data['status'] == 'MERGED'
926 assert A.reported == 2
927 assert B.reported == 2
928 assert C.reported == 2
James E. Blair8c803f82012-07-31 16:25:42 -0700929
930 def test_patch_order(self):
931 "Test that dependent patches are tested in the right order"
932 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
933 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
934 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
935 A.addApproval('CRVW', 2)
936 B.addApproval('CRVW', 2)
937 C.addApproval('CRVW', 2)
938
939 M2 = self.fake_gerrit.addFakeChange('org/project', 'master', 'M2')
940 M1 = self.fake_gerrit.addFakeChange('org/project', 'master', 'M1')
941 M2.setMerged()
942 M1.setMerged()
943
944 # C -> B -> A -> M1 -> M2
945 # M2 is here to make sure it is never queried. If it is, it
946 # means zuul is walking down the entire history of merged
947 # changes.
948
949 C.setDependsOn(B, 1)
950 B.setDependsOn(A, 1)
951 A.setDependsOn(M1, 1)
952 M1.setDependsOn(M2, 1)
953
954 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
955
956 self.waitUntilSettled()
957
958 assert A.data['status'] == 'NEW'
959 assert B.data['status'] == 'NEW'
960 assert C.data['status'] == 'NEW'
961
962 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
963 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
964
965 self.waitUntilSettled()
966 assert M2.queried == 0
967 assert A.data['status'] == 'MERGED'
968 assert B.data['status'] == 'MERGED'
969 assert C.data['status'] == 'MERGED'
970 assert A.reported == 2
971 assert B.reported == 2
972 assert C.reported == 2
973
974 def test_can_merge(self):
James E. Blair4886cc12012-07-18 15:39:41 -0700975 "Test whether a change is ready to merge"
James E. Blair8c803f82012-07-31 16:25:42 -0700976 # TODO: move to test_gerrit (this is a unit test!)
977 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
James E. Blair4aea70c2012-07-26 14:23:24 -0700978 a = self.sched.trigger.getChange(1, 2)
979 mgr = self.sched.pipelines['gate'].manager
980 assert not self.sched.trigger.canMerge(a, mgr.getSubmitAllowNeeds())
James E. Blair8c803f82012-07-31 16:25:42 -0700981
982 A.addApproval('CRVW', 2)
James E. Blair4aea70c2012-07-26 14:23:24 -0700983 a = self.sched.trigger.getChange(1, 2)
984 assert not self.sched.trigger.canMerge(a, mgr.getSubmitAllowNeeds())
James E. Blair8c803f82012-07-31 16:25:42 -0700985
986 A.addApproval('APRV', 1)
James E. Blair4aea70c2012-07-26 14:23:24 -0700987 a = self.sched.trigger.getChange(1, 2)
988 assert self.sched.trigger.canMerge(a, mgr.getSubmitAllowNeeds())
James E. Blair8c803f82012-07-31 16:25:42 -0700989
990 return True
James E. Blair4886cc12012-07-18 15:39:41 -0700991
992 def test_build_configuration(self):
993 "Test that zuul merges the right commits for testing"
994 self.fake_jenkins.hold_jobs_in_queue = True
995 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
996 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
997 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
998 A.addApproval('CRVW', 2)
999 B.addApproval('CRVW', 2)
1000 C.addApproval('CRVW', 2)
1001 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
1002 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
1003 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
1004 self.waitUntilSettled()
1005
1006 jobs = self.fake_jenkins.all_jobs
1007
1008 self.fake_jenkins.fakeRelease('.*-merge')
1009 self.waitUntilSettled()
1010 self.fake_jenkins.fakeRelease('.*-merge')
1011 self.waitUntilSettled()
1012 self.fake_jenkins.fakeRelease('.*-merge')
1013 self.waitUntilSettled()
1014 ref = jobs[-1].parameters['ZUUL_REF']
1015 self.fake_jenkins.hold_jobs_in_queue = False
1016 self.fake_jenkins.fakeRelease()
James E. Blair973721f2012-08-15 10:19:43 -07001017 self.waitUntilSettled()
James E. Blair4886cc12012-07-18 15:39:41 -07001018
1019 path = os.path.join("/tmp/zuul-test/git/org/project")
1020 repo = git.Repo(path)
1021 repo_messages = [c.message.strip() for c in repo.iter_commits(ref)]
1022 repo_messages.reverse()
1023 print ' repo messages :', repo_messages
1024 correct_messages = ['initial commit', 'A-1', 'B-1', 'C-1']
1025 assert repo_messages == correct_messages
James E. Blair973721f2012-08-15 10:19:43 -07001026
1027 def test_build_configuration_conflict(self):
1028 "Test that merge conflicts are handled"
1029 self.fake_jenkins.hold_jobs_in_queue = True
1030 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
1031 A.addPatchset(['conflict'])
1032 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
1033 B.addPatchset(['conflict'])
1034 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
1035 A.addApproval('CRVW', 2)
1036 B.addApproval('CRVW', 2)
1037 C.addApproval('CRVW', 2)
1038 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
1039 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
1040 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
1041 self.waitUntilSettled()
1042
1043 jobs = self.fake_jenkins.all_jobs
1044
1045 self.fake_jenkins.fakeRelease('.*-merge')
1046 self.waitUntilSettled()
1047 self.fake_jenkins.fakeRelease('.*-merge')
1048 self.waitUntilSettled()
1049 self.fake_jenkins.fakeRelease('.*-merge')
1050 self.waitUntilSettled()
1051 ref = jobs[-1].parameters['ZUUL_REF']
1052 self.fake_jenkins.hold_jobs_in_queue = False
1053 self.fake_jenkins.fakeRelease()
1054 self.waitUntilSettled()
1055
1056 assert A.data['status'] == 'MERGED'
1057 assert B.data['status'] == 'NEW'
1058 assert C.data['status'] == 'MERGED'
1059 assert A.reported == 2
1060 assert B.reported == 2
1061 assert C.reported == 2