blob: 2167a3b713a4f841e8779279a062abd2fcf850a2 [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
James E. Blairbf1a4f22017-03-17 10:59:37 -070024from zuul.lib import encryption
Clint Byrum88fdfde2017-03-28 16:22:42 -070025from zuul.lib import yamlutil as yaml
Maru Newby3fe5f852015-01-13 04:22:14 +000026
James E. Blair18f86a32017-03-15 14:43:26 -070027from tests.base import BaseTestCase, FIXTURE_DIR
Maru Newby3fe5f852015-01-13 04:22:14 +000028
29
James E. Blairb3f5db12017-03-17 12:57:39 -070030class FakeSource(object):
31 def __init__(self, name):
32 self.name = name
33
34
Maru Newby3fe5f852015-01-13 04:22:14 +000035class TestJob(BaseTestCase):
36
James E. Blaira7f51ca2017-02-07 16:01:26 -080037 def setUp(self):
38 super(TestJob, self).setUp()
James E. Blairb3f5db12017-03-17 12:57:39 -070039 self.tenant = model.Tenant('tenant')
40 self.layout = model.Layout()
41 self.project = model.Project('project', 'connection')
42 self.source = FakeSource('connection')
43 self.tenant.addProjectRepo(self.source, self.project)
44 self.pipeline = model.Pipeline('gate', self.layout)
45 self.layout.addPipeline(self.pipeline)
46 self.queue = model.ChangeQueue(self.pipeline)
47
James E. Blair18f86a32017-03-15 14:43:26 -070048 private_key_file = os.path.join(FIXTURE_DIR, 'private.pem')
49 with open(private_key_file, "rb") as f:
James E. Blairbf1a4f22017-03-17 10:59:37 -070050 self.project.private_key, self.project.public_key = \
51 encryption.deserialize_rsa_keypair(f.read())
James E. Blair6f140c72017-03-03 10:32:07 -080052 self.context = model.SourceContext(self.project, 'master',
53 'test', True)
James E. Blairec7ff302017-03-04 07:31:32 -080054 self.start_mark = yaml.Mark('name', 0, 0, 0, '', 0)
James E. Blaira7f51ca2017-02-07 16:01:26 -080055
Maru Newby3fe5f852015-01-13 04:22:14 +000056 @property
57 def job(self):
James E. Blair5ac93842017-01-20 06:47:34 -080058 tenant = model.Tenant('tenant')
James E. Blair83005782015-12-11 14:46:03 -080059 layout = model.Layout()
James E. Blair5ac93842017-01-20 06:47:34 -080060 job = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -080061 '_source_context': self.context,
62 '_start_mark': self.start_mark,
James E. Blair83005782015-12-11 14:46:03 -080063 'name': 'job',
64 'irrelevant-files': [
65 '^docs/.*$'
66 ]})
Maru Newby3fe5f852015-01-13 04:22:14 +000067 return job
68
69 def test_change_matches_returns_false_for_matched_skip_if(self):
70 change = model.Change('project')
Alexander Evseevdbe6fab2015-11-19 12:46:34 +030071 change.files = ['/COMMIT_MSG', 'docs/foo']
Maru Newby3fe5f852015-01-13 04:22:14 +000072 self.assertFalse(self.job.changeMatches(change))
73
74 def test_change_matches_returns_true_for_unmatched_skip_if(self):
75 change = model.Change('project')
Alexander Evseevdbe6fab2015-11-19 12:46:34 +030076 change.files = ['/COMMIT_MSG', 'foo']
Maru Newby3fe5f852015-01-13 04:22:14 +000077 self.assertTrue(self.job.changeMatches(change))
78
Maru Newby79427a42015-02-17 17:54:45 +000079 def test_job_sets_defaults_for_boolean_attributes(self):
James E. Blair83005782015-12-11 14:46:03 -080080 self.assertIsNotNone(self.job.voting)
81
82 def test_job_inheritance(self):
James E. Blaira7f51ca2017-02-07 16:01:26 -080083 # This is standard job inheritance.
84
85 base_pre = model.PlaybookContext(self.context, 'base-pre')
86 base_run = model.PlaybookContext(self.context, 'base-run')
87 base_post = model.PlaybookContext(self.context, 'base-post')
88
89 base = model.Job('base')
90 base.timeout = 30
91 base.pre_run = [base_pre]
92 base.run = [base_run]
93 base.post_run = [base_post]
James E. Blair8525e2b2017-03-15 14:05:47 -070094 base.auth = model.AuthContext()
James E. Blaira7f51ca2017-02-07 16:01:26 -080095
96 py27 = model.Job('py27')
97 self.assertEqual(None, py27.timeout)
98 py27.inheritFrom(base)
99 self.assertEqual(30, py27.timeout)
100 self.assertEqual(['base-pre'],
101 [x.path for x in py27.pre_run])
102 self.assertEqual(['base-run'],
103 [x.path for x in py27.run])
104 self.assertEqual(['base-post'],
105 [x.path for x in py27.post_run])
James E. Blair8525e2b2017-03-15 14:05:47 -0700106 self.assertEqual(None, py27.auth)
James E. Blaira7f51ca2017-02-07 16:01:26 -0800107
108 def test_job_variants(self):
109 # This simulates freezing a job.
110
111 py27_pre = model.PlaybookContext(self.context, 'py27-pre')
112 py27_run = model.PlaybookContext(self.context, 'py27-run')
113 py27_post = model.PlaybookContext(self.context, 'py27-post')
114
115 py27 = model.Job('py27')
116 py27.timeout = 30
117 py27.pre_run = [py27_pre]
118 py27.run = [py27_run]
119 py27.post_run = [py27_post]
James E. Blair8525e2b2017-03-15 14:05:47 -0700120 auth = model.AuthContext()
121 auth.secrets.append('foo')
James E. Blaira7f51ca2017-02-07 16:01:26 -0800122 py27.auth = auth
123
124 job = py27.copy()
125 self.assertEqual(30, job.timeout)
126
127 # Apply the diablo variant
128 diablo = model.Job('py27')
129 diablo.timeout = 40
130 job.applyVariant(diablo)
131
132 self.assertEqual(40, job.timeout)
133 self.assertEqual(['py27-pre'],
134 [x.path for x in job.pre_run])
135 self.assertEqual(['py27-run'],
136 [x.path for x in job.run])
137 self.assertEqual(['py27-post'],
138 [x.path for x in job.post_run])
139 self.assertEqual(auth, job.auth)
140
141 # Set the job to final for the following checks
142 job.final = True
143 self.assertTrue(job.voting)
144
145 good_final = model.Job('py27')
146 good_final.voting = False
147 job.applyVariant(good_final)
148 self.assertFalse(job.voting)
149
150 bad_final = model.Job('py27')
151 bad_final.timeout = 600
152 with testtools.ExpectedException(
153 Exception,
154 "Unable to modify final job"):
155 job.applyVariant(bad_final)
156
157 def test_job_inheritance_configloader(self):
158 # TODO(jeblair): move this to a configloader test
James E. Blair5ac93842017-01-20 06:47:34 -0800159 tenant = model.Tenant('tenant')
James E. Blair83005782015-12-11 14:46:03 -0800160 layout = model.Layout()
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700161
162 pipeline = model.Pipeline('gate', layout)
163 layout.addPipeline(pipeline)
164 queue = model.ChangeQueue(pipeline)
James E. Blairc73c73a2017-01-20 15:15:15 -0800165 project = model.Project('project', None)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700166
James E. Blair5ac93842017-01-20 06:47:34 -0800167 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800168 '_source_context': self.context,
169 '_start_mark': self.start_mark,
James E. Blair83005782015-12-11 14:46:03 -0800170 'name': 'base',
171 'timeout': 30,
James E. Blaira7f51ca2017-02-07 16:01:26 -0800172 'pre-run': 'base-pre',
173 'post-run': 'base-post',
James E. Blair1774dd52017-02-03 10:52:32 -0800174 'nodes': [{
175 'name': 'controller',
176 'image': 'base',
177 }],
James E. Blair83005782015-12-11 14:46:03 -0800178 })
179 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800180 python27 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800181 '_source_context': self.context,
182 '_start_mark': self.start_mark,
James E. Blair83005782015-12-11 14:46:03 -0800183 'name': 'python27',
184 'parent': 'base',
James E. Blaira7f51ca2017-02-07 16:01:26 -0800185 'pre-run': 'py27-pre',
186 'post-run': 'py27-post',
James E. Blair1774dd52017-02-03 10:52:32 -0800187 'nodes': [{
188 'name': 'controller',
189 'image': 'new',
190 }],
James E. Blair83005782015-12-11 14:46:03 -0800191 'timeout': 40,
192 })
193 layout.addJob(python27)
James E. Blair5ac93842017-01-20 06:47:34 -0800194 python27diablo = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800195 '_source_context': self.context,
196 '_start_mark': self.start_mark,
James E. Blair83005782015-12-11 14:46:03 -0800197 'name': 'python27',
198 'branches': [
199 'stable/diablo'
200 ],
James E. Blaira7f51ca2017-02-07 16:01:26 -0800201 'pre-run': 'py27-diablo-pre',
202 'run': 'py27-diablo',
203 'post-run': 'py27-diablo-post',
James E. Blair1774dd52017-02-03 10:52:32 -0800204 'nodes': [{
205 'name': 'controller',
206 'image': 'old',
207 }],
James E. Blair83005782015-12-11 14:46:03 -0800208 'timeout': 50,
209 })
210 layout.addJob(python27diablo)
211
James E. Blair5ac93842017-01-20 06:47:34 -0800212 python27essex = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800213 '_source_context': self.context,
214 '_start_mark': self.start_mark,
James E. Blaira7f51ca2017-02-07 16:01:26 -0800215 'name': 'python27',
216 'branches': [
217 'stable/essex'
218 ],
219 'pre-run': 'py27-essex-pre',
220 'post-run': 'py27-essex-post',
221 })
222 layout.addJob(python27essex)
223
James E. Blairff555742017-02-19 11:34:27 -0800224 project_config = configloader.ProjectParser.fromYaml(tenant, layout, [{
James E. Blairec7ff302017-03-04 07:31:32 -0800225 '_source_context': self.context,
226 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700227 'name': 'project',
228 'gate': {
229 'jobs': [
230 'python27'
231 ]
232 }
James E. Blairff555742017-02-19 11:34:27 -0800233 }])
James E. Blairf59f3cf2017-02-19 14:50:26 -0800234 layout.addProjectConfig(project_config)
James E. Blair83005782015-12-11 14:46:03 -0800235
James E. Blair83005782015-12-11 14:46:03 -0800236 change = model.Change(project)
James E. Blair1774dd52017-02-03 10:52:32 -0800237 # Test master
James E. Blair83005782015-12-11 14:46:03 -0800238 change.branch = 'master'
239 item = queue.enqueueChange(change)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700240 item.current_build_set.layout = layout
James E. Blair83005782015-12-11 14:46:03 -0800241
242 self.assertTrue(base.changeMatches(change))
243 self.assertTrue(python27.changeMatches(change))
244 self.assertFalse(python27diablo.changeMatches(change))
James E. Blaira7f51ca2017-02-07 16:01:26 -0800245 self.assertFalse(python27essex.changeMatches(change))
James E. Blair83005782015-12-11 14:46:03 -0800246
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200247 item.freezeJobGraph()
James E. Blair83005782015-12-11 14:46:03 -0800248 self.assertEqual(len(item.getJobs()), 1)
249 job = item.getJobs()[0]
250 self.assertEqual(job.name, 'python27')
251 self.assertEqual(job.timeout, 40)
James E. Blair1774dd52017-02-03 10:52:32 -0800252 nodes = job.nodeset.getNodes()
253 self.assertEqual(len(nodes), 1)
254 self.assertEqual(nodes[0].image, 'new')
James E. Blaira7f51ca2017-02-07 16:01:26 -0800255 self.assertEqual([x.path for x in job.pre_run],
256 ['playbooks/base-pre',
257 'playbooks/py27-pre'])
258 self.assertEqual([x.path for x in job.post_run],
259 ['playbooks/py27-post',
260 'playbooks/base-post'])
261 self.assertEqual([x.path for x in job.run],
262 ['playbooks/python27',
263 'playbooks/base'])
James E. Blair83005782015-12-11 14:46:03 -0800264
James E. Blair1774dd52017-02-03 10:52:32 -0800265 # Test diablo
James E. Blair83005782015-12-11 14:46:03 -0800266 change.branch = 'stable/diablo'
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700267 item = queue.enqueueChange(change)
268 item.current_build_set.layout = layout
James E. Blair83005782015-12-11 14:46:03 -0800269
270 self.assertTrue(base.changeMatches(change))
271 self.assertTrue(python27.changeMatches(change))
272 self.assertTrue(python27diablo.changeMatches(change))
James E. Blaira7f51ca2017-02-07 16:01:26 -0800273 self.assertFalse(python27essex.changeMatches(change))
James E. Blair83005782015-12-11 14:46:03 -0800274
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200275 item.freezeJobGraph()
James E. Blair83005782015-12-11 14:46:03 -0800276 self.assertEqual(len(item.getJobs()), 1)
277 job = item.getJobs()[0]
278 self.assertEqual(job.name, 'python27')
279 self.assertEqual(job.timeout, 50)
James E. Blair1774dd52017-02-03 10:52:32 -0800280 nodes = job.nodeset.getNodes()
281 self.assertEqual(len(nodes), 1)
282 self.assertEqual(nodes[0].image, 'old')
James E. Blaira7f51ca2017-02-07 16:01:26 -0800283 self.assertEqual([x.path for x in job.pre_run],
284 ['playbooks/base-pre',
285 'playbooks/py27-pre',
286 'playbooks/py27-diablo-pre'])
287 self.assertEqual([x.path for x in job.post_run],
288 ['playbooks/py27-diablo-post',
289 'playbooks/py27-post',
290 'playbooks/base-post'])
291 self.assertEqual([x.path for x in job.run],
292 ['playbooks/py27-diablo']),
293
294 # Test essex
295 change.branch = 'stable/essex'
296 item = queue.enqueueChange(change)
297 item.current_build_set.layout = layout
298
299 self.assertTrue(base.changeMatches(change))
300 self.assertTrue(python27.changeMatches(change))
301 self.assertFalse(python27diablo.changeMatches(change))
302 self.assertTrue(python27essex.changeMatches(change))
303
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200304 item.freezeJobGraph()
James E. Blaira7f51ca2017-02-07 16:01:26 -0800305 self.assertEqual(len(item.getJobs()), 1)
306 job = item.getJobs()[0]
307 self.assertEqual(job.name, 'python27')
308 self.assertEqual([x.path for x in job.pre_run],
309 ['playbooks/base-pre',
310 'playbooks/py27-pre',
311 'playbooks/py27-essex-pre'])
312 self.assertEqual([x.path for x in job.post_run],
313 ['playbooks/py27-essex-post',
314 'playbooks/py27-post',
315 'playbooks/base-post'])
316 self.assertEqual([x.path for x in job.run],
317 ['playbooks/python27',
318 'playbooks/base'])
James E. Blairce8a2132016-05-19 15:21:52 -0700319
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000320 def test_job_auth_inheritance(self):
James E. Blair5ac93842017-01-20 06:47:34 -0800321 tenant = model.Tenant('tenant')
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000322 layout = model.Layout()
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000323
James E. Blair01f83b72017-03-15 13:03:40 -0700324 conf = yaml.safe_load('''
325- secret:
326 name: pypi-credentials
327 data:
328 username: test-username
James E. Blair717e8e92017-03-17 11:03:27 -0700329 password: !encrypted/pkcs1-oaep |
James E. Blair01f83b72017-03-15 13:03:40 -0700330 BFhtdnm8uXx7kn79RFL/zJywmzLkT1GY78P3bOtp4WghUFWobkifSu7ZpaV4NeO0s71YUsi1wGZZ
331 L0LveZjUN0t6OU1VZKSG8R5Ly7urjaSo1pPVIq5Rtt/H7W14Lecd+cUeKb4joeusC9drN3AA8a4o
332 ykcVpt1wVqUnTbMGC9ARMCQP6eopcs1l7tzMseprW4RDNhIuz3CRgd0QBMPl6VDoFgBPB8vxtJw+
333 3m0rqBYZCLZgCXekqlny8s2s92nJMuUABbJOEcDRarzibDsSXsfJt1y+5n7yOURsC7lovMg4GF/v
334 Cl/0YMKjBO5bpv9EM5fToeKYyPGSKQoHOnCYceb3cAVcv5UawcCic8XjhEhp4K7WPdYf2HVAC/qt
335 xhbpjTxG4U5Q/SoppOJ60WqEkQvbXs6n5Dvy7xmph6GWmU/bAv3eUK3pdD3xa2Ue1lHWz3U+rsYr
336 aI+AKYsMYx3RBlfAmCeC1ve2BXPrqnOo7G8tnUvfdYPbK4Aakk0ds/AVqFHEZN+S6hRBmBjLaRFW
337 Z3QSO1NjbBxWnaHKZYT7nkrJm8AMCgZU0ZArFLpaufKCeiK5ECSsDxic4FIsY1OkWT42qEUfL0Wd
338 +150AKGNZpPJnnP3QYY4W/MWcKH/zdO400+zWN52WevbSqZy90tqKDJrBkMl1ydqbuw1E4ZHvIs=
339''')[0]['secret']
340
341 conf['_source_context'] = self.context
342 conf['_start_mark'] = self.start_mark
343
344 secret = configloader.SecretParser.fromYaml(layout, conf)
345 layout.addSecret(secret)
346
James E. Blair5ac93842017-01-20 06:47:34 -0800347 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800348 '_source_context': self.context,
349 '_start_mark': self.start_mark,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000350 'name': 'base',
351 'timeout': 30,
352 })
353 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800354 pypi_upload_without_inherit = configloader.JobParser.fromYaml(
355 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800356 '_source_context': self.context,
357 '_start_mark': self.start_mark,
James E. Blair5ac93842017-01-20 06:47:34 -0800358 'name': 'pypi-upload-without-inherit',
359 'parent': 'base',
360 'timeout': 40,
361 'auth': {
362 'secrets': [
363 'pypi-credentials',
364 ]
365 }
366 })
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000367 layout.addJob(pypi_upload_without_inherit)
James E. Blair5ac93842017-01-20 06:47:34 -0800368 pypi_upload_with_inherit = configloader.JobParser.fromYaml(
369 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800370 '_source_context': self.context,
371 '_start_mark': self.start_mark,
James E. Blair5ac93842017-01-20 06:47:34 -0800372 'name': 'pypi-upload-with-inherit',
373 'parent': 'base',
374 'timeout': 40,
375 'auth': {
376 'inherit': True,
377 'secrets': [
378 'pypi-credentials',
379 ]
380 }
381 })
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000382 layout.addJob(pypi_upload_with_inherit)
383 pypi_upload_with_inherit_false = configloader.JobParser.fromYaml(
James E. Blair5ac93842017-01-20 06:47:34 -0800384 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800385 '_source_context': self.context,
386 '_start_mark': self.start_mark,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000387 'name': 'pypi-upload-with-inherit-false',
388 'parent': 'base',
389 'timeout': 40,
390 'auth': {
391 'inherit': False,
Ricardo Carrillo Cruz12c892b2016-11-18 15:35:49 +0000392 'secrets': [
393 'pypi-credentials',
394 ]
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000395 }
396 })
397 layout.addJob(pypi_upload_with_inherit_false)
James E. Blair5ac93842017-01-20 06:47:34 -0800398 in_repo_job_without_inherit = configloader.JobParser.fromYaml(
399 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800400 '_source_context': self.context,
401 '_start_mark': self.start_mark,
James E. Blair5ac93842017-01-20 06:47:34 -0800402 'name': 'in-repo-job-without-inherit',
403 'parent': 'pypi-upload-without-inherit',
404 })
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000405 layout.addJob(in_repo_job_without_inherit)
James E. Blair5ac93842017-01-20 06:47:34 -0800406 in_repo_job_with_inherit = configloader.JobParser.fromYaml(
407 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800408 '_source_context': self.context,
409 '_start_mark': self.start_mark,
James E. Blair5ac93842017-01-20 06:47:34 -0800410 'name': 'in-repo-job-with-inherit',
411 'parent': 'pypi-upload-with-inherit',
412 })
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000413 layout.addJob(in_repo_job_with_inherit)
414 in_repo_job_with_inherit_false = configloader.JobParser.fromYaml(
James E. Blair5ac93842017-01-20 06:47:34 -0800415 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800416 '_source_context': self.context,
417 '_start_mark': self.start_mark,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000418 'name': 'in-repo-job-with-inherit-false',
419 'parent': 'pypi-upload-with-inherit-false',
420 })
421 layout.addJob(in_repo_job_with_inherit_false)
422
James E. Blair8525e2b2017-03-15 14:05:47 -0700423 self.assertEqual(None, in_repo_job_without_inherit.auth)
424 self.assertEqual(1, len(in_repo_job_with_inherit.auth.secrets))
425 self.assertEqual(in_repo_job_with_inherit.auth.secrets[0].name,
426 'pypi-credentials')
427 self.assertEqual(None, in_repo_job_with_inherit_false.auth)
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000428
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700429 def test_job_inheritance_job_tree(self):
James E. Blair5ac93842017-01-20 06:47:34 -0800430 tenant = model.Tenant('tenant')
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700431 layout = model.Layout()
432
433 pipeline = model.Pipeline('gate', layout)
434 layout.addPipeline(pipeline)
435 queue = model.ChangeQueue(pipeline)
436
James E. Blair5ac93842017-01-20 06:47:34 -0800437 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800438 '_source_context': self.context,
439 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700440 'name': 'base',
441 'timeout': 30,
442 })
443 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800444 python27 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800445 '_source_context': self.context,
446 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700447 'name': 'python27',
448 'parent': 'base',
449 'timeout': 40,
450 })
451 layout.addJob(python27)
James E. Blair5ac93842017-01-20 06:47:34 -0800452 python27diablo = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800453 '_source_context': self.context,
454 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700455 'name': 'python27',
456 'branches': [
457 'stable/diablo'
458 ],
459 'timeout': 50,
460 })
461 layout.addJob(python27diablo)
462
James E. Blairff555742017-02-19 11:34:27 -0800463 project_config = configloader.ProjectParser.fromYaml(tenant, layout, [{
James E. Blairec7ff302017-03-04 07:31:32 -0800464 '_source_context': self.context,
465 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700466 'name': 'project',
467 'gate': {
468 'jobs': [
469 {'python27': {'timeout': 70}}
470 ]
471 }
James E. Blairff555742017-02-19 11:34:27 -0800472 }])
James E. Blairf59f3cf2017-02-19 14:50:26 -0800473 layout.addProjectConfig(project_config)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700474
James E. Blairec7ff302017-03-04 07:31:32 -0800475 change = model.Change(self.project)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700476 change.branch = 'master'
477 item = queue.enqueueChange(change)
478 item.current_build_set.layout = layout
479
480 self.assertTrue(base.changeMatches(change))
481 self.assertTrue(python27.changeMatches(change))
482 self.assertFalse(python27diablo.changeMatches(change))
483
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200484 item.freezeJobGraph()
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700485 self.assertEqual(len(item.getJobs()), 1)
486 job = item.getJobs()[0]
487 self.assertEqual(job.name, 'python27')
488 self.assertEqual(job.timeout, 70)
489
490 change.branch = 'stable/diablo'
491 item = queue.enqueueChange(change)
492 item.current_build_set.layout = layout
493
494 self.assertTrue(base.changeMatches(change))
495 self.assertTrue(python27.changeMatches(change))
496 self.assertTrue(python27diablo.changeMatches(change))
497
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200498 item.freezeJobGraph()
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700499 self.assertEqual(len(item.getJobs()), 1)
500 job = item.getJobs()[0]
501 self.assertEqual(job.name, 'python27')
502 self.assertEqual(job.timeout, 70)
503
Clint Byrum85493602016-11-18 11:59:47 -0800504 def test_inheritance_keeps_matchers(self):
James E. Blair5ac93842017-01-20 06:47:34 -0800505 tenant = model.Tenant('tenant')
Clint Byrum85493602016-11-18 11:59:47 -0800506 layout = model.Layout()
507
508 pipeline = model.Pipeline('gate', layout)
509 layout.addPipeline(pipeline)
510 queue = model.ChangeQueue(pipeline)
James E. Blairc73c73a2017-01-20 15:15:15 -0800511 project = model.Project('project', None)
Clint Byrum85493602016-11-18 11:59:47 -0800512
James E. Blair5ac93842017-01-20 06:47:34 -0800513 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800514 '_source_context': self.context,
515 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800516 'name': 'base',
517 'timeout': 30,
518 })
519 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800520 python27 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800521 '_source_context': self.context,
522 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800523 'name': 'python27',
524 'parent': 'base',
525 'timeout': 40,
526 'irrelevant-files': ['^ignored-file$'],
527 })
528 layout.addJob(python27)
529
James E. Blairff555742017-02-19 11:34:27 -0800530 project_config = configloader.ProjectParser.fromYaml(tenant, layout, [{
James E. Blairec7ff302017-03-04 07:31:32 -0800531 '_source_context': self.context,
532 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800533 'name': 'project',
534 'gate': {
535 'jobs': [
536 'python27',
537 ]
538 }
James E. Blairff555742017-02-19 11:34:27 -0800539 }])
James E. Blairf59f3cf2017-02-19 14:50:26 -0800540 layout.addProjectConfig(project_config)
Clint Byrum85493602016-11-18 11:59:47 -0800541
542 change = model.Change(project)
543 change.branch = 'master'
544 change.files = ['/COMMIT_MSG', 'ignored-file']
545 item = queue.enqueueChange(change)
546 item.current_build_set.layout = layout
547
548 self.assertTrue(base.changeMatches(change))
549 self.assertFalse(python27.changeMatches(change))
550
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200551 item.freezeJobGraph()
Clint Byrum85493602016-11-18 11:59:47 -0800552 self.assertEqual([], item.getJobs())
553
James E. Blair4317e9f2016-07-15 10:05:47 -0700554 def test_job_source_project(self):
James E. Blair5ac93842017-01-20 06:47:34 -0800555 tenant = model.Tenant('tenant')
James E. Blair4317e9f2016-07-15 10:05:47 -0700556 layout = model.Layout()
James E. Blairc73c73a2017-01-20 15:15:15 -0800557 base_project = model.Project('base_project', None)
James E. Blair6f140c72017-03-03 10:32:07 -0800558 base_context = model.SourceContext(base_project, 'master',
559 'test', True)
James E. Blaircdab2032017-02-01 09:09:29 -0800560
James E. Blair5ac93842017-01-20 06:47:34 -0800561 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800562 '_source_context': base_context,
James E. Blairec7ff302017-03-04 07:31:32 -0800563 '_start_mark': self.start_mark,
James E. Blair4317e9f2016-07-15 10:05:47 -0700564 'name': 'base',
565 })
566 layout.addJob(base)
567
James E. Blairc73c73a2017-01-20 15:15:15 -0800568 other_project = model.Project('other_project', None)
James E. Blair6f140c72017-03-03 10:32:07 -0800569 other_context = model.SourceContext(other_project, 'master',
570 'test', True)
James E. Blair5ac93842017-01-20 06:47:34 -0800571 base2 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800572 '_source_context': other_context,
James E. Blairec7ff302017-03-04 07:31:32 -0800573 '_start_mark': self.start_mark,
James E. Blair4317e9f2016-07-15 10:05:47 -0700574 'name': 'base',
575 })
576 with testtools.ExpectedException(
577 Exception,
578 "Job base in other_project is not permitted "
579 "to shadow job base in base_project"):
580 layout.addJob(base2)
581
James E. Blairb3f5db12017-03-17 12:57:39 -0700582 def test_job_allowed_projects(self):
583 job = configloader.JobParser.fromYaml(self.tenant, self.layout, {
584 '_source_context': self.context,
585 '_start_mark': self.start_mark,
586 'name': 'job',
587 'allowed-projects': ['project'],
588 })
589 self.layout.addJob(job)
590
591 project2 = model.Project('project2', None)
592 context2 = model.SourceContext(project2, 'master',
593 'test', True)
594
595 project2_config = configloader.ProjectParser.fromYaml(
596 self.tenant, self.layout, [{
597 '_source_context': context2,
598 '_start_mark': self.start_mark,
599 'name': 'project2',
600 'gate': {
601 'jobs': [
602 'job'
603 ]
604 }
605 }]
606 )
607 self.layout.addProjectConfig(project2_config)
608
609 change = model.Change(project2)
610 # Test master
611 change.branch = 'master'
612 item = self.queue.enqueueChange(change)
613 item.current_build_set.layout = self.layout
614 with testtools.ExpectedException(
615 Exception,
616 "Project project2 is not allowed to run job job"):
617 item.freezeJobGraph()
618
James E. Blaird2348362017-03-17 13:59:35 -0700619 def test_job_pipeline_allow_secrets(self):
620 self.pipeline.allow_secrets = False
621 job = configloader.JobParser.fromYaml(self.tenant, self.layout, {
622 '_source_context': self.context,
623 '_start_mark': self.start_mark,
624 'name': 'job',
625 })
626 auth = model.AuthContext()
627 auth.secrets.append('foo')
628 job.auth = auth
629
630 self.layout.addJob(job)
631
632 project_config = configloader.ProjectParser.fromYaml(
633 self.tenant, self.layout, [{
634 '_source_context': self.context,
635 '_start_mark': self.start_mark,
636 'name': 'project',
637 'gate': {
638 'jobs': [
639 'job'
640 ]
641 }
642 }]
643 )
644 self.layout.addProjectConfig(project_config)
645
646 change = model.Change(self.project)
647 # Test master
648 change.branch = 'master'
649 item = self.queue.enqueueChange(change)
650 item.current_build_set.layout = self.layout
651 with testtools.ExpectedException(
652 Exception,
653 "Pipeline gate does not allow jobs with secrets"):
654 item.freezeJobGraph()
655
James E. Blairce8a2132016-05-19 15:21:52 -0700656
657class TestJobTimeData(BaseTestCase):
658 def setUp(self):
659 super(TestJobTimeData, self).setUp()
660 self.tmp_root = self.useFixture(fixtures.TempDir(
661 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
662 ).path
663
664 def test_empty_timedata(self):
665 path = os.path.join(self.tmp_root, 'job-name')
666 self.assertFalse(os.path.exists(path))
667 self.assertFalse(os.path.exists(path + '.tmp'))
668 td = model.JobTimeData(path)
669 self.assertEqual(td.success_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
670 self.assertEqual(td.failure_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
671 self.assertEqual(td.results, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
672
673 def test_save_reload(self):
674 path = os.path.join(self.tmp_root, 'job-name')
675 self.assertFalse(os.path.exists(path))
676 self.assertFalse(os.path.exists(path + '.tmp'))
677 td = model.JobTimeData(path)
678 self.assertEqual(td.success_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
679 self.assertEqual(td.failure_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
680 self.assertEqual(td.results, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
681 success_times = []
682 failure_times = []
683 results = []
684 for x in range(10):
685 success_times.append(int(random.random() * 1000))
686 failure_times.append(int(random.random() * 1000))
687 results.append(0)
688 results.append(1)
689 random.shuffle(results)
690 s = f = 0
691 for result in results:
692 if result:
693 td.add(failure_times[f], 'FAILURE')
694 f += 1
695 else:
696 td.add(success_times[s], 'SUCCESS')
697 s += 1
698 self.assertEqual(td.success_times, success_times)
699 self.assertEqual(td.failure_times, failure_times)
700 self.assertEqual(td.results, results[10:])
701 td.save()
702 self.assertTrue(os.path.exists(path))
703 self.assertFalse(os.path.exists(path + '.tmp'))
704 td = model.JobTimeData(path)
705 td.load()
706 self.assertEqual(td.success_times, success_times)
707 self.assertEqual(td.failure_times, failure_times)
708 self.assertEqual(td.results, results[10:])
709
710
711class TestTimeDataBase(BaseTestCase):
712 def setUp(self):
713 super(TestTimeDataBase, self).setUp()
714 self.tmp_root = self.useFixture(fixtures.TempDir(
715 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
716 ).path
717 self.db = model.TimeDataBase(self.tmp_root)
718
719 def test_timedatabase(self):
720 self.assertEqual(self.db.getEstimatedTime('job-name'), 0)
721 self.db.update('job-name', 50, 'SUCCESS')
722 self.assertEqual(self.db.getEstimatedTime('job-name'), 50)
723 self.db.update('job-name', 100, 'SUCCESS')
724 self.assertEqual(self.db.getEstimatedTime('job-name'), 75)
725 for x in range(10):
726 self.db.update('job-name', 100, 'SUCCESS')
727 self.assertEqual(self.db.getEstimatedTime('job-name'), 100)
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200728
729
730class TestGraph(BaseTestCase):
731 def test_job_graph_disallows_multiple_jobs_with_same_name(self):
732 graph = model.JobGraph()
733 job1 = model.Job('job')
734 job2 = model.Job('job')
735 graph.addJob(job1)
736 with testtools.ExpectedException(Exception,
737 "Job job already added"):
738 graph.addJob(job2)
739
740 def test_job_graph_disallows_circular_dependencies(self):
741 graph = model.JobGraph()
742 jobs = [model.Job('job%d' % i) for i in range(0, 10)]
743 prevjob = None
744 for j in jobs[:3]:
745 if prevjob:
746 j.dependencies = frozenset([prevjob.name])
747 graph.addJob(j)
748 prevjob = j
749 # 0 triggers 1 triggers 2 triggers 3...
750
751 # Cannot depend on itself
752 with testtools.ExpectedException(
753 Exception,
754 "Dependency cycle detected in job jobX"):
755 j = model.Job('jobX')
756 j.dependencies = frozenset([j.name])
757 graph.addJob(j)
758
759 # Disallow circular dependencies
760 with testtools.ExpectedException(
761 Exception,
762 "Dependency cycle detected in job job3"):
763 jobs[4].dependencies = frozenset([jobs[3].name])
764 graph.addJob(jobs[4])
765 jobs[3].dependencies = frozenset([jobs[4].name])
766 graph.addJob(jobs[3])
767
768 jobs[5].dependencies = frozenset([jobs[4].name])
769 graph.addJob(jobs[5])
770
771 with testtools.ExpectedException(
772 Exception,
773 "Dependency cycle detected in job job3"):
774 jobs[3].dependencies = frozenset([jobs[5].name])
775 graph.addJob(jobs[3])
776
777 jobs[3].dependencies = frozenset([jobs[2].name])
778 graph.addJob(jobs[3])
779 jobs[6].dependencies = frozenset([jobs[2].name])
780 graph.addJob(jobs[6])