blob: a61e13cf26248e2607d46ec532279bd8038366ee [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. Blairb0fcae42012-07-17 11:12:10 -070031
32import zuul
33import zuul.scheduler
34import zuul.launcher.jenkins
35import zuul.trigger.gerrit
36
37FIXTURE_DIR = os.path.join(os.path.dirname(__file__),
38 'fixtures')
39CONFIG = ConfigParser.ConfigParser()
40CONFIG.read(os.path.join(FIXTURE_DIR, "zuul.conf"))
41
42CONFIG.set('zuul', 'layout_config',
43 os.path.join(FIXTURE_DIR, "layout.yaml"))
44
45logging.basicConfig(level=logging.DEBUG)
46
47
James E. Blair8cc15a82012-08-01 11:17:57 -070048def random_sha1():
49 return hashlib.sha1(str(random.random())).hexdigest()
50
51
James E. Blairb0fcae42012-07-17 11:12:10 -070052class FakeChange(object):
James E. Blair8c803f82012-07-31 16:25:42 -070053 categories = {'APRV': ('Approved', -1, 1),
54 'CRVW': ('Code-Review', -2, 2),
55 'VRFY': ('Verified', -2, 2)}
James E. Blairb0fcae42012-07-17 11:12:10 -070056
James E. Blair8cc15a82012-08-01 11:17:57 -070057 def __init__(self, gerrit, number, project, branch, subject, status='NEW'):
58 self.gerrit = gerrit
James E. Blaird466dc42012-07-31 10:42:56 -070059 self.reported = 0
James E. Blair8c803f82012-07-31 16:25:42 -070060 self.queried = 0
James E. Blairb0fcae42012-07-17 11:12:10 -070061 self.patchsets = []
James E. Blairb0fcae42012-07-17 11:12:10 -070062 self.number = number
63 self.project = project
64 self.branch = branch
65 self.subject = subject
66 self.latest_patchset = 0
James E. Blair8c803f82012-07-31 16:25:42 -070067 self.depends_on_change = None
68 self.needed_by_changes = []
James E. Blairb0fcae42012-07-17 11:12:10 -070069 self.data = {
70 'branch': branch,
71 'comments': [],
72 'commitMessage': subject,
73 'createdOn': time.time(),
James E. Blair8cc15a82012-08-01 11:17:57 -070074 'id': 'I' + random_sha1(),
James E. Blairb0fcae42012-07-17 11:12:10 -070075 'lastUpdated': time.time(),
76 'number': str(number),
77 'open': True,
78 'owner': {'email': 'user@example.com',
79 'name': 'User Name',
80 'username': 'username'},
81 'patchSets': self.patchsets,
82 'project': project,
83 'status': status,
84 'subject': subject,
James E. Blair8c803f82012-07-31 16:25:42 -070085 'submitRecords': [],
James E. Blairb0fcae42012-07-17 11:12:10 -070086 'url': 'https://hostname/%s' % number}
87
88 self.addPatchset()
James E. Blair8c803f82012-07-31 16:25:42 -070089 self.data['submitRecords'] = self.getSubmitRecords()
James E. Blairb0fcae42012-07-17 11:12:10 -070090
91 def addPatchset(self, files=None):
92 self.latest_patchset += 1
93 d = {'approvals': [],
94 'createdOn': time.time(),
95 'files': [{'file': '/COMMIT_MSG',
96 'type': 'ADDED'},
97 {'file': 'README',
98 'type': 'MODIFIED'}],
James E. Blair8c803f82012-07-31 16:25:42 -070099 'number': str(self.latest_patchset),
James E. Blairb0fcae42012-07-17 11:12:10 -0700100 'ref': 'refs/changes/1/%s/%s' % (self.number,
101 self.latest_patchset),
James E. Blair8cc15a82012-08-01 11:17:57 -0700102 'revision': random_sha1(),
James E. Blairb0fcae42012-07-17 11:12:10 -0700103 'uploader': {'email': 'user@example.com',
104 'name': 'User name',
105 'username': 'user'}}
106 self.data['currentPatchSet'] = d
107 self.patchsets.append(d)
James E. Blair8c803f82012-07-31 16:25:42 -0700108 self.data['submitRecords'] = self.getSubmitRecords()
James E. Blairb0fcae42012-07-17 11:12:10 -0700109
110 def addApproval(self, category, value):
James E. Blair8c803f82012-07-31 16:25:42 -0700111 approval = {'description': self.categories[category][0],
112 'type': category,
113 'value': str(value)}
114 self.patchsets[-1]['approvals'].append(approval)
115 event = {'approvals': [approval],
James E. Blairb0fcae42012-07-17 11:12:10 -0700116 'author': {'email': 'user@example.com',
117 'name': 'User Name',
118 'username': 'username'},
119 'change': {'branch': self.branch,
120 'id': 'Iaa69c46accf97d0598111724a38250ae76a22c87',
121 'number': str(self.number),
122 'owner': {'email': 'user@example.com',
123 'name': 'User Name',
124 'username': 'username'},
125 'project': self.project,
126 'subject': self.subject,
127 'topic': 'master',
128 'url': 'https://hostname/459'},
129 'comment': '',
130 'patchSet': self.patchsets[-1],
131 'type': 'comment-added'}
James E. Blair8c803f82012-07-31 16:25:42 -0700132 self.data['submitRecords'] = self.getSubmitRecords()
James E. Blairb0fcae42012-07-17 11:12:10 -0700133 return json.loads(json.dumps(event))
134
James E. Blair8c803f82012-07-31 16:25:42 -0700135 def getSubmitRecords(self):
136 status = {}
137 for cat in self.categories.keys():
138 status[cat] = 0
139
140 for a in self.patchsets[-1]['approvals']:
141 cur = status[a['type']]
142 cat_min, cat_max = self.categories[a['type']][1:]
143 new = int(a['value'])
144 if new == cat_min:
145 cur = new
146 elif abs(new) > abs(cur):
147 cur = new
148 status[a['type']] = cur
149
150 labels = []
151 ok = True
152 for typ, cat in self.categories.items():
153 cur = status[typ]
154 cat_min, cat_max = cat[1:]
155 if cur == cat_min:
156 value = 'REJECT'
157 ok = False
158 elif cur == cat_max:
159 value = 'OK'
160 else:
161 value = 'NEED'
162 ok = False
163 labels.append({'label': cat[0], 'status': value})
164 if ok:
165 return [{'status': 'OK'}]
166 return [{'status': 'NOT_READY',
167 'labels': labels}]
168
169 def setDependsOn(self, other, patchset):
170 self.depends_on_change = other
171 d = {'id': other.data['id'],
172 'number': other.data['number'],
173 'ref': other.patchsets[patchset - 1]['ref']
174 }
175 self.data['dependsOn'] = [d]
176
177 other.needed_by_changes.append(self)
178 needed = other.data.get('neededBy', [])
179 d = {'id': self.data['id'],
180 'number': self.data['number'],
181 'ref': self.patchsets[patchset - 1]['ref'],
182 'revision': self.patchsets[patchset - 1]['revision']
183 }
184 needed.append(d)
185 other.data['neededBy'] = needed
186
James E. Blairb0fcae42012-07-17 11:12:10 -0700187 def query(self):
James E. Blair8c803f82012-07-31 16:25:42 -0700188 self.queried += 1
189 d = self.data.get('dependsOn')
190 if d:
191 d = d[0]
192 if (self.depends_on_change.patchsets[-1]['ref'] == d['ref']):
193 d['isCurrentPatchSet'] = True
194 else:
195 d['isCurrentPatchSet'] = False
James E. Blairb0fcae42012-07-17 11:12:10 -0700196 return json.loads(json.dumps(self.data))
197
198 def setMerged(self):
199 self.data['status'] = 'MERGED'
200 self.open = False
James E. Blair8cc15a82012-08-01 11:17:57 -0700201 branch_heads = self.gerrit.heads[self.project]
202 branch_heads[self.branch] = self.patchsets[-1]['revision']
James E. Blairb0fcae42012-07-17 11:12:10 -0700203
James E. Blaird466dc42012-07-31 10:42:56 -0700204 def setReported(self):
205 self.reported += 1
206
James E. Blairb0fcae42012-07-17 11:12:10 -0700207
208class FakeGerrit(object):
209 def __init__(self, *args, **kw):
210 self.event_queue = Queue.Queue()
211 self.fixture_dir = os.path.join(FIXTURE_DIR, 'gerrit')
212 self.change_number = 0
213 self.changes = {}
James E. Blair8cc15a82012-08-01 11:17:57 -0700214 self.heads = {}
James E. Blairb0fcae42012-07-17 11:12:10 -0700215
216 def addFakeChange(self, project, branch, subject):
James E. Blair8cc15a82012-08-01 11:17:57 -0700217 branch_heads = self.heads.get(project, {})
218 if branch not in branch_heads:
219 branch_heads[branch] = random_sha1()
220 self.heads[project] = branch_heads
221
James E. Blairb0fcae42012-07-17 11:12:10 -0700222 self.change_number += 1
James E. Blair8cc15a82012-08-01 11:17:57 -0700223 c = FakeChange(self, self.change_number, project, branch, subject)
James E. Blairb0fcae42012-07-17 11:12:10 -0700224 self.changes[self.change_number] = c
225 return c
226
227 def addEvent(self, data):
228 return self.event_queue.put(data)
229
230 def getEvent(self):
231 return self.event_queue.get()
232
233 def eventDone(self):
234 self.event_queue.task_done()
235
236 def review(self, project, changeid, message, action):
James E. Blaird466dc42012-07-31 10:42:56 -0700237 number, ps = changeid.split(',')
238 change = self.changes[int(number)]
James E. Blairb0fcae42012-07-17 11:12:10 -0700239 if 'submit' in action:
James E. Blairb0fcae42012-07-17 11:12:10 -0700240 change.setMerged()
James E. Blaird466dc42012-07-31 10:42:56 -0700241 if message:
242 change.setReported()
James E. Blairb0fcae42012-07-17 11:12:10 -0700243
244 def query(self, number):
245 change = self.changes[int(number)]
246 return change.query()
247
248 def startWatching(self, *args, **kw):
249 pass
250
251
252class FakeJenkinsEvent(object):
253 def __init__(self, name, number, parameters, phase, status=None):
254 data = {'build':
255 {'full_url': 'https://server/job/%s/%s/' % (name, number),
256 'number': number,
257 'parameters': parameters,
258 'phase': phase,
259 'url': 'job/%s/%s/' % (name, number)},
260 'name': name,
261 'url': 'job/%s/' % name}
262 if status:
263 data['build']['status'] = status
264 self.body = json.dumps(data)
265
266
267class FakeJenkinsJob(threading.Thread):
268 log = logging.getLogger("zuul.test")
269
270 def __init__(self, jenkins, callback, name, number, parameters):
271 threading.Thread.__init__(self)
272 self.jenkins = jenkins
273 self.callback = callback
274 self.name = name
275 self.number = number
276 self.parameters = parameters
277 self.wait_condition = threading.Condition()
278 self.waiting = False
James E. Blaird466dc42012-07-31 10:42:56 -0700279 self.aborted = False
280 self.canceled = False
281 self.created = time.time()
James E. Blairb0fcae42012-07-17 11:12:10 -0700282
283 def release(self):
284 self.wait_condition.acquire()
285 self.wait_condition.notify()
286 self.waiting = False
287 self.log.debug("Job %s released" % (self.parameters['UUID']))
288 self.wait_condition.release()
289
290 def isWaiting(self):
291 self.wait_condition.acquire()
292 if self.waiting:
293 ret = True
294 else:
295 ret = False
296 self.wait_condition.release()
297 return ret
298
299 def _wait(self):
300 self.wait_condition.acquire()
301 self.waiting = True
302 self.log.debug("Job %s waiting" % (self.parameters['UUID']))
303 self.wait_condition.wait()
304 self.wait_condition.release()
305
306 def run(self):
307 self.jenkins.fakeEnqueue(self)
308 if self.jenkins.hold_jobs_in_queue:
309 self._wait()
310 self.jenkins.fakeDequeue(self)
James E. Blaird466dc42012-07-31 10:42:56 -0700311 if self.canceled:
312 self.jenkins.all_jobs.remove(self)
313 return
James E. Blairb0fcae42012-07-17 11:12:10 -0700314 self.callback.jenkins_endpoint(FakeJenkinsEvent(
315 self.name, self.number, self.parameters,
316 'STARTED'))
317 if self.jenkins.hold_jobs_in_build:
318 self._wait()
319 self.log.debug("Job %s continuing" % (self.parameters['UUID']))
James E. Blairb02a3bb2012-07-30 17:49:55 -0700320
321 result = 'SUCCESS'
322 if self.jenkins.fakeShouldFailTest(
323 self.name,
324 self.parameters['GERRIT_CHANGES']):
325 result = 'FAILURE'
James E. Blaird466dc42012-07-31 10:42:56 -0700326 if self.aborted:
327 result = 'ABORTED'
James E. Blairb02a3bb2012-07-30 17:49:55 -0700328
James E. Blairb0fcae42012-07-17 11:12:10 -0700329 self.jenkins.fakeAddHistory(name=self.name, number=self.number,
James E. Blairb02a3bb2012-07-30 17:49:55 -0700330 result=result)
James E. Blairb0fcae42012-07-17 11:12:10 -0700331 self.callback.jenkins_endpoint(FakeJenkinsEvent(
332 self.name, self.number, self.parameters,
James E. Blairb02a3bb2012-07-30 17:49:55 -0700333 'COMPLETED', result))
James E. Blairb0fcae42012-07-17 11:12:10 -0700334 self.callback.jenkins_endpoint(FakeJenkinsEvent(
335 self.name, self.number, self.parameters,
James E. Blairb02a3bb2012-07-30 17:49:55 -0700336 'FINISHED', result))
James E. Blairb0fcae42012-07-17 11:12:10 -0700337 self.jenkins.all_jobs.remove(self)
338
339
340class FakeJenkins(object):
341 log = logging.getLogger("zuul.test")
342
343 def __init__(self, *args, **kw):
344 self.queue = []
345 self.all_jobs = []
346 self.job_counter = {}
James E. Blaird466dc42012-07-31 10:42:56 -0700347 self.queue_counter = 0
James E. Blairb0fcae42012-07-17 11:12:10 -0700348 self.job_history = []
349 self.hold_jobs_in_queue = False
350 self.hold_jobs_in_build = False
James E. Blairb02a3bb2012-07-30 17:49:55 -0700351 self.fail_tests = {}
James E. Blairb0fcae42012-07-17 11:12:10 -0700352
353 def fakeEnqueue(self, job):
354 self.queue.append(job)
355
356 def fakeDequeue(self, job):
357 self.queue.remove(job)
358
359 def fakeAddHistory(self, **kw):
360 self.job_history.append(kw)
361
362 def fakeRelease(self, regex=None):
363 all_jobs = self.all_jobs[:]
364 self.log.debug("releasing jobs %s (%s)" % (regex, len(self.all_jobs)))
365 for job in all_jobs:
366 if not regex or re.match(regex, job.name):
367 self.log.debug("releasing job %s" % (job.parameters['UUID']))
368 job.release()
369 else:
370 self.log.debug("not releasing job %s" % (
371 job.parameters['UUID']))
372 self.log.debug("done releasing jobs %s (%s)" % (regex,
373 len(self.all_jobs)))
374
375 def fakeAllWaiting(self, regex=None):
376 all_jobs = self.all_jobs[:]
377 for job in all_jobs:
378 self.log.debug("job %s %s" % (job.parameters['UUID'],
379 job.isWaiting()))
380 if not job.isWaiting():
381 return False
382 return True
383
James E. Blairb02a3bb2012-07-30 17:49:55 -0700384 def fakeAddFailTest(self, name, change):
385 l = self.fail_tests.get(name, [])
386 l.append(change)
387 self.fail_tests[name] = l
388
389 def fakeShouldFailTest(self, name, changes):
390 l = self.fail_tests.get(name, [])
391 for change in l:
392 if change in changes:
393 return True
394 return False
395
James E. Blairb0fcae42012-07-17 11:12:10 -0700396 def build_job(self, name, parameters):
397 count = self.job_counter.get(name, 0)
398 count += 1
399 self.job_counter[name] = count
James E. Blaird466dc42012-07-31 10:42:56 -0700400
401 queue_count = self.queue_counter
402 self.queue_counter += 1
James E. Blairb0fcae42012-07-17 11:12:10 -0700403 job = FakeJenkinsJob(self, self.callback, name, count, parameters)
James E. Blaird466dc42012-07-31 10:42:56 -0700404 job.queue_id = queue_count
405
James E. Blairb0fcae42012-07-17 11:12:10 -0700406 self.all_jobs.append(job)
407 job.start()
408
James E. Blaird466dc42012-07-31 10:42:56 -0700409 def stop_build(self, name, number):
410 for job in self.all_jobs:
411 if job.name == name and job.number == number:
412 job.aborted = True
413 job.release()
414 return
415
416 def cancel_queue(self, id):
417 for job in self.queue:
418 if job.queue_id == id:
419 job.canceled = True
420 job.release()
421 return
422
423 def get_queue_info(self):
424 items = []
425 for job in self.queue:
426 paramstr = ''
427 paramlst = []
428 d = {'actions': [{'parameters': paramlst},
429 {'causes': [{'shortDescription':
430 'Started by user Jenkins',
431 'userId': 'jenkins',
432 'userName': 'Jenkins'}]}],
433 'blocked': False,
434 'buildable': True,
435 'buildableStartMilliseconds': (job.created * 1000) + 5,
436 'id': job.queue_id,
437 'inQueueSince': (job.created * 1000),
438 'params': paramstr,
439 'stuck': False,
440 'task': {'color': 'blue',
441 'name': job.name,
442 'url': 'https://server/job/%s/' % job.name},
443 'why': 'Waiting for next available executor'}
444 for k, v in job.parameters.items():
445 paramstr += "\n(StringParameterValue) %s='%s'" % (k, v)
446 pd = {'name': k, 'value': v}
447 paramlst.append(pd)
448 items.append(d)
449 return items
450
James E. Blairb0fcae42012-07-17 11:12:10 -0700451 def set_build_description(self, *args, **kw):
452 pass
453
454
455class FakeJenkinsCallback(zuul.launcher.jenkins.JenkinsCallback):
456 def start(self):
457 pass
458
459
James E. Blair8cc15a82012-08-01 11:17:57 -0700460class FakeURLOpener(object):
461 def __init__(self, fake_gerrit, url):
462 self.fake_gerrit = fake_gerrit
463 self.url = url
464
465 def read(self):
466 res = urlparse.urlparse(self.url)
467 path = res.path
468 project = '/'.join(path.split('/')[2:-2])
469 heads = self.fake_gerrit.heads[project]
470 ret = ''
471 for change in self.fake_gerrit.changes.values():
472 for ps in change.patchsets:
473 ret += ps['revision'] + '\t' + ps['ref'] + '\n'
474 for head, ref in heads.items():
475 ret += ref + '\trefs/heads/' + head + '\n'
476 return ret
477
478
James E. Blairb0fcae42012-07-17 11:12:10 -0700479class testScheduler(unittest.TestCase):
480 log = logging.getLogger("zuul.test")
481
482 def setUp(self):
483 self.config = CONFIG
484 self.sched = zuul.scheduler.Scheduler()
485
486 def jenkinsFactory(*args, **kw):
487 self.fake_jenkins = FakeJenkins()
488 return self.fake_jenkins
489
490 def jenkinsCallbackFactory(*args, **kw):
491 self.fake_jenkins_callback = FakeJenkinsCallback(*args, **kw)
492 return self.fake_jenkins_callback
493
James E. Blair8cc15a82012-08-01 11:17:57 -0700494 def URLOpenerFactory(*args, **kw):
495 args = [self.fake_gerrit] + list(args)
496 return FakeURLOpener(*args, **kw)
497
James E. Blairb0fcae42012-07-17 11:12:10 -0700498 zuul.launcher.jenkins.ExtendedJenkins = jenkinsFactory
499 zuul.launcher.jenkins.JenkinsCallback = jenkinsCallbackFactory
James E. Blair8cc15a82012-08-01 11:17:57 -0700500 urllib2.urlopen = URLOpenerFactory
James E. Blairb0fcae42012-07-17 11:12:10 -0700501 self.jenkins = zuul.launcher.jenkins.Jenkins(self.config, self.sched)
502 self.fake_jenkins.callback = self.fake_jenkins_callback
503
504 zuul.lib.gerrit.Gerrit = FakeGerrit
505
506 self.gerrit = zuul.trigger.gerrit.Gerrit(self.config, self.sched)
James E. Blair8cc15a82012-08-01 11:17:57 -0700507 self.gerrit.replication_timeout = 1.5
508 self.gerrit.replication_retry_interval = 0.5
James E. Blairb0fcae42012-07-17 11:12:10 -0700509 self.fake_gerrit = self.gerrit.gerrit
510
511 self.sched.setLauncher(self.jenkins)
512 self.sched.setTrigger(self.gerrit)
513
514 self.sched.start()
515 self.sched.reconfigure(self.config)
516 self.sched.resume()
517
518 def tearDown(self):
519 self.jenkins.stop()
520 self.gerrit.stop()
521 self.sched.stop()
522 self.sched.join()
523
524 def waitUntilSettled(self):
525 self.log.debug("Waiting until settled...")
526 start = time.time()
527 while True:
528 if time.time() - start > 10:
529 print 'queue status:',
530 print self.sched.trigger_event_queue.empty(),
531 print self.sched.result_event_queue.empty(),
532 print self.fake_gerrit.event_queue.empty(),
533 raise Exception("Timeout waiting for Zuul to settle")
534 self.fake_gerrit.event_queue.join()
535 self.sched.queue_lock.acquire()
536 if (self.sched.trigger_event_queue.empty() and
537 self.sched.result_event_queue.empty() and
538 self.fake_gerrit.event_queue.empty() and
539 self.fake_jenkins.fakeAllWaiting()):
540 self.sched.queue_lock.release()
541 self.log.debug("...settled.")
542 return
543 self.sched.queue_lock.release()
544 self.sched.wake_event.wait(0.1)
545
James E. Blaird466dc42012-07-31 10:42:56 -0700546 def countJobResults(self, jobs, result):
547 jobs = filter(lambda x: x['result'] == result, jobs)
548 return len(jobs)
549
James E. Blairb0fcae42012-07-17 11:12:10 -0700550 def test_jobs_launched(self):
551 "Test that jobs are launched and a change is merged"
552 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
James E. Blair8c803f82012-07-31 16:25:42 -0700553 A.addApproval('CRVW', 2)
James E. Blairb0fcae42012-07-17 11:12:10 -0700554 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
555 self.waitUntilSettled()
556 jobs = self.fake_jenkins.job_history
557 job_names = [x['name'] for x in jobs]
558 assert 'project-merge' in job_names
559 assert 'project-test1' in job_names
560 assert 'project-test2' in job_names
561 assert jobs[0]['result'] == 'SUCCESS'
562 assert jobs[1]['result'] == 'SUCCESS'
563 assert jobs[2]['result'] == 'SUCCESS'
564 assert A.data['status'] == 'MERGED'
James E. Blaird466dc42012-07-31 10:42:56 -0700565 assert A.reported == 2
James E. Blairb0fcae42012-07-17 11:12:10 -0700566
567 def test_parallel_changes(self):
568 "Test that changes are tested in parallel and merged in series"
569 self.fake_jenkins.hold_jobs_in_build = True
570 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
571 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
572 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
James E. Blair8c803f82012-07-31 16:25:42 -0700573 A.addApproval('CRVW', 2)
574 B.addApproval('CRVW', 2)
575 C.addApproval('CRVW', 2)
James E. Blairb0fcae42012-07-17 11:12:10 -0700576
577 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
578 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
579 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
580
581 self.waitUntilSettled()
582 jobs = self.fake_jenkins.all_jobs
583 assert len(jobs) == 1
584 assert jobs[0].name == 'project-merge'
585 assert (jobs[0].parameters['GERRIT_CHANGES'] ==
586 'org/project:master:refs/changes/1/1/1')
587
588 self.fake_jenkins.fakeRelease('.*-merge')
589 self.waitUntilSettled()
590 assert len(jobs) == 3
591 assert jobs[0].name == 'project-test1'
592 assert (jobs[0].parameters['GERRIT_CHANGES'] ==
593 'org/project:master:refs/changes/1/1/1')
594 assert jobs[1].name == 'project-test2'
595 assert (jobs[1].parameters['GERRIT_CHANGES'] ==
596 'org/project:master:refs/changes/1/1/1')
597 assert jobs[2].name == 'project-merge'
598 assert (jobs[2].parameters['GERRIT_CHANGES'] ==
599 'org/project:master:refs/changes/1/1/1^'
600 'org/project:master:refs/changes/1/2/1')
601
602 self.fake_jenkins.fakeRelease('.*-merge')
603 self.waitUntilSettled()
604 assert len(jobs) == 5
605 assert jobs[0].name == 'project-test1'
606 assert (jobs[0].parameters['GERRIT_CHANGES'] ==
607 'org/project:master:refs/changes/1/1/1')
608 assert jobs[1].name == 'project-test2'
609 assert (jobs[1].parameters['GERRIT_CHANGES'] ==
610 'org/project:master:refs/changes/1/1/1')
611
612 assert jobs[2].name == 'project-test1'
613 assert (jobs[2].parameters['GERRIT_CHANGES'] ==
614 'org/project:master:refs/changes/1/1/1^'
615 'org/project:master:refs/changes/1/2/1')
616 assert jobs[3].name == 'project-test2'
617 assert (jobs[3].parameters['GERRIT_CHANGES'] ==
618 'org/project:master:refs/changes/1/1/1^'
619 'org/project:master:refs/changes/1/2/1')
620
621 assert jobs[4].name == 'project-merge'
622 assert (jobs[4].parameters['GERRIT_CHANGES'] ==
623 'org/project:master:refs/changes/1/1/1^'
624 'org/project:master:refs/changes/1/2/1^'
625 'org/project:master:refs/changes/1/3/1')
626
627 self.fake_jenkins.fakeRelease('.*-merge')
628 self.waitUntilSettled()
629 assert len(jobs) == 6
630 assert jobs[0].name == 'project-test1'
631 assert (jobs[0].parameters['GERRIT_CHANGES'] ==
632 'org/project:master:refs/changes/1/1/1')
633 assert jobs[1].name == 'project-test2'
634 assert (jobs[1].parameters['GERRIT_CHANGES'] ==
635 'org/project:master:refs/changes/1/1/1')
636
637 assert jobs[2].name == 'project-test1'
638 assert (jobs[2].parameters['GERRIT_CHANGES'] ==
639 'org/project:master:refs/changes/1/1/1^'
640 'org/project:master:refs/changes/1/2/1')
641 assert jobs[3].name == 'project-test2'
642 assert (jobs[3].parameters['GERRIT_CHANGES'] ==
643 'org/project:master:refs/changes/1/1/1^'
644 'org/project:master:refs/changes/1/2/1')
645
646 assert jobs[4].name == 'project-test1'
647 assert (jobs[4].parameters['GERRIT_CHANGES'] ==
648 'org/project:master:refs/changes/1/1/1^'
649 'org/project:master:refs/changes/1/2/1^'
650 'org/project:master:refs/changes/1/3/1')
651 assert jobs[5].name == 'project-test2'
652 assert (jobs[5].parameters['GERRIT_CHANGES'] ==
653 'org/project:master:refs/changes/1/1/1^'
654 'org/project:master:refs/changes/1/2/1^'
655 'org/project:master:refs/changes/1/3/1')
656
657 self.fake_jenkins.hold_jobs_in_build = False
658 self.fake_jenkins.fakeRelease()
659 self.waitUntilSettled()
660 assert len(jobs) == 0
661
662 jobs = self.fake_jenkins.job_history
663 assert len(jobs) == 9
664 assert A.data['status'] == 'MERGED'
665 assert B.data['status'] == 'MERGED'
666 assert C.data['status'] == 'MERGED'
James E. Blaird466dc42012-07-31 10:42:56 -0700667 assert A.reported == 2
668 assert B.reported == 2
669 assert C.reported == 2
James E. Blairb02a3bb2012-07-30 17:49:55 -0700670
671 def test_failed_changes(self):
672 "Test that a change behind a failed change is retested"
673 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
674 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
James E. Blair8c803f82012-07-31 16:25:42 -0700675 A.addApproval('CRVW', 2)
676 B.addApproval('CRVW', 2)
James E. Blairb02a3bb2012-07-30 17:49:55 -0700677
678 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
679 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
680
681 self.fake_jenkins.fakeAddFailTest(
682 'project-test1',
683 'org/project:master:refs/changes/1/1/1')
684
685 self.waitUntilSettled()
686 jobs = self.fake_jenkins.job_history
687 assert len(jobs) > 6
688 assert A.data['status'] == 'NEW'
689 assert B.data['status'] == 'MERGED'
James E. Blaird466dc42012-07-31 10:42:56 -0700690 assert A.reported == 2
691 assert B.reported == 2
James E. Blairb02a3bb2012-07-30 17:49:55 -0700692
693 def test_independent_queues(self):
694 "Test that changes end up in the right queues"
695 self.fake_jenkins.hold_jobs_in_build = True
696 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
697 B = self.fake_gerrit.addFakeChange('org/project1', 'master', 'B')
698 C = self.fake_gerrit.addFakeChange('org/project2', 'master', 'C')
James E. Blair8c803f82012-07-31 16:25:42 -0700699 A.addApproval('CRVW', 2)
700 B.addApproval('CRVW', 2)
701 C.addApproval('CRVW', 2)
James E. Blairb02a3bb2012-07-30 17:49:55 -0700702
703 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
704 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
705 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
706
707 jobs = self.fake_jenkins.all_jobs
708 self.waitUntilSettled()
709
710 # There should be one merge job at the head of each queue running
711 assert len(jobs) == 2
712 assert jobs[0].name == 'project-merge'
713 assert (jobs[0].parameters['GERRIT_CHANGES'] ==
714 'org/project:master:refs/changes/1/1/1')
715 assert jobs[1].name == 'project1-merge'
716 assert (jobs[1].parameters['GERRIT_CHANGES'] ==
717 'org/project1:master:refs/changes/1/2/1')
718
719 # Release the current merge jobs
720 self.fake_jenkins.fakeRelease('.*-merge')
721 self.waitUntilSettled()
722 # Release the merge job for project2 which is behind project1
723 self.fake_jenkins.fakeRelease('.*-merge')
724 self.waitUntilSettled()
725
726 # All the test jobs should be running:
727 # project1 (3) + project2 (3) + project (2) = 8
728 assert len(jobs) == 8
729
730 self.fake_jenkins.fakeRelease()
731 self.waitUntilSettled()
732 assert len(jobs) == 0
733
734 jobs = self.fake_jenkins.job_history
735 assert len(jobs) == 11
736 assert A.data['status'] == 'MERGED'
737 assert B.data['status'] == 'MERGED'
738 assert C.data['status'] == 'MERGED'
James E. Blaird466dc42012-07-31 10:42:56 -0700739 assert A.reported == 2
740 assert B.reported == 2
741 assert C.reported == 2
742
743 def test_failed_change_at_head(self):
744 "Test that if a change at the head fails, jobs behind it are canceled"
745 self.fake_jenkins.hold_jobs_in_build = True
746
747 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
748 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
749 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
James E. Blair8c803f82012-07-31 16:25:42 -0700750 A.addApproval('CRVW', 2)
751 B.addApproval('CRVW', 2)
752 C.addApproval('CRVW', 2)
James E. Blaird466dc42012-07-31 10:42:56 -0700753
754 self.fake_jenkins.fakeAddFailTest(
755 'project-test1',
756 'org/project:master:refs/changes/1/1/1')
757
758 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
759 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
760 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
761
762 self.waitUntilSettled()
763 jobs = self.fake_jenkins.all_jobs
764 finished_jobs = self.fake_jenkins.job_history
765
766 assert len(jobs) == 1
767 assert jobs[0].name == 'project-merge'
768 assert (jobs[0].parameters['GERRIT_CHANGES'] ==
769 'org/project:master:refs/changes/1/1/1')
770
771 self.fake_jenkins.fakeRelease('.*-merge')
772 self.waitUntilSettled()
773 self.fake_jenkins.fakeRelease('.*-merge')
774 self.waitUntilSettled()
775 self.fake_jenkins.fakeRelease('.*-merge')
776 self.waitUntilSettled()
777
778 assert len(jobs) == 6
779 assert jobs[0].name == 'project-test1'
780 assert jobs[1].name == 'project-test2'
781 assert jobs[2].name == 'project-test1'
782 assert jobs[3].name == 'project-test2'
783 assert jobs[4].name == 'project-test1'
784 assert jobs[5].name == 'project-test2'
785
786 jobs[0].release()
787 self.waitUntilSettled()
788
789 assert len(jobs) == 1 # project-test2
790 assert self.countJobResults(finished_jobs, 'ABORTED') == 4
791
792 self.fake_jenkins.hold_jobs_in_build = False
793 self.fake_jenkins.fakeRelease()
794 self.waitUntilSettled()
795
796 assert len(jobs) == 0
797 assert len(finished_jobs) == 15
798 assert A.data['status'] == 'NEW'
799 assert B.data['status'] == 'MERGED'
800 assert C.data['status'] == 'MERGED'
801 assert A.reported == 2
802 assert B.reported == 2
803 assert C.reported == 2
804
805 def test_failed_change_at_head_with_queue(self):
806 "Test that if a change at the head fails, queued jobs are canceled"
807 self.fake_jenkins.hold_jobs_in_queue = True
808
809 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
810 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
811 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
James E. Blair8c803f82012-07-31 16:25:42 -0700812 A.addApproval('CRVW', 2)
813 B.addApproval('CRVW', 2)
814 C.addApproval('CRVW', 2)
James E. Blaird466dc42012-07-31 10:42:56 -0700815
816 self.fake_jenkins.fakeAddFailTest(
817 'project-test1',
818 'org/project:master:refs/changes/1/1/1')
819
820 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
821 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
822 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
823
824 self.waitUntilSettled()
825 jobs = self.fake_jenkins.all_jobs
826 finished_jobs = self.fake_jenkins.job_history
827 queue = self.fake_jenkins.queue
828
829 assert len(jobs) == 1
830 assert len(queue) == 1
831 assert jobs[0].name == 'project-merge'
832 assert (jobs[0].parameters['GERRIT_CHANGES'] ==
833 'org/project:master:refs/changes/1/1/1')
834
835 self.fake_jenkins.fakeRelease('.*-merge')
836 self.waitUntilSettled()
837 self.fake_jenkins.fakeRelease('.*-merge')
838 self.waitUntilSettled()
839 self.fake_jenkins.fakeRelease('.*-merge')
840 self.waitUntilSettled()
841
842 assert len(jobs) == 6
843 assert len(queue) == 6
844 assert jobs[0].name == 'project-test1'
845 assert jobs[1].name == 'project-test2'
846 assert jobs[2].name == 'project-test1'
847 assert jobs[3].name == 'project-test2'
848 assert jobs[4].name == 'project-test1'
849 assert jobs[5].name == 'project-test2'
850
851 jobs[0].release()
852 self.waitUntilSettled()
853
854 assert len(jobs) == 1 # project-test2
855 assert len(queue) == 1
856 assert self.countJobResults(finished_jobs, 'ABORTED') == 0
857
858 self.fake_jenkins.hold_jobs_in_queue = False
859 self.fake_jenkins.fakeRelease()
860 self.waitUntilSettled()
861
862 assert len(jobs) == 0
863 assert len(finished_jobs) == 11
864 assert A.data['status'] == 'NEW'
865 assert B.data['status'] == 'MERGED'
866 assert C.data['status'] == 'MERGED'
867 assert A.reported == 2
868 assert B.reported == 2
869 assert C.reported == 2
James E. Blair8c803f82012-07-31 16:25:42 -0700870
871 def test_patch_order(self):
872 "Test that dependent patches are tested in the right order"
873 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
874 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
875 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
876 A.addApproval('CRVW', 2)
877 B.addApproval('CRVW', 2)
878 C.addApproval('CRVW', 2)
879
880 M2 = self.fake_gerrit.addFakeChange('org/project', 'master', 'M2')
881 M1 = self.fake_gerrit.addFakeChange('org/project', 'master', 'M1')
882 M2.setMerged()
883 M1.setMerged()
884
885 # C -> B -> A -> M1 -> M2
886 # M2 is here to make sure it is never queried. If it is, it
887 # means zuul is walking down the entire history of merged
888 # changes.
889
890 C.setDependsOn(B, 1)
891 B.setDependsOn(A, 1)
892 A.setDependsOn(M1, 1)
893 M1.setDependsOn(M2, 1)
894
895 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
896
897 self.waitUntilSettled()
898
899 assert A.data['status'] == 'NEW'
900 assert B.data['status'] == 'NEW'
901 assert C.data['status'] == 'NEW'
902
903 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
904 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
905
906 self.waitUntilSettled()
907 assert M2.queried == 0
908 assert A.data['status'] == 'MERGED'
909 assert B.data['status'] == 'MERGED'
910 assert C.data['status'] == 'MERGED'
911 assert A.reported == 2
912 assert B.reported == 2
913 assert C.reported == 2
914
915 def test_can_merge(self):
916 "Test that whether a change is ready to merge"
917 # TODO: move to test_gerrit (this is a unit test!)
918 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
919 a = self.sched.trigger.getChange(1, 2, 'gate')
920 assert not a.can_merge
921
922 A.addApproval('CRVW', 2)
923 a = self.sched.trigger.getChange(1, 2, 'gate')
924 assert not a.can_merge
925
926 A.addApproval('APRV', 1)
927 a = self.sched.trigger.getChange(1, 2, 'gate')
928 assert a.can_merge
929
930 return True