blob: 377193fd4b0ae59a118a3a3009c0bb51ab45bf8f [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. Blairec7ff302017-03-04 07:31:32 -080021import yaml
James E. Blairce8a2132016-05-19 15:21:52 -070022
Maru Newby3fe5f852015-01-13 04:22:14 +000023from zuul import model
James E. Blair83005782015-12-11 14:46:03 -080024from zuul import configloader
James E. Blairbf1a4f22017-03-17 10:59:37 -070025from zuul.lib import encryption
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
30class TestJob(BaseTestCase):
31
James E. Blaira7f51ca2017-02-07 16:01:26 -080032 def setUp(self):
33 super(TestJob, self).setUp()
34 self.project = model.Project('project', None)
James E. Blair18f86a32017-03-15 14:43:26 -070035 private_key_file = os.path.join(FIXTURE_DIR, 'private.pem')
36 with open(private_key_file, "rb") as f:
James E. Blairbf1a4f22017-03-17 10:59:37 -070037 self.project.private_key, self.project.public_key = \
38 encryption.deserialize_rsa_keypair(f.read())
James E. Blair6f140c72017-03-03 10:32:07 -080039 self.context = model.SourceContext(self.project, 'master',
40 'test', True)
James E. Blairec7ff302017-03-04 07:31:32 -080041 self.start_mark = yaml.Mark('name', 0, 0, 0, '', 0)
James E. Blaira7f51ca2017-02-07 16:01:26 -080042
Maru Newby3fe5f852015-01-13 04:22:14 +000043 @property
44 def job(self):
James E. Blair5ac93842017-01-20 06:47:34 -080045 tenant = model.Tenant('tenant')
James E. Blair83005782015-12-11 14:46:03 -080046 layout = model.Layout()
James E. Blair5ac93842017-01-20 06:47:34 -080047 job = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -080048 '_source_context': self.context,
49 '_start_mark': self.start_mark,
James E. Blair83005782015-12-11 14:46:03 -080050 'name': 'job',
51 'irrelevant-files': [
52 '^docs/.*$'
53 ]})
Maru Newby3fe5f852015-01-13 04:22:14 +000054 return job
55
56 def test_change_matches_returns_false_for_matched_skip_if(self):
57 change = model.Change('project')
Alexander Evseevdbe6fab2015-11-19 12:46:34 +030058 change.files = ['/COMMIT_MSG', 'docs/foo']
Maru Newby3fe5f852015-01-13 04:22:14 +000059 self.assertFalse(self.job.changeMatches(change))
60
61 def test_change_matches_returns_true_for_unmatched_skip_if(self):
62 change = model.Change('project')
Alexander Evseevdbe6fab2015-11-19 12:46:34 +030063 change.files = ['/COMMIT_MSG', 'foo']
Maru Newby3fe5f852015-01-13 04:22:14 +000064 self.assertTrue(self.job.changeMatches(change))
65
Maru Newby79427a42015-02-17 17:54:45 +000066 def test_job_sets_defaults_for_boolean_attributes(self):
James E. Blair83005782015-12-11 14:46:03 -080067 self.assertIsNotNone(self.job.voting)
68
69 def test_job_inheritance(self):
James E. Blaira7f51ca2017-02-07 16:01:26 -080070 # This is standard job inheritance.
71
72 base_pre = model.PlaybookContext(self.context, 'base-pre')
73 base_run = model.PlaybookContext(self.context, 'base-run')
74 base_post = model.PlaybookContext(self.context, 'base-post')
75
76 base = model.Job('base')
77 base.timeout = 30
78 base.pre_run = [base_pre]
79 base.run = [base_run]
80 base.post_run = [base_post]
James E. Blair8525e2b2017-03-15 14:05:47 -070081 base.auth = model.AuthContext()
James E. Blaira7f51ca2017-02-07 16:01:26 -080082
83 py27 = model.Job('py27')
84 self.assertEqual(None, py27.timeout)
85 py27.inheritFrom(base)
86 self.assertEqual(30, py27.timeout)
87 self.assertEqual(['base-pre'],
88 [x.path for x in py27.pre_run])
89 self.assertEqual(['base-run'],
90 [x.path for x in py27.run])
91 self.assertEqual(['base-post'],
92 [x.path for x in py27.post_run])
James E. Blair8525e2b2017-03-15 14:05:47 -070093 self.assertEqual(None, py27.auth)
James E. Blaira7f51ca2017-02-07 16:01:26 -080094
95 def test_job_variants(self):
96 # This simulates freezing a job.
97
98 py27_pre = model.PlaybookContext(self.context, 'py27-pre')
99 py27_run = model.PlaybookContext(self.context, 'py27-run')
100 py27_post = model.PlaybookContext(self.context, 'py27-post')
101
102 py27 = model.Job('py27')
103 py27.timeout = 30
104 py27.pre_run = [py27_pre]
105 py27.run = [py27_run]
106 py27.post_run = [py27_post]
James E. Blair8525e2b2017-03-15 14:05:47 -0700107 auth = model.AuthContext()
108 auth.secrets.append('foo')
James E. Blaira7f51ca2017-02-07 16:01:26 -0800109 py27.auth = auth
110
111 job = py27.copy()
112 self.assertEqual(30, job.timeout)
113
114 # Apply the diablo variant
115 diablo = model.Job('py27')
116 diablo.timeout = 40
117 job.applyVariant(diablo)
118
119 self.assertEqual(40, job.timeout)
120 self.assertEqual(['py27-pre'],
121 [x.path for x in job.pre_run])
122 self.assertEqual(['py27-run'],
123 [x.path for x in job.run])
124 self.assertEqual(['py27-post'],
125 [x.path for x in job.post_run])
126 self.assertEqual(auth, job.auth)
127
128 # Set the job to final for the following checks
129 job.final = True
130 self.assertTrue(job.voting)
131
132 good_final = model.Job('py27')
133 good_final.voting = False
134 job.applyVariant(good_final)
135 self.assertFalse(job.voting)
136
137 bad_final = model.Job('py27')
138 bad_final.timeout = 600
139 with testtools.ExpectedException(
140 Exception,
141 "Unable to modify final job"):
142 job.applyVariant(bad_final)
143
144 def test_job_inheritance_configloader(self):
145 # TODO(jeblair): move this to a configloader test
James E. Blair5ac93842017-01-20 06:47:34 -0800146 tenant = model.Tenant('tenant')
James E. Blair83005782015-12-11 14:46:03 -0800147 layout = model.Layout()
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700148
149 pipeline = model.Pipeline('gate', layout)
150 layout.addPipeline(pipeline)
151 queue = model.ChangeQueue(pipeline)
James E. Blairc73c73a2017-01-20 15:15:15 -0800152 project = model.Project('project', None)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700153
James E. Blair5ac93842017-01-20 06:47:34 -0800154 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800155 '_source_context': self.context,
156 '_start_mark': self.start_mark,
James E. Blair83005782015-12-11 14:46:03 -0800157 'name': 'base',
158 'timeout': 30,
James E. Blaira7f51ca2017-02-07 16:01:26 -0800159 'pre-run': 'base-pre',
160 'post-run': 'base-post',
James E. Blair1774dd52017-02-03 10:52:32 -0800161 'nodes': [{
162 'name': 'controller',
163 'image': 'base',
164 }],
James E. Blair83005782015-12-11 14:46:03 -0800165 })
166 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800167 python27 = 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': 'python27',
171 'parent': 'base',
James E. Blaira7f51ca2017-02-07 16:01:26 -0800172 'pre-run': 'py27-pre',
173 'post-run': 'py27-post',
James E. Blair1774dd52017-02-03 10:52:32 -0800174 'nodes': [{
175 'name': 'controller',
176 'image': 'new',
177 }],
James E. Blair83005782015-12-11 14:46:03 -0800178 'timeout': 40,
179 })
180 layout.addJob(python27)
James E. Blair5ac93842017-01-20 06:47:34 -0800181 python27diablo = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800182 '_source_context': self.context,
183 '_start_mark': self.start_mark,
James E. Blair83005782015-12-11 14:46:03 -0800184 'name': 'python27',
185 'branches': [
186 'stable/diablo'
187 ],
James E. Blaira7f51ca2017-02-07 16:01:26 -0800188 'pre-run': 'py27-diablo-pre',
189 'run': 'py27-diablo',
190 'post-run': 'py27-diablo-post',
James E. Blair1774dd52017-02-03 10:52:32 -0800191 'nodes': [{
192 'name': 'controller',
193 'image': 'old',
194 }],
James E. Blair83005782015-12-11 14:46:03 -0800195 'timeout': 50,
196 })
197 layout.addJob(python27diablo)
198
James E. Blair5ac93842017-01-20 06:47:34 -0800199 python27essex = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800200 '_source_context': self.context,
201 '_start_mark': self.start_mark,
James E. Blaira7f51ca2017-02-07 16:01:26 -0800202 'name': 'python27',
203 'branches': [
204 'stable/essex'
205 ],
206 'pre-run': 'py27-essex-pre',
207 'post-run': 'py27-essex-post',
208 })
209 layout.addJob(python27essex)
210
James E. Blairff555742017-02-19 11:34:27 -0800211 project_config = configloader.ProjectParser.fromYaml(tenant, layout, [{
James E. Blairec7ff302017-03-04 07:31:32 -0800212 '_source_context': self.context,
213 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700214 'name': 'project',
215 'gate': {
216 'jobs': [
217 'python27'
218 ]
219 }
James E. Blairff555742017-02-19 11:34:27 -0800220 }])
James E. Blairf59f3cf2017-02-19 14:50:26 -0800221 layout.addProjectConfig(project_config)
James E. Blair83005782015-12-11 14:46:03 -0800222
James E. Blair83005782015-12-11 14:46:03 -0800223 change = model.Change(project)
James E. Blair1774dd52017-02-03 10:52:32 -0800224 # Test master
James E. Blair83005782015-12-11 14:46:03 -0800225 change.branch = 'master'
226 item = queue.enqueueChange(change)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700227 item.current_build_set.layout = layout
James E. Blair83005782015-12-11 14:46:03 -0800228
229 self.assertTrue(base.changeMatches(change))
230 self.assertTrue(python27.changeMatches(change))
231 self.assertFalse(python27diablo.changeMatches(change))
James E. Blaira7f51ca2017-02-07 16:01:26 -0800232 self.assertFalse(python27essex.changeMatches(change))
James E. Blair83005782015-12-11 14:46:03 -0800233
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200234 item.freezeJobGraph()
James E. Blair83005782015-12-11 14:46:03 -0800235 self.assertEqual(len(item.getJobs()), 1)
236 job = item.getJobs()[0]
237 self.assertEqual(job.name, 'python27')
238 self.assertEqual(job.timeout, 40)
James E. Blair1774dd52017-02-03 10:52:32 -0800239 nodes = job.nodeset.getNodes()
240 self.assertEqual(len(nodes), 1)
241 self.assertEqual(nodes[0].image, 'new')
James E. Blaira7f51ca2017-02-07 16:01:26 -0800242 self.assertEqual([x.path for x in job.pre_run],
243 ['playbooks/base-pre',
244 'playbooks/py27-pre'])
245 self.assertEqual([x.path for x in job.post_run],
246 ['playbooks/py27-post',
247 'playbooks/base-post'])
248 self.assertEqual([x.path for x in job.run],
249 ['playbooks/python27',
250 'playbooks/base'])
James E. Blair83005782015-12-11 14:46:03 -0800251
James E. Blair1774dd52017-02-03 10:52:32 -0800252 # Test diablo
James E. Blair83005782015-12-11 14:46:03 -0800253 change.branch = 'stable/diablo'
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700254 item = queue.enqueueChange(change)
255 item.current_build_set.layout = layout
James E. Blair83005782015-12-11 14:46:03 -0800256
257 self.assertTrue(base.changeMatches(change))
258 self.assertTrue(python27.changeMatches(change))
259 self.assertTrue(python27diablo.changeMatches(change))
James E. Blaira7f51ca2017-02-07 16:01:26 -0800260 self.assertFalse(python27essex.changeMatches(change))
James E. Blair83005782015-12-11 14:46:03 -0800261
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200262 item.freezeJobGraph()
James E. Blair83005782015-12-11 14:46:03 -0800263 self.assertEqual(len(item.getJobs()), 1)
264 job = item.getJobs()[0]
265 self.assertEqual(job.name, 'python27')
266 self.assertEqual(job.timeout, 50)
James E. Blair1774dd52017-02-03 10:52:32 -0800267 nodes = job.nodeset.getNodes()
268 self.assertEqual(len(nodes), 1)
269 self.assertEqual(nodes[0].image, 'old')
James E. Blaira7f51ca2017-02-07 16:01:26 -0800270 self.assertEqual([x.path for x in job.pre_run],
271 ['playbooks/base-pre',
272 'playbooks/py27-pre',
273 'playbooks/py27-diablo-pre'])
274 self.assertEqual([x.path for x in job.post_run],
275 ['playbooks/py27-diablo-post',
276 'playbooks/py27-post',
277 'playbooks/base-post'])
278 self.assertEqual([x.path for x in job.run],
279 ['playbooks/py27-diablo']),
280
281 # Test essex
282 change.branch = 'stable/essex'
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.assertFalse(python27diablo.changeMatches(change))
289 self.assertTrue(python27essex.changeMatches(change))
290
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200291 item.freezeJobGraph()
James E. Blaira7f51ca2017-02-07 16:01:26 -0800292 self.assertEqual(len(item.getJobs()), 1)
293 job = item.getJobs()[0]
294 self.assertEqual(job.name, 'python27')
295 self.assertEqual([x.path for x in job.pre_run],
296 ['playbooks/base-pre',
297 'playbooks/py27-pre',
298 'playbooks/py27-essex-pre'])
299 self.assertEqual([x.path for x in job.post_run],
300 ['playbooks/py27-essex-post',
301 'playbooks/py27-post',
302 'playbooks/base-post'])
303 self.assertEqual([x.path for x in job.run],
304 ['playbooks/python27',
305 'playbooks/base'])
James E. Blairce8a2132016-05-19 15:21:52 -0700306
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000307 def test_job_auth_inheritance(self):
James E. Blair5ac93842017-01-20 06:47:34 -0800308 tenant = model.Tenant('tenant')
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000309 layout = model.Layout()
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000310
James E. Blair01f83b72017-03-15 13:03:40 -0700311 conf = yaml.safe_load('''
312- secret:
313 name: pypi-credentials
314 data:
315 username: test-username
316 password: !encrypted/pkcs1 |
317 BFhtdnm8uXx7kn79RFL/zJywmzLkT1GY78P3bOtp4WghUFWobkifSu7ZpaV4NeO0s71YUsi1wGZZ
318 L0LveZjUN0t6OU1VZKSG8R5Ly7urjaSo1pPVIq5Rtt/H7W14Lecd+cUeKb4joeusC9drN3AA8a4o
319 ykcVpt1wVqUnTbMGC9ARMCQP6eopcs1l7tzMseprW4RDNhIuz3CRgd0QBMPl6VDoFgBPB8vxtJw+
320 3m0rqBYZCLZgCXekqlny8s2s92nJMuUABbJOEcDRarzibDsSXsfJt1y+5n7yOURsC7lovMg4GF/v
321 Cl/0YMKjBO5bpv9EM5fToeKYyPGSKQoHOnCYceb3cAVcv5UawcCic8XjhEhp4K7WPdYf2HVAC/qt
322 xhbpjTxG4U5Q/SoppOJ60WqEkQvbXs6n5Dvy7xmph6GWmU/bAv3eUK3pdD3xa2Ue1lHWz3U+rsYr
323 aI+AKYsMYx3RBlfAmCeC1ve2BXPrqnOo7G8tnUvfdYPbK4Aakk0ds/AVqFHEZN+S6hRBmBjLaRFW
324 Z3QSO1NjbBxWnaHKZYT7nkrJm8AMCgZU0ZArFLpaufKCeiK5ECSsDxic4FIsY1OkWT42qEUfL0Wd
325 +150AKGNZpPJnnP3QYY4W/MWcKH/zdO400+zWN52WevbSqZy90tqKDJrBkMl1ydqbuw1E4ZHvIs=
326''')[0]['secret']
327
328 conf['_source_context'] = self.context
329 conf['_start_mark'] = self.start_mark
330
331 secret = configloader.SecretParser.fromYaml(layout, conf)
332 layout.addSecret(secret)
333
James E. Blair5ac93842017-01-20 06:47:34 -0800334 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800335 '_source_context': self.context,
336 '_start_mark': self.start_mark,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000337 'name': 'base',
338 'timeout': 30,
339 })
340 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800341 pypi_upload_without_inherit = configloader.JobParser.fromYaml(
342 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800343 '_source_context': self.context,
344 '_start_mark': self.start_mark,
James E. Blair5ac93842017-01-20 06:47:34 -0800345 'name': 'pypi-upload-without-inherit',
346 'parent': 'base',
347 'timeout': 40,
348 'auth': {
349 'secrets': [
350 'pypi-credentials',
351 ]
352 }
353 })
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000354 layout.addJob(pypi_upload_without_inherit)
James E. Blair5ac93842017-01-20 06:47:34 -0800355 pypi_upload_with_inherit = configloader.JobParser.fromYaml(
356 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800357 '_source_context': self.context,
358 '_start_mark': self.start_mark,
James E. Blair5ac93842017-01-20 06:47:34 -0800359 'name': 'pypi-upload-with-inherit',
360 'parent': 'base',
361 'timeout': 40,
362 'auth': {
363 'inherit': True,
364 'secrets': [
365 'pypi-credentials',
366 ]
367 }
368 })
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000369 layout.addJob(pypi_upload_with_inherit)
370 pypi_upload_with_inherit_false = configloader.JobParser.fromYaml(
James E. Blair5ac93842017-01-20 06:47:34 -0800371 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800372 '_source_context': self.context,
373 '_start_mark': self.start_mark,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000374 'name': 'pypi-upload-with-inherit-false',
375 'parent': 'base',
376 'timeout': 40,
377 'auth': {
378 'inherit': False,
Ricardo Carrillo Cruz12c892b2016-11-18 15:35:49 +0000379 'secrets': [
380 'pypi-credentials',
381 ]
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000382 }
383 })
384 layout.addJob(pypi_upload_with_inherit_false)
James E. Blair5ac93842017-01-20 06:47:34 -0800385 in_repo_job_without_inherit = configloader.JobParser.fromYaml(
386 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800387 '_source_context': self.context,
388 '_start_mark': self.start_mark,
James E. Blair5ac93842017-01-20 06:47:34 -0800389 'name': 'in-repo-job-without-inherit',
390 'parent': 'pypi-upload-without-inherit',
391 })
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000392 layout.addJob(in_repo_job_without_inherit)
James E. Blair5ac93842017-01-20 06:47:34 -0800393 in_repo_job_with_inherit = configloader.JobParser.fromYaml(
394 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800395 '_source_context': self.context,
396 '_start_mark': self.start_mark,
James E. Blair5ac93842017-01-20 06:47:34 -0800397 'name': 'in-repo-job-with-inherit',
398 'parent': 'pypi-upload-with-inherit',
399 })
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000400 layout.addJob(in_repo_job_with_inherit)
401 in_repo_job_with_inherit_false = configloader.JobParser.fromYaml(
James E. Blair5ac93842017-01-20 06:47:34 -0800402 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800403 '_source_context': self.context,
404 '_start_mark': self.start_mark,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000405 'name': 'in-repo-job-with-inherit-false',
406 'parent': 'pypi-upload-with-inherit-false',
407 })
408 layout.addJob(in_repo_job_with_inherit_false)
409
James E. Blair8525e2b2017-03-15 14:05:47 -0700410 self.assertEqual(None, in_repo_job_without_inherit.auth)
411 self.assertEqual(1, len(in_repo_job_with_inherit.auth.secrets))
412 self.assertEqual(in_repo_job_with_inherit.auth.secrets[0].name,
413 'pypi-credentials')
414 self.assertEqual(None, in_repo_job_with_inherit_false.auth)
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000415
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700416 def test_job_inheritance_job_tree(self):
James E. Blair5ac93842017-01-20 06:47:34 -0800417 tenant = model.Tenant('tenant')
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700418 layout = model.Layout()
419
420 pipeline = model.Pipeline('gate', layout)
421 layout.addPipeline(pipeline)
422 queue = model.ChangeQueue(pipeline)
423
James E. Blair5ac93842017-01-20 06:47:34 -0800424 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800425 '_source_context': self.context,
426 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700427 'name': 'base',
428 'timeout': 30,
429 })
430 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800431 python27 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800432 '_source_context': self.context,
433 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700434 'name': 'python27',
435 'parent': 'base',
436 'timeout': 40,
437 })
438 layout.addJob(python27)
James E. Blair5ac93842017-01-20 06:47:34 -0800439 python27diablo = 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': 'python27',
443 'branches': [
444 'stable/diablo'
445 ],
446 'timeout': 50,
447 })
448 layout.addJob(python27diablo)
449
James E. Blairff555742017-02-19 11:34:27 -0800450 project_config = configloader.ProjectParser.fromYaml(tenant, layout, [{
James E. Blairec7ff302017-03-04 07:31:32 -0800451 '_source_context': self.context,
452 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700453 'name': 'project',
454 'gate': {
455 'jobs': [
456 {'python27': {'timeout': 70}}
457 ]
458 }
James E. Blairff555742017-02-19 11:34:27 -0800459 }])
James E. Blairf59f3cf2017-02-19 14:50:26 -0800460 layout.addProjectConfig(project_config)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700461
James E. Blairec7ff302017-03-04 07:31:32 -0800462 change = model.Change(self.project)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700463 change.branch = 'master'
464 item = queue.enqueueChange(change)
465 item.current_build_set.layout = layout
466
467 self.assertTrue(base.changeMatches(change))
468 self.assertTrue(python27.changeMatches(change))
469 self.assertFalse(python27diablo.changeMatches(change))
470
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200471 item.freezeJobGraph()
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700472 self.assertEqual(len(item.getJobs()), 1)
473 job = item.getJobs()[0]
474 self.assertEqual(job.name, 'python27')
475 self.assertEqual(job.timeout, 70)
476
477 change.branch = 'stable/diablo'
478 item = queue.enqueueChange(change)
479 item.current_build_set.layout = layout
480
481 self.assertTrue(base.changeMatches(change))
482 self.assertTrue(python27.changeMatches(change))
483 self.assertTrue(python27diablo.changeMatches(change))
484
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200485 item.freezeJobGraph()
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700486 self.assertEqual(len(item.getJobs()), 1)
487 job = item.getJobs()[0]
488 self.assertEqual(job.name, 'python27')
489 self.assertEqual(job.timeout, 70)
490
Clint Byrum85493602016-11-18 11:59:47 -0800491 def test_inheritance_keeps_matchers(self):
James E. Blair5ac93842017-01-20 06:47:34 -0800492 tenant = model.Tenant('tenant')
Clint Byrum85493602016-11-18 11:59:47 -0800493 layout = model.Layout()
494
495 pipeline = model.Pipeline('gate', layout)
496 layout.addPipeline(pipeline)
497 queue = model.ChangeQueue(pipeline)
James E. Blairc73c73a2017-01-20 15:15:15 -0800498 project = model.Project('project', None)
Clint Byrum85493602016-11-18 11:59:47 -0800499
James E. Blair5ac93842017-01-20 06:47:34 -0800500 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800501 '_source_context': self.context,
502 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800503 'name': 'base',
504 'timeout': 30,
505 })
506 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800507 python27 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800508 '_source_context': self.context,
509 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800510 'name': 'python27',
511 'parent': 'base',
512 'timeout': 40,
513 'irrelevant-files': ['^ignored-file$'],
514 })
515 layout.addJob(python27)
516
James E. Blairff555742017-02-19 11:34:27 -0800517 project_config = configloader.ProjectParser.fromYaml(tenant, layout, [{
James E. Blairec7ff302017-03-04 07:31:32 -0800518 '_source_context': self.context,
519 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800520 'name': 'project',
521 'gate': {
522 'jobs': [
523 'python27',
524 ]
525 }
James E. Blairff555742017-02-19 11:34:27 -0800526 }])
James E. Blairf59f3cf2017-02-19 14:50:26 -0800527 layout.addProjectConfig(project_config)
Clint Byrum85493602016-11-18 11:59:47 -0800528
529 change = model.Change(project)
530 change.branch = 'master'
531 change.files = ['/COMMIT_MSG', 'ignored-file']
532 item = queue.enqueueChange(change)
533 item.current_build_set.layout = layout
534
535 self.assertTrue(base.changeMatches(change))
536 self.assertFalse(python27.changeMatches(change))
537
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200538 item.freezeJobGraph()
Clint Byrum85493602016-11-18 11:59:47 -0800539 self.assertEqual([], item.getJobs())
540
James E. Blair4317e9f2016-07-15 10:05:47 -0700541 def test_job_source_project(self):
James E. Blair5ac93842017-01-20 06:47:34 -0800542 tenant = model.Tenant('tenant')
James E. Blair4317e9f2016-07-15 10:05:47 -0700543 layout = model.Layout()
James E. Blairc73c73a2017-01-20 15:15:15 -0800544 base_project = model.Project('base_project', None)
James E. Blair6f140c72017-03-03 10:32:07 -0800545 base_context = model.SourceContext(base_project, 'master',
546 'test', True)
James E. Blaircdab2032017-02-01 09:09:29 -0800547
James E. Blair5ac93842017-01-20 06:47:34 -0800548 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800549 '_source_context': base_context,
James E. Blairec7ff302017-03-04 07:31:32 -0800550 '_start_mark': self.start_mark,
James E. Blair4317e9f2016-07-15 10:05:47 -0700551 'name': 'base',
552 })
553 layout.addJob(base)
554
James E. Blairc73c73a2017-01-20 15:15:15 -0800555 other_project = model.Project('other_project', None)
James E. Blair6f140c72017-03-03 10:32:07 -0800556 other_context = model.SourceContext(other_project, 'master',
557 'test', True)
James E. Blair5ac93842017-01-20 06:47:34 -0800558 base2 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800559 '_source_context': other_context,
James E. Blairec7ff302017-03-04 07:31:32 -0800560 '_start_mark': self.start_mark,
James E. Blair4317e9f2016-07-15 10:05:47 -0700561 'name': 'base',
562 })
563 with testtools.ExpectedException(
564 Exception,
565 "Job base in other_project is not permitted "
566 "to shadow job base in base_project"):
567 layout.addJob(base2)
568
James E. Blairce8a2132016-05-19 15:21:52 -0700569
570class TestJobTimeData(BaseTestCase):
571 def setUp(self):
572 super(TestJobTimeData, self).setUp()
573 self.tmp_root = self.useFixture(fixtures.TempDir(
574 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
575 ).path
576
577 def test_empty_timedata(self):
578 path = os.path.join(self.tmp_root, 'job-name')
579 self.assertFalse(os.path.exists(path))
580 self.assertFalse(os.path.exists(path + '.tmp'))
581 td = model.JobTimeData(path)
582 self.assertEqual(td.success_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
583 self.assertEqual(td.failure_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
584 self.assertEqual(td.results, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
585
586 def test_save_reload(self):
587 path = os.path.join(self.tmp_root, 'job-name')
588 self.assertFalse(os.path.exists(path))
589 self.assertFalse(os.path.exists(path + '.tmp'))
590 td = model.JobTimeData(path)
591 self.assertEqual(td.success_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
592 self.assertEqual(td.failure_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
593 self.assertEqual(td.results, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
594 success_times = []
595 failure_times = []
596 results = []
597 for x in range(10):
598 success_times.append(int(random.random() * 1000))
599 failure_times.append(int(random.random() * 1000))
600 results.append(0)
601 results.append(1)
602 random.shuffle(results)
603 s = f = 0
604 for result in results:
605 if result:
606 td.add(failure_times[f], 'FAILURE')
607 f += 1
608 else:
609 td.add(success_times[s], 'SUCCESS')
610 s += 1
611 self.assertEqual(td.success_times, success_times)
612 self.assertEqual(td.failure_times, failure_times)
613 self.assertEqual(td.results, results[10:])
614 td.save()
615 self.assertTrue(os.path.exists(path))
616 self.assertFalse(os.path.exists(path + '.tmp'))
617 td = model.JobTimeData(path)
618 td.load()
619 self.assertEqual(td.success_times, success_times)
620 self.assertEqual(td.failure_times, failure_times)
621 self.assertEqual(td.results, results[10:])
622
623
624class TestTimeDataBase(BaseTestCase):
625 def setUp(self):
626 super(TestTimeDataBase, self).setUp()
627 self.tmp_root = self.useFixture(fixtures.TempDir(
628 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
629 ).path
630 self.db = model.TimeDataBase(self.tmp_root)
631
632 def test_timedatabase(self):
633 self.assertEqual(self.db.getEstimatedTime('job-name'), 0)
634 self.db.update('job-name', 50, 'SUCCESS')
635 self.assertEqual(self.db.getEstimatedTime('job-name'), 50)
636 self.db.update('job-name', 100, 'SUCCESS')
637 self.assertEqual(self.db.getEstimatedTime('job-name'), 75)
638 for x in range(10):
639 self.db.update('job-name', 100, 'SUCCESS')
640 self.assertEqual(self.db.getEstimatedTime('job-name'), 100)
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200641
642
643class TestGraph(BaseTestCase):
644 def test_job_graph_disallows_multiple_jobs_with_same_name(self):
645 graph = model.JobGraph()
646 job1 = model.Job('job')
647 job2 = model.Job('job')
648 graph.addJob(job1)
649 with testtools.ExpectedException(Exception,
650 "Job job already added"):
651 graph.addJob(job2)
652
653 def test_job_graph_disallows_circular_dependencies(self):
654 graph = model.JobGraph()
655 jobs = [model.Job('job%d' % i) for i in range(0, 10)]
656 prevjob = None
657 for j in jobs[:3]:
658 if prevjob:
659 j.dependencies = frozenset([prevjob.name])
660 graph.addJob(j)
661 prevjob = j
662 # 0 triggers 1 triggers 2 triggers 3...
663
664 # Cannot depend on itself
665 with testtools.ExpectedException(
666 Exception,
667 "Dependency cycle detected in job jobX"):
668 j = model.Job('jobX')
669 j.dependencies = frozenset([j.name])
670 graph.addJob(j)
671
672 # Disallow circular dependencies
673 with testtools.ExpectedException(
674 Exception,
675 "Dependency cycle detected in job job3"):
676 jobs[4].dependencies = frozenset([jobs[3].name])
677 graph.addJob(jobs[4])
678 jobs[3].dependencies = frozenset([jobs[4].name])
679 graph.addJob(jobs[3])
680
681 jobs[5].dependencies = frozenset([jobs[4].name])
682 graph.addJob(jobs[5])
683
684 with testtools.ExpectedException(
685 Exception,
686 "Dependency cycle detected in job job3"):
687 jobs[3].dependencies = frozenset([jobs[5].name])
688 graph.addJob(jobs[3])
689
690 jobs[3].dependencies = frozenset([jobs[2].name])
691 graph.addJob(jobs[3])
692 jobs[6].dependencies = frozenset([jobs[2].name])
693 graph.addJob(jobs[6])