blob: a71dc28814062e0e4f3ff92665b04d694c591602 [file] [log] [blame]
Maru Newby3fe5f852015-01-13 04:22:14 +00001# Copyright 2015 Red Hat, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
Joshua Hesketh0aa7e8b2016-07-14 00:12:25 +100015
James E. Blairce8a2132016-05-19 15:21:52 -070016import os
17import random
18
19import fixtures
James E. Blair4317e9f2016-07-15 10:05:47 -070020import testtools
James E. Blairce8a2132016-05-19 15:21:52 -070021
Maru Newby3fe5f852015-01-13 04:22:14 +000022from zuul import model
James E. Blair83005782015-12-11 14:46:03 -080023from zuul import configloader
Maru Newby3fe5f852015-01-13 04:22:14 +000024
25from tests.base import BaseTestCase
26
27
28class TestJob(BaseTestCase):
29
30 @property
31 def job(self):
James E. Blair83005782015-12-11 14:46:03 -080032 layout = model.Layout()
33 job = configloader.JobParser.fromYaml(layout, {
34 'name': 'job',
35 'irrelevant-files': [
36 '^docs/.*$'
37 ]})
Maru Newby3fe5f852015-01-13 04:22:14 +000038 return job
39
40 def test_change_matches_returns_false_for_matched_skip_if(self):
41 change = model.Change('project')
Alexander Evseevdbe6fab2015-11-19 12:46:34 +030042 change.files = ['/COMMIT_MSG', 'docs/foo']
Maru Newby3fe5f852015-01-13 04:22:14 +000043 self.assertFalse(self.job.changeMatches(change))
44
45 def test_change_matches_returns_true_for_unmatched_skip_if(self):
46 change = model.Change('project')
Alexander Evseevdbe6fab2015-11-19 12:46:34 +030047 change.files = ['/COMMIT_MSG', 'foo']
Maru Newby3fe5f852015-01-13 04:22:14 +000048 self.assertTrue(self.job.changeMatches(change))
49
Maru Newby79427a42015-02-17 17:54:45 +000050 def test_job_sets_defaults_for_boolean_attributes(self):
James E. Blair83005782015-12-11 14:46:03 -080051 self.assertIsNotNone(self.job.voting)
52
53 def test_job_inheritance(self):
54 layout = model.Layout()
James E. Blair8b1dc3f2016-07-05 16:49:00 -070055
56 pipeline = model.Pipeline('gate', layout)
57 layout.addPipeline(pipeline)
58 queue = model.ChangeQueue(pipeline)
James E. Blairc73c73a2017-01-20 15:15:15 -080059 project = model.Project('project', None)
James E. Blair8b1dc3f2016-07-05 16:49:00 -070060
James E. Blair83005782015-12-11 14:46:03 -080061 base = configloader.JobParser.fromYaml(layout, {
James E. Blair4317e9f2016-07-15 10:05:47 -070062 '_source_project': project,
James E. Blair83005782015-12-11 14:46:03 -080063 'name': 'base',
64 'timeout': 30,
James E. Blair1774dd52017-02-03 10:52:32 -080065 'nodes': [{
66 'name': 'controller',
67 'image': 'base',
68 }],
James E. Blair83005782015-12-11 14:46:03 -080069 })
70 layout.addJob(base)
71 python27 = configloader.JobParser.fromYaml(layout, {
James E. Blair4317e9f2016-07-15 10:05:47 -070072 '_source_project': project,
James E. Blair83005782015-12-11 14:46:03 -080073 'name': 'python27',
74 'parent': 'base',
James E. Blair1774dd52017-02-03 10:52:32 -080075 'nodes': [{
76 'name': 'controller',
77 'image': 'new',
78 }],
James E. Blair83005782015-12-11 14:46:03 -080079 'timeout': 40,
80 })
81 layout.addJob(python27)
82 python27diablo = configloader.JobParser.fromYaml(layout, {
James E. Blair4317e9f2016-07-15 10:05:47 -070083 '_source_project': project,
James E. Blair83005782015-12-11 14:46:03 -080084 'name': 'python27',
85 'branches': [
86 'stable/diablo'
87 ],
James E. Blair1774dd52017-02-03 10:52:32 -080088 'nodes': [{
89 'name': 'controller',
90 'image': 'old',
91 }],
James E. Blair83005782015-12-11 14:46:03 -080092 'timeout': 50,
93 })
94 layout.addJob(python27diablo)
95
James E. Blair8b1dc3f2016-07-05 16:49:00 -070096 project_config = configloader.ProjectParser.fromYaml(layout, {
James E. Blair66b274e2017-01-31 14:47:52 -080097 '_source_project': project,
98 '_source_branch': 'master',
99 '_source_configrepo': True,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700100 'name': 'project',
101 'gate': {
102 'jobs': [
103 'python27'
104 ]
105 }
106 })
107 layout.addProjectConfig(project_config, update_pipeline=False)
James E. Blair83005782015-12-11 14:46:03 -0800108
James E. Blair83005782015-12-11 14:46:03 -0800109 change = model.Change(project)
James E. Blair1774dd52017-02-03 10:52:32 -0800110 # Test master
James E. Blair83005782015-12-11 14:46:03 -0800111 change.branch = 'master'
112 item = queue.enqueueChange(change)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700113 item.current_build_set.layout = layout
James E. Blair83005782015-12-11 14:46:03 -0800114
115 self.assertTrue(base.changeMatches(change))
116 self.assertTrue(python27.changeMatches(change))
117 self.assertFalse(python27diablo.changeMatches(change))
118
119 item.freezeJobTree()
120 self.assertEqual(len(item.getJobs()), 1)
121 job = item.getJobs()[0]
122 self.assertEqual(job.name, 'python27')
123 self.assertEqual(job.timeout, 40)
James E. Blair1774dd52017-02-03 10:52:32 -0800124 nodes = job.nodeset.getNodes()
125 self.assertEqual(len(nodes), 1)
126 self.assertEqual(nodes[0].image, 'new')
James E. Blair83005782015-12-11 14:46:03 -0800127
James E. Blair1774dd52017-02-03 10:52:32 -0800128 # Test diablo
James E. Blair83005782015-12-11 14:46:03 -0800129 change.branch = 'stable/diablo'
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700130 item = queue.enqueueChange(change)
131 item.current_build_set.layout = layout
James E. Blair83005782015-12-11 14:46:03 -0800132
133 self.assertTrue(base.changeMatches(change))
134 self.assertTrue(python27.changeMatches(change))
135 self.assertTrue(python27diablo.changeMatches(change))
136
137 item.freezeJobTree()
138 self.assertEqual(len(item.getJobs()), 1)
139 job = item.getJobs()[0]
140 self.assertEqual(job.name, 'python27')
141 self.assertEqual(job.timeout, 50)
James E. Blair1774dd52017-02-03 10:52:32 -0800142 nodes = job.nodeset.getNodes()
143 self.assertEqual(len(nodes), 1)
144 self.assertEqual(nodes[0].image, 'old')
James E. Blairce8a2132016-05-19 15:21:52 -0700145
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000146 def test_job_auth_inheritance(self):
147 layout = model.Layout()
James E. Blairc73c73a2017-01-20 15:15:15 -0800148 project = model.Project('project', None)
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000149
150 base = configloader.JobParser.fromYaml(layout, {
151 '_source_project': project,
152 'name': 'base',
153 'timeout': 30,
154 })
155 layout.addJob(base)
156 pypi_upload_without_inherit = configloader.JobParser.fromYaml(layout, {
157 '_source_project': project,
158 'name': 'pypi-upload-without-inherit',
159 'parent': 'base',
160 'timeout': 40,
161 'auth': {
Ricardo Carrillo Cruz12c892b2016-11-18 15:35:49 +0000162 'secrets': [
163 'pypi-credentials',
164 ]
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000165 }
166 })
167 layout.addJob(pypi_upload_without_inherit)
168 pypi_upload_with_inherit = configloader.JobParser.fromYaml(layout, {
169 '_source_project': project,
170 'name': 'pypi-upload-with-inherit',
171 'parent': 'base',
172 'timeout': 40,
173 'auth': {
174 'inherit': True,
Ricardo Carrillo Cruz12c892b2016-11-18 15:35:49 +0000175 'secrets': [
176 'pypi-credentials',
177 ]
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000178 }
179 })
180 layout.addJob(pypi_upload_with_inherit)
181 pypi_upload_with_inherit_false = configloader.JobParser.fromYaml(
182 layout, {
183 '_source_project': project,
184 'name': 'pypi-upload-with-inherit-false',
185 'parent': 'base',
186 'timeout': 40,
187 'auth': {
188 'inherit': False,
Ricardo Carrillo Cruz12c892b2016-11-18 15:35:49 +0000189 'secrets': [
190 'pypi-credentials',
191 ]
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000192 }
193 })
194 layout.addJob(pypi_upload_with_inherit_false)
195 in_repo_job_without_inherit = configloader.JobParser.fromYaml(layout, {
196 '_source_project': project,
197 'name': 'in-repo-job-without-inherit',
198 'parent': 'pypi-upload-without-inherit',
199 })
200 layout.addJob(in_repo_job_without_inherit)
201 in_repo_job_with_inherit = configloader.JobParser.fromYaml(layout, {
202 '_source_project': project,
203 'name': 'in-repo-job-with-inherit',
204 'parent': 'pypi-upload-with-inherit',
205 })
206 layout.addJob(in_repo_job_with_inherit)
207 in_repo_job_with_inherit_false = configloader.JobParser.fromYaml(
208 layout, {
209 '_source_project': project,
210 'name': 'in-repo-job-with-inherit-false',
211 'parent': 'pypi-upload-with-inherit-false',
212 })
213 layout.addJob(in_repo_job_with_inherit_false)
214
215 self.assertNotIn('auth', in_repo_job_without_inherit.auth)
Ricardo Carrillo Cruz12c892b2016-11-18 15:35:49 +0000216 self.assertIn('secrets', in_repo_job_with_inherit.auth)
217 self.assertEquals(in_repo_job_with_inherit.auth['secrets'],
218 ['pypi-credentials'])
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000219 self.assertNotIn('auth', in_repo_job_with_inherit_false.auth)
220
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700221 def test_job_inheritance_job_tree(self):
222 layout = model.Layout()
223
224 pipeline = model.Pipeline('gate', layout)
225 layout.addPipeline(pipeline)
226 queue = model.ChangeQueue(pipeline)
James E. Blairc73c73a2017-01-20 15:15:15 -0800227 project = model.Project('project', None)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700228
229 base = configloader.JobParser.fromYaml(layout, {
James E. Blair4317e9f2016-07-15 10:05:47 -0700230 '_source_project': project,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700231 'name': 'base',
232 'timeout': 30,
233 })
234 layout.addJob(base)
235 python27 = configloader.JobParser.fromYaml(layout, {
James E. Blair4317e9f2016-07-15 10:05:47 -0700236 '_source_project': project,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700237 'name': 'python27',
238 'parent': 'base',
239 'timeout': 40,
240 })
241 layout.addJob(python27)
242 python27diablo = configloader.JobParser.fromYaml(layout, {
James E. Blair4317e9f2016-07-15 10:05:47 -0700243 '_source_project': project,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700244 'name': 'python27',
245 'branches': [
246 'stable/diablo'
247 ],
248 'timeout': 50,
249 })
250 layout.addJob(python27diablo)
251
252 project_config = configloader.ProjectParser.fromYaml(layout, {
James E. Blair66b274e2017-01-31 14:47:52 -0800253 '_source_project': project,
254 '_source_branch': 'master',
255 '_source_configrepo': True,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700256 'name': 'project',
257 'gate': {
258 'jobs': [
259 {'python27': {'timeout': 70}}
260 ]
261 }
262 })
263 layout.addProjectConfig(project_config, update_pipeline=False)
264
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700265 change = model.Change(project)
266 change.branch = 'master'
267 item = queue.enqueueChange(change)
268 item.current_build_set.layout = layout
269
270 self.assertTrue(base.changeMatches(change))
271 self.assertTrue(python27.changeMatches(change))
272 self.assertFalse(python27diablo.changeMatches(change))
273
274 item.freezeJobTree()
275 self.assertEqual(len(item.getJobs()), 1)
276 job = item.getJobs()[0]
277 self.assertEqual(job.name, 'python27')
278 self.assertEqual(job.timeout, 70)
279
280 change.branch = 'stable/diablo'
281 item = queue.enqueueChange(change)
282 item.current_build_set.layout = layout
283
284 self.assertTrue(base.changeMatches(change))
285 self.assertTrue(python27.changeMatches(change))
286 self.assertTrue(python27diablo.changeMatches(change))
287
288 item.freezeJobTree()
289 self.assertEqual(len(item.getJobs()), 1)
290 job = item.getJobs()[0]
291 self.assertEqual(job.name, 'python27')
292 self.assertEqual(job.timeout, 70)
293
Clint Byrum85493602016-11-18 11:59:47 -0800294 def test_inheritance_keeps_matchers(self):
295 layout = model.Layout()
296
297 pipeline = model.Pipeline('gate', layout)
298 layout.addPipeline(pipeline)
299 queue = model.ChangeQueue(pipeline)
James E. Blairc73c73a2017-01-20 15:15:15 -0800300 project = model.Project('project', None)
Clint Byrum85493602016-11-18 11:59:47 -0800301
302 base = configloader.JobParser.fromYaml(layout, {
303 '_source_project': project,
304 'name': 'base',
305 'timeout': 30,
306 })
307 layout.addJob(base)
308 python27 = configloader.JobParser.fromYaml(layout, {
309 '_source_project': project,
310 'name': 'python27',
311 'parent': 'base',
312 'timeout': 40,
313 'irrelevant-files': ['^ignored-file$'],
314 })
315 layout.addJob(python27)
316
317 project_config = configloader.ProjectParser.fromYaml(layout, {
James E. Blair66b274e2017-01-31 14:47:52 -0800318 '_source_project': project,
319 '_source_branch': 'master',
320 '_source_configrepo': True,
Clint Byrum85493602016-11-18 11:59:47 -0800321 'name': 'project',
322 'gate': {
323 'jobs': [
324 'python27',
325 ]
326 }
327 })
328 layout.addProjectConfig(project_config, update_pipeline=False)
329
330 change = model.Change(project)
331 change.branch = 'master'
332 change.files = ['/COMMIT_MSG', 'ignored-file']
333 item = queue.enqueueChange(change)
334 item.current_build_set.layout = layout
335
336 self.assertTrue(base.changeMatches(change))
337 self.assertFalse(python27.changeMatches(change))
338
339 item.freezeJobTree()
340 self.assertEqual([], item.getJobs())
341
James E. Blair4317e9f2016-07-15 10:05:47 -0700342 def test_job_source_project(self):
343 layout = model.Layout()
James E. Blairc73c73a2017-01-20 15:15:15 -0800344 base_project = model.Project('base_project', None)
James E. Blair4317e9f2016-07-15 10:05:47 -0700345 base = configloader.JobParser.fromYaml(layout, {
346 '_source_project': base_project,
347 'name': 'base',
348 })
349 layout.addJob(base)
350
James E. Blairc73c73a2017-01-20 15:15:15 -0800351 other_project = model.Project('other_project', None)
James E. Blair4317e9f2016-07-15 10:05:47 -0700352 base2 = configloader.JobParser.fromYaml(layout, {
353 '_source_project': other_project,
354 'name': 'base',
355 })
356 with testtools.ExpectedException(
357 Exception,
358 "Job base in other_project is not permitted "
359 "to shadow job base in base_project"):
360 layout.addJob(base2)
361
James E. Blairce8a2132016-05-19 15:21:52 -0700362
363class TestJobTimeData(BaseTestCase):
364 def setUp(self):
365 super(TestJobTimeData, self).setUp()
366 self.tmp_root = self.useFixture(fixtures.TempDir(
367 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
368 ).path
369
370 def test_empty_timedata(self):
371 path = os.path.join(self.tmp_root, 'job-name')
372 self.assertFalse(os.path.exists(path))
373 self.assertFalse(os.path.exists(path + '.tmp'))
374 td = model.JobTimeData(path)
375 self.assertEqual(td.success_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
376 self.assertEqual(td.failure_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
377 self.assertEqual(td.results, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
378
379 def test_save_reload(self):
380 path = os.path.join(self.tmp_root, 'job-name')
381 self.assertFalse(os.path.exists(path))
382 self.assertFalse(os.path.exists(path + '.tmp'))
383 td = model.JobTimeData(path)
384 self.assertEqual(td.success_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
385 self.assertEqual(td.failure_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
386 self.assertEqual(td.results, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
387 success_times = []
388 failure_times = []
389 results = []
390 for x in range(10):
391 success_times.append(int(random.random() * 1000))
392 failure_times.append(int(random.random() * 1000))
393 results.append(0)
394 results.append(1)
395 random.shuffle(results)
396 s = f = 0
397 for result in results:
398 if result:
399 td.add(failure_times[f], 'FAILURE')
400 f += 1
401 else:
402 td.add(success_times[s], 'SUCCESS')
403 s += 1
404 self.assertEqual(td.success_times, success_times)
405 self.assertEqual(td.failure_times, failure_times)
406 self.assertEqual(td.results, results[10:])
407 td.save()
408 self.assertTrue(os.path.exists(path))
409 self.assertFalse(os.path.exists(path + '.tmp'))
410 td = model.JobTimeData(path)
411 td.load()
412 self.assertEqual(td.success_times, success_times)
413 self.assertEqual(td.failure_times, failure_times)
414 self.assertEqual(td.results, results[10:])
415
416
417class TestTimeDataBase(BaseTestCase):
418 def setUp(self):
419 super(TestTimeDataBase, self).setUp()
420 self.tmp_root = self.useFixture(fixtures.TempDir(
421 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
422 ).path
423 self.db = model.TimeDataBase(self.tmp_root)
424
425 def test_timedatabase(self):
426 self.assertEqual(self.db.getEstimatedTime('job-name'), 0)
427 self.db.update('job-name', 50, 'SUCCESS')
428 self.assertEqual(self.db.getEstimatedTime('job-name'), 50)
429 self.db.update('job-name', 100, 'SUCCESS')
430 self.assertEqual(self.db.getEstimatedTime('job-name'), 75)
431 for x in range(10):
432 self.db.update('job-name', 100, 'SUCCESS')
433 self.assertEqual(self.db.getEstimatedTime('job-name'), 100)