blob: b7dc706cdfdea239a5bdd9eb355d73073585193c [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()
James E. Blaircdab2032017-02-01 09:09:29 -080033 project = model.Project('project', None)
34 context = model.SourceContext(project, 'master', True)
James E. Blair83005782015-12-11 14:46:03 -080035 job = configloader.JobParser.fromYaml(layout, {
James E. Blaircdab2032017-02-01 09:09:29 -080036 '_source_context': context,
James E. Blair83005782015-12-11 14:46:03 -080037 'name': 'job',
38 'irrelevant-files': [
39 '^docs/.*$'
40 ]})
Maru Newby3fe5f852015-01-13 04:22:14 +000041 return job
42
43 def test_change_matches_returns_false_for_matched_skip_if(self):
44 change = model.Change('project')
Alexander Evseevdbe6fab2015-11-19 12:46:34 +030045 change.files = ['/COMMIT_MSG', 'docs/foo']
Maru Newby3fe5f852015-01-13 04:22:14 +000046 self.assertFalse(self.job.changeMatches(change))
47
48 def test_change_matches_returns_true_for_unmatched_skip_if(self):
49 change = model.Change('project')
Alexander Evseevdbe6fab2015-11-19 12:46:34 +030050 change.files = ['/COMMIT_MSG', 'foo']
Maru Newby3fe5f852015-01-13 04:22:14 +000051 self.assertTrue(self.job.changeMatches(change))
52
Maru Newby79427a42015-02-17 17:54:45 +000053 def test_job_sets_defaults_for_boolean_attributes(self):
James E. Blair83005782015-12-11 14:46:03 -080054 self.assertIsNotNone(self.job.voting)
55
56 def test_job_inheritance(self):
57 layout = model.Layout()
James E. Blair8b1dc3f2016-07-05 16:49:00 -070058
59 pipeline = model.Pipeline('gate', layout)
60 layout.addPipeline(pipeline)
61 queue = model.ChangeQueue(pipeline)
James E. Blairc73c73a2017-01-20 15:15:15 -080062 project = model.Project('project', None)
James E. Blaircdab2032017-02-01 09:09:29 -080063 context = model.SourceContext(project, 'master', True)
James E. Blair8b1dc3f2016-07-05 16:49:00 -070064
James E. Blair83005782015-12-11 14:46:03 -080065 base = configloader.JobParser.fromYaml(layout, {
James E. Blaircdab2032017-02-01 09:09:29 -080066 '_source_context': context,
James E. Blair83005782015-12-11 14:46:03 -080067 'name': 'base',
68 'timeout': 30,
James E. Blair1774dd52017-02-03 10:52:32 -080069 'nodes': [{
70 'name': 'controller',
71 'image': 'base',
72 }],
James E. Blair83005782015-12-11 14:46:03 -080073 })
74 layout.addJob(base)
75 python27 = configloader.JobParser.fromYaml(layout, {
James E. Blaircdab2032017-02-01 09:09:29 -080076 '_source_context': context,
James E. Blair83005782015-12-11 14:46:03 -080077 'name': 'python27',
78 'parent': 'base',
James E. Blair1774dd52017-02-03 10:52:32 -080079 'nodes': [{
80 'name': 'controller',
81 'image': 'new',
82 }],
James E. Blair83005782015-12-11 14:46:03 -080083 'timeout': 40,
84 })
85 layout.addJob(python27)
86 python27diablo = configloader.JobParser.fromYaml(layout, {
James E. Blaircdab2032017-02-01 09:09:29 -080087 '_source_context': context,
James E. Blair83005782015-12-11 14:46:03 -080088 'name': 'python27',
89 'branches': [
90 'stable/diablo'
91 ],
James E. Blair1774dd52017-02-03 10:52:32 -080092 'nodes': [{
93 'name': 'controller',
94 'image': 'old',
95 }],
James E. Blair83005782015-12-11 14:46:03 -080096 'timeout': 50,
97 })
98 layout.addJob(python27diablo)
99
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700100 project_config = configloader.ProjectParser.fromYaml(layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800101 '_source_context': context,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700102 'name': 'project',
103 'gate': {
104 'jobs': [
105 'python27'
106 ]
107 }
108 })
109 layout.addProjectConfig(project_config, update_pipeline=False)
James E. Blair83005782015-12-11 14:46:03 -0800110
James E. Blair83005782015-12-11 14:46:03 -0800111 change = model.Change(project)
James E. Blair1774dd52017-02-03 10:52:32 -0800112 # Test master
James E. Blair83005782015-12-11 14:46:03 -0800113 change.branch = 'master'
114 item = queue.enqueueChange(change)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700115 item.current_build_set.layout = layout
James E. Blair83005782015-12-11 14:46:03 -0800116
117 self.assertTrue(base.changeMatches(change))
118 self.assertTrue(python27.changeMatches(change))
119 self.assertFalse(python27diablo.changeMatches(change))
120
121 item.freezeJobTree()
122 self.assertEqual(len(item.getJobs()), 1)
123 job = item.getJobs()[0]
124 self.assertEqual(job.name, 'python27')
125 self.assertEqual(job.timeout, 40)
James E. Blair1774dd52017-02-03 10:52:32 -0800126 nodes = job.nodeset.getNodes()
127 self.assertEqual(len(nodes), 1)
128 self.assertEqual(nodes[0].image, 'new')
James E. Blair83005782015-12-11 14:46:03 -0800129
James E. Blair1774dd52017-02-03 10:52:32 -0800130 # Test diablo
James E. Blair83005782015-12-11 14:46:03 -0800131 change.branch = 'stable/diablo'
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700132 item = queue.enqueueChange(change)
133 item.current_build_set.layout = layout
James E. Blair83005782015-12-11 14:46:03 -0800134
135 self.assertTrue(base.changeMatches(change))
136 self.assertTrue(python27.changeMatches(change))
137 self.assertTrue(python27diablo.changeMatches(change))
138
139 item.freezeJobTree()
140 self.assertEqual(len(item.getJobs()), 1)
141 job = item.getJobs()[0]
142 self.assertEqual(job.name, 'python27')
143 self.assertEqual(job.timeout, 50)
James E. Blair1774dd52017-02-03 10:52:32 -0800144 nodes = job.nodeset.getNodes()
145 self.assertEqual(len(nodes), 1)
146 self.assertEqual(nodes[0].image, 'old')
James E. Blairce8a2132016-05-19 15:21:52 -0700147
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000148 def test_job_auth_inheritance(self):
149 layout = model.Layout()
James E. Blairc73c73a2017-01-20 15:15:15 -0800150 project = model.Project('project', None)
James E. Blaircdab2032017-02-01 09:09:29 -0800151 context = model.SourceContext(project, 'master', True)
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000152
153 base = configloader.JobParser.fromYaml(layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800154 '_source_context': context,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000155 'name': 'base',
156 'timeout': 30,
157 })
158 layout.addJob(base)
159 pypi_upload_without_inherit = configloader.JobParser.fromYaml(layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800160 '_source_context': context,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000161 'name': 'pypi-upload-without-inherit',
162 'parent': 'base',
163 'timeout': 40,
164 'auth': {
Ricardo Carrillo Cruz12c892b2016-11-18 15:35:49 +0000165 'secrets': [
166 'pypi-credentials',
167 ]
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000168 }
169 })
170 layout.addJob(pypi_upload_without_inherit)
171 pypi_upload_with_inherit = configloader.JobParser.fromYaml(layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800172 '_source_context': context,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000173 'name': 'pypi-upload-with-inherit',
174 'parent': 'base',
175 'timeout': 40,
176 'auth': {
177 'inherit': True,
Ricardo Carrillo Cruz12c892b2016-11-18 15:35:49 +0000178 'secrets': [
179 'pypi-credentials',
180 ]
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000181 }
182 })
183 layout.addJob(pypi_upload_with_inherit)
184 pypi_upload_with_inherit_false = configloader.JobParser.fromYaml(
185 layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800186 '_source_context': context,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000187 'name': 'pypi-upload-with-inherit-false',
188 'parent': 'base',
189 'timeout': 40,
190 'auth': {
191 'inherit': False,
Ricardo Carrillo Cruz12c892b2016-11-18 15:35:49 +0000192 'secrets': [
193 'pypi-credentials',
194 ]
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000195 }
196 })
197 layout.addJob(pypi_upload_with_inherit_false)
198 in_repo_job_without_inherit = configloader.JobParser.fromYaml(layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800199 '_source_context': context,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000200 'name': 'in-repo-job-without-inherit',
201 'parent': 'pypi-upload-without-inherit',
202 })
203 layout.addJob(in_repo_job_without_inherit)
204 in_repo_job_with_inherit = configloader.JobParser.fromYaml(layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800205 '_source_context': context,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000206 'name': 'in-repo-job-with-inherit',
207 'parent': 'pypi-upload-with-inherit',
208 })
209 layout.addJob(in_repo_job_with_inherit)
210 in_repo_job_with_inherit_false = configloader.JobParser.fromYaml(
211 layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800212 '_source_context': context,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000213 'name': 'in-repo-job-with-inherit-false',
214 'parent': 'pypi-upload-with-inherit-false',
215 })
216 layout.addJob(in_repo_job_with_inherit_false)
217
218 self.assertNotIn('auth', in_repo_job_without_inherit.auth)
Ricardo Carrillo Cruz12c892b2016-11-18 15:35:49 +0000219 self.assertIn('secrets', in_repo_job_with_inherit.auth)
220 self.assertEquals(in_repo_job_with_inherit.auth['secrets'],
221 ['pypi-credentials'])
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000222 self.assertNotIn('auth', in_repo_job_with_inherit_false.auth)
223
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700224 def test_job_inheritance_job_tree(self):
225 layout = model.Layout()
226
227 pipeline = model.Pipeline('gate', layout)
228 layout.addPipeline(pipeline)
229 queue = model.ChangeQueue(pipeline)
James E. Blairc73c73a2017-01-20 15:15:15 -0800230 project = model.Project('project', None)
James E. Blaircdab2032017-02-01 09:09:29 -0800231 context = model.SourceContext(project, 'master', True)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700232
233 base = configloader.JobParser.fromYaml(layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800234 '_source_context': context,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700235 'name': 'base',
236 'timeout': 30,
237 })
238 layout.addJob(base)
239 python27 = configloader.JobParser.fromYaml(layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800240 '_source_context': context,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700241 'name': 'python27',
242 'parent': 'base',
243 'timeout': 40,
244 })
245 layout.addJob(python27)
246 python27diablo = configloader.JobParser.fromYaml(layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800247 '_source_context': context,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700248 'name': 'python27',
249 'branches': [
250 'stable/diablo'
251 ],
252 'timeout': 50,
253 })
254 layout.addJob(python27diablo)
255
256 project_config = configloader.ProjectParser.fromYaml(layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800257 '_source_context': context,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700258 'name': 'project',
259 'gate': {
260 'jobs': [
261 {'python27': {'timeout': 70}}
262 ]
263 }
264 })
265 layout.addProjectConfig(project_config, update_pipeline=False)
266
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700267 change = model.Change(project)
268 change.branch = 'master'
269 item = queue.enqueueChange(change)
270 item.current_build_set.layout = layout
271
272 self.assertTrue(base.changeMatches(change))
273 self.assertTrue(python27.changeMatches(change))
274 self.assertFalse(python27diablo.changeMatches(change))
275
276 item.freezeJobTree()
277 self.assertEqual(len(item.getJobs()), 1)
278 job = item.getJobs()[0]
279 self.assertEqual(job.name, 'python27')
280 self.assertEqual(job.timeout, 70)
281
282 change.branch = 'stable/diablo'
283 item = queue.enqueueChange(change)
284 item.current_build_set.layout = layout
285
286 self.assertTrue(base.changeMatches(change))
287 self.assertTrue(python27.changeMatches(change))
288 self.assertTrue(python27diablo.changeMatches(change))
289
290 item.freezeJobTree()
291 self.assertEqual(len(item.getJobs()), 1)
292 job = item.getJobs()[0]
293 self.assertEqual(job.name, 'python27')
294 self.assertEqual(job.timeout, 70)
295
Clint Byrum85493602016-11-18 11:59:47 -0800296 def test_inheritance_keeps_matchers(self):
297 layout = model.Layout()
298
299 pipeline = model.Pipeline('gate', layout)
300 layout.addPipeline(pipeline)
301 queue = model.ChangeQueue(pipeline)
James E. Blairc73c73a2017-01-20 15:15:15 -0800302 project = model.Project('project', None)
James E. Blaircdab2032017-02-01 09:09:29 -0800303 context = model.SourceContext(project, 'master', True)
Clint Byrum85493602016-11-18 11:59:47 -0800304
305 base = configloader.JobParser.fromYaml(layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800306 '_source_context': context,
Clint Byrum85493602016-11-18 11:59:47 -0800307 'name': 'base',
308 'timeout': 30,
309 })
310 layout.addJob(base)
311 python27 = configloader.JobParser.fromYaml(layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800312 '_source_context': context,
Clint Byrum85493602016-11-18 11:59:47 -0800313 'name': 'python27',
314 'parent': 'base',
315 'timeout': 40,
316 'irrelevant-files': ['^ignored-file$'],
317 })
318 layout.addJob(python27)
319
320 project_config = configloader.ProjectParser.fromYaml(layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800321 '_source_context': context,
Clint Byrum85493602016-11-18 11:59:47 -0800322 'name': 'project',
323 'gate': {
324 'jobs': [
325 'python27',
326 ]
327 }
328 })
329 layout.addProjectConfig(project_config, update_pipeline=False)
330
331 change = model.Change(project)
332 change.branch = 'master'
333 change.files = ['/COMMIT_MSG', 'ignored-file']
334 item = queue.enqueueChange(change)
335 item.current_build_set.layout = layout
336
337 self.assertTrue(base.changeMatches(change))
338 self.assertFalse(python27.changeMatches(change))
339
340 item.freezeJobTree()
341 self.assertEqual([], item.getJobs())
342
James E. Blair4317e9f2016-07-15 10:05:47 -0700343 def test_job_source_project(self):
344 layout = model.Layout()
James E. Blairc73c73a2017-01-20 15:15:15 -0800345 base_project = model.Project('base_project', None)
James E. Blaircdab2032017-02-01 09:09:29 -0800346 base_context = model.SourceContext(base_project, 'master', True)
347
James E. Blair4317e9f2016-07-15 10:05:47 -0700348 base = configloader.JobParser.fromYaml(layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800349 '_source_context': base_context,
James E. Blair4317e9f2016-07-15 10:05:47 -0700350 'name': 'base',
351 })
352 layout.addJob(base)
353
James E. Blairc73c73a2017-01-20 15:15:15 -0800354 other_project = model.Project('other_project', None)
James E. Blaircdab2032017-02-01 09:09:29 -0800355 other_context = model.SourceContext(other_project, 'master', True)
James E. Blair4317e9f2016-07-15 10:05:47 -0700356 base2 = configloader.JobParser.fromYaml(layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800357 '_source_context': other_context,
James E. Blair4317e9f2016-07-15 10:05:47 -0700358 'name': 'base',
359 })
360 with testtools.ExpectedException(
361 Exception,
362 "Job base in other_project is not permitted "
363 "to shadow job base in base_project"):
364 layout.addJob(base2)
365
James E. Blairce8a2132016-05-19 15:21:52 -0700366
367class TestJobTimeData(BaseTestCase):
368 def setUp(self):
369 super(TestJobTimeData, self).setUp()
370 self.tmp_root = self.useFixture(fixtures.TempDir(
371 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
372 ).path
373
374 def test_empty_timedata(self):
375 path = os.path.join(self.tmp_root, 'job-name')
376 self.assertFalse(os.path.exists(path))
377 self.assertFalse(os.path.exists(path + '.tmp'))
378 td = model.JobTimeData(path)
379 self.assertEqual(td.success_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
380 self.assertEqual(td.failure_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
381 self.assertEqual(td.results, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
382
383 def test_save_reload(self):
384 path = os.path.join(self.tmp_root, 'job-name')
385 self.assertFalse(os.path.exists(path))
386 self.assertFalse(os.path.exists(path + '.tmp'))
387 td = model.JobTimeData(path)
388 self.assertEqual(td.success_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
389 self.assertEqual(td.failure_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
390 self.assertEqual(td.results, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
391 success_times = []
392 failure_times = []
393 results = []
394 for x in range(10):
395 success_times.append(int(random.random() * 1000))
396 failure_times.append(int(random.random() * 1000))
397 results.append(0)
398 results.append(1)
399 random.shuffle(results)
400 s = f = 0
401 for result in results:
402 if result:
403 td.add(failure_times[f], 'FAILURE')
404 f += 1
405 else:
406 td.add(success_times[s], 'SUCCESS')
407 s += 1
408 self.assertEqual(td.success_times, success_times)
409 self.assertEqual(td.failure_times, failure_times)
410 self.assertEqual(td.results, results[10:])
411 td.save()
412 self.assertTrue(os.path.exists(path))
413 self.assertFalse(os.path.exists(path + '.tmp'))
414 td = model.JobTimeData(path)
415 td.load()
416 self.assertEqual(td.success_times, success_times)
417 self.assertEqual(td.failure_times, failure_times)
418 self.assertEqual(td.results, results[10:])
419
420
421class TestTimeDataBase(BaseTestCase):
422 def setUp(self):
423 super(TestTimeDataBase, self).setUp()
424 self.tmp_root = self.useFixture(fixtures.TempDir(
425 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
426 ).path
427 self.db = model.TimeDataBase(self.tmp_root)
428
429 def test_timedatabase(self):
430 self.assertEqual(self.db.getEstimatedTime('job-name'), 0)
431 self.db.update('job-name', 50, 'SUCCESS')
432 self.assertEqual(self.db.getEstimatedTime('job-name'), 50)
433 self.db.update('job-name', 100, 'SUCCESS')
434 self.assertEqual(self.db.getEstimatedTime('job-name'), 75)
435 for x in range(10):
436 self.db.update('job-name', 100, 'SUCCESS')
437 self.assertEqual(self.db.getEstimatedTime('job-name'), 100)