blob: a52a2ee916715ca2d80d350dc38e5dcdb199efc5 [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. Blair1cebebf2017-07-14 11:39:03 -070057 m = yaml.Mark('name', 0, 0, 0, '', 0)
58 self.start_mark = configloader.ZuulMark(m, m, '')
James E. Blaira7f51ca2017-02-07 16:01:26 -080059
Maru Newby3fe5f852015-01-13 04:22:14 +000060 @property
61 def job(self):
James E. Blair5ac93842017-01-20 06:47:34 -080062 tenant = model.Tenant('tenant')
James E. Blair6459db12017-06-29 14:57:20 -070063 layout = model.Layout(tenant)
James E. Blair5ac93842017-01-20 06:47:34 -080064 job = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -080065 '_source_context': self.context,
66 '_start_mark': self.start_mark,
James E. Blair83005782015-12-11 14:46:03 -080067 'name': 'job',
68 'irrelevant-files': [
69 '^docs/.*$'
70 ]})
Maru Newby3fe5f852015-01-13 04:22:14 +000071 return job
72
73 def test_change_matches_returns_false_for_matched_skip_if(self):
74 change = model.Change('project')
Alexander Evseevdbe6fab2015-11-19 12:46:34 +030075 change.files = ['/COMMIT_MSG', 'docs/foo']
Maru Newby3fe5f852015-01-13 04:22:14 +000076 self.assertFalse(self.job.changeMatches(change))
77
Jan Hruban570d01c2016-03-10 21:51:32 +010078 def test_change_matches_returns_false_for_single_matched_skip_if(self):
79 change = model.Change('project')
80 change.files = ['docs/foo']
81 self.assertFalse(self.job.changeMatches(change))
82
Maru Newby3fe5f852015-01-13 04:22:14 +000083 def test_change_matches_returns_true_for_unmatched_skip_if(self):
84 change = model.Change('project')
Alexander Evseevdbe6fab2015-11-19 12:46:34 +030085 change.files = ['/COMMIT_MSG', 'foo']
Maru Newby3fe5f852015-01-13 04:22:14 +000086 self.assertTrue(self.job.changeMatches(change))
87
Jan Hruban570d01c2016-03-10 21:51:32 +010088 def test_change_matches_returns_true_for_single_unmatched_skip_if(self):
89 change = model.Change('project')
90 change.files = ['foo']
91 self.assertTrue(self.job.changeMatches(change))
92
Maru Newby79427a42015-02-17 17:54:45 +000093 def test_job_sets_defaults_for_boolean_attributes(self):
James E. Blair83005782015-12-11 14:46:03 -080094 self.assertIsNotNone(self.job.voting)
95
96 def test_job_inheritance(self):
James E. Blaira7f51ca2017-02-07 16:01:26 -080097 # This is standard job inheritance.
98
James E. Blair74a82cf2017-07-12 17:23:08 -070099 base_pre = model.PlaybookContext(self.context, 'base-pre', [])
100 base_run = model.PlaybookContext(self.context, 'base-run', [])
101 base_post = model.PlaybookContext(self.context, 'base-post', [])
James E. Blaira7f51ca2017-02-07 16:01:26 -0800102
103 base = model.Job('base')
104 base.timeout = 30
105 base.pre_run = [base_pre]
106 base.run = [base_run]
107 base.post_run = [base_post]
James E. Blair8525e2b2017-03-15 14:05:47 -0700108 base.auth = model.AuthContext()
James E. Blaira7f51ca2017-02-07 16:01:26 -0800109
110 py27 = model.Job('py27')
Monty Taylor38b553a2017-06-05 13:06:10 -0500111 self.assertIsNone(py27.timeout)
James E. Blaira7f51ca2017-02-07 16:01:26 -0800112 py27.inheritFrom(base)
113 self.assertEqual(30, py27.timeout)
114 self.assertEqual(['base-pre'],
115 [x.path for x in py27.pre_run])
116 self.assertEqual(['base-run'],
117 [x.path for x in py27.run])
118 self.assertEqual(['base-post'],
119 [x.path for x in py27.post_run])
Monty Taylor38b553a2017-06-05 13:06:10 -0500120 self.assertIsNone(py27.auth)
James E. Blaira7f51ca2017-02-07 16:01:26 -0800121
122 def test_job_variants(self):
123 # This simulates freezing a job.
124
James E. Blair74a82cf2017-07-12 17:23:08 -0700125 py27_pre = model.PlaybookContext(self.context, 'py27-pre', [])
126 py27_run = model.PlaybookContext(self.context, 'py27-run', [])
127 py27_post = model.PlaybookContext(self.context, 'py27-post', [])
James E. Blaira7f51ca2017-02-07 16:01:26 -0800128
129 py27 = model.Job('py27')
130 py27.timeout = 30
131 py27.pre_run = [py27_pre]
132 py27.run = [py27_run]
133 py27.post_run = [py27_post]
James E. Blair8525e2b2017-03-15 14:05:47 -0700134 auth = model.AuthContext()
135 auth.secrets.append('foo')
James E. Blaira7f51ca2017-02-07 16:01:26 -0800136 py27.auth = auth
137
138 job = py27.copy()
139 self.assertEqual(30, job.timeout)
140
141 # Apply the diablo variant
142 diablo = model.Job('py27')
143 diablo.timeout = 40
144 job.applyVariant(diablo)
145
146 self.assertEqual(40, job.timeout)
147 self.assertEqual(['py27-pre'],
148 [x.path for x in job.pre_run])
149 self.assertEqual(['py27-run'],
150 [x.path for x in job.run])
151 self.assertEqual(['py27-post'],
152 [x.path for x in job.post_run])
153 self.assertEqual(auth, job.auth)
154
155 # Set the job to final for the following checks
156 job.final = True
157 self.assertTrue(job.voting)
158
159 good_final = model.Job('py27')
160 good_final.voting = False
161 job.applyVariant(good_final)
162 self.assertFalse(job.voting)
163
164 bad_final = model.Job('py27')
165 bad_final.timeout = 600
166 with testtools.ExpectedException(
167 Exception,
168 "Unable to modify final job"):
169 job.applyVariant(bad_final)
170
171 def test_job_inheritance_configloader(self):
172 # TODO(jeblair): move this to a configloader test
James E. Blair5ac93842017-01-20 06:47:34 -0800173 tenant = model.Tenant('tenant')
James E. Blair6459db12017-06-29 14:57:20 -0700174 layout = model.Layout(tenant)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700175
176 pipeline = model.Pipeline('gate', layout)
177 layout.addPipeline(pipeline)
178 queue = model.ChangeQueue(pipeline)
James E. Blair0a899752017-03-29 13:22:16 -0700179 project = model.Project('project', self.source)
James E. Blair08d9b782017-06-29 14:22:48 -0700180 tpc = model.TenantProjectConfig(project)
181 tenant.addUntrustedProject(tpc)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700182
James E. Blair5ac93842017-01-20 06:47:34 -0800183 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800184 '_source_context': self.context,
185 '_start_mark': self.start_mark,
James E. Blair83005782015-12-11 14:46:03 -0800186 'name': 'base',
187 'timeout': 30,
James E. Blaira7f51ca2017-02-07 16:01:26 -0800188 'pre-run': 'base-pre',
189 'post-run': 'base-post',
James E. Blair1774dd52017-02-03 10:52:32 -0800190 'nodes': [{
191 'name': 'controller',
James E. Blair16d96a02017-06-08 11:32:56 -0700192 'label': 'base',
James E. Blair1774dd52017-02-03 10:52:32 -0800193 }],
James E. Blair83005782015-12-11 14:46:03 -0800194 })
195 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800196 python27 = 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 'parent': 'base',
James E. Blaira7f51ca2017-02-07 16:01:26 -0800201 'pre-run': 'py27-pre',
202 'post-run': 'py27-post',
James E. Blair1774dd52017-02-03 10:52:32 -0800203 'nodes': [{
204 'name': 'controller',
James E. Blair16d96a02017-06-08 11:32:56 -0700205 'label': 'new',
James E. Blair1774dd52017-02-03 10:52:32 -0800206 }],
James E. Blair83005782015-12-11 14:46:03 -0800207 'timeout': 40,
208 })
209 layout.addJob(python27)
James E. Blair5ac93842017-01-20 06:47:34 -0800210 python27diablo = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800211 '_source_context': self.context,
212 '_start_mark': self.start_mark,
James E. Blair83005782015-12-11 14:46:03 -0800213 'name': 'python27',
214 'branches': [
215 'stable/diablo'
216 ],
James E. Blaira7f51ca2017-02-07 16:01:26 -0800217 'pre-run': 'py27-diablo-pre',
218 'run': 'py27-diablo',
219 'post-run': 'py27-diablo-post',
James E. Blair1774dd52017-02-03 10:52:32 -0800220 'nodes': [{
221 'name': 'controller',
James E. Blair16d96a02017-06-08 11:32:56 -0700222 'label': 'old',
James E. Blair1774dd52017-02-03 10:52:32 -0800223 }],
James E. Blair83005782015-12-11 14:46:03 -0800224 'timeout': 50,
225 })
226 layout.addJob(python27diablo)
227
James E. Blair5ac93842017-01-20 06:47:34 -0800228 python27essex = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800229 '_source_context': self.context,
230 '_start_mark': self.start_mark,
James E. Blaira7f51ca2017-02-07 16:01:26 -0800231 'name': 'python27',
232 'branches': [
233 'stable/essex'
234 ],
235 'pre-run': 'py27-essex-pre',
236 'post-run': 'py27-essex-post',
237 })
238 layout.addJob(python27essex)
239
James E. Blairff555742017-02-19 11:34:27 -0800240 project_config = configloader.ProjectParser.fromYaml(tenant, layout, [{
James E. Blairec7ff302017-03-04 07:31:32 -0800241 '_source_context': self.context,
242 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700243 'name': 'project',
244 'gate': {
245 'jobs': [
246 'python27'
247 ]
248 }
James E. Blairff555742017-02-19 11:34:27 -0800249 }])
James E. Blairf59f3cf2017-02-19 14:50:26 -0800250 layout.addProjectConfig(project_config)
James E. Blair83005782015-12-11 14:46:03 -0800251
James E. Blair83005782015-12-11 14:46:03 -0800252 change = model.Change(project)
James E. Blair1774dd52017-02-03 10:52:32 -0800253 # Test master
James E. Blair83005782015-12-11 14:46:03 -0800254 change.branch = 'master'
255 item = queue.enqueueChange(change)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700256 item.current_build_set.layout = layout
James E. Blair83005782015-12-11 14:46:03 -0800257
258 self.assertTrue(base.changeMatches(change))
259 self.assertTrue(python27.changeMatches(change))
260 self.assertFalse(python27diablo.changeMatches(change))
James E. Blaira7f51ca2017-02-07 16:01:26 -0800261 self.assertFalse(python27essex.changeMatches(change))
James E. Blair83005782015-12-11 14:46:03 -0800262
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200263 item.freezeJobGraph()
James E. Blair83005782015-12-11 14:46:03 -0800264 self.assertEqual(len(item.getJobs()), 1)
265 job = item.getJobs()[0]
266 self.assertEqual(job.name, 'python27')
267 self.assertEqual(job.timeout, 40)
James E. Blair1774dd52017-02-03 10:52:32 -0800268 nodes = job.nodeset.getNodes()
269 self.assertEqual(len(nodes), 1)
James E. Blair16d96a02017-06-08 11:32:56 -0700270 self.assertEqual(nodes[0].label, 'new')
James E. Blaira7f51ca2017-02-07 16:01:26 -0800271 self.assertEqual([x.path for x in job.pre_run],
Tobias Henkel165450e2017-06-26 22:53:45 +0200272 ['base-pre',
273 'py27-pre'])
James E. Blaira7f51ca2017-02-07 16:01:26 -0800274 self.assertEqual([x.path for x in job.post_run],
Tobias Henkel165450e2017-06-26 22:53:45 +0200275 ['py27-post',
276 'base-post'])
James E. Blaira7f51ca2017-02-07 16:01:26 -0800277 self.assertEqual([x.path for x in job.run],
278 ['playbooks/python27',
279 'playbooks/base'])
James E. Blair83005782015-12-11 14:46:03 -0800280
James E. Blair1774dd52017-02-03 10:52:32 -0800281 # Test diablo
James E. Blair83005782015-12-11 14:46:03 -0800282 change.branch = 'stable/diablo'
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700283 item = queue.enqueueChange(change)
284 item.current_build_set.layout = layout
James E. Blair83005782015-12-11 14:46:03 -0800285
286 self.assertTrue(base.changeMatches(change))
287 self.assertTrue(python27.changeMatches(change))
288 self.assertTrue(python27diablo.changeMatches(change))
James E. Blaira7f51ca2017-02-07 16:01:26 -0800289 self.assertFalse(python27essex.changeMatches(change))
James E. Blair83005782015-12-11 14:46:03 -0800290
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200291 item.freezeJobGraph()
James E. Blair83005782015-12-11 14:46:03 -0800292 self.assertEqual(len(item.getJobs()), 1)
293 job = item.getJobs()[0]
294 self.assertEqual(job.name, 'python27')
295 self.assertEqual(job.timeout, 50)
James E. Blair1774dd52017-02-03 10:52:32 -0800296 nodes = job.nodeset.getNodes()
297 self.assertEqual(len(nodes), 1)
James E. Blair16d96a02017-06-08 11:32:56 -0700298 self.assertEqual(nodes[0].label, 'old')
James E. Blaira7f51ca2017-02-07 16:01:26 -0800299 self.assertEqual([x.path for x in job.pre_run],
Tobias Henkel165450e2017-06-26 22:53:45 +0200300 ['base-pre',
301 'py27-pre',
302 'py27-diablo-pre'])
James E. Blaira7f51ca2017-02-07 16:01:26 -0800303 self.assertEqual([x.path for x in job.post_run],
Tobias Henkel165450e2017-06-26 22:53:45 +0200304 ['py27-diablo-post',
305 'py27-post',
306 'base-post'])
James E. Blaira7f51ca2017-02-07 16:01:26 -0800307 self.assertEqual([x.path for x in job.run],
Tobias Henkel165450e2017-06-26 22:53:45 +0200308 ['py27-diablo']),
James E. Blaira7f51ca2017-02-07 16:01:26 -0800309
310 # Test essex
311 change.branch = 'stable/essex'
312 item = queue.enqueueChange(change)
313 item.current_build_set.layout = layout
314
315 self.assertTrue(base.changeMatches(change))
316 self.assertTrue(python27.changeMatches(change))
317 self.assertFalse(python27diablo.changeMatches(change))
318 self.assertTrue(python27essex.changeMatches(change))
319
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200320 item.freezeJobGraph()
James E. Blaira7f51ca2017-02-07 16:01:26 -0800321 self.assertEqual(len(item.getJobs()), 1)
322 job = item.getJobs()[0]
323 self.assertEqual(job.name, 'python27')
324 self.assertEqual([x.path for x in job.pre_run],
Tobias Henkel165450e2017-06-26 22:53:45 +0200325 ['base-pre',
326 'py27-pre',
327 'py27-essex-pre'])
James E. Blaira7f51ca2017-02-07 16:01:26 -0800328 self.assertEqual([x.path for x in job.post_run],
Tobias Henkel165450e2017-06-26 22:53:45 +0200329 ['py27-essex-post',
330 'py27-post',
331 'base-post'])
James E. Blaira7f51ca2017-02-07 16:01:26 -0800332 self.assertEqual([x.path for x in job.run],
333 ['playbooks/python27',
334 'playbooks/base'])
James E. Blairce8a2132016-05-19 15:21:52 -0700335
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000336 def test_job_auth_inheritance(self):
James E. Blair6459db12017-06-29 14:57:20 -0700337 tenant = self.tenant
338 layout = self.layout
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000339
James E. Blair01f83b72017-03-15 13:03:40 -0700340 conf = yaml.safe_load('''
341- secret:
342 name: pypi-credentials
343 data:
344 username: test-username
James E. Blair717e8e92017-03-17 11:03:27 -0700345 password: !encrypted/pkcs1-oaep |
James E. Blair01f83b72017-03-15 13:03:40 -0700346 BFhtdnm8uXx7kn79RFL/zJywmzLkT1GY78P3bOtp4WghUFWobkifSu7ZpaV4NeO0s71YUsi1wGZZ
347 L0LveZjUN0t6OU1VZKSG8R5Ly7urjaSo1pPVIq5Rtt/H7W14Lecd+cUeKb4joeusC9drN3AA8a4o
348 ykcVpt1wVqUnTbMGC9ARMCQP6eopcs1l7tzMseprW4RDNhIuz3CRgd0QBMPl6VDoFgBPB8vxtJw+
349 3m0rqBYZCLZgCXekqlny8s2s92nJMuUABbJOEcDRarzibDsSXsfJt1y+5n7yOURsC7lovMg4GF/v
350 Cl/0YMKjBO5bpv9EM5fToeKYyPGSKQoHOnCYceb3cAVcv5UawcCic8XjhEhp4K7WPdYf2HVAC/qt
351 xhbpjTxG4U5Q/SoppOJ60WqEkQvbXs6n5Dvy7xmph6GWmU/bAv3eUK3pdD3xa2Ue1lHWz3U+rsYr
352 aI+AKYsMYx3RBlfAmCeC1ve2BXPrqnOo7G8tnUvfdYPbK4Aakk0ds/AVqFHEZN+S6hRBmBjLaRFW
353 Z3QSO1NjbBxWnaHKZYT7nkrJm8AMCgZU0ZArFLpaufKCeiK5ECSsDxic4FIsY1OkWT42qEUfL0Wd
354 +150AKGNZpPJnnP3QYY4W/MWcKH/zdO400+zWN52WevbSqZy90tqKDJrBkMl1ydqbuw1E4ZHvIs=
355''')[0]['secret']
356
357 conf['_source_context'] = self.context
358 conf['_start_mark'] = self.start_mark
359
360 secret = configloader.SecretParser.fromYaml(layout, conf)
361 layout.addSecret(secret)
362
James E. Blair6459db12017-06-29 14:57:20 -0700363 base = configloader.JobParser.fromYaml(self.tenant, self.layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800364 '_source_context': self.context,
365 '_start_mark': self.start_mark,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000366 'name': 'base',
367 'timeout': 30,
368 })
369 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800370 pypi_upload_without_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-without-inherit',
375 'parent': 'base',
376 'timeout': 40,
377 'auth': {
378 'secrets': [
379 'pypi-credentials',
380 ]
381 }
382 })
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000383 layout.addJob(pypi_upload_without_inherit)
James E. Blair5ac93842017-01-20 06:47:34 -0800384 pypi_upload_with_inherit = configloader.JobParser.fromYaml(
385 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800386 '_source_context': self.context,
387 '_start_mark': self.start_mark,
James E. Blair5ac93842017-01-20 06:47:34 -0800388 'name': 'pypi-upload-with-inherit',
389 'parent': 'base',
390 'timeout': 40,
391 'auth': {
392 'inherit': True,
393 'secrets': [
394 'pypi-credentials',
395 ]
396 }
397 })
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000398 layout.addJob(pypi_upload_with_inherit)
399 pypi_upload_with_inherit_false = configloader.JobParser.fromYaml(
James E. Blair5ac93842017-01-20 06:47:34 -0800400 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800401 '_source_context': self.context,
402 '_start_mark': self.start_mark,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000403 'name': 'pypi-upload-with-inherit-false',
404 'parent': 'base',
405 'timeout': 40,
406 'auth': {
407 'inherit': False,
Ricardo Carrillo Cruz12c892b2016-11-18 15:35:49 +0000408 'secrets': [
409 'pypi-credentials',
410 ]
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000411 }
412 })
413 layout.addJob(pypi_upload_with_inherit_false)
James E. Blair5ac93842017-01-20 06:47:34 -0800414 in_repo_job_without_inherit = configloader.JobParser.fromYaml(
415 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800416 '_source_context': self.context,
417 '_start_mark': self.start_mark,
James E. Blair5ac93842017-01-20 06:47:34 -0800418 'name': 'in-repo-job-without-inherit',
419 'parent': 'pypi-upload-without-inherit',
420 })
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000421 layout.addJob(in_repo_job_without_inherit)
James E. Blair5ac93842017-01-20 06:47:34 -0800422 in_repo_job_with_inherit = configloader.JobParser.fromYaml(
423 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800424 '_source_context': self.context,
425 '_start_mark': self.start_mark,
James E. Blair5ac93842017-01-20 06:47:34 -0800426 'name': 'in-repo-job-with-inherit',
427 'parent': 'pypi-upload-with-inherit',
428 })
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000429 layout.addJob(in_repo_job_with_inherit)
430 in_repo_job_with_inherit_false = configloader.JobParser.fromYaml(
James E. Blair5ac93842017-01-20 06:47:34 -0800431 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800432 '_source_context': self.context,
433 '_start_mark': self.start_mark,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000434 'name': 'in-repo-job-with-inherit-false',
435 'parent': 'pypi-upload-with-inherit-false',
436 })
437 layout.addJob(in_repo_job_with_inherit_false)
438
Monty Taylor38b553a2017-06-05 13:06:10 -0500439 self.assertIsNone(in_repo_job_without_inherit.auth)
James E. Blair8525e2b2017-03-15 14:05:47 -0700440 self.assertEqual(1, len(in_repo_job_with_inherit.auth.secrets))
441 self.assertEqual(in_repo_job_with_inherit.auth.secrets[0].name,
442 'pypi-credentials')
Monty Taylor38b553a2017-06-05 13:06:10 -0500443 self.assertIsNone(in_repo_job_with_inherit_false.auth)
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000444
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700445 def test_job_inheritance_job_tree(self):
James E. Blair5ac93842017-01-20 06:47:34 -0800446 tenant = model.Tenant('tenant')
James E. Blair6459db12017-06-29 14:57:20 -0700447 layout = model.Layout(tenant)
James E. Blair08d9b782017-06-29 14:22:48 -0700448 tpc = model.TenantProjectConfig(self.project)
449 tenant.addUntrustedProject(tpc)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700450
451 pipeline = model.Pipeline('gate', layout)
452 layout.addPipeline(pipeline)
453 queue = model.ChangeQueue(pipeline)
454
James E. Blair5ac93842017-01-20 06:47:34 -0800455 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800456 '_source_context': self.context,
457 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700458 'name': 'base',
459 'timeout': 30,
460 })
461 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800462 python27 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800463 '_source_context': self.context,
464 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700465 'name': 'python27',
466 'parent': 'base',
467 'timeout': 40,
468 })
469 layout.addJob(python27)
James E. Blair5ac93842017-01-20 06:47:34 -0800470 python27diablo = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800471 '_source_context': self.context,
472 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700473 'name': 'python27',
474 'branches': [
475 'stable/diablo'
476 ],
477 'timeout': 50,
478 })
479 layout.addJob(python27diablo)
480
James E. Blairff555742017-02-19 11:34:27 -0800481 project_config = configloader.ProjectParser.fromYaml(tenant, layout, [{
James E. Blairec7ff302017-03-04 07:31:32 -0800482 '_source_context': self.context,
483 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700484 'name': 'project',
485 'gate': {
486 'jobs': [
487 {'python27': {'timeout': 70}}
488 ]
489 }
James E. Blairff555742017-02-19 11:34:27 -0800490 }])
James E. Blairf59f3cf2017-02-19 14:50:26 -0800491 layout.addProjectConfig(project_config)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700492
James E. Blairec7ff302017-03-04 07:31:32 -0800493 change = model.Change(self.project)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700494 change.branch = 'master'
495 item = queue.enqueueChange(change)
496 item.current_build_set.layout = layout
497
498 self.assertTrue(base.changeMatches(change))
499 self.assertTrue(python27.changeMatches(change))
500 self.assertFalse(python27diablo.changeMatches(change))
501
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200502 item.freezeJobGraph()
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700503 self.assertEqual(len(item.getJobs()), 1)
504 job = item.getJobs()[0]
505 self.assertEqual(job.name, 'python27')
506 self.assertEqual(job.timeout, 70)
507
508 change.branch = 'stable/diablo'
509 item = queue.enqueueChange(change)
510 item.current_build_set.layout = layout
511
512 self.assertTrue(base.changeMatches(change))
513 self.assertTrue(python27.changeMatches(change))
514 self.assertTrue(python27diablo.changeMatches(change))
515
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200516 item.freezeJobGraph()
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700517 self.assertEqual(len(item.getJobs()), 1)
518 job = item.getJobs()[0]
519 self.assertEqual(job.name, 'python27')
520 self.assertEqual(job.timeout, 70)
521
Clint Byrum85493602016-11-18 11:59:47 -0800522 def test_inheritance_keeps_matchers(self):
James E. Blair5ac93842017-01-20 06:47:34 -0800523 tenant = model.Tenant('tenant')
James E. Blair6459db12017-06-29 14:57:20 -0700524 layout = model.Layout(tenant)
Clint Byrum85493602016-11-18 11:59:47 -0800525
526 pipeline = model.Pipeline('gate', layout)
527 layout.addPipeline(pipeline)
528 queue = model.ChangeQueue(pipeline)
James E. Blair0a899752017-03-29 13:22:16 -0700529 project = model.Project('project', self.source)
James E. Blair08d9b782017-06-29 14:22:48 -0700530 tpc = model.TenantProjectConfig(project)
531 tenant.addUntrustedProject(tpc)
Clint Byrum85493602016-11-18 11:59:47 -0800532
James E. Blair5ac93842017-01-20 06:47:34 -0800533 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800534 '_source_context': self.context,
535 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800536 'name': 'base',
537 'timeout': 30,
538 })
539 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800540 python27 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800541 '_source_context': self.context,
542 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800543 'name': 'python27',
544 'parent': 'base',
545 'timeout': 40,
546 'irrelevant-files': ['^ignored-file$'],
547 })
548 layout.addJob(python27)
549
James E. Blairff555742017-02-19 11:34:27 -0800550 project_config = configloader.ProjectParser.fromYaml(tenant, layout, [{
James E. Blairec7ff302017-03-04 07:31:32 -0800551 '_source_context': self.context,
552 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800553 'name': 'project',
554 'gate': {
555 'jobs': [
556 'python27',
557 ]
558 }
James E. Blairff555742017-02-19 11:34:27 -0800559 }])
James E. Blairf59f3cf2017-02-19 14:50:26 -0800560 layout.addProjectConfig(project_config)
Clint Byrum85493602016-11-18 11:59:47 -0800561
562 change = model.Change(project)
563 change.branch = 'master'
564 change.files = ['/COMMIT_MSG', 'ignored-file']
565 item = queue.enqueueChange(change)
566 item.current_build_set.layout = layout
567
568 self.assertTrue(base.changeMatches(change))
569 self.assertFalse(python27.changeMatches(change))
570
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200571 item.freezeJobGraph()
Clint Byrum85493602016-11-18 11:59:47 -0800572 self.assertEqual([], item.getJobs())
573
James E. Blair4317e9f2016-07-15 10:05:47 -0700574 def test_job_source_project(self):
James E. Blair6459db12017-06-29 14:57:20 -0700575 tenant = self.tenant
576 layout = self.layout
James E. Blair0a899752017-03-29 13:22:16 -0700577 base_project = model.Project('base_project', self.source)
James E. Blair6f140c72017-03-03 10:32:07 -0800578 base_context = model.SourceContext(base_project, 'master',
579 'test', True)
James E. Blair6459db12017-06-29 14:57:20 -0700580 tpc = model.TenantProjectConfig(base_project)
581 tenant.addUntrustedProject(tpc)
James E. Blaircdab2032017-02-01 09:09:29 -0800582
James E. Blair5ac93842017-01-20 06:47:34 -0800583 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800584 '_source_context': base_context,
James E. Blairec7ff302017-03-04 07:31:32 -0800585 '_start_mark': self.start_mark,
James E. Blair4317e9f2016-07-15 10:05:47 -0700586 'name': 'base',
587 })
588 layout.addJob(base)
589
James E. Blair0a899752017-03-29 13:22:16 -0700590 other_project = model.Project('other_project', self.source)
James E. Blair6f140c72017-03-03 10:32:07 -0800591 other_context = model.SourceContext(other_project, 'master',
592 'test', True)
James E. Blair6459db12017-06-29 14:57:20 -0700593 tpc = model.TenantProjectConfig(other_project)
594 tenant.addUntrustedProject(tpc)
James E. Blair5ac93842017-01-20 06:47:34 -0800595 base2 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800596 '_source_context': other_context,
James E. Blairec7ff302017-03-04 07:31:32 -0800597 '_start_mark': self.start_mark,
James E. Blair4317e9f2016-07-15 10:05:47 -0700598 'name': 'base',
599 })
600 with testtools.ExpectedException(
601 Exception,
602 "Job base in other_project is not permitted "
603 "to shadow job base in base_project"):
604 layout.addJob(base2)
605
James E. Blairb3f5db12017-03-17 12:57:39 -0700606 def test_job_allowed_projects(self):
607 job = configloader.JobParser.fromYaml(self.tenant, self.layout, {
608 '_source_context': self.context,
609 '_start_mark': self.start_mark,
610 'name': 'job',
611 'allowed-projects': ['project'],
612 })
613 self.layout.addJob(job)
614
James E. Blair0a899752017-03-29 13:22:16 -0700615 project2 = model.Project('project2', self.source)
James E. Blair08d9b782017-06-29 14:22:48 -0700616 tpc2 = model.TenantProjectConfig(project2)
617 self.tenant.addUntrustedProject(tpc2)
James E. Blairb3f5db12017-03-17 12:57:39 -0700618 context2 = model.SourceContext(project2, 'master',
619 'test', True)
620
621 project2_config = configloader.ProjectParser.fromYaml(
622 self.tenant, self.layout, [{
623 '_source_context': context2,
624 '_start_mark': self.start_mark,
625 'name': 'project2',
626 'gate': {
627 'jobs': [
628 'job'
629 ]
630 }
631 }]
632 )
633 self.layout.addProjectConfig(project2_config)
634
635 change = model.Change(project2)
636 # Test master
637 change.branch = 'master'
638 item = self.queue.enqueueChange(change)
639 item.current_build_set.layout = self.layout
640 with testtools.ExpectedException(
641 Exception,
642 "Project project2 is not allowed to run job job"):
643 item.freezeJobGraph()
644
James E. Blaird2348362017-03-17 13:59:35 -0700645 def test_job_pipeline_allow_secrets(self):
646 self.pipeline.allow_secrets = False
647 job = configloader.JobParser.fromYaml(self.tenant, self.layout, {
648 '_source_context': self.context,
649 '_start_mark': self.start_mark,
650 'name': 'job',
651 })
652 auth = model.AuthContext()
653 auth.secrets.append('foo')
654 job.auth = auth
655
656 self.layout.addJob(job)
657
658 project_config = configloader.ProjectParser.fromYaml(
659 self.tenant, self.layout, [{
660 '_source_context': self.context,
661 '_start_mark': self.start_mark,
662 'name': 'project',
663 'gate': {
664 'jobs': [
665 'job'
666 ]
667 }
668 }]
669 )
670 self.layout.addProjectConfig(project_config)
671
672 change = model.Change(self.project)
673 # Test master
674 change.branch = 'master'
675 item = self.queue.enqueueChange(change)
676 item.current_build_set.layout = self.layout
677 with testtools.ExpectedException(
678 Exception,
679 "Pipeline gate does not allow jobs with secrets"):
680 item.freezeJobGraph()
681
James E. Blairce8a2132016-05-19 15:21:52 -0700682
683class TestJobTimeData(BaseTestCase):
684 def setUp(self):
685 super(TestJobTimeData, self).setUp()
686 self.tmp_root = self.useFixture(fixtures.TempDir(
687 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
688 ).path
689
690 def test_empty_timedata(self):
691 path = os.path.join(self.tmp_root, 'job-name')
692 self.assertFalse(os.path.exists(path))
693 self.assertFalse(os.path.exists(path + '.tmp'))
694 td = model.JobTimeData(path)
695 self.assertEqual(td.success_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
696 self.assertEqual(td.failure_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
697 self.assertEqual(td.results, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
698
699 def test_save_reload(self):
700 path = os.path.join(self.tmp_root, 'job-name')
701 self.assertFalse(os.path.exists(path))
702 self.assertFalse(os.path.exists(path + '.tmp'))
703 td = model.JobTimeData(path)
704 self.assertEqual(td.success_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
705 self.assertEqual(td.failure_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
706 self.assertEqual(td.results, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
707 success_times = []
708 failure_times = []
709 results = []
710 for x in range(10):
711 success_times.append(int(random.random() * 1000))
712 failure_times.append(int(random.random() * 1000))
713 results.append(0)
714 results.append(1)
715 random.shuffle(results)
716 s = f = 0
717 for result in results:
718 if result:
719 td.add(failure_times[f], 'FAILURE')
720 f += 1
721 else:
722 td.add(success_times[s], 'SUCCESS')
723 s += 1
724 self.assertEqual(td.success_times, success_times)
725 self.assertEqual(td.failure_times, failure_times)
726 self.assertEqual(td.results, results[10:])
727 td.save()
728 self.assertTrue(os.path.exists(path))
729 self.assertFalse(os.path.exists(path + '.tmp'))
730 td = model.JobTimeData(path)
731 td.load()
732 self.assertEqual(td.success_times, success_times)
733 self.assertEqual(td.failure_times, failure_times)
734 self.assertEqual(td.results, results[10:])
735
736
737class TestTimeDataBase(BaseTestCase):
738 def setUp(self):
739 super(TestTimeDataBase, self).setUp()
740 self.tmp_root = self.useFixture(fixtures.TempDir(
741 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
742 ).path
743 self.db = model.TimeDataBase(self.tmp_root)
744
745 def test_timedatabase(self):
746 self.assertEqual(self.db.getEstimatedTime('job-name'), 0)
747 self.db.update('job-name', 50, 'SUCCESS')
748 self.assertEqual(self.db.getEstimatedTime('job-name'), 50)
749 self.db.update('job-name', 100, 'SUCCESS')
750 self.assertEqual(self.db.getEstimatedTime('job-name'), 75)
751 for x in range(10):
752 self.db.update('job-name', 100, 'SUCCESS')
753 self.assertEqual(self.db.getEstimatedTime('job-name'), 100)
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200754
755
756class TestGraph(BaseTestCase):
757 def test_job_graph_disallows_multiple_jobs_with_same_name(self):
758 graph = model.JobGraph()
759 job1 = model.Job('job')
760 job2 = model.Job('job')
761 graph.addJob(job1)
762 with testtools.ExpectedException(Exception,
763 "Job job already added"):
764 graph.addJob(job2)
765
766 def test_job_graph_disallows_circular_dependencies(self):
767 graph = model.JobGraph()
768 jobs = [model.Job('job%d' % i) for i in range(0, 10)]
769 prevjob = None
770 for j in jobs[:3]:
771 if prevjob:
772 j.dependencies = frozenset([prevjob.name])
773 graph.addJob(j)
774 prevjob = j
775 # 0 triggers 1 triggers 2 triggers 3...
776
777 # Cannot depend on itself
778 with testtools.ExpectedException(
779 Exception,
780 "Dependency cycle detected in job jobX"):
781 j = model.Job('jobX')
782 j.dependencies = frozenset([j.name])
783 graph.addJob(j)
784
785 # Disallow circular dependencies
786 with testtools.ExpectedException(
787 Exception,
788 "Dependency cycle detected in job job3"):
789 jobs[4].dependencies = frozenset([jobs[3].name])
790 graph.addJob(jobs[4])
791 jobs[3].dependencies = frozenset([jobs[4].name])
792 graph.addJob(jobs[3])
793
794 jobs[5].dependencies = frozenset([jobs[4].name])
795 graph.addJob(jobs[5])
796
797 with testtools.ExpectedException(
798 Exception,
799 "Dependency cycle detected in job job3"):
800 jobs[3].dependencies = frozenset([jobs[5].name])
801 graph.addJob(jobs[3])
802
803 jobs[3].dependencies = frozenset([jobs[2].name])
804 graph.addJob(jobs[3])
805 jobs[6].dependencies = frozenset([jobs[2].name])
806 graph.addJob(jobs[6])
James E. Blairc2a54fd2017-03-29 15:19:26 -0700807
808
809class TestTenant(BaseTestCase):
810 def test_add_project(self):
811 tenant = model.Tenant('tenant')
812 connection1 = Dummy(connection_name='dummy_connection1')
813 source1 = Dummy(canonical_hostname='git1.example.com',
814 name='dummy', # TODOv3(jeblair): remove
815 connection=connection1)
816
817 source1_project1 = model.Project('project1', source1)
James E. Blair08d9b782017-06-29 14:22:48 -0700818 source1_project1_tpc = model.TenantProjectConfig(source1_project1)
819 tenant.addConfigProject(source1_project1_tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700820 d = {'project1':
821 {'git1.example.com': source1_project1}}
822 self.assertEqual(d, tenant.projects)
823 self.assertEqual((True, source1_project1),
824 tenant.getProject('project1'))
825 self.assertEqual((True, source1_project1),
826 tenant.getProject('git1.example.com/project1'))
827
828 source1_project2 = model.Project('project2', source1)
James E. Blair08d9b782017-06-29 14:22:48 -0700829 tpc = model.TenantProjectConfig(source1_project2)
830 tenant.addUntrustedProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700831 d = {'project1':
832 {'git1.example.com': source1_project1},
833 'project2':
834 {'git1.example.com': source1_project2}}
835 self.assertEqual(d, tenant.projects)
836 self.assertEqual((False, source1_project2),
837 tenant.getProject('project2'))
838 self.assertEqual((False, source1_project2),
839 tenant.getProject('git1.example.com/project2'))
840
841 connection2 = Dummy(connection_name='dummy_connection2')
842 source2 = Dummy(canonical_hostname='git2.example.com',
843 name='dummy', # TODOv3(jeblair): remove
844 connection=connection2)
845
846 source2_project1 = model.Project('project1', source2)
James E. Blair08d9b782017-06-29 14:22:48 -0700847 tpc = model.TenantProjectConfig(source2_project1)
848 tenant.addUntrustedProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700849 d = {'project1':
850 {'git1.example.com': source1_project1,
851 'git2.example.com': source2_project1},
852 'project2':
853 {'git1.example.com': source1_project2}}
854 self.assertEqual(d, tenant.projects)
855 with testtools.ExpectedException(
856 Exception,
857 "Project name 'project1' is ambiguous"):
858 tenant.getProject('project1')
859 self.assertEqual((False, source1_project2),
860 tenant.getProject('project2'))
861 self.assertEqual((True, source1_project1),
862 tenant.getProject('git1.example.com/project1'))
863 self.assertEqual((False, source2_project1),
864 tenant.getProject('git2.example.com/project1'))
865
866 source2_project2 = model.Project('project2', source2)
James E. Blair08d9b782017-06-29 14:22:48 -0700867 tpc = model.TenantProjectConfig(source2_project2)
868 tenant.addConfigProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700869 d = {'project1':
870 {'git1.example.com': source1_project1,
871 'git2.example.com': source2_project1},
872 'project2':
873 {'git1.example.com': source1_project2,
874 'git2.example.com': source2_project2}}
875 self.assertEqual(d, tenant.projects)
876 with testtools.ExpectedException(
877 Exception,
878 "Project name 'project1' is ambiguous"):
879 tenant.getProject('project1')
880 with testtools.ExpectedException(
881 Exception,
882 "Project name 'project2' is ambiguous"):
883 tenant.getProject('project2')
884 self.assertEqual((True, source1_project1),
885 tenant.getProject('git1.example.com/project1'))
886 self.assertEqual((False, source2_project1),
887 tenant.getProject('git2.example.com/project1'))
888 self.assertEqual((False, source1_project2),
889 tenant.getProject('git1.example.com/project2'))
890 self.assertEqual((True, source2_project2),
891 tenant.getProject('git2.example.com/project2'))
892
893 source1_project2b = model.Project('subpath/project2', source1)
James E. Blair08d9b782017-06-29 14:22:48 -0700894 tpc = model.TenantProjectConfig(source1_project2b)
895 tenant.addConfigProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700896 d = {'project1':
897 {'git1.example.com': source1_project1,
898 'git2.example.com': source2_project1},
899 'project2':
900 {'git1.example.com': source1_project2,
901 'git2.example.com': source2_project2},
902 'subpath/project2':
903 {'git1.example.com': source1_project2b}}
904 self.assertEqual(d, tenant.projects)
905 self.assertEqual((False, source1_project2),
906 tenant.getProject('git1.example.com/project2'))
907 self.assertEqual((True, source2_project2),
908 tenant.getProject('git2.example.com/project2'))
909 self.assertEqual((True, source1_project2b),
910 tenant.getProject('subpath/project2'))
911 self.assertEqual(
912 (True, source1_project2b),
913 tenant.getProject('git1.example.com/subpath/project2'))
914
915 source2_project2b = model.Project('subpath/project2', source2)
James E. Blair08d9b782017-06-29 14:22:48 -0700916 tpc = model.TenantProjectConfig(source2_project2b)
917 tenant.addConfigProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700918 d = {'project1':
919 {'git1.example.com': source1_project1,
920 'git2.example.com': source2_project1},
921 'project2':
922 {'git1.example.com': source1_project2,
923 'git2.example.com': source2_project2},
924 'subpath/project2':
925 {'git1.example.com': source1_project2b,
926 'git2.example.com': source2_project2b}}
927 self.assertEqual(d, tenant.projects)
928 self.assertEqual((False, source1_project2),
929 tenant.getProject('git1.example.com/project2'))
930 self.assertEqual((True, source2_project2),
931 tenant.getProject('git2.example.com/project2'))
932 with testtools.ExpectedException(
933 Exception,
934 "Project name 'subpath/project2' is ambiguous"):
935 tenant.getProject('subpath/project2')
936 self.assertEqual(
937 (True, source1_project2b),
938 tenant.getProject('git1.example.com/subpath/project2'))
939 self.assertEqual(
940 (True, source2_project2b),
941 tenant.getProject('git2.example.com/subpath/project2'))
942
943 with testtools.ExpectedException(
944 Exception,
945 "Project project1 is already in project index"):
James E. Blair08d9b782017-06-29 14:22:48 -0700946 tenant._addProject(source1_project1_tpc)