blob: ae24f062eae3f5fd9440fea6b2e0ef56d3c65aeb [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')
James E. Blair6459db12017-06-29 14:57:20 -070043 self.layout = model.Layout(self.tenant)
James E. Blair0a899752017-03-29 13:22:16 -070044 self.project = model.Project('project', self.source)
James E. Blair08d9b782017-06-29 14:22:48 -070045 self.tpc = model.TenantProjectConfig(self.project)
46 self.tenant.addUntrustedProject(self.tpc)
James E. Blairb3f5db12017-03-17 12:57:39 -070047 self.pipeline = model.Pipeline('gate', self.layout)
48 self.layout.addPipeline(self.pipeline)
49 self.queue = model.ChangeQueue(self.pipeline)
50
James E. Blair18f86a32017-03-15 14:43:26 -070051 private_key_file = os.path.join(FIXTURE_DIR, 'private.pem')
52 with open(private_key_file, "rb") as f:
James E. Blairbf1a4f22017-03-17 10:59:37 -070053 self.project.private_key, self.project.public_key = \
54 encryption.deserialize_rsa_keypair(f.read())
James E. Blair6f140c72017-03-03 10:32:07 -080055 self.context = model.SourceContext(self.project, 'master',
56 'test', True)
James E. Blair892cca62017-08-09 11:36:58 -070057 self.untrusted_context = model.SourceContext(self.project, 'master',
58 'test', False)
James E. Blair1cebebf2017-07-14 11:39:03 -070059 m = yaml.Mark('name', 0, 0, 0, '', 0)
60 self.start_mark = configloader.ZuulMark(m, m, '')
James E. Blaira7f51ca2017-02-07 16:01:26 -080061
Maru Newby3fe5f852015-01-13 04:22:14 +000062 @property
63 def job(self):
James E. Blair5ac93842017-01-20 06:47:34 -080064 tenant = model.Tenant('tenant')
James E. Blair6459db12017-06-29 14:57:20 -070065 layout = model.Layout(tenant)
James E. Blair5ac93842017-01-20 06:47:34 -080066 job = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -080067 '_source_context': self.context,
68 '_start_mark': self.start_mark,
James E. Blair83005782015-12-11 14:46:03 -080069 'name': 'job',
James E. Blair2bab6e72017-08-07 09:52:45 -070070 'parent': None,
James E. Blair83005782015-12-11 14:46:03 -080071 'irrelevant-files': [
72 '^docs/.*$'
73 ]})
Maru Newby3fe5f852015-01-13 04:22:14 +000074 return job
75
76 def test_change_matches_returns_false_for_matched_skip_if(self):
77 change = model.Change('project')
Alexander Evseevdbe6fab2015-11-19 12:46:34 +030078 change.files = ['/COMMIT_MSG', 'docs/foo']
Maru Newby3fe5f852015-01-13 04:22:14 +000079 self.assertFalse(self.job.changeMatches(change))
80
Jan Hruban570d01c2016-03-10 21:51:32 +010081 def test_change_matches_returns_false_for_single_matched_skip_if(self):
82 change = model.Change('project')
83 change.files = ['docs/foo']
84 self.assertFalse(self.job.changeMatches(change))
85
Maru Newby3fe5f852015-01-13 04:22:14 +000086 def test_change_matches_returns_true_for_unmatched_skip_if(self):
87 change = model.Change('project')
Alexander Evseevdbe6fab2015-11-19 12:46:34 +030088 change.files = ['/COMMIT_MSG', 'foo']
Maru Newby3fe5f852015-01-13 04:22:14 +000089 self.assertTrue(self.job.changeMatches(change))
90
Jan Hruban570d01c2016-03-10 21:51:32 +010091 def test_change_matches_returns_true_for_single_unmatched_skip_if(self):
92 change = model.Change('project')
93 change.files = ['foo']
94 self.assertTrue(self.job.changeMatches(change))
95
Maru Newby79427a42015-02-17 17:54:45 +000096 def test_job_sets_defaults_for_boolean_attributes(self):
James E. Blair83005782015-12-11 14:46:03 -080097 self.assertIsNotNone(self.job.voting)
98
James E. Blaira7f51ca2017-02-07 16:01:26 -080099 def test_job_variants(self):
100 # This simulates freezing a job.
101
James E. Blair892cca62017-08-09 11:36:58 -0700102 secrets = ['foo']
103 py27_pre = model.PlaybookContext(self.context, 'py27-pre', [], secrets)
104 py27_run = model.PlaybookContext(self.context, 'py27-run', [], secrets)
105 py27_post = model.PlaybookContext(self.context, 'py27-post', [],
106 secrets)
James E. Blaira7f51ca2017-02-07 16:01:26 -0800107
108 py27 = model.Job('py27')
109 py27.timeout = 30
110 py27.pre_run = [py27_pre]
111 py27.run = [py27_run]
112 py27.post_run = [py27_post]
James E. Blaira7f51ca2017-02-07 16:01:26 -0800113
114 job = py27.copy()
115 self.assertEqual(30, job.timeout)
116
117 # Apply the diablo variant
118 diablo = model.Job('py27')
119 diablo.timeout = 40
120 job.applyVariant(diablo)
121
122 self.assertEqual(40, job.timeout)
123 self.assertEqual(['py27-pre'],
124 [x.path for x in job.pre_run])
125 self.assertEqual(['py27-run'],
126 [x.path for x in job.run])
127 self.assertEqual(['py27-post'],
128 [x.path for x in job.post_run])
James E. Blair892cca62017-08-09 11:36:58 -0700129 self.assertEqual(secrets, job.pre_run[0].secrets)
130 self.assertEqual(secrets, job.run[0].secrets)
131 self.assertEqual(secrets, job.post_run[0].secrets)
James E. Blaira7f51ca2017-02-07 16:01:26 -0800132
133 # Set the job to final for the following checks
134 job.final = True
135 self.assertTrue(job.voting)
136
137 good_final = model.Job('py27')
138 good_final.voting = False
139 job.applyVariant(good_final)
140 self.assertFalse(job.voting)
141
142 bad_final = model.Job('py27')
143 bad_final.timeout = 600
144 with testtools.ExpectedException(
145 Exception,
146 "Unable to modify final job"):
147 job.applyVariant(bad_final)
148
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000149 def test_job_auth_inheritance(self):
James E. Blair6459db12017-06-29 14:57:20 -0700150 tenant = self.tenant
151 layout = self.layout
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000152
James E. Blair01f83b72017-03-15 13:03:40 -0700153 conf = yaml.safe_load('''
154- secret:
James E. Blair892cca62017-08-09 11:36:58 -0700155 name: trusted-secret
James E. Blair01f83b72017-03-15 13:03:40 -0700156 data:
157 username: test-username
James E. Blair9118c012017-08-03 11:19:16 -0700158 longpassword: !encrypted/pkcs1-oaep
159 - BFhtdnm8uXx7kn79RFL/zJywmzLkT1GY78P3bOtp4WghUFWobkifSu7ZpaV4NeO0s71Y
160 Usi1wGZZL0LveZjUN0t6OU1VZKSG8R5Ly7urjaSo1pPVIq5Rtt/H7W14Lecd+cUeKb4j
161 oeusC9drN3AA8a4oykcVpt1wVqUnTbMGC9ARMCQP6eopcs1l7tzMseprW4RDNhIuz3CR
162 gd0QBMPl6VDoFgBPB8vxtJw+3m0rqBYZCLZgCXekqlny8s2s92nJMuUABbJOEcDRarzi
163 bDsSXsfJt1y+5n7yOURsC7lovMg4GF/vCl/0YMKjBO5bpv9EM5fToeKYyPGSKQoHOnCY
164 ceb3cAVcv5UawcCic8XjhEhp4K7WPdYf2HVAC/qtxhbpjTxG4U5Q/SoppOJ60WqEkQvb
165 Xs6n5Dvy7xmph6GWmU/bAv3eUK3pdD3xa2Ue1lHWz3U+rsYraI+AKYsMYx3RBlfAmCeC
166 1ve2BXPrqnOo7G8tnUvfdYPbK4Aakk0ds/AVqFHEZN+S6hRBmBjLaRFWZ3QSO1NjbBxW
167 naHKZYT7nkrJm8AMCgZU0ZArFLpaufKCeiK5ECSsDxic4FIsY1OkWT42qEUfL0Wd+150
168 AKGNZpPJnnP3QYY4W/MWcKH/zdO400+zWN52WevbSqZy90tqKDJrBkMl1ydqbuw1E4ZH
169 vIs=
170 - BFhtdnm8uXx7kn79RFL/zJywmzLkT1GY78P3bOtp4WghUFWobkifSu7ZpaV4NeO0s71Y
171 Usi1wGZZL0LveZjUN0t6OU1VZKSG8R5Ly7urjaSo1pPVIq5Rtt/H7W14Lecd+cUeKb4j
172 oeusC9drN3AA8a4oykcVpt1wVqUnTbMGC9ARMCQP6eopcs1l7tzMseprW4RDNhIuz3CR
173 gd0QBMPl6VDoFgBPB8vxtJw+3m0rqBYZCLZgCXekqlny8s2s92nJMuUABbJOEcDRarzi
174 bDsSXsfJt1y+5n7yOURsC7lovMg4GF/vCl/0YMKjBO5bpv9EM5fToeKYyPGSKQoHOnCY
175 ceb3cAVcv5UawcCic8XjhEhp4K7WPdYf2HVAC/qtxhbpjTxG4U5Q/SoppOJ60WqEkQvb
176 Xs6n5Dvy7xmph6GWmU/bAv3eUK3pdD3xa2Ue1lHWz3U+rsYraI+AKYsMYx3RBlfAmCeC
177 1ve2BXPrqnOo7G8tnUvfdYPbK4Aakk0ds/AVqFHEZN+S6hRBmBjLaRFWZ3QSO1NjbBxW
178 naHKZYT7nkrJm8AMCgZU0ZArFLpaufKCeiK5ECSsDxic4FIsY1OkWT42qEUfL0Wd+150
179 AKGNZpPJnnP3QYY4W/MWcKH/zdO400+zWN52WevbSqZy90tqKDJrBkMl1ydqbuw1E4ZH
180 vIs=
James E. Blair717e8e92017-03-17 11:03:27 -0700181 password: !encrypted/pkcs1-oaep |
James E. Blair9118c012017-08-03 11:19:16 -0700182 BFhtdnm8uXx7kn79RFL/zJywmzLkT1GY78P3bOtp4WghUFWobkifSu7ZpaV4NeO0s71Y
183 Usi1wGZZL0LveZjUN0t6OU1VZKSG8R5Ly7urjaSo1pPVIq5Rtt/H7W14Lecd+cUeKb4j
184 oeusC9drN3AA8a4oykcVpt1wVqUnTbMGC9ARMCQP6eopcs1l7tzMseprW4RDNhIuz3CR
185 gd0QBMPl6VDoFgBPB8vxtJw+3m0rqBYZCLZgCXekqlny8s2s92nJMuUABbJOEcDRarzi
186 bDsSXsfJt1y+5n7yOURsC7lovMg4GF/vCl/0YMKjBO5bpv9EM5fToeKYyPGSKQoHOnCY
187 ceb3cAVcv5UawcCic8XjhEhp4K7WPdYf2HVAC/qtxhbpjTxG4U5Q/SoppOJ60WqEkQvb
188 Xs6n5Dvy7xmph6GWmU/bAv3eUK3pdD3xa2Ue1lHWz3U+rsYraI+AKYsMYx3RBlfAmCeC
189 1ve2BXPrqnOo7G8tnUvfdYPbK4Aakk0ds/AVqFHEZN+S6hRBmBjLaRFWZ3QSO1NjbBxW
190 naHKZYT7nkrJm8AMCgZU0ZArFLpaufKCeiK5ECSsDxic4FIsY1OkWT42qEUfL0Wd+150
191 AKGNZpPJnnP3QYY4W/MWcKH/zdO400+zWN52WevbSqZy90tqKDJrBkMl1ydqbuw1E4ZH
192 vIs=
James E. Blair01f83b72017-03-15 13:03:40 -0700193''')[0]['secret']
194
195 conf['_source_context'] = self.context
196 conf['_start_mark'] = self.start_mark
197
James E. Blair892cca62017-08-09 11:36:58 -0700198 trusted_secret = configloader.SecretParser.fromYaml(layout, conf)
199 layout.addSecret(trusted_secret)
200
201 conf['name'] = 'untrusted-secret'
202 conf['_source_context'] = self.untrusted_context
203
204 untrusted_secret = configloader.SecretParser.fromYaml(layout, conf)
205 layout.addSecret(untrusted_secret)
James E. Blair01f83b72017-03-15 13:03:40 -0700206
James E. Blair6459db12017-06-29 14:57:20 -0700207 base = configloader.JobParser.fromYaml(self.tenant, self.layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800208 '_source_context': self.context,
209 '_start_mark': self.start_mark,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000210 'name': 'base',
James E. Blair2bab6e72017-08-07 09:52:45 -0700211 'parent': None,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000212 'timeout': 30,
213 })
214 layout.addJob(base)
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000215
James E. Blair892cca62017-08-09 11:36:58 -0700216 trusted_secrets_job = configloader.JobParser.fromYaml(
217 tenant, layout, {
218 '_source_context': self.context,
219 '_start_mark': self.start_mark,
220 'name': 'trusted-secrets',
221 'parent': 'base',
222 'timeout': 40,
James E. Blaire19e88a2017-08-09 15:14:29 -0700223 'secrets': [
224 'trusted-secret',
225 ]
James E. Blair892cca62017-08-09 11:36:58 -0700226 })
227 layout.addJob(trusted_secrets_job)
228 untrusted_secrets_job = configloader.JobParser.fromYaml(
229 tenant, layout, {
230 '_source_context': self.untrusted_context,
231 '_start_mark': self.start_mark,
232 'name': 'untrusted-secrets',
233 'parent': 'base',
234 'timeout': 40,
James E. Blaire19e88a2017-08-09 15:14:29 -0700235 'secrets': [
236 'untrusted-secret',
237 ]
James E. Blair892cca62017-08-09 11:36:58 -0700238 })
239 layout.addJob(untrusted_secrets_job)
240 trusted_secrets_trusted_child_job = configloader.JobParser.fromYaml(
241 tenant, layout, {
242 '_source_context': self.context,
243 '_start_mark': self.start_mark,
244 'name': 'trusted-secrets-trusted-child',
245 'parent': 'trusted-secrets',
246 })
247 layout.addJob(trusted_secrets_trusted_child_job)
248 trusted_secrets_untrusted_child_job = configloader.JobParser.fromYaml(
249 tenant, layout, {
250 '_source_context': self.untrusted_context,
251 '_start_mark': self.start_mark,
252 'name': 'trusted-secrets-untrusted-child',
253 'parent': 'trusted-secrets',
254 })
255 layout.addJob(trusted_secrets_untrusted_child_job)
256 untrusted_secrets_trusted_child_job = configloader.JobParser.fromYaml(
257 tenant, layout, {
258 '_source_context': self.context,
259 '_start_mark': self.start_mark,
260 'name': 'untrusted-secrets-trusted-child',
261 'parent': 'untrusted-secrets',
262 })
263 layout.addJob(untrusted_secrets_trusted_child_job)
264 untrusted_secrets_untrusted_child_job = \
265 configloader.JobParser.fromYaml(
266 tenant, layout, {
267 '_source_context': self.untrusted_context,
268 '_start_mark': self.start_mark,
269 'name': 'untrusted-secrets-untrusted-child',
270 'parent': 'untrusted-secrets',
271 })
272 layout.addJob(untrusted_secrets_untrusted_child_job)
273
James E. Blair8eb564a2017-08-10 09:21:41 -0700274 self.assertIsNone(trusted_secrets_job.post_review)
275 self.assertTrue(untrusted_secrets_job.post_review)
James E. Blair892cca62017-08-09 11:36:58 -0700276 self.assertIsNone(
James E. Blair8eb564a2017-08-10 09:21:41 -0700277 trusted_secrets_trusted_child_job.post_review)
James E. Blair892cca62017-08-09 11:36:58 -0700278 self.assertIsNone(
James E. Blair8eb564a2017-08-10 09:21:41 -0700279 trusted_secrets_untrusted_child_job.post_review)
James E. Blair892cca62017-08-09 11:36:58 -0700280 self.assertTrue(
James E. Blair8eb564a2017-08-10 09:21:41 -0700281 untrusted_secrets_trusted_child_job.post_review)
James E. Blair892cca62017-08-09 11:36:58 -0700282 self.assertTrue(
James E. Blair8eb564a2017-08-10 09:21:41 -0700283 untrusted_secrets_untrusted_child_job.post_review)
James E. Blair892cca62017-08-09 11:36:58 -0700284
285 self.assertEqual(trusted_secrets_job.implied_run[0].secrets[0].name,
286 'trusted-secret')
287 self.assertEqual(trusted_secrets_job.implied_run[0].secrets[0].
James E. Blair9118c012017-08-03 11:19:16 -0700288 secret_data['longpassword'],
289 'test-passwordtest-password')
James E. Blair892cca62017-08-09 11:36:58 -0700290 self.assertEqual(trusted_secrets_job.implied_run[0].secrets[0].
James E. Blair9118c012017-08-03 11:19:16 -0700291 secret_data['password'],
292 'test-password')
James E. Blair892cca62017-08-09 11:36:58 -0700293 self.assertEqual(
294 len(trusted_secrets_trusted_child_job.implied_run[0].secrets), 0)
295 self.assertEqual(
296 len(trusted_secrets_untrusted_child_job.implied_run[0].secrets), 0)
297
298 self.assertEqual(untrusted_secrets_job.implied_run[0].secrets[0].name,
299 'untrusted-secret')
300 self.assertEqual(
301 len(untrusted_secrets_trusted_child_job.implied_run[0].secrets), 0)
302 self.assertEqual(
303 len(untrusted_secrets_untrusted_child_job.implied_run[0].secrets),
304 0)
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000305
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700306 def test_job_inheritance_job_tree(self):
James E. Blair5ac93842017-01-20 06:47:34 -0800307 tenant = model.Tenant('tenant')
James E. Blair6459db12017-06-29 14:57:20 -0700308 layout = model.Layout(tenant)
James E. Blairaf8b2082017-10-03 15:38:27 -0700309
James E. Blair08d9b782017-06-29 14:22:48 -0700310 tpc = model.TenantProjectConfig(self.project)
311 tenant.addUntrustedProject(tpc)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700312
313 pipeline = model.Pipeline('gate', layout)
314 layout.addPipeline(pipeline)
315 queue = model.ChangeQueue(pipeline)
316
James E. Blair5ac93842017-01-20 06:47:34 -0800317 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800318 '_source_context': self.context,
319 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700320 'name': 'base',
James E. Blair2bab6e72017-08-07 09:52:45 -0700321 'parent': None,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700322 'timeout': 30,
323 })
324 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800325 python27 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800326 '_source_context': self.context,
327 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700328 'name': 'python27',
329 'parent': 'base',
330 'timeout': 40,
331 })
332 layout.addJob(python27)
James E. Blair5ac93842017-01-20 06:47:34 -0800333 python27diablo = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800334 '_source_context': self.context,
335 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700336 'name': 'python27',
337 'branches': [
338 'stable/diablo'
339 ],
340 'timeout': 50,
341 })
342 layout.addJob(python27diablo)
343
James E. Blairaf8b2082017-10-03 15:38:27 -0700344 project_template_parser = configloader.ProjectTemplateParser(
345 tenant, layout)
346 project_parser = configloader.ProjectParser(
347 tenant, layout, project_template_parser)
348 project_config = project_parser.fromYaml([{
James E. Blairec7ff302017-03-04 07:31:32 -0800349 '_source_context': self.context,
350 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700351 'name': 'project',
352 'gate': {
353 'jobs': [
354 {'python27': {'timeout': 70}}
355 ]
356 }
James E. Blairff555742017-02-19 11:34:27 -0800357 }])
James E. Blairf59f3cf2017-02-19 14:50:26 -0800358 layout.addProjectConfig(project_config)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700359
James E. Blairec7ff302017-03-04 07:31:32 -0800360 change = model.Change(self.project)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700361 change.branch = 'master'
362 item = queue.enqueueChange(change)
James E. Blair29a24fd2017-10-02 15:04:56 -0700363 item.layout = layout
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700364
365 self.assertTrue(base.changeMatches(change))
366 self.assertTrue(python27.changeMatches(change))
367 self.assertFalse(python27diablo.changeMatches(change))
368
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200369 item.freezeJobGraph()
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700370 self.assertEqual(len(item.getJobs()), 1)
371 job = item.getJobs()[0]
372 self.assertEqual(job.name, 'python27')
373 self.assertEqual(job.timeout, 70)
374
375 change.branch = 'stable/diablo'
376 item = queue.enqueueChange(change)
James E. Blair29a24fd2017-10-02 15:04:56 -0700377 item.layout = layout
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700378
379 self.assertTrue(base.changeMatches(change))
380 self.assertTrue(python27.changeMatches(change))
381 self.assertTrue(python27diablo.changeMatches(change))
382
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200383 item.freezeJobGraph()
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700384 self.assertEqual(len(item.getJobs()), 1)
385 job = item.getJobs()[0]
386 self.assertEqual(job.name, 'python27')
387 self.assertEqual(job.timeout, 70)
388
Clint Byrum85493602016-11-18 11:59:47 -0800389 def test_inheritance_keeps_matchers(self):
James E. Blair5ac93842017-01-20 06:47:34 -0800390 tenant = model.Tenant('tenant')
James E. Blair6459db12017-06-29 14:57:20 -0700391 layout = model.Layout(tenant)
Clint Byrum85493602016-11-18 11:59:47 -0800392
393 pipeline = model.Pipeline('gate', layout)
394 layout.addPipeline(pipeline)
395 queue = model.ChangeQueue(pipeline)
James E. Blair0a899752017-03-29 13:22:16 -0700396 project = model.Project('project', self.source)
James E. Blair08d9b782017-06-29 14:22:48 -0700397 tpc = model.TenantProjectConfig(project)
398 tenant.addUntrustedProject(tpc)
Clint Byrum85493602016-11-18 11:59:47 -0800399
James E. Blair5ac93842017-01-20 06:47:34 -0800400 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800401 '_source_context': self.context,
402 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800403 'name': 'base',
James E. Blair2bab6e72017-08-07 09:52:45 -0700404 'parent': None,
Clint Byrum85493602016-11-18 11:59:47 -0800405 'timeout': 30,
406 })
407 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800408 python27 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800409 '_source_context': self.context,
410 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800411 'name': 'python27',
412 'parent': 'base',
413 'timeout': 40,
414 'irrelevant-files': ['^ignored-file$'],
415 })
416 layout.addJob(python27)
417
James E. Blairaf8b2082017-10-03 15:38:27 -0700418 project_template_parser = configloader.ProjectTemplateParser(
419 tenant, layout)
420 project_parser = configloader.ProjectParser(
421 tenant, layout, project_template_parser)
422 project_config = project_parser.fromYaml([{
James E. Blairec7ff302017-03-04 07:31:32 -0800423 '_source_context': self.context,
424 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800425 'name': 'project',
426 'gate': {
427 'jobs': [
428 'python27',
429 ]
430 }
James E. Blairff555742017-02-19 11:34:27 -0800431 }])
James E. Blairf59f3cf2017-02-19 14:50:26 -0800432 layout.addProjectConfig(project_config)
Clint Byrum85493602016-11-18 11:59:47 -0800433
434 change = model.Change(project)
435 change.branch = 'master'
436 change.files = ['/COMMIT_MSG', 'ignored-file']
437 item = queue.enqueueChange(change)
James E. Blair29a24fd2017-10-02 15:04:56 -0700438 item.layout = layout
Clint Byrum85493602016-11-18 11:59:47 -0800439
440 self.assertTrue(base.changeMatches(change))
441 self.assertFalse(python27.changeMatches(change))
442
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200443 item.freezeJobGraph()
Clint Byrum85493602016-11-18 11:59:47 -0800444 self.assertEqual([], item.getJobs())
445
James E. Blair4317e9f2016-07-15 10:05:47 -0700446 def test_job_source_project(self):
James E. Blair6459db12017-06-29 14:57:20 -0700447 tenant = self.tenant
448 layout = self.layout
James E. Blair0a899752017-03-29 13:22:16 -0700449 base_project = model.Project('base_project', self.source)
James E. Blair6f140c72017-03-03 10:32:07 -0800450 base_context = model.SourceContext(base_project, 'master',
451 'test', True)
James E. Blair6459db12017-06-29 14:57:20 -0700452 tpc = model.TenantProjectConfig(base_project)
453 tenant.addUntrustedProject(tpc)
James E. Blaircdab2032017-02-01 09:09:29 -0800454
James E. Blair5ac93842017-01-20 06:47:34 -0800455 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800456 '_source_context': base_context,
James E. Blairec7ff302017-03-04 07:31:32 -0800457 '_start_mark': self.start_mark,
James E. Blair2bab6e72017-08-07 09:52:45 -0700458 'parent': None,
James E. Blair4317e9f2016-07-15 10:05:47 -0700459 'name': 'base',
460 })
461 layout.addJob(base)
462
James E. Blair0a899752017-03-29 13:22:16 -0700463 other_project = model.Project('other_project', self.source)
James E. Blair6f140c72017-03-03 10:32:07 -0800464 other_context = model.SourceContext(other_project, 'master',
465 'test', True)
James E. Blair6459db12017-06-29 14:57:20 -0700466 tpc = model.TenantProjectConfig(other_project)
467 tenant.addUntrustedProject(tpc)
James E. Blair5ac93842017-01-20 06:47:34 -0800468 base2 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800469 '_source_context': other_context,
James E. Blairec7ff302017-03-04 07:31:32 -0800470 '_start_mark': self.start_mark,
James E. Blair4317e9f2016-07-15 10:05:47 -0700471 'name': 'base',
472 })
473 with testtools.ExpectedException(
474 Exception,
475 "Job base in other_project is not permitted "
476 "to shadow job base in base_project"):
477 layout.addJob(base2)
478
James E. Blairb3f5db12017-03-17 12:57:39 -0700479 def test_job_allowed_projects(self):
480 job = configloader.JobParser.fromYaml(self.tenant, self.layout, {
481 '_source_context': self.context,
482 '_start_mark': self.start_mark,
483 'name': 'job',
James E. Blair2bab6e72017-08-07 09:52:45 -0700484 'parent': None,
James E. Blairb3f5db12017-03-17 12:57:39 -0700485 'allowed-projects': ['project'],
486 })
487 self.layout.addJob(job)
488
James E. Blair0a899752017-03-29 13:22:16 -0700489 project2 = model.Project('project2', self.source)
James E. Blair08d9b782017-06-29 14:22:48 -0700490 tpc2 = model.TenantProjectConfig(project2)
491 self.tenant.addUntrustedProject(tpc2)
James E. Blairb3f5db12017-03-17 12:57:39 -0700492 context2 = model.SourceContext(project2, 'master',
493 'test', True)
494
James E. Blairaf8b2082017-10-03 15:38:27 -0700495 project_template_parser = configloader.ProjectTemplateParser(
496 self.tenant, self.layout)
497 project_parser = configloader.ProjectParser(
498 self.tenant, self.layout, project_template_parser)
499 project2_config = project_parser.fromYaml(
500 [{
James E. Blairb3f5db12017-03-17 12:57:39 -0700501 '_source_context': context2,
502 '_start_mark': self.start_mark,
503 'name': 'project2',
504 'gate': {
505 'jobs': [
506 'job'
507 ]
508 }
509 }]
510 )
511 self.layout.addProjectConfig(project2_config)
512
513 change = model.Change(project2)
514 # Test master
515 change.branch = 'master'
516 item = self.queue.enqueueChange(change)
James E. Blair29a24fd2017-10-02 15:04:56 -0700517 item.layout = self.layout
James E. Blairb3f5db12017-03-17 12:57:39 -0700518 with testtools.ExpectedException(
519 Exception,
520 "Project project2 is not allowed to run job job"):
521 item.freezeJobGraph()
522
James E. Blair8eb564a2017-08-10 09:21:41 -0700523 def test_job_pipeline_allow_untrusted_secrets(self):
524 self.pipeline.post_review = False
James E. Blaird2348362017-03-17 13:59:35 -0700525 job = configloader.JobParser.fromYaml(self.tenant, self.layout, {
526 '_source_context': self.context,
527 '_start_mark': self.start_mark,
528 'name': 'job',
James E. Blair2bab6e72017-08-07 09:52:45 -0700529 'parent': None,
James E. Blaird2348362017-03-17 13:59:35 -0700530 })
James E. Blair8eb564a2017-08-10 09:21:41 -0700531 job.post_review = True
James E. Blaird2348362017-03-17 13:59:35 -0700532
533 self.layout.addJob(job)
534
James E. Blairaf8b2082017-10-03 15:38:27 -0700535 project_template_parser = configloader.ProjectTemplateParser(
536 self.tenant, self.layout)
537 project_parser = configloader.ProjectParser(
538 self.tenant, self.layout, project_template_parser)
539 project_config = project_parser.fromYaml(
540 [{
James E. Blaird2348362017-03-17 13:59:35 -0700541 '_source_context': self.context,
542 '_start_mark': self.start_mark,
543 'name': 'project',
544 'gate': {
545 'jobs': [
546 'job'
547 ]
548 }
549 }]
550 )
551 self.layout.addProjectConfig(project_config)
552
553 change = model.Change(self.project)
554 # Test master
555 change.branch = 'master'
556 item = self.queue.enqueueChange(change)
James E. Blair29a24fd2017-10-02 15:04:56 -0700557 item.layout = self.layout
James E. Blaird2348362017-03-17 13:59:35 -0700558 with testtools.ExpectedException(
559 Exception,
James E. Blair8eb564a2017-08-10 09:21:41 -0700560 "Pre-review pipeline gate does not allow post-review job"):
James E. Blaird2348362017-03-17 13:59:35 -0700561 item.freezeJobGraph()
562
James E. Blairce8a2132016-05-19 15:21:52 -0700563
564class TestJobTimeData(BaseTestCase):
565 def setUp(self):
566 super(TestJobTimeData, self).setUp()
567 self.tmp_root = self.useFixture(fixtures.TempDir(
568 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
569 ).path
570
571 def test_empty_timedata(self):
572 path = os.path.join(self.tmp_root, 'job-name')
573 self.assertFalse(os.path.exists(path))
574 self.assertFalse(os.path.exists(path + '.tmp'))
575 td = model.JobTimeData(path)
576 self.assertEqual(td.success_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
577 self.assertEqual(td.failure_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
578 self.assertEqual(td.results, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
579
580 def test_save_reload(self):
581 path = os.path.join(self.tmp_root, 'job-name')
582 self.assertFalse(os.path.exists(path))
583 self.assertFalse(os.path.exists(path + '.tmp'))
584 td = model.JobTimeData(path)
585 self.assertEqual(td.success_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
586 self.assertEqual(td.failure_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
587 self.assertEqual(td.results, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
588 success_times = []
589 failure_times = []
590 results = []
591 for x in range(10):
592 success_times.append(int(random.random() * 1000))
593 failure_times.append(int(random.random() * 1000))
594 results.append(0)
595 results.append(1)
596 random.shuffle(results)
597 s = f = 0
598 for result in results:
599 if result:
600 td.add(failure_times[f], 'FAILURE')
601 f += 1
602 else:
603 td.add(success_times[s], 'SUCCESS')
604 s += 1
605 self.assertEqual(td.success_times, success_times)
606 self.assertEqual(td.failure_times, failure_times)
607 self.assertEqual(td.results, results[10:])
608 td.save()
609 self.assertTrue(os.path.exists(path))
610 self.assertFalse(os.path.exists(path + '.tmp'))
611 td = model.JobTimeData(path)
612 td.load()
613 self.assertEqual(td.success_times, success_times)
614 self.assertEqual(td.failure_times, failure_times)
615 self.assertEqual(td.results, results[10:])
616
617
618class TestTimeDataBase(BaseTestCase):
619 def setUp(self):
620 super(TestTimeDataBase, self).setUp()
621 self.tmp_root = self.useFixture(fixtures.TempDir(
622 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
623 ).path
624 self.db = model.TimeDataBase(self.tmp_root)
625
626 def test_timedatabase(self):
James E. Blairae0f23c2017-09-13 10:55:15 -0600627 pipeline = Dummy(layout=Dummy(tenant=Dummy(name='test-tenant')))
628 change = Dummy(project=Dummy(canonical_name='git.example.com/foo/bar'))
629 job = Dummy(name='job-name')
630 item = Dummy(pipeline=pipeline,
631 change=change)
632 build = Dummy(build_set=Dummy(item=item),
633 job=job)
634
635 self.assertEqual(self.db.getEstimatedTime(build), 0)
636 self.db.update(build, 50, 'SUCCESS')
637 self.assertEqual(self.db.getEstimatedTime(build), 50)
638 self.db.update(build, 100, 'SUCCESS')
639 self.assertEqual(self.db.getEstimatedTime(build), 75)
James E. Blairce8a2132016-05-19 15:21:52 -0700640 for x in range(10):
James E. Blairae0f23c2017-09-13 10:55:15 -0600641 self.db.update(build, 100, 'SUCCESS')
642 self.assertEqual(self.db.getEstimatedTime(build), 100)
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200643
644
645class TestGraph(BaseTestCase):
646 def test_job_graph_disallows_multiple_jobs_with_same_name(self):
647 graph = model.JobGraph()
648 job1 = model.Job('job')
649 job2 = model.Job('job')
650 graph.addJob(job1)
651 with testtools.ExpectedException(Exception,
652 "Job job already added"):
653 graph.addJob(job2)
654
655 def test_job_graph_disallows_circular_dependencies(self):
656 graph = model.JobGraph()
657 jobs = [model.Job('job%d' % i) for i in range(0, 10)]
658 prevjob = None
659 for j in jobs[:3]:
660 if prevjob:
661 j.dependencies = frozenset([prevjob.name])
662 graph.addJob(j)
663 prevjob = j
664 # 0 triggers 1 triggers 2 triggers 3...
665
666 # Cannot depend on itself
667 with testtools.ExpectedException(
668 Exception,
669 "Dependency cycle detected in job jobX"):
670 j = model.Job('jobX')
671 j.dependencies = frozenset([j.name])
672 graph.addJob(j)
673
674 # Disallow circular dependencies
675 with testtools.ExpectedException(
676 Exception,
677 "Dependency cycle detected in job job3"):
678 jobs[4].dependencies = frozenset([jobs[3].name])
679 graph.addJob(jobs[4])
680 jobs[3].dependencies = frozenset([jobs[4].name])
681 graph.addJob(jobs[3])
682
683 jobs[5].dependencies = frozenset([jobs[4].name])
684 graph.addJob(jobs[5])
685
686 with testtools.ExpectedException(
687 Exception,
688 "Dependency cycle detected in job job3"):
689 jobs[3].dependencies = frozenset([jobs[5].name])
690 graph.addJob(jobs[3])
691
692 jobs[3].dependencies = frozenset([jobs[2].name])
693 graph.addJob(jobs[3])
694 jobs[6].dependencies = frozenset([jobs[2].name])
695 graph.addJob(jobs[6])
James E. Blairc2a54fd2017-03-29 15:19:26 -0700696
697
698class TestTenant(BaseTestCase):
699 def test_add_project(self):
700 tenant = model.Tenant('tenant')
701 connection1 = Dummy(connection_name='dummy_connection1')
702 source1 = Dummy(canonical_hostname='git1.example.com',
703 name='dummy', # TODOv3(jeblair): remove
704 connection=connection1)
705
706 source1_project1 = model.Project('project1', source1)
James E. Blair08d9b782017-06-29 14:22:48 -0700707 source1_project1_tpc = model.TenantProjectConfig(source1_project1)
708 tenant.addConfigProject(source1_project1_tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700709 d = {'project1':
710 {'git1.example.com': source1_project1}}
711 self.assertEqual(d, tenant.projects)
712 self.assertEqual((True, source1_project1),
713 tenant.getProject('project1'))
714 self.assertEqual((True, source1_project1),
715 tenant.getProject('git1.example.com/project1'))
716
717 source1_project2 = model.Project('project2', source1)
James E. Blair08d9b782017-06-29 14:22:48 -0700718 tpc = model.TenantProjectConfig(source1_project2)
719 tenant.addUntrustedProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700720 d = {'project1':
721 {'git1.example.com': source1_project1},
722 'project2':
723 {'git1.example.com': source1_project2}}
724 self.assertEqual(d, tenant.projects)
725 self.assertEqual((False, source1_project2),
726 tenant.getProject('project2'))
727 self.assertEqual((False, source1_project2),
728 tenant.getProject('git1.example.com/project2'))
729
730 connection2 = Dummy(connection_name='dummy_connection2')
731 source2 = Dummy(canonical_hostname='git2.example.com',
732 name='dummy', # TODOv3(jeblair): remove
733 connection=connection2)
734
735 source2_project1 = model.Project('project1', source2)
James E. Blair08d9b782017-06-29 14:22:48 -0700736 tpc = model.TenantProjectConfig(source2_project1)
737 tenant.addUntrustedProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700738 d = {'project1':
739 {'git1.example.com': source1_project1,
740 'git2.example.com': source2_project1},
741 'project2':
742 {'git1.example.com': source1_project2}}
743 self.assertEqual(d, tenant.projects)
744 with testtools.ExpectedException(
745 Exception,
746 "Project name 'project1' is ambiguous"):
747 tenant.getProject('project1')
748 self.assertEqual((False, source1_project2),
749 tenant.getProject('project2'))
750 self.assertEqual((True, source1_project1),
751 tenant.getProject('git1.example.com/project1'))
752 self.assertEqual((False, source2_project1),
753 tenant.getProject('git2.example.com/project1'))
754
755 source2_project2 = model.Project('project2', source2)
James E. Blair08d9b782017-06-29 14:22:48 -0700756 tpc = model.TenantProjectConfig(source2_project2)
757 tenant.addConfigProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700758 d = {'project1':
759 {'git1.example.com': source1_project1,
760 'git2.example.com': source2_project1},
761 'project2':
762 {'git1.example.com': source1_project2,
763 'git2.example.com': source2_project2}}
764 self.assertEqual(d, tenant.projects)
765 with testtools.ExpectedException(
766 Exception,
767 "Project name 'project1' is ambiguous"):
768 tenant.getProject('project1')
769 with testtools.ExpectedException(
770 Exception,
771 "Project name 'project2' is ambiguous"):
772 tenant.getProject('project2')
773 self.assertEqual((True, source1_project1),
774 tenant.getProject('git1.example.com/project1'))
775 self.assertEqual((False, source2_project1),
776 tenant.getProject('git2.example.com/project1'))
777 self.assertEqual((False, source1_project2),
778 tenant.getProject('git1.example.com/project2'))
779 self.assertEqual((True, source2_project2),
780 tenant.getProject('git2.example.com/project2'))
781
782 source1_project2b = model.Project('subpath/project2', source1)
James E. Blair08d9b782017-06-29 14:22:48 -0700783 tpc = model.TenantProjectConfig(source1_project2b)
784 tenant.addConfigProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700785 d = {'project1':
786 {'git1.example.com': source1_project1,
787 'git2.example.com': source2_project1},
788 'project2':
789 {'git1.example.com': source1_project2,
790 'git2.example.com': source2_project2},
791 'subpath/project2':
792 {'git1.example.com': source1_project2b}}
793 self.assertEqual(d, tenant.projects)
794 self.assertEqual((False, source1_project2),
795 tenant.getProject('git1.example.com/project2'))
796 self.assertEqual((True, source2_project2),
797 tenant.getProject('git2.example.com/project2'))
798 self.assertEqual((True, source1_project2b),
799 tenant.getProject('subpath/project2'))
800 self.assertEqual(
801 (True, source1_project2b),
802 tenant.getProject('git1.example.com/subpath/project2'))
803
804 source2_project2b = model.Project('subpath/project2', source2)
James E. Blair08d9b782017-06-29 14:22:48 -0700805 tpc = model.TenantProjectConfig(source2_project2b)
806 tenant.addConfigProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700807 d = {'project1':
808 {'git1.example.com': source1_project1,
809 'git2.example.com': source2_project1},
810 'project2':
811 {'git1.example.com': source1_project2,
812 'git2.example.com': source2_project2},
813 'subpath/project2':
814 {'git1.example.com': source1_project2b,
815 'git2.example.com': source2_project2b}}
816 self.assertEqual(d, tenant.projects)
817 self.assertEqual((False, source1_project2),
818 tenant.getProject('git1.example.com/project2'))
819 self.assertEqual((True, source2_project2),
820 tenant.getProject('git2.example.com/project2'))
821 with testtools.ExpectedException(
822 Exception,
823 "Project name 'subpath/project2' is ambiguous"):
824 tenant.getProject('subpath/project2')
825 self.assertEqual(
826 (True, source1_project2b),
827 tenant.getProject('git1.example.com/subpath/project2'))
828 self.assertEqual(
829 (True, source2_project2b),
830 tenant.getProject('git2.example.com/subpath/project2'))
831
832 with testtools.ExpectedException(
833 Exception,
834 "Project project1 is already in project index"):
James E. Blair08d9b782017-06-29 14:22:48 -0700835 tenant._addProject(source1_project1_tpc)