blob: fc6d4a473cd6264f85ac3626bbf91c6c590efb35 [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
77def add_fake_change_to_repo(project, branch, change_num, patchset, msg):
78 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)
88 fn = os.path.join(path, '%s-%s' % (branch, change_num))
89 f = open(fn, 'w')
90 f.write("test\n")
91 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. Blair4886cc12012-07-18 15:39:41 -0700180 add_fake_change_to_repo(self.project, self.branch,
181 self.number, self.latest_patchset,
182 self.subject + '-' + str(self.latest_patchset))
James E. Blairb0fcae42012-07-17 11:12:10 -0700183
184 def addApproval(self, category, value):
James E. Blair8c803f82012-07-31 16:25:42 -0700185 approval = {'description': self.categories[category][0],
186 'type': category,
187 'value': str(value)}
188 self.patchsets[-1]['approvals'].append(approval)
189 event = {'approvals': [approval],
James E. Blairb0fcae42012-07-17 11:12:10 -0700190 'author': {'email': 'user@example.com',
191 'name': 'User Name',
192 'username': 'username'},
193 'change': {'branch': self.branch,
194 'id': 'Iaa69c46accf97d0598111724a38250ae76a22c87',
195 'number': str(self.number),
196 'owner': {'email': 'user@example.com',
197 'name': 'User Name',
198 'username': 'username'},
199 'project': self.project,
200 'subject': self.subject,
201 'topic': 'master',
202 'url': 'https://hostname/459'},
203 'comment': '',
204 'patchSet': self.patchsets[-1],
205 'type': 'comment-added'}
James E. Blair8c803f82012-07-31 16:25:42 -0700206 self.data['submitRecords'] = self.getSubmitRecords()
James E. Blairb0fcae42012-07-17 11:12:10 -0700207 return json.loads(json.dumps(event))
208
James E. Blair8c803f82012-07-31 16:25:42 -0700209 def getSubmitRecords(self):
210 status = {}
211 for cat in self.categories.keys():
212 status[cat] = 0
213
214 for a in self.patchsets[-1]['approvals']:
215 cur = status[a['type']]
216 cat_min, cat_max = self.categories[a['type']][1:]
217 new = int(a['value'])
218 if new == cat_min:
219 cur = new
220 elif abs(new) > abs(cur):
221 cur = new
222 status[a['type']] = cur
223
224 labels = []
225 ok = True
226 for typ, cat in self.categories.items():
227 cur = status[typ]
228 cat_min, cat_max = cat[1:]
229 if cur == cat_min:
230 value = 'REJECT'
231 ok = False
232 elif cur == cat_max:
233 value = 'OK'
234 else:
235 value = 'NEED'
236 ok = False
237 labels.append({'label': cat[0], 'status': value})
238 if ok:
239 return [{'status': 'OK'}]
240 return [{'status': 'NOT_READY',
241 'labels': labels}]
242
243 def setDependsOn(self, other, patchset):
244 self.depends_on_change = other
245 d = {'id': other.data['id'],
246 'number': other.data['number'],
247 'ref': other.patchsets[patchset - 1]['ref']
248 }
249 self.data['dependsOn'] = [d]
250
251 other.needed_by_changes.append(self)
252 needed = other.data.get('neededBy', [])
253 d = {'id': self.data['id'],
254 'number': self.data['number'],
255 'ref': self.patchsets[patchset - 1]['ref'],
256 'revision': self.patchsets[patchset - 1]['revision']
257 }
258 needed.append(d)
259 other.data['neededBy'] = needed
260
James E. Blairb0fcae42012-07-17 11:12:10 -0700261 def query(self):
James E. Blair8c803f82012-07-31 16:25:42 -0700262 self.queried += 1
263 d = self.data.get('dependsOn')
264 if d:
265 d = d[0]
266 if (self.depends_on_change.patchsets[-1]['ref'] == d['ref']):
267 d['isCurrentPatchSet'] = True
268 else:
269 d['isCurrentPatchSet'] = False
James E. Blairb0fcae42012-07-17 11:12:10 -0700270 return json.loads(json.dumps(self.data))
271
272 def setMerged(self):
273 self.data['status'] = 'MERGED'
274 self.open = False
James E. Blair8cc15a82012-08-01 11:17:57 -0700275 branch_heads = self.gerrit.heads[self.project]
276 branch_heads[self.branch] = self.patchsets[-1]['revision']
James E. Blairb0fcae42012-07-17 11:12:10 -0700277
James E. Blaird466dc42012-07-31 10:42:56 -0700278 def setReported(self):
279 self.reported += 1
280
James E. Blairb0fcae42012-07-17 11:12:10 -0700281
282class FakeGerrit(object):
283 def __init__(self, *args, **kw):
284 self.event_queue = Queue.Queue()
285 self.fixture_dir = os.path.join(FIXTURE_DIR, 'gerrit')
286 self.change_number = 0
287 self.changes = {}
James E. Blair8cc15a82012-08-01 11:17:57 -0700288 self.heads = {}
James E. Blairb0fcae42012-07-17 11:12:10 -0700289
290 def addFakeChange(self, project, branch, subject):
James E. Blair8cc15a82012-08-01 11:17:57 -0700291 branch_heads = self.heads.get(project, {})
292 if branch not in branch_heads:
293 branch_heads[branch] = random_sha1()
294 self.heads[project] = branch_heads
295
James E. Blairb0fcae42012-07-17 11:12:10 -0700296 self.change_number += 1
James E. Blair8cc15a82012-08-01 11:17:57 -0700297 c = FakeChange(self, self.change_number, project, branch, subject)
James E. Blairb0fcae42012-07-17 11:12:10 -0700298 self.changes[self.change_number] = c
299 return c
300
301 def addEvent(self, data):
302 return self.event_queue.put(data)
303
304 def getEvent(self):
305 return self.event_queue.get()
306
307 def eventDone(self):
308 self.event_queue.task_done()
309
310 def review(self, project, changeid, message, action):
James E. Blaird466dc42012-07-31 10:42:56 -0700311 number, ps = changeid.split(',')
312 change = self.changes[int(number)]
James E. Blairb0fcae42012-07-17 11:12:10 -0700313 if 'submit' in action:
James E. Blairb0fcae42012-07-17 11:12:10 -0700314 change.setMerged()
James E. Blaird466dc42012-07-31 10:42:56 -0700315 if message:
316 change.setReported()
James E. Blairb0fcae42012-07-17 11:12:10 -0700317
318 def query(self, number):
319 change = self.changes[int(number)]
320 return change.query()
321
322 def startWatching(self, *args, **kw):
323 pass
324
325
326class FakeJenkinsEvent(object):
327 def __init__(self, name, number, parameters, phase, status=None):
328 data = {'build':
329 {'full_url': 'https://server/job/%s/%s/' % (name, number),
330 'number': number,
331 'parameters': parameters,
332 'phase': phase,
333 'url': 'job/%s/%s/' % (name, number)},
334 'name': name,
335 'url': 'job/%s/' % name}
336 if status:
337 data['build']['status'] = status
338 self.body = json.dumps(data)
339
340
341class FakeJenkinsJob(threading.Thread):
342 log = logging.getLogger("zuul.test")
343
344 def __init__(self, jenkins, callback, name, number, parameters):
345 threading.Thread.__init__(self)
346 self.jenkins = jenkins
347 self.callback = callback
348 self.name = name
349 self.number = number
350 self.parameters = parameters
351 self.wait_condition = threading.Condition()
352 self.waiting = False
James E. Blaird466dc42012-07-31 10:42:56 -0700353 self.aborted = False
354 self.canceled = False
355 self.created = time.time()
James E. Blairb0fcae42012-07-17 11:12:10 -0700356
357 def release(self):
358 self.wait_condition.acquire()
359 self.wait_condition.notify()
360 self.waiting = False
361 self.log.debug("Job %s released" % (self.parameters['UUID']))
362 self.wait_condition.release()
363
364 def isWaiting(self):
365 self.wait_condition.acquire()
366 if self.waiting:
367 ret = True
368 else:
369 ret = False
370 self.wait_condition.release()
371 return ret
372
373 def _wait(self):
374 self.wait_condition.acquire()
375 self.waiting = True
376 self.log.debug("Job %s waiting" % (self.parameters['UUID']))
377 self.wait_condition.wait()
378 self.wait_condition.release()
379
380 def run(self):
381 self.jenkins.fakeEnqueue(self)
382 if self.jenkins.hold_jobs_in_queue:
383 self._wait()
384 self.jenkins.fakeDequeue(self)
James E. Blaird466dc42012-07-31 10:42:56 -0700385 if self.canceled:
386 self.jenkins.all_jobs.remove(self)
387 return
James E. Blairb0fcae42012-07-17 11:12:10 -0700388 self.callback.jenkins_endpoint(FakeJenkinsEvent(
389 self.name, self.number, self.parameters,
390 'STARTED'))
391 if self.jenkins.hold_jobs_in_build:
392 self._wait()
393 self.log.debug("Job %s continuing" % (self.parameters['UUID']))
James E. Blairb02a3bb2012-07-30 17:49:55 -0700394
395 result = 'SUCCESS'
396 if self.jenkins.fakeShouldFailTest(
397 self.name,
James E. Blair4886cc12012-07-18 15:39:41 -0700398 self.parameters['ZUUL_REF']):
James E. Blairb02a3bb2012-07-30 17:49:55 -0700399 result = 'FAILURE'
James E. Blaird466dc42012-07-31 10:42:56 -0700400 if self.aborted:
401 result = 'ABORTED'
James E. Blairb02a3bb2012-07-30 17:49:55 -0700402
James E. Blairb0fcae42012-07-17 11:12:10 -0700403 self.jenkins.fakeAddHistory(name=self.name, number=self.number,
James E. Blairb02a3bb2012-07-30 17:49:55 -0700404 result=result)
James E. Blairb0fcae42012-07-17 11:12:10 -0700405 self.callback.jenkins_endpoint(FakeJenkinsEvent(
406 self.name, self.number, self.parameters,
James E. Blairb02a3bb2012-07-30 17:49:55 -0700407 'COMPLETED', result))
James E. Blairb0fcae42012-07-17 11:12:10 -0700408 self.callback.jenkins_endpoint(FakeJenkinsEvent(
409 self.name, self.number, self.parameters,
James E. Blairb02a3bb2012-07-30 17:49:55 -0700410 'FINISHED', result))
James E. Blairb0fcae42012-07-17 11:12:10 -0700411 self.jenkins.all_jobs.remove(self)
412
413
414class FakeJenkins(object):
415 log = logging.getLogger("zuul.test")
416
417 def __init__(self, *args, **kw):
418 self.queue = []
419 self.all_jobs = []
420 self.job_counter = {}
James E. Blaird466dc42012-07-31 10:42:56 -0700421 self.queue_counter = 0
James E. Blairb0fcae42012-07-17 11:12:10 -0700422 self.job_history = []
423 self.hold_jobs_in_queue = False
424 self.hold_jobs_in_build = False
James E. Blairb02a3bb2012-07-30 17:49:55 -0700425 self.fail_tests = {}
James E. Blairb0fcae42012-07-17 11:12:10 -0700426
427 def fakeEnqueue(self, job):
428 self.queue.append(job)
429
430 def fakeDequeue(self, job):
431 self.queue.remove(job)
432
433 def fakeAddHistory(self, **kw):
434 self.job_history.append(kw)
435
436 def fakeRelease(self, regex=None):
437 all_jobs = self.all_jobs[:]
438 self.log.debug("releasing jobs %s (%s)" % (regex, len(self.all_jobs)))
439 for job in all_jobs:
440 if not regex or re.match(regex, job.name):
441 self.log.debug("releasing job %s" % (job.parameters['UUID']))
442 job.release()
443 else:
444 self.log.debug("not releasing job %s" % (
445 job.parameters['UUID']))
446 self.log.debug("done releasing jobs %s (%s)" % (regex,
447 len(self.all_jobs)))
448
449 def fakeAllWaiting(self, regex=None):
450 all_jobs = self.all_jobs[:]
451 for job in all_jobs:
452 self.log.debug("job %s %s" % (job.parameters['UUID'],
453 job.isWaiting()))
454 if not job.isWaiting():
455 return False
456 return True
457
James E. Blairb02a3bb2012-07-30 17:49:55 -0700458 def fakeAddFailTest(self, name, change):
459 l = self.fail_tests.get(name, [])
460 l.append(change)
461 self.fail_tests[name] = l
462
James E. Blair4886cc12012-07-18 15:39:41 -0700463 def fakeShouldFailTest(self, name, ref):
James E. Blairb02a3bb2012-07-30 17:49:55 -0700464 l = self.fail_tests.get(name, [])
465 for change in l:
James E. Blair4886cc12012-07-18 15:39:41 -0700466 if ref_has_change(ref, change):
James E. Blairb02a3bb2012-07-30 17:49:55 -0700467 return True
468 return False
469
James E. Blairb0fcae42012-07-17 11:12:10 -0700470 def build_job(self, name, parameters):
471 count = self.job_counter.get(name, 0)
472 count += 1
473 self.job_counter[name] = count
James E. Blaird466dc42012-07-31 10:42:56 -0700474
475 queue_count = self.queue_counter
476 self.queue_counter += 1
James E. Blairb0fcae42012-07-17 11:12:10 -0700477 job = FakeJenkinsJob(self, self.callback, name, count, parameters)
James E. Blaird466dc42012-07-31 10:42:56 -0700478 job.queue_id = queue_count
479
James E. Blairb0fcae42012-07-17 11:12:10 -0700480 self.all_jobs.append(job)
481 job.start()
482
James E. Blaird466dc42012-07-31 10:42:56 -0700483 def stop_build(self, name, number):
484 for job in self.all_jobs:
485 if job.name == name and job.number == number:
486 job.aborted = True
487 job.release()
488 return
489
490 def cancel_queue(self, id):
491 for job in self.queue:
492 if job.queue_id == id:
493 job.canceled = True
494 job.release()
495 return
496
497 def get_queue_info(self):
498 items = []
499 for job in self.queue:
500 paramstr = ''
501 paramlst = []
502 d = {'actions': [{'parameters': paramlst},
503 {'causes': [{'shortDescription':
504 'Started by user Jenkins',
505 'userId': 'jenkins',
506 'userName': 'Jenkins'}]}],
507 'blocked': False,
508 'buildable': True,
509 'buildableStartMilliseconds': (job.created * 1000) + 5,
510 'id': job.queue_id,
511 'inQueueSince': (job.created * 1000),
512 'params': paramstr,
513 'stuck': False,
514 'task': {'color': 'blue',
515 'name': job.name,
516 'url': 'https://server/job/%s/' % job.name},
517 'why': 'Waiting for next available executor'}
518 for k, v in job.parameters.items():
519 paramstr += "\n(StringParameterValue) %s='%s'" % (k, v)
520 pd = {'name': k, 'value': v}
521 paramlst.append(pd)
522 items.append(d)
523 return items
524
James E. Blairb0fcae42012-07-17 11:12:10 -0700525 def set_build_description(self, *args, **kw):
526 pass
527
528
529class FakeJenkinsCallback(zuul.launcher.jenkins.JenkinsCallback):
530 def start(self):
531 pass
532
533
James E. Blair8cc15a82012-08-01 11:17:57 -0700534class FakeURLOpener(object):
535 def __init__(self, fake_gerrit, url):
536 self.fake_gerrit = fake_gerrit
537 self.url = url
538
539 def read(self):
540 res = urlparse.urlparse(self.url)
541 path = res.path
542 project = '/'.join(path.split('/')[2:-2])
543 heads = self.fake_gerrit.heads[project]
544 ret = ''
545 for change in self.fake_gerrit.changes.values():
546 for ps in change.patchsets:
547 ret += ps['revision'] + '\t' + ps['ref'] + '\n'
548 for head, ref in heads.items():
549 ret += ref + '\trefs/heads/' + head + '\n'
550 return ret
551
552
James E. Blair4886cc12012-07-18 15:39:41 -0700553class FakeGerritTrigger(zuul.trigger.gerrit.Gerrit):
554 def getGitUrl(self, project):
555 return "/tmp/zuul-test/upstream/%s" % project
556
557
James E. Blairb0fcae42012-07-17 11:12:10 -0700558class testScheduler(unittest.TestCase):
559 log = logging.getLogger("zuul.test")
560
561 def setUp(self):
James E. Blair4886cc12012-07-18 15:39:41 -0700562 if os.path.exists("/tmp/zuul-test"):
563 shutil.rmtree("/tmp/zuul-test")
564 os.makedirs("/tmp/zuul-test")
565 os.makedirs("/tmp/zuul-test/upstream")
566 os.makedirs("/tmp/zuul-test/git")
567
568 # For each project in config:
569 init_repo("org/project")
570 init_repo("org/project1")
571 init_repo("org/project2")
James E. Blairb0fcae42012-07-17 11:12:10 -0700572 self.config = CONFIG
573 self.sched = zuul.scheduler.Scheduler()
574
575 def jenkinsFactory(*args, **kw):
576 self.fake_jenkins = FakeJenkins()
577 return self.fake_jenkins
578
579 def jenkinsCallbackFactory(*args, **kw):
580 self.fake_jenkins_callback = FakeJenkinsCallback(*args, **kw)
581 return self.fake_jenkins_callback
582
James E. Blair8cc15a82012-08-01 11:17:57 -0700583 def URLOpenerFactory(*args, **kw):
584 args = [self.fake_gerrit] + list(args)
585 return FakeURLOpener(*args, **kw)
586
James E. Blairb0fcae42012-07-17 11:12:10 -0700587 zuul.launcher.jenkins.ExtendedJenkins = jenkinsFactory
588 zuul.launcher.jenkins.JenkinsCallback = jenkinsCallbackFactory
James E. Blair8cc15a82012-08-01 11:17:57 -0700589 urllib2.urlopen = URLOpenerFactory
James E. Blairb0fcae42012-07-17 11:12:10 -0700590 self.jenkins = zuul.launcher.jenkins.Jenkins(self.config, self.sched)
591 self.fake_jenkins.callback = self.fake_jenkins_callback
592
593 zuul.lib.gerrit.Gerrit = FakeGerrit
594
James E. Blair4886cc12012-07-18 15:39:41 -0700595 self.gerrit = FakeGerritTrigger(self.config, self.sched)
James E. Blair8cc15a82012-08-01 11:17:57 -0700596 self.gerrit.replication_timeout = 1.5
597 self.gerrit.replication_retry_interval = 0.5
James E. Blairb0fcae42012-07-17 11:12:10 -0700598 self.fake_gerrit = self.gerrit.gerrit
599
600 self.sched.setLauncher(self.jenkins)
601 self.sched.setTrigger(self.gerrit)
602
603 self.sched.start()
604 self.sched.reconfigure(self.config)
605 self.sched.resume()
606
607 def tearDown(self):
608 self.jenkins.stop()
609 self.gerrit.stop()
610 self.sched.stop()
611 self.sched.join()
James E. Blair4886cc12012-07-18 15:39:41 -0700612 #shutil.rmtree("/tmp/zuul-test")
James E. Blairb0fcae42012-07-17 11:12:10 -0700613
614 def waitUntilSettled(self):
615 self.log.debug("Waiting until settled...")
616 start = time.time()
617 while True:
618 if time.time() - start > 10:
619 print 'queue status:',
620 print self.sched.trigger_event_queue.empty(),
621 print self.sched.result_event_queue.empty(),
622 print self.fake_gerrit.event_queue.empty(),
623 raise Exception("Timeout waiting for Zuul to settle")
624 self.fake_gerrit.event_queue.join()
625 self.sched.queue_lock.acquire()
626 if (self.sched.trigger_event_queue.empty() and
627 self.sched.result_event_queue.empty() and
628 self.fake_gerrit.event_queue.empty() and
629 self.fake_jenkins.fakeAllWaiting()):
630 self.sched.queue_lock.release()
631 self.log.debug("...settled.")
632 return
633 self.sched.queue_lock.release()
634 self.sched.wake_event.wait(0.1)
635
James E. Blaird466dc42012-07-31 10:42:56 -0700636 def countJobResults(self, jobs, result):
637 jobs = filter(lambda x: x['result'] == result, jobs)
638 return len(jobs)
639
James E. Blairb0fcae42012-07-17 11:12:10 -0700640 def test_jobs_launched(self):
641 "Test that jobs are launched and a change is merged"
642 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
James E. Blair8c803f82012-07-31 16:25:42 -0700643 A.addApproval('CRVW', 2)
James E. Blairb0fcae42012-07-17 11:12:10 -0700644 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
645 self.waitUntilSettled()
646 jobs = self.fake_jenkins.job_history
647 job_names = [x['name'] for x in jobs]
648 assert 'project-merge' in job_names
649 assert 'project-test1' in job_names
650 assert 'project-test2' in job_names
651 assert jobs[0]['result'] == 'SUCCESS'
652 assert jobs[1]['result'] == 'SUCCESS'
653 assert jobs[2]['result'] == 'SUCCESS'
654 assert A.data['status'] == 'MERGED'
James E. Blaird466dc42012-07-31 10:42:56 -0700655 assert A.reported == 2
James E. Blairb0fcae42012-07-17 11:12:10 -0700656
657 def test_parallel_changes(self):
658 "Test that changes are tested in parallel and merged in series"
659 self.fake_jenkins.hold_jobs_in_build = True
660 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
661 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
662 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
James E. Blair8c803f82012-07-31 16:25:42 -0700663 A.addApproval('CRVW', 2)
664 B.addApproval('CRVW', 2)
665 C.addApproval('CRVW', 2)
James E. Blairb0fcae42012-07-17 11:12:10 -0700666
667 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
668 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
669 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
670
671 self.waitUntilSettled()
672 jobs = self.fake_jenkins.all_jobs
673 assert len(jobs) == 1
674 assert jobs[0].name == 'project-merge'
James E. Blair4886cc12012-07-18 15:39:41 -0700675 assert job_has_changes(jobs[0], A)
James E. Blairb0fcae42012-07-17 11:12:10 -0700676
677 self.fake_jenkins.fakeRelease('.*-merge')
678 self.waitUntilSettled()
679 assert len(jobs) == 3
680 assert jobs[0].name == 'project-test1'
James E. Blair4886cc12012-07-18 15:39:41 -0700681 assert job_has_changes(jobs[0], A)
James E. Blairb0fcae42012-07-17 11:12:10 -0700682 assert jobs[1].name == 'project-test2'
James E. Blair4886cc12012-07-18 15:39:41 -0700683 assert job_has_changes(jobs[1], A)
James E. Blairb0fcae42012-07-17 11:12:10 -0700684 assert jobs[2].name == 'project-merge'
James E. Blair4886cc12012-07-18 15:39:41 -0700685 assert job_has_changes(jobs[2], A, B)
James E. Blairb0fcae42012-07-17 11:12:10 -0700686
687 self.fake_jenkins.fakeRelease('.*-merge')
688 self.waitUntilSettled()
689 assert len(jobs) == 5
690 assert jobs[0].name == 'project-test1'
James E. Blair4886cc12012-07-18 15:39:41 -0700691 assert job_has_changes(jobs[0], A)
James E. Blairb0fcae42012-07-17 11:12:10 -0700692 assert jobs[1].name == 'project-test2'
James E. Blair4886cc12012-07-18 15:39:41 -0700693 assert job_has_changes(jobs[1], A)
James E. Blairb0fcae42012-07-17 11:12:10 -0700694
695 assert jobs[2].name == 'project-test1'
James E. Blair4886cc12012-07-18 15:39:41 -0700696 assert job_has_changes(jobs[2], A, B)
James E. Blairb0fcae42012-07-17 11:12:10 -0700697 assert jobs[3].name == 'project-test2'
James E. Blair4886cc12012-07-18 15:39:41 -0700698 assert job_has_changes(jobs[3], A, B)
James E. Blairb0fcae42012-07-17 11:12:10 -0700699
700 assert jobs[4].name == 'project-merge'
James E. Blair4886cc12012-07-18 15:39:41 -0700701 assert job_has_changes(jobs[4], A, B, C)
James E. Blairb0fcae42012-07-17 11:12:10 -0700702
703 self.fake_jenkins.fakeRelease('.*-merge')
704 self.waitUntilSettled()
705 assert len(jobs) == 6
706 assert jobs[0].name == 'project-test1'
James E. Blair4886cc12012-07-18 15:39:41 -0700707 assert job_has_changes(jobs[0], A)
James E. Blairb0fcae42012-07-17 11:12:10 -0700708 assert jobs[1].name == 'project-test2'
James E. Blair4886cc12012-07-18 15:39:41 -0700709 assert job_has_changes(jobs[1], A)
James E. Blairb0fcae42012-07-17 11:12:10 -0700710
711 assert jobs[2].name == 'project-test1'
James E. Blair4886cc12012-07-18 15:39:41 -0700712 assert job_has_changes(jobs[2], A, B)
James E. Blairb0fcae42012-07-17 11:12:10 -0700713 assert jobs[3].name == 'project-test2'
James E. Blair4886cc12012-07-18 15:39:41 -0700714 assert job_has_changes(jobs[3], A, B)
James E. Blairb0fcae42012-07-17 11:12:10 -0700715
716 assert jobs[4].name == 'project-test1'
James E. Blair4886cc12012-07-18 15:39:41 -0700717 assert job_has_changes(jobs[4], A, B, C)
James E. Blairb0fcae42012-07-17 11:12:10 -0700718 assert jobs[5].name == 'project-test2'
James E. Blair4886cc12012-07-18 15:39:41 -0700719 assert job_has_changes(jobs[5], A, B, C)
James E. Blairb0fcae42012-07-17 11:12:10 -0700720
721 self.fake_jenkins.hold_jobs_in_build = False
722 self.fake_jenkins.fakeRelease()
723 self.waitUntilSettled()
724 assert len(jobs) == 0
725
726 jobs = self.fake_jenkins.job_history
727 assert len(jobs) == 9
728 assert A.data['status'] == 'MERGED'
729 assert B.data['status'] == 'MERGED'
730 assert C.data['status'] == 'MERGED'
James E. Blaird466dc42012-07-31 10:42:56 -0700731 assert A.reported == 2
732 assert B.reported == 2
733 assert C.reported == 2
James E. Blairb02a3bb2012-07-30 17:49:55 -0700734
735 def test_failed_changes(self):
736 "Test that a change behind a failed change is retested"
737 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
738 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
James E. Blair8c803f82012-07-31 16:25:42 -0700739 A.addApproval('CRVW', 2)
740 B.addApproval('CRVW', 2)
James E. Blairb02a3bb2012-07-30 17:49:55 -0700741
742 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
743 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
744
James E. Blair4886cc12012-07-18 15:39:41 -0700745 self.fake_jenkins.fakeAddFailTest('project-test1', A)
James E. Blairb02a3bb2012-07-30 17:49:55 -0700746
747 self.waitUntilSettled()
748 jobs = self.fake_jenkins.job_history
749 assert len(jobs) > 6
750 assert A.data['status'] == 'NEW'
751 assert B.data['status'] == 'MERGED'
James E. Blaird466dc42012-07-31 10:42:56 -0700752 assert A.reported == 2
753 assert B.reported == 2
James E. Blairb02a3bb2012-07-30 17:49:55 -0700754
755 def test_independent_queues(self):
756 "Test that changes end up in the right queues"
757 self.fake_jenkins.hold_jobs_in_build = True
758 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
759 B = self.fake_gerrit.addFakeChange('org/project1', 'master', 'B')
760 C = self.fake_gerrit.addFakeChange('org/project2', 'master', 'C')
James E. Blair8c803f82012-07-31 16:25:42 -0700761 A.addApproval('CRVW', 2)
762 B.addApproval('CRVW', 2)
763 C.addApproval('CRVW', 2)
James E. Blairb02a3bb2012-07-30 17:49:55 -0700764
765 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
766 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
767 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
768
769 jobs = self.fake_jenkins.all_jobs
770 self.waitUntilSettled()
771
772 # There should be one merge job at the head of each queue running
773 assert len(jobs) == 2
774 assert jobs[0].name == 'project-merge'
James E. Blair4886cc12012-07-18 15:39:41 -0700775 assert job_has_changes(jobs[0], A)
James E. Blairb02a3bb2012-07-30 17:49:55 -0700776 assert jobs[1].name == 'project1-merge'
James E. Blair4886cc12012-07-18 15:39:41 -0700777 assert job_has_changes(jobs[1], B)
James E. Blairb02a3bb2012-07-30 17:49:55 -0700778
779 # Release the current merge jobs
780 self.fake_jenkins.fakeRelease('.*-merge')
781 self.waitUntilSettled()
782 # Release the merge job for project2 which is behind project1
783 self.fake_jenkins.fakeRelease('.*-merge')
784 self.waitUntilSettled()
785
786 # All the test jobs should be running:
787 # project1 (3) + project2 (3) + project (2) = 8
788 assert len(jobs) == 8
789
790 self.fake_jenkins.fakeRelease()
791 self.waitUntilSettled()
792 assert len(jobs) == 0
793
794 jobs = self.fake_jenkins.job_history
795 assert len(jobs) == 11
796 assert A.data['status'] == 'MERGED'
797 assert B.data['status'] == 'MERGED'
798 assert C.data['status'] == 'MERGED'
James E. Blaird466dc42012-07-31 10:42:56 -0700799 assert A.reported == 2
800 assert B.reported == 2
801 assert C.reported == 2
802
803 def test_failed_change_at_head(self):
804 "Test that if a change at the head fails, jobs behind it are canceled"
805 self.fake_jenkins.hold_jobs_in_build = True
806
807 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
808 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
809 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
James E. Blair8c803f82012-07-31 16:25:42 -0700810 A.addApproval('CRVW', 2)
811 B.addApproval('CRVW', 2)
812 C.addApproval('CRVW', 2)
James E. Blaird466dc42012-07-31 10:42:56 -0700813
James E. Blair4886cc12012-07-18 15:39:41 -0700814 self.fake_jenkins.fakeAddFailTest('project-test1', A)
James E. Blaird466dc42012-07-31 10:42:56 -0700815
816 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
817 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
818 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
819
820 self.waitUntilSettled()
821 jobs = self.fake_jenkins.all_jobs
822 finished_jobs = self.fake_jenkins.job_history
823
824 assert len(jobs) == 1
825 assert jobs[0].name == 'project-merge'
James E. Blair4886cc12012-07-18 15:39:41 -0700826 assert job_has_changes(jobs[0], A)
James E. Blaird466dc42012-07-31 10:42:56 -0700827
828 self.fake_jenkins.fakeRelease('.*-merge')
829 self.waitUntilSettled()
830 self.fake_jenkins.fakeRelease('.*-merge')
831 self.waitUntilSettled()
832 self.fake_jenkins.fakeRelease('.*-merge')
833 self.waitUntilSettled()
834
835 assert len(jobs) == 6
836 assert jobs[0].name == 'project-test1'
837 assert jobs[1].name == 'project-test2'
838 assert jobs[2].name == 'project-test1'
839 assert jobs[3].name == 'project-test2'
840 assert jobs[4].name == 'project-test1'
841 assert jobs[5].name == 'project-test2'
842
843 jobs[0].release()
844 self.waitUntilSettled()
845
846 assert len(jobs) == 1 # project-test2
847 assert self.countJobResults(finished_jobs, 'ABORTED') == 4
848
849 self.fake_jenkins.hold_jobs_in_build = False
850 self.fake_jenkins.fakeRelease()
851 self.waitUntilSettled()
852
853 assert len(jobs) == 0
854 assert len(finished_jobs) == 15
855 assert A.data['status'] == 'NEW'
856 assert B.data['status'] == 'MERGED'
857 assert C.data['status'] == 'MERGED'
858 assert A.reported == 2
859 assert B.reported == 2
860 assert C.reported == 2
861
862 def test_failed_change_at_head_with_queue(self):
863 "Test that if a change at the head fails, queued jobs are canceled"
864 self.fake_jenkins.hold_jobs_in_queue = True
865
866 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
867 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
868 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
James E. Blair8c803f82012-07-31 16:25:42 -0700869 A.addApproval('CRVW', 2)
870 B.addApproval('CRVW', 2)
871 C.addApproval('CRVW', 2)
James E. Blaird466dc42012-07-31 10:42:56 -0700872
James E. Blair4886cc12012-07-18 15:39:41 -0700873 self.fake_jenkins.fakeAddFailTest('project-test1', A)
James E. Blaird466dc42012-07-31 10:42:56 -0700874
875 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
876 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
877 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
878
879 self.waitUntilSettled()
880 jobs = self.fake_jenkins.all_jobs
881 finished_jobs = self.fake_jenkins.job_history
882 queue = self.fake_jenkins.queue
883
884 assert len(jobs) == 1
885 assert len(queue) == 1
886 assert jobs[0].name == 'project-merge'
James E. Blair4886cc12012-07-18 15:39:41 -0700887 assert job_has_changes(jobs[0], A)
James E. Blaird466dc42012-07-31 10:42:56 -0700888
889 self.fake_jenkins.fakeRelease('.*-merge')
890 self.waitUntilSettled()
891 self.fake_jenkins.fakeRelease('.*-merge')
892 self.waitUntilSettled()
893 self.fake_jenkins.fakeRelease('.*-merge')
894 self.waitUntilSettled()
895
896 assert len(jobs) == 6
897 assert len(queue) == 6
898 assert jobs[0].name == 'project-test1'
899 assert jobs[1].name == 'project-test2'
900 assert jobs[2].name == 'project-test1'
901 assert jobs[3].name == 'project-test2'
902 assert jobs[4].name == 'project-test1'
903 assert jobs[5].name == 'project-test2'
904
905 jobs[0].release()
906 self.waitUntilSettled()
907
908 assert len(jobs) == 1 # project-test2
909 assert len(queue) == 1
910 assert self.countJobResults(finished_jobs, 'ABORTED') == 0
911
912 self.fake_jenkins.hold_jobs_in_queue = False
913 self.fake_jenkins.fakeRelease()
914 self.waitUntilSettled()
915
916 assert len(jobs) == 0
917 assert len(finished_jobs) == 11
918 assert A.data['status'] == 'NEW'
919 assert B.data['status'] == 'MERGED'
920 assert C.data['status'] == 'MERGED'
921 assert A.reported == 2
922 assert B.reported == 2
923 assert C.reported == 2
James E. Blair8c803f82012-07-31 16:25:42 -0700924
925 def test_patch_order(self):
926 "Test that dependent patches are tested in the right order"
927 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
928 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
929 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
930 A.addApproval('CRVW', 2)
931 B.addApproval('CRVW', 2)
932 C.addApproval('CRVW', 2)
933
934 M2 = self.fake_gerrit.addFakeChange('org/project', 'master', 'M2')
935 M1 = self.fake_gerrit.addFakeChange('org/project', 'master', 'M1')
936 M2.setMerged()
937 M1.setMerged()
938
939 # C -> B -> A -> M1 -> M2
940 # M2 is here to make sure it is never queried. If it is, it
941 # means zuul is walking down the entire history of merged
942 # changes.
943
944 C.setDependsOn(B, 1)
945 B.setDependsOn(A, 1)
946 A.setDependsOn(M1, 1)
947 M1.setDependsOn(M2, 1)
948
949 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
950
951 self.waitUntilSettled()
952
953 assert A.data['status'] == 'NEW'
954 assert B.data['status'] == 'NEW'
955 assert C.data['status'] == 'NEW'
956
957 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
958 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
959
960 self.waitUntilSettled()
961 assert M2.queried == 0
962 assert A.data['status'] == 'MERGED'
963 assert B.data['status'] == 'MERGED'
964 assert C.data['status'] == 'MERGED'
965 assert A.reported == 2
966 assert B.reported == 2
967 assert C.reported == 2
968
969 def test_can_merge(self):
James E. Blair4886cc12012-07-18 15:39:41 -0700970 "Test whether a change is ready to merge"
James E. Blair8c803f82012-07-31 16:25:42 -0700971 # TODO: move to test_gerrit (this is a unit test!)
972 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
James E. Blair4aea70c2012-07-26 14:23:24 -0700973 a = self.sched.trigger.getChange(1, 2)
974 mgr = self.sched.pipelines['gate'].manager
975 assert not self.sched.trigger.canMerge(a, mgr.getSubmitAllowNeeds())
James E. Blair8c803f82012-07-31 16:25:42 -0700976
977 A.addApproval('CRVW', 2)
James E. Blair4aea70c2012-07-26 14:23:24 -0700978 a = self.sched.trigger.getChange(1, 2)
979 assert not self.sched.trigger.canMerge(a, mgr.getSubmitAllowNeeds())
James E. Blair8c803f82012-07-31 16:25:42 -0700980
981 A.addApproval('APRV', 1)
James E. Blair4aea70c2012-07-26 14:23:24 -0700982 a = self.sched.trigger.getChange(1, 2)
983 assert self.sched.trigger.canMerge(a, mgr.getSubmitAllowNeeds())
James E. Blair8c803f82012-07-31 16:25:42 -0700984
985 return True
James E. Blair4886cc12012-07-18 15:39:41 -0700986
987 def test_build_configuration(self):
988 "Test that zuul merges the right commits for testing"
989 self.fake_jenkins.hold_jobs_in_queue = True
990 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
991 B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
992 C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
993 A.addApproval('CRVW', 2)
994 B.addApproval('CRVW', 2)
995 C.addApproval('CRVW', 2)
996 self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
997 self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
998 self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
999 self.waitUntilSettled()
1000
1001 jobs = self.fake_jenkins.all_jobs
1002
1003 self.fake_jenkins.fakeRelease('.*-merge')
1004 self.waitUntilSettled()
1005 self.fake_jenkins.fakeRelease('.*-merge')
1006 self.waitUntilSettled()
1007 self.fake_jenkins.fakeRelease('.*-merge')
1008 self.waitUntilSettled()
1009 ref = jobs[-1].parameters['ZUUL_REF']
1010 self.fake_jenkins.hold_jobs_in_queue = False
1011 self.fake_jenkins.fakeRelease()
1012
1013 path = os.path.join("/tmp/zuul-test/git/org/project")
1014 repo = git.Repo(path)
1015 repo_messages = [c.message.strip() for c in repo.iter_commits(ref)]
1016 repo_messages.reverse()
1017 print ' repo messages :', repo_messages
1018 correct_messages = ['initial commit', 'A-1', 'B-1', 'C-1']
1019 assert repo_messages == correct_messages