blob: a40054fb6ac08e8a25472b1cdaa0ccaf2a28a9bd [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. Blair0a899752017-03-29 13:22:16 -070030class Dummy(object):
31 def __init__(self, **kw):
32 for k, v in kw.items():
33 setattr(self, k, v)
James E. Blairb3f5db12017-03-17 12:57:39 -070034
35
Maru Newby3fe5f852015-01-13 04:22:14 +000036class TestJob(BaseTestCase):
James E. Blaira7f51ca2017-02-07 16:01:26 -080037 def setUp(self):
38 super(TestJob, self).setUp()
James E. Blair0a899752017-03-29 13:22:16 -070039 self.connection = Dummy(connection_name='dummy_connection')
40 self.source = Dummy(canonical_hostname='git.example.com',
James E. Blair0a899752017-03-29 13:22:16 -070041 connection=self.connection)
James E. Blairb3f5db12017-03-17 12:57:39 -070042 self.tenant = model.Tenant('tenant')
43 self.layout = model.Layout()
James E. Blair0a899752017-03-29 13:22:16 -070044 self.project = model.Project('project', self.source)
James E. Blair8a395f92017-03-30 11:15:33 -070045 self.tenant.addProjectRepo(self.project)
James E. Blairb3f5db12017-03-17 12:57:39 -070046 self.pipeline = model.Pipeline('gate', self.layout)
47 self.layout.addPipeline(self.pipeline)
48 self.queue = model.ChangeQueue(self.pipeline)
49
James E. Blair18f86a32017-03-15 14:43:26 -070050 private_key_file = os.path.join(FIXTURE_DIR, 'private.pem')
51 with open(private_key_file, "rb") as f:
James E. Blairbf1a4f22017-03-17 10:59:37 -070052 self.project.private_key, self.project.public_key = \
53 encryption.deserialize_rsa_keypair(f.read())
James E. Blair6f140c72017-03-03 10:32:07 -080054 self.context = model.SourceContext(self.project, 'master',
55 'test', True)
James E. Blairec7ff302017-03-04 07:31:32 -080056 self.start_mark = yaml.Mark('name', 0, 0, 0, '', 0)
James E. Blaira7f51ca2017-02-07 16:01:26 -080057
Maru Newby3fe5f852015-01-13 04:22:14 +000058 @property
59 def job(self):
James E. Blair5ac93842017-01-20 06:47:34 -080060 tenant = model.Tenant('tenant')
James E. Blair83005782015-12-11 14:46:03 -080061 layout = model.Layout()
James E. Blair5ac93842017-01-20 06:47:34 -080062 job = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -080063 '_source_context': self.context,
64 '_start_mark': self.start_mark,
James E. Blair83005782015-12-11 14:46:03 -080065 'name': 'job',
66 'irrelevant-files': [
67 '^docs/.*$'
68 ]})
Maru Newby3fe5f852015-01-13 04:22:14 +000069 return job
70
71 def test_change_matches_returns_false_for_matched_skip_if(self):
72 change = model.Change('project')
Alexander Evseevdbe6fab2015-11-19 12:46:34 +030073 change.files = ['/COMMIT_MSG', 'docs/foo']
Maru Newby3fe5f852015-01-13 04:22:14 +000074 self.assertFalse(self.job.changeMatches(change))
75
76 def test_change_matches_returns_true_for_unmatched_skip_if(self):
77 change = model.Change('project')
Alexander Evseevdbe6fab2015-11-19 12:46:34 +030078 change.files = ['/COMMIT_MSG', 'foo']
Maru Newby3fe5f852015-01-13 04:22:14 +000079 self.assertTrue(self.job.changeMatches(change))
80
Maru Newby79427a42015-02-17 17:54:45 +000081 def test_job_sets_defaults_for_boolean_attributes(self):
James E. Blair83005782015-12-11 14:46:03 -080082 self.assertIsNotNone(self.job.voting)
83
84 def test_job_inheritance(self):
James E. Blaira7f51ca2017-02-07 16:01:26 -080085 # This is standard job inheritance.
86
87 base_pre = model.PlaybookContext(self.context, 'base-pre')
88 base_run = model.PlaybookContext(self.context, 'base-run')
89 base_post = model.PlaybookContext(self.context, 'base-post')
90
91 base = model.Job('base')
92 base.timeout = 30
93 base.pre_run = [base_pre]
94 base.run = [base_run]
95 base.post_run = [base_post]
James E. Blair8525e2b2017-03-15 14:05:47 -070096 base.auth = model.AuthContext()
James E. Blaira7f51ca2017-02-07 16:01:26 -080097
98 py27 = model.Job('py27')
99 self.assertEqual(None, py27.timeout)
100 py27.inheritFrom(base)
101 self.assertEqual(30, py27.timeout)
102 self.assertEqual(['base-pre'],
103 [x.path for x in py27.pre_run])
104 self.assertEqual(['base-run'],
105 [x.path for x in py27.run])
106 self.assertEqual(['base-post'],
107 [x.path for x in py27.post_run])
James E. Blair8525e2b2017-03-15 14:05:47 -0700108 self.assertEqual(None, py27.auth)
James E. Blaira7f51ca2017-02-07 16:01:26 -0800109
110 def test_job_variants(self):
111 # This simulates freezing a job.
112
113 py27_pre = model.PlaybookContext(self.context, 'py27-pre')
114 py27_run = model.PlaybookContext(self.context, 'py27-run')
115 py27_post = model.PlaybookContext(self.context, 'py27-post')
116
117 py27 = model.Job('py27')
118 py27.timeout = 30
119 py27.pre_run = [py27_pre]
120 py27.run = [py27_run]
121 py27.post_run = [py27_post]
James E. Blair8525e2b2017-03-15 14:05:47 -0700122 auth = model.AuthContext()
123 auth.secrets.append('foo')
James E. Blaira7f51ca2017-02-07 16:01:26 -0800124 py27.auth = auth
125
126 job = py27.copy()
127 self.assertEqual(30, job.timeout)
128
129 # Apply the diablo variant
130 diablo = model.Job('py27')
131 diablo.timeout = 40
132 job.applyVariant(diablo)
133
134 self.assertEqual(40, job.timeout)
135 self.assertEqual(['py27-pre'],
136 [x.path for x in job.pre_run])
137 self.assertEqual(['py27-run'],
138 [x.path for x in job.run])
139 self.assertEqual(['py27-post'],
140 [x.path for x in job.post_run])
141 self.assertEqual(auth, job.auth)
142
143 # Set the job to final for the following checks
144 job.final = True
145 self.assertTrue(job.voting)
146
147 good_final = model.Job('py27')
148 good_final.voting = False
149 job.applyVariant(good_final)
150 self.assertFalse(job.voting)
151
152 bad_final = model.Job('py27')
153 bad_final.timeout = 600
154 with testtools.ExpectedException(
155 Exception,
156 "Unable to modify final job"):
157 job.applyVariant(bad_final)
158
159 def test_job_inheritance_configloader(self):
160 # TODO(jeblair): move this to a configloader test
James E. Blair5ac93842017-01-20 06:47:34 -0800161 tenant = model.Tenant('tenant')
James E. Blair83005782015-12-11 14:46:03 -0800162 layout = model.Layout()
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700163
164 pipeline = model.Pipeline('gate', layout)
165 layout.addPipeline(pipeline)
166 queue = model.ChangeQueue(pipeline)
James E. Blair0a899752017-03-29 13:22:16 -0700167 project = model.Project('project', self.source)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700168
James E. Blair5ac93842017-01-20 06:47:34 -0800169 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800170 '_source_context': self.context,
171 '_start_mark': self.start_mark,
James E. Blair83005782015-12-11 14:46:03 -0800172 'name': 'base',
173 'timeout': 30,
James E. Blaira7f51ca2017-02-07 16:01:26 -0800174 'pre-run': 'base-pre',
175 'post-run': 'base-post',
James E. Blair1774dd52017-02-03 10:52:32 -0800176 'nodes': [{
177 'name': 'controller',
178 'image': 'base',
179 }],
James E. Blair83005782015-12-11 14:46:03 -0800180 })
181 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800182 python27 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800183 '_source_context': self.context,
184 '_start_mark': self.start_mark,
James E. Blair83005782015-12-11 14:46:03 -0800185 'name': 'python27',
186 'parent': 'base',
James E. Blaira7f51ca2017-02-07 16:01:26 -0800187 'pre-run': 'py27-pre',
188 'post-run': 'py27-post',
James E. Blair1774dd52017-02-03 10:52:32 -0800189 'nodes': [{
190 'name': 'controller',
191 'image': 'new',
192 }],
James E. Blair83005782015-12-11 14:46:03 -0800193 'timeout': 40,
194 })
195 layout.addJob(python27)
James E. Blair5ac93842017-01-20 06:47:34 -0800196 python27diablo = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800197 '_source_context': self.context,
198 '_start_mark': self.start_mark,
James E. Blair83005782015-12-11 14:46:03 -0800199 'name': 'python27',
200 'branches': [
201 'stable/diablo'
202 ],
James E. Blaira7f51ca2017-02-07 16:01:26 -0800203 'pre-run': 'py27-diablo-pre',
204 'run': 'py27-diablo',
205 'post-run': 'py27-diablo-post',
James E. Blair1774dd52017-02-03 10:52:32 -0800206 'nodes': [{
207 'name': 'controller',
208 'image': 'old',
209 }],
James E. Blair83005782015-12-11 14:46:03 -0800210 'timeout': 50,
211 })
212 layout.addJob(python27diablo)
213
James E. Blair5ac93842017-01-20 06:47:34 -0800214 python27essex = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800215 '_source_context': self.context,
216 '_start_mark': self.start_mark,
James E. Blaira7f51ca2017-02-07 16:01:26 -0800217 'name': 'python27',
218 'branches': [
219 'stable/essex'
220 ],
221 'pre-run': 'py27-essex-pre',
222 'post-run': 'py27-essex-post',
223 })
224 layout.addJob(python27essex)
225
James E. Blairff555742017-02-19 11:34:27 -0800226 project_config = configloader.ProjectParser.fromYaml(tenant, layout, [{
James E. Blairec7ff302017-03-04 07:31:32 -0800227 '_source_context': self.context,
228 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700229 'name': 'project',
230 'gate': {
231 'jobs': [
232 'python27'
233 ]
234 }
James E. Blairff555742017-02-19 11:34:27 -0800235 }])
James E. Blairf59f3cf2017-02-19 14:50:26 -0800236 layout.addProjectConfig(project_config)
James E. Blair83005782015-12-11 14:46:03 -0800237
James E. Blair83005782015-12-11 14:46:03 -0800238 change = model.Change(project)
James E. Blair1774dd52017-02-03 10:52:32 -0800239 # Test master
James E. Blair83005782015-12-11 14:46:03 -0800240 change.branch = 'master'
241 item = queue.enqueueChange(change)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700242 item.current_build_set.layout = layout
James E. Blair83005782015-12-11 14:46:03 -0800243
244 self.assertTrue(base.changeMatches(change))
245 self.assertTrue(python27.changeMatches(change))
246 self.assertFalse(python27diablo.changeMatches(change))
James E. Blaira7f51ca2017-02-07 16:01:26 -0800247 self.assertFalse(python27essex.changeMatches(change))
James E. Blair83005782015-12-11 14:46:03 -0800248
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200249 item.freezeJobGraph()
James E. Blair83005782015-12-11 14:46:03 -0800250 self.assertEqual(len(item.getJobs()), 1)
251 job = item.getJobs()[0]
252 self.assertEqual(job.name, 'python27')
253 self.assertEqual(job.timeout, 40)
James E. Blair1774dd52017-02-03 10:52:32 -0800254 nodes = job.nodeset.getNodes()
255 self.assertEqual(len(nodes), 1)
256 self.assertEqual(nodes[0].image, 'new')
James E. Blaira7f51ca2017-02-07 16:01:26 -0800257 self.assertEqual([x.path for x in job.pre_run],
258 ['playbooks/base-pre',
259 'playbooks/py27-pre'])
260 self.assertEqual([x.path for x in job.post_run],
261 ['playbooks/py27-post',
262 'playbooks/base-post'])
263 self.assertEqual([x.path for x in job.run],
264 ['playbooks/python27',
265 'playbooks/base'])
James E. Blair83005782015-12-11 14:46:03 -0800266
James E. Blair1774dd52017-02-03 10:52:32 -0800267 # Test diablo
James E. Blair83005782015-12-11 14:46:03 -0800268 change.branch = 'stable/diablo'
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700269 item = queue.enqueueChange(change)
270 item.current_build_set.layout = layout
James E. Blair83005782015-12-11 14:46:03 -0800271
272 self.assertTrue(base.changeMatches(change))
273 self.assertTrue(python27.changeMatches(change))
274 self.assertTrue(python27diablo.changeMatches(change))
James E. Blaira7f51ca2017-02-07 16:01:26 -0800275 self.assertFalse(python27essex.changeMatches(change))
James E. Blair83005782015-12-11 14:46:03 -0800276
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200277 item.freezeJobGraph()
James E. Blair83005782015-12-11 14:46:03 -0800278 self.assertEqual(len(item.getJobs()), 1)
279 job = item.getJobs()[0]
280 self.assertEqual(job.name, 'python27')
281 self.assertEqual(job.timeout, 50)
James E. Blair1774dd52017-02-03 10:52:32 -0800282 nodes = job.nodeset.getNodes()
283 self.assertEqual(len(nodes), 1)
284 self.assertEqual(nodes[0].image, 'old')
James E. Blaira7f51ca2017-02-07 16:01:26 -0800285 self.assertEqual([x.path for x in job.pre_run],
286 ['playbooks/base-pre',
287 'playbooks/py27-pre',
288 'playbooks/py27-diablo-pre'])
289 self.assertEqual([x.path for x in job.post_run],
290 ['playbooks/py27-diablo-post',
291 'playbooks/py27-post',
292 'playbooks/base-post'])
293 self.assertEqual([x.path for x in job.run],
294 ['playbooks/py27-diablo']),
295
296 # Test essex
297 change.branch = 'stable/essex'
298 item = queue.enqueueChange(change)
299 item.current_build_set.layout = layout
300
301 self.assertTrue(base.changeMatches(change))
302 self.assertTrue(python27.changeMatches(change))
303 self.assertFalse(python27diablo.changeMatches(change))
304 self.assertTrue(python27essex.changeMatches(change))
305
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200306 item.freezeJobGraph()
James E. Blaira7f51ca2017-02-07 16:01:26 -0800307 self.assertEqual(len(item.getJobs()), 1)
308 job = item.getJobs()[0]
309 self.assertEqual(job.name, 'python27')
310 self.assertEqual([x.path for x in job.pre_run],
311 ['playbooks/base-pre',
312 'playbooks/py27-pre',
313 'playbooks/py27-essex-pre'])
314 self.assertEqual([x.path for x in job.post_run],
315 ['playbooks/py27-essex-post',
316 'playbooks/py27-post',
317 'playbooks/base-post'])
318 self.assertEqual([x.path for x in job.run],
319 ['playbooks/python27',
320 'playbooks/base'])
James E. Blairce8a2132016-05-19 15:21:52 -0700321
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000322 def test_job_auth_inheritance(self):
James E. Blair5ac93842017-01-20 06:47:34 -0800323 tenant = model.Tenant('tenant')
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000324 layout = model.Layout()
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000325
James E. Blair01f83b72017-03-15 13:03:40 -0700326 conf = yaml.safe_load('''
327- secret:
328 name: pypi-credentials
329 data:
330 username: test-username
James E. Blair717e8e92017-03-17 11:03:27 -0700331 password: !encrypted/pkcs1-oaep |
James E. Blair01f83b72017-03-15 13:03:40 -0700332 BFhtdnm8uXx7kn79RFL/zJywmzLkT1GY78P3bOtp4WghUFWobkifSu7ZpaV4NeO0s71YUsi1wGZZ
333 L0LveZjUN0t6OU1VZKSG8R5Ly7urjaSo1pPVIq5Rtt/H7W14Lecd+cUeKb4joeusC9drN3AA8a4o
334 ykcVpt1wVqUnTbMGC9ARMCQP6eopcs1l7tzMseprW4RDNhIuz3CRgd0QBMPl6VDoFgBPB8vxtJw+
335 3m0rqBYZCLZgCXekqlny8s2s92nJMuUABbJOEcDRarzibDsSXsfJt1y+5n7yOURsC7lovMg4GF/v
336 Cl/0YMKjBO5bpv9EM5fToeKYyPGSKQoHOnCYceb3cAVcv5UawcCic8XjhEhp4K7WPdYf2HVAC/qt
337 xhbpjTxG4U5Q/SoppOJ60WqEkQvbXs6n5Dvy7xmph6GWmU/bAv3eUK3pdD3xa2Ue1lHWz3U+rsYr
338 aI+AKYsMYx3RBlfAmCeC1ve2BXPrqnOo7G8tnUvfdYPbK4Aakk0ds/AVqFHEZN+S6hRBmBjLaRFW
339 Z3QSO1NjbBxWnaHKZYT7nkrJm8AMCgZU0ZArFLpaufKCeiK5ECSsDxic4FIsY1OkWT42qEUfL0Wd
340 +150AKGNZpPJnnP3QYY4W/MWcKH/zdO400+zWN52WevbSqZy90tqKDJrBkMl1ydqbuw1E4ZHvIs=
341''')[0]['secret']
342
343 conf['_source_context'] = self.context
344 conf['_start_mark'] = self.start_mark
345
346 secret = configloader.SecretParser.fromYaml(layout, conf)
347 layout.addSecret(secret)
348
James E. Blair5ac93842017-01-20 06:47:34 -0800349 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800350 '_source_context': self.context,
351 '_start_mark': self.start_mark,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000352 'name': 'base',
353 'timeout': 30,
354 })
355 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800356 pypi_upload_without_inherit = configloader.JobParser.fromYaml(
357 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800358 '_source_context': self.context,
359 '_start_mark': self.start_mark,
James E. Blair5ac93842017-01-20 06:47:34 -0800360 'name': 'pypi-upload-without-inherit',
361 'parent': 'base',
362 'timeout': 40,
363 'auth': {
364 'secrets': [
365 'pypi-credentials',
366 ]
367 }
368 })
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000369 layout.addJob(pypi_upload_without_inherit)
James E. Blair5ac93842017-01-20 06:47:34 -0800370 pypi_upload_with_inherit = configloader.JobParser.fromYaml(
371 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800372 '_source_context': self.context,
373 '_start_mark': self.start_mark,
James E. Blair5ac93842017-01-20 06:47:34 -0800374 'name': 'pypi-upload-with-inherit',
375 'parent': 'base',
376 'timeout': 40,
377 'auth': {
378 'inherit': True,
379 'secrets': [
380 'pypi-credentials',
381 ]
382 }
383 })
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000384 layout.addJob(pypi_upload_with_inherit)
385 pypi_upload_with_inherit_false = configloader.JobParser.fromYaml(
James E. Blair5ac93842017-01-20 06:47:34 -0800386 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800387 '_source_context': self.context,
388 '_start_mark': self.start_mark,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000389 'name': 'pypi-upload-with-inherit-false',
390 'parent': 'base',
391 'timeout': 40,
392 'auth': {
393 'inherit': False,
Ricardo Carrillo Cruz12c892b2016-11-18 15:35:49 +0000394 'secrets': [
395 'pypi-credentials',
396 ]
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000397 }
398 })
399 layout.addJob(pypi_upload_with_inherit_false)
James E. Blair5ac93842017-01-20 06:47:34 -0800400 in_repo_job_without_inherit = configloader.JobParser.fromYaml(
401 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800402 '_source_context': self.context,
403 '_start_mark': self.start_mark,
James E. Blair5ac93842017-01-20 06:47:34 -0800404 'name': 'in-repo-job-without-inherit',
405 'parent': 'pypi-upload-without-inherit',
406 })
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000407 layout.addJob(in_repo_job_without_inherit)
James E. Blair5ac93842017-01-20 06:47:34 -0800408 in_repo_job_with_inherit = configloader.JobParser.fromYaml(
409 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800410 '_source_context': self.context,
411 '_start_mark': self.start_mark,
James E. Blair5ac93842017-01-20 06:47:34 -0800412 'name': 'in-repo-job-with-inherit',
413 'parent': 'pypi-upload-with-inherit',
414 })
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000415 layout.addJob(in_repo_job_with_inherit)
416 in_repo_job_with_inherit_false = configloader.JobParser.fromYaml(
James E. Blair5ac93842017-01-20 06:47:34 -0800417 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800418 '_source_context': self.context,
419 '_start_mark': self.start_mark,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000420 'name': 'in-repo-job-with-inherit-false',
421 'parent': 'pypi-upload-with-inherit-false',
422 })
423 layout.addJob(in_repo_job_with_inherit_false)
424
James E. Blair8525e2b2017-03-15 14:05:47 -0700425 self.assertEqual(None, in_repo_job_without_inherit.auth)
426 self.assertEqual(1, len(in_repo_job_with_inherit.auth.secrets))
427 self.assertEqual(in_repo_job_with_inherit.auth.secrets[0].name,
428 'pypi-credentials')
429 self.assertEqual(None, in_repo_job_with_inherit_false.auth)
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000430
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700431 def test_job_inheritance_job_tree(self):
James E. Blair5ac93842017-01-20 06:47:34 -0800432 tenant = model.Tenant('tenant')
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700433 layout = model.Layout()
434
435 pipeline = model.Pipeline('gate', layout)
436 layout.addPipeline(pipeline)
437 queue = model.ChangeQueue(pipeline)
438
James E. Blair5ac93842017-01-20 06:47:34 -0800439 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800440 '_source_context': self.context,
441 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700442 'name': 'base',
443 'timeout': 30,
444 })
445 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800446 python27 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800447 '_source_context': self.context,
448 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700449 'name': 'python27',
450 'parent': 'base',
451 'timeout': 40,
452 })
453 layout.addJob(python27)
James E. Blair5ac93842017-01-20 06:47:34 -0800454 python27diablo = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800455 '_source_context': self.context,
456 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700457 'name': 'python27',
458 'branches': [
459 'stable/diablo'
460 ],
461 'timeout': 50,
462 })
463 layout.addJob(python27diablo)
464
James E. Blairff555742017-02-19 11:34:27 -0800465 project_config = configloader.ProjectParser.fromYaml(tenant, layout, [{
James E. Blairec7ff302017-03-04 07:31:32 -0800466 '_source_context': self.context,
467 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700468 'name': 'project',
469 'gate': {
470 'jobs': [
471 {'python27': {'timeout': 70}}
472 ]
473 }
James E. Blairff555742017-02-19 11:34:27 -0800474 }])
James E. Blairf59f3cf2017-02-19 14:50:26 -0800475 layout.addProjectConfig(project_config)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700476
James E. Blairec7ff302017-03-04 07:31:32 -0800477 change = model.Change(self.project)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700478 change.branch = 'master'
479 item = queue.enqueueChange(change)
480 item.current_build_set.layout = layout
481
482 self.assertTrue(base.changeMatches(change))
483 self.assertTrue(python27.changeMatches(change))
484 self.assertFalse(python27diablo.changeMatches(change))
485
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200486 item.freezeJobGraph()
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700487 self.assertEqual(len(item.getJobs()), 1)
488 job = item.getJobs()[0]
489 self.assertEqual(job.name, 'python27')
490 self.assertEqual(job.timeout, 70)
491
492 change.branch = 'stable/diablo'
493 item = queue.enqueueChange(change)
494 item.current_build_set.layout = layout
495
496 self.assertTrue(base.changeMatches(change))
497 self.assertTrue(python27.changeMatches(change))
498 self.assertTrue(python27diablo.changeMatches(change))
499
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200500 item.freezeJobGraph()
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700501 self.assertEqual(len(item.getJobs()), 1)
502 job = item.getJobs()[0]
503 self.assertEqual(job.name, 'python27')
504 self.assertEqual(job.timeout, 70)
505
Clint Byrum85493602016-11-18 11:59:47 -0800506 def test_inheritance_keeps_matchers(self):
James E. Blair5ac93842017-01-20 06:47:34 -0800507 tenant = model.Tenant('tenant')
Clint Byrum85493602016-11-18 11:59:47 -0800508 layout = model.Layout()
509
510 pipeline = model.Pipeline('gate', layout)
511 layout.addPipeline(pipeline)
512 queue = model.ChangeQueue(pipeline)
James E. Blair0a899752017-03-29 13:22:16 -0700513 project = model.Project('project', self.source)
Clint Byrum85493602016-11-18 11:59:47 -0800514
James E. Blair5ac93842017-01-20 06:47:34 -0800515 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800516 '_source_context': self.context,
517 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800518 'name': 'base',
519 'timeout': 30,
520 })
521 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800522 python27 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800523 '_source_context': self.context,
524 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800525 'name': 'python27',
526 'parent': 'base',
527 'timeout': 40,
528 'irrelevant-files': ['^ignored-file$'],
529 })
530 layout.addJob(python27)
531
James E. Blairff555742017-02-19 11:34:27 -0800532 project_config = configloader.ProjectParser.fromYaml(tenant, layout, [{
James E. Blairec7ff302017-03-04 07:31:32 -0800533 '_source_context': self.context,
534 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800535 'name': 'project',
536 'gate': {
537 'jobs': [
538 'python27',
539 ]
540 }
James E. Blairff555742017-02-19 11:34:27 -0800541 }])
James E. Blairf59f3cf2017-02-19 14:50:26 -0800542 layout.addProjectConfig(project_config)
Clint Byrum85493602016-11-18 11:59:47 -0800543
544 change = model.Change(project)
545 change.branch = 'master'
546 change.files = ['/COMMIT_MSG', 'ignored-file']
547 item = queue.enqueueChange(change)
548 item.current_build_set.layout = layout
549
550 self.assertTrue(base.changeMatches(change))
551 self.assertFalse(python27.changeMatches(change))
552
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200553 item.freezeJobGraph()
Clint Byrum85493602016-11-18 11:59:47 -0800554 self.assertEqual([], item.getJobs())
555
James E. Blair4317e9f2016-07-15 10:05:47 -0700556 def test_job_source_project(self):
James E. Blair5ac93842017-01-20 06:47:34 -0800557 tenant = model.Tenant('tenant')
James E. Blair4317e9f2016-07-15 10:05:47 -0700558 layout = model.Layout()
James E. Blair0a899752017-03-29 13:22:16 -0700559 base_project = model.Project('base_project', self.source)
James E. Blair6f140c72017-03-03 10:32:07 -0800560 base_context = model.SourceContext(base_project, 'master',
561 'test', True)
James E. Blaircdab2032017-02-01 09:09:29 -0800562
James E. Blair5ac93842017-01-20 06:47:34 -0800563 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800564 '_source_context': base_context,
James E. Blairec7ff302017-03-04 07:31:32 -0800565 '_start_mark': self.start_mark,
James E. Blair4317e9f2016-07-15 10:05:47 -0700566 'name': 'base',
567 })
568 layout.addJob(base)
569
James E. Blair0a899752017-03-29 13:22:16 -0700570 other_project = model.Project('other_project', self.source)
James E. Blair6f140c72017-03-03 10:32:07 -0800571 other_context = model.SourceContext(other_project, 'master',
572 'test', True)
James E. Blair5ac93842017-01-20 06:47:34 -0800573 base2 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800574 '_source_context': other_context,
James E. Blairec7ff302017-03-04 07:31:32 -0800575 '_start_mark': self.start_mark,
James E. Blair4317e9f2016-07-15 10:05:47 -0700576 'name': 'base',
577 })
578 with testtools.ExpectedException(
579 Exception,
580 "Job base in other_project is not permitted "
581 "to shadow job base in base_project"):
582 layout.addJob(base2)
583
James E. Blairb3f5db12017-03-17 12:57:39 -0700584 def test_job_allowed_projects(self):
585 job = configloader.JobParser.fromYaml(self.tenant, self.layout, {
586 '_source_context': self.context,
587 '_start_mark': self.start_mark,
588 'name': 'job',
589 'allowed-projects': ['project'],
590 })
591 self.layout.addJob(job)
592
James E. Blair0a899752017-03-29 13:22:16 -0700593 project2 = model.Project('project2', self.source)
James E. Blairb3f5db12017-03-17 12:57:39 -0700594 context2 = model.SourceContext(project2, 'master',
595 'test', True)
596
597 project2_config = configloader.ProjectParser.fromYaml(
598 self.tenant, self.layout, [{
599 '_source_context': context2,
600 '_start_mark': self.start_mark,
601 'name': 'project2',
602 'gate': {
603 'jobs': [
604 'job'
605 ]
606 }
607 }]
608 )
609 self.layout.addProjectConfig(project2_config)
610
611 change = model.Change(project2)
612 # Test master
613 change.branch = 'master'
614 item = self.queue.enqueueChange(change)
615 item.current_build_set.layout = self.layout
616 with testtools.ExpectedException(
617 Exception,
618 "Project project2 is not allowed to run job job"):
619 item.freezeJobGraph()
620
James E. Blaird2348362017-03-17 13:59:35 -0700621 def test_job_pipeline_allow_secrets(self):
622 self.pipeline.allow_secrets = False
623 job = configloader.JobParser.fromYaml(self.tenant, self.layout, {
624 '_source_context': self.context,
625 '_start_mark': self.start_mark,
626 'name': 'job',
627 })
628 auth = model.AuthContext()
629 auth.secrets.append('foo')
630 job.auth = auth
631
632 self.layout.addJob(job)
633
634 project_config = configloader.ProjectParser.fromYaml(
635 self.tenant, self.layout, [{
636 '_source_context': self.context,
637 '_start_mark': self.start_mark,
638 'name': 'project',
639 'gate': {
640 'jobs': [
641 'job'
642 ]
643 }
644 }]
645 )
646 self.layout.addProjectConfig(project_config)
647
648 change = model.Change(self.project)
649 # Test master
650 change.branch = 'master'
651 item = self.queue.enqueueChange(change)
652 item.current_build_set.layout = self.layout
653 with testtools.ExpectedException(
654 Exception,
655 "Pipeline gate does not allow jobs with secrets"):
656 item.freezeJobGraph()
657
James E. Blairce8a2132016-05-19 15:21:52 -0700658
659class TestJobTimeData(BaseTestCase):
660 def setUp(self):
661 super(TestJobTimeData, self).setUp()
662 self.tmp_root = self.useFixture(fixtures.TempDir(
663 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
664 ).path
665
666 def test_empty_timedata(self):
667 path = os.path.join(self.tmp_root, 'job-name')
668 self.assertFalse(os.path.exists(path))
669 self.assertFalse(os.path.exists(path + '.tmp'))
670 td = model.JobTimeData(path)
671 self.assertEqual(td.success_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
672 self.assertEqual(td.failure_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
673 self.assertEqual(td.results, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
674
675 def test_save_reload(self):
676 path = os.path.join(self.tmp_root, 'job-name')
677 self.assertFalse(os.path.exists(path))
678 self.assertFalse(os.path.exists(path + '.tmp'))
679 td = model.JobTimeData(path)
680 self.assertEqual(td.success_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
681 self.assertEqual(td.failure_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
682 self.assertEqual(td.results, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
683 success_times = []
684 failure_times = []
685 results = []
686 for x in range(10):
687 success_times.append(int(random.random() * 1000))
688 failure_times.append(int(random.random() * 1000))
689 results.append(0)
690 results.append(1)
691 random.shuffle(results)
692 s = f = 0
693 for result in results:
694 if result:
695 td.add(failure_times[f], 'FAILURE')
696 f += 1
697 else:
698 td.add(success_times[s], 'SUCCESS')
699 s += 1
700 self.assertEqual(td.success_times, success_times)
701 self.assertEqual(td.failure_times, failure_times)
702 self.assertEqual(td.results, results[10:])
703 td.save()
704 self.assertTrue(os.path.exists(path))
705 self.assertFalse(os.path.exists(path + '.tmp'))
706 td = model.JobTimeData(path)
707 td.load()
708 self.assertEqual(td.success_times, success_times)
709 self.assertEqual(td.failure_times, failure_times)
710 self.assertEqual(td.results, results[10:])
711
712
713class TestTimeDataBase(BaseTestCase):
714 def setUp(self):
715 super(TestTimeDataBase, self).setUp()
716 self.tmp_root = self.useFixture(fixtures.TempDir(
717 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
718 ).path
719 self.db = model.TimeDataBase(self.tmp_root)
720
721 def test_timedatabase(self):
722 self.assertEqual(self.db.getEstimatedTime('job-name'), 0)
723 self.db.update('job-name', 50, 'SUCCESS')
724 self.assertEqual(self.db.getEstimatedTime('job-name'), 50)
725 self.db.update('job-name', 100, 'SUCCESS')
726 self.assertEqual(self.db.getEstimatedTime('job-name'), 75)
727 for x in range(10):
728 self.db.update('job-name', 100, 'SUCCESS')
729 self.assertEqual(self.db.getEstimatedTime('job-name'), 100)
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200730
731
732class TestGraph(BaseTestCase):
733 def test_job_graph_disallows_multiple_jobs_with_same_name(self):
734 graph = model.JobGraph()
735 job1 = model.Job('job')
736 job2 = model.Job('job')
737 graph.addJob(job1)
738 with testtools.ExpectedException(Exception,
739 "Job job already added"):
740 graph.addJob(job2)
741
742 def test_job_graph_disallows_circular_dependencies(self):
743 graph = model.JobGraph()
744 jobs = [model.Job('job%d' % i) for i in range(0, 10)]
745 prevjob = None
746 for j in jobs[:3]:
747 if prevjob:
748 j.dependencies = frozenset([prevjob.name])
749 graph.addJob(j)
750 prevjob = j
751 # 0 triggers 1 triggers 2 triggers 3...
752
753 # Cannot depend on itself
754 with testtools.ExpectedException(
755 Exception,
756 "Dependency cycle detected in job jobX"):
757 j = model.Job('jobX')
758 j.dependencies = frozenset([j.name])
759 graph.addJob(j)
760
761 # Disallow circular dependencies
762 with testtools.ExpectedException(
763 Exception,
764 "Dependency cycle detected in job job3"):
765 jobs[4].dependencies = frozenset([jobs[3].name])
766 graph.addJob(jobs[4])
767 jobs[3].dependencies = frozenset([jobs[4].name])
768 graph.addJob(jobs[3])
769
770 jobs[5].dependencies = frozenset([jobs[4].name])
771 graph.addJob(jobs[5])
772
773 with testtools.ExpectedException(
774 Exception,
775 "Dependency cycle detected in job job3"):
776 jobs[3].dependencies = frozenset([jobs[5].name])
777 graph.addJob(jobs[3])
778
779 jobs[3].dependencies = frozenset([jobs[2].name])
780 graph.addJob(jobs[3])
781 jobs[6].dependencies = frozenset([jobs[2].name])
782 graph.addJob(jobs[6])
James E. Blairc2a54fd2017-03-29 15:19:26 -0700783
784
785class TestTenant(BaseTestCase):
786 def test_add_project(self):
787 tenant = model.Tenant('tenant')
788 connection1 = Dummy(connection_name='dummy_connection1')
789 source1 = Dummy(canonical_hostname='git1.example.com',
790 name='dummy', # TODOv3(jeblair): remove
791 connection=connection1)
792
793 source1_project1 = model.Project('project1', source1)
James E. Blair8a395f92017-03-30 11:15:33 -0700794 tenant.addConfigRepo(source1_project1)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700795 d = {'project1':
796 {'git1.example.com': source1_project1}}
797 self.assertEqual(d, tenant.projects)
798 self.assertEqual((True, source1_project1),
799 tenant.getProject('project1'))
800 self.assertEqual((True, source1_project1),
801 tenant.getProject('git1.example.com/project1'))
802
803 source1_project2 = model.Project('project2', source1)
James E. Blair8a395f92017-03-30 11:15:33 -0700804 tenant.addProjectRepo(source1_project2)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700805 d = {'project1':
806 {'git1.example.com': source1_project1},
807 'project2':
808 {'git1.example.com': source1_project2}}
809 self.assertEqual(d, tenant.projects)
810 self.assertEqual((False, source1_project2),
811 tenant.getProject('project2'))
812 self.assertEqual((False, source1_project2),
813 tenant.getProject('git1.example.com/project2'))
814
815 connection2 = Dummy(connection_name='dummy_connection2')
816 source2 = Dummy(canonical_hostname='git2.example.com',
817 name='dummy', # TODOv3(jeblair): remove
818 connection=connection2)
819
820 source2_project1 = model.Project('project1', source2)
James E. Blair8a395f92017-03-30 11:15:33 -0700821 tenant.addProjectRepo(source2_project1)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700822 d = {'project1':
823 {'git1.example.com': source1_project1,
824 'git2.example.com': source2_project1},
825 'project2':
826 {'git1.example.com': source1_project2}}
827 self.assertEqual(d, tenant.projects)
828 with testtools.ExpectedException(
829 Exception,
830 "Project name 'project1' is ambiguous"):
831 tenant.getProject('project1')
832 self.assertEqual((False, source1_project2),
833 tenant.getProject('project2'))
834 self.assertEqual((True, source1_project1),
835 tenant.getProject('git1.example.com/project1'))
836 self.assertEqual((False, source2_project1),
837 tenant.getProject('git2.example.com/project1'))
838
839 source2_project2 = model.Project('project2', source2)
James E. Blair8a395f92017-03-30 11:15:33 -0700840 tenant.addConfigRepo(source2_project2)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700841 d = {'project1':
842 {'git1.example.com': source1_project1,
843 'git2.example.com': source2_project1},
844 'project2':
845 {'git1.example.com': source1_project2,
846 'git2.example.com': source2_project2}}
847 self.assertEqual(d, tenant.projects)
848 with testtools.ExpectedException(
849 Exception,
850 "Project name 'project1' is ambiguous"):
851 tenant.getProject('project1')
852 with testtools.ExpectedException(
853 Exception,
854 "Project name 'project2' is ambiguous"):
855 tenant.getProject('project2')
856 self.assertEqual((True, source1_project1),
857 tenant.getProject('git1.example.com/project1'))
858 self.assertEqual((False, source2_project1),
859 tenant.getProject('git2.example.com/project1'))
860 self.assertEqual((False, source1_project2),
861 tenant.getProject('git1.example.com/project2'))
862 self.assertEqual((True, source2_project2),
863 tenant.getProject('git2.example.com/project2'))
864
865 source1_project2b = model.Project('subpath/project2', source1)
James E. Blair8a395f92017-03-30 11:15:33 -0700866 tenant.addConfigRepo(source1_project2b)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700867 d = {'project1':
868 {'git1.example.com': source1_project1,
869 'git2.example.com': source2_project1},
870 'project2':
871 {'git1.example.com': source1_project2,
872 'git2.example.com': source2_project2},
873 'subpath/project2':
874 {'git1.example.com': source1_project2b}}
875 self.assertEqual(d, tenant.projects)
876 self.assertEqual((False, source1_project2),
877 tenant.getProject('git1.example.com/project2'))
878 self.assertEqual((True, source2_project2),
879 tenant.getProject('git2.example.com/project2'))
880 self.assertEqual((True, source1_project2b),
881 tenant.getProject('subpath/project2'))
882 self.assertEqual(
883 (True, source1_project2b),
884 tenant.getProject('git1.example.com/subpath/project2'))
885
886 source2_project2b = model.Project('subpath/project2', source2)
James E. Blair8a395f92017-03-30 11:15:33 -0700887 tenant.addConfigRepo(source2_project2b)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700888 d = {'project1':
889 {'git1.example.com': source1_project1,
890 'git2.example.com': source2_project1},
891 'project2':
892 {'git1.example.com': source1_project2,
893 'git2.example.com': source2_project2},
894 'subpath/project2':
895 {'git1.example.com': source1_project2b,
896 'git2.example.com': source2_project2b}}
897 self.assertEqual(d, tenant.projects)
898 self.assertEqual((False, source1_project2),
899 tenant.getProject('git1.example.com/project2'))
900 self.assertEqual((True, source2_project2),
901 tenant.getProject('git2.example.com/project2'))
902 with testtools.ExpectedException(
903 Exception,
904 "Project name 'subpath/project2' is ambiguous"):
905 tenant.getProject('subpath/project2')
906 self.assertEqual(
907 (True, source1_project2b),
908 tenant.getProject('git1.example.com/subpath/project2'))
909 self.assertEqual(
910 (True, source2_project2b),
911 tenant.getProject('git2.example.com/subpath/project2'))
912
913 with testtools.ExpectedException(
914 Exception,
915 "Project project1 is already in project index"):
916 tenant._addProject(source1_project1)