blob: 7fe101e90343a7394b75091ec2c25ada93351f4a [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. Blairec7ff302017-03-04 07:31:32 -080057 self.start_mark = yaml.Mark('name', 0, 0, 0, '', 0)
James E. Blaira7f51ca2017-02-07 16:01:26 -080058
Maru Newby3fe5f852015-01-13 04:22:14 +000059 @property
60 def job(self):
James E. Blair5ac93842017-01-20 06:47:34 -080061 tenant = model.Tenant('tenant')
James E. Blair6459db12017-06-29 14:57:20 -070062 layout = model.Layout(tenant)
James E. Blair5ac93842017-01-20 06:47:34 -080063 job = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -080064 '_source_context': self.context,
65 '_start_mark': self.start_mark,
James E. Blair83005782015-12-11 14:46:03 -080066 'name': 'job',
67 'irrelevant-files': [
68 '^docs/.*$'
69 ]})
Maru Newby3fe5f852015-01-13 04:22:14 +000070 return job
71
72 def test_change_matches_returns_false_for_matched_skip_if(self):
73 change = model.Change('project')
Alexander Evseevdbe6fab2015-11-19 12:46:34 +030074 change.files = ['/COMMIT_MSG', 'docs/foo']
Maru Newby3fe5f852015-01-13 04:22:14 +000075 self.assertFalse(self.job.changeMatches(change))
76
Jan Hruban570d01c2016-03-10 21:51:32 +010077 def test_change_matches_returns_false_for_single_matched_skip_if(self):
78 change = model.Change('project')
79 change.files = ['docs/foo']
80 self.assertFalse(self.job.changeMatches(change))
81
Maru Newby3fe5f852015-01-13 04:22:14 +000082 def test_change_matches_returns_true_for_unmatched_skip_if(self):
83 change = model.Change('project')
Alexander Evseevdbe6fab2015-11-19 12:46:34 +030084 change.files = ['/COMMIT_MSG', 'foo']
Maru Newby3fe5f852015-01-13 04:22:14 +000085 self.assertTrue(self.job.changeMatches(change))
86
Jan Hruban570d01c2016-03-10 21:51:32 +010087 def test_change_matches_returns_true_for_single_unmatched_skip_if(self):
88 change = model.Change('project')
89 change.files = ['foo']
90 self.assertTrue(self.job.changeMatches(change))
91
Maru Newby79427a42015-02-17 17:54:45 +000092 def test_job_sets_defaults_for_boolean_attributes(self):
James E. Blair83005782015-12-11 14:46:03 -080093 self.assertIsNotNone(self.job.voting)
94
95 def test_job_inheritance(self):
James E. Blaira7f51ca2017-02-07 16:01:26 -080096 # This is standard job inheritance.
97
98 base_pre = model.PlaybookContext(self.context, 'base-pre')
99 base_run = model.PlaybookContext(self.context, 'base-run')
100 base_post = model.PlaybookContext(self.context, 'base-post')
101
102 base = model.Job('base')
103 base.timeout = 30
104 base.pre_run = [base_pre]
105 base.run = [base_run]
106 base.post_run = [base_post]
James E. Blair8525e2b2017-03-15 14:05:47 -0700107 base.auth = model.AuthContext()
James E. Blaira7f51ca2017-02-07 16:01:26 -0800108
109 py27 = model.Job('py27')
Monty Taylor38b553a2017-06-05 13:06:10 -0500110 self.assertIsNone(py27.timeout)
James E. Blaira7f51ca2017-02-07 16:01:26 -0800111 py27.inheritFrom(base)
112 self.assertEqual(30, py27.timeout)
113 self.assertEqual(['base-pre'],
114 [x.path for x in py27.pre_run])
115 self.assertEqual(['base-run'],
116 [x.path for x in py27.run])
117 self.assertEqual(['base-post'],
118 [x.path for x in py27.post_run])
Monty Taylor38b553a2017-06-05 13:06:10 -0500119 self.assertIsNone(py27.auth)
James E. Blaira7f51ca2017-02-07 16:01:26 -0800120
121 def test_job_variants(self):
122 # This simulates freezing a job.
123
124 py27_pre = model.PlaybookContext(self.context, 'py27-pre')
125 py27_run = model.PlaybookContext(self.context, 'py27-run')
126 py27_post = model.PlaybookContext(self.context, 'py27-post')
127
128 py27 = model.Job('py27')
129 py27.timeout = 30
130 py27.pre_run = [py27_pre]
131 py27.run = [py27_run]
132 py27.post_run = [py27_post]
James E. Blair8525e2b2017-03-15 14:05:47 -0700133 auth = model.AuthContext()
134 auth.secrets.append('foo')
James E. Blaira7f51ca2017-02-07 16:01:26 -0800135 py27.auth = auth
136
137 job = py27.copy()
138 self.assertEqual(30, job.timeout)
139
140 # Apply the diablo variant
141 diablo = model.Job('py27')
142 diablo.timeout = 40
143 job.applyVariant(diablo)
144
145 self.assertEqual(40, job.timeout)
146 self.assertEqual(['py27-pre'],
147 [x.path for x in job.pre_run])
148 self.assertEqual(['py27-run'],
149 [x.path for x in job.run])
150 self.assertEqual(['py27-post'],
151 [x.path for x in job.post_run])
152 self.assertEqual(auth, job.auth)
153
154 # Set the job to final for the following checks
155 job.final = True
156 self.assertTrue(job.voting)
157
158 good_final = model.Job('py27')
159 good_final.voting = False
160 job.applyVariant(good_final)
161 self.assertFalse(job.voting)
162
163 bad_final = model.Job('py27')
164 bad_final.timeout = 600
165 with testtools.ExpectedException(
166 Exception,
167 "Unable to modify final job"):
168 job.applyVariant(bad_final)
169
170 def test_job_inheritance_configloader(self):
171 # TODO(jeblair): move this to a configloader test
James E. Blair5ac93842017-01-20 06:47:34 -0800172 tenant = model.Tenant('tenant')
James E. Blair6459db12017-06-29 14:57:20 -0700173 layout = model.Layout(tenant)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700174
175 pipeline = model.Pipeline('gate', layout)
176 layout.addPipeline(pipeline)
177 queue = model.ChangeQueue(pipeline)
James E. Blair0a899752017-03-29 13:22:16 -0700178 project = model.Project('project', self.source)
James E. Blair08d9b782017-06-29 14:22:48 -0700179 tpc = model.TenantProjectConfig(project)
180 tenant.addUntrustedProject(tpc)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700181
James E. Blair5ac93842017-01-20 06:47:34 -0800182 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800183 '_source_context': self.context,
184 '_start_mark': self.start_mark,
James E. Blair83005782015-12-11 14:46:03 -0800185 'name': 'base',
186 'timeout': 30,
James E. Blaira7f51ca2017-02-07 16:01:26 -0800187 'pre-run': 'base-pre',
188 'post-run': 'base-post',
James E. Blair1774dd52017-02-03 10:52:32 -0800189 'nodes': [{
190 'name': 'controller',
James E. Blair16d96a02017-06-08 11:32:56 -0700191 'label': 'base',
James E. Blair1774dd52017-02-03 10:52:32 -0800192 }],
James E. Blair83005782015-12-11 14:46:03 -0800193 })
194 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800195 python27 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800196 '_source_context': self.context,
197 '_start_mark': self.start_mark,
James E. Blair83005782015-12-11 14:46:03 -0800198 'name': 'python27',
199 'parent': 'base',
James E. Blaira7f51ca2017-02-07 16:01:26 -0800200 'pre-run': 'py27-pre',
201 'post-run': 'py27-post',
James E. Blair1774dd52017-02-03 10:52:32 -0800202 'nodes': [{
203 'name': 'controller',
James E. Blair16d96a02017-06-08 11:32:56 -0700204 'label': 'new',
James E. Blair1774dd52017-02-03 10:52:32 -0800205 }],
James E. Blair83005782015-12-11 14:46:03 -0800206 'timeout': 40,
207 })
208 layout.addJob(python27)
James E. Blair5ac93842017-01-20 06:47:34 -0800209 python27diablo = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800210 '_source_context': self.context,
211 '_start_mark': self.start_mark,
James E. Blair83005782015-12-11 14:46:03 -0800212 'name': 'python27',
213 'branches': [
214 'stable/diablo'
215 ],
James E. Blaira7f51ca2017-02-07 16:01:26 -0800216 'pre-run': 'py27-diablo-pre',
217 'run': 'py27-diablo',
218 'post-run': 'py27-diablo-post',
James E. Blair1774dd52017-02-03 10:52:32 -0800219 'nodes': [{
220 'name': 'controller',
James E. Blair16d96a02017-06-08 11:32:56 -0700221 'label': 'old',
James E. Blair1774dd52017-02-03 10:52:32 -0800222 }],
James E. Blair83005782015-12-11 14:46:03 -0800223 'timeout': 50,
224 })
225 layout.addJob(python27diablo)
226
James E. Blair5ac93842017-01-20 06:47:34 -0800227 python27essex = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800228 '_source_context': self.context,
229 '_start_mark': self.start_mark,
James E. Blaira7f51ca2017-02-07 16:01:26 -0800230 'name': 'python27',
231 'branches': [
232 'stable/essex'
233 ],
234 'pre-run': 'py27-essex-pre',
235 'post-run': 'py27-essex-post',
236 })
237 layout.addJob(python27essex)
238
James E. Blairff555742017-02-19 11:34:27 -0800239 project_config = configloader.ProjectParser.fromYaml(tenant, layout, [{
James E. Blairec7ff302017-03-04 07:31:32 -0800240 '_source_context': self.context,
241 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700242 'name': 'project',
243 'gate': {
244 'jobs': [
245 'python27'
246 ]
247 }
James E. Blairff555742017-02-19 11:34:27 -0800248 }])
James E. Blairf59f3cf2017-02-19 14:50:26 -0800249 layout.addProjectConfig(project_config)
James E. Blair83005782015-12-11 14:46:03 -0800250
James E. Blair83005782015-12-11 14:46:03 -0800251 change = model.Change(project)
James E. Blair1774dd52017-02-03 10:52:32 -0800252 # Test master
James E. Blair83005782015-12-11 14:46:03 -0800253 change.branch = 'master'
254 item = queue.enqueueChange(change)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700255 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.assertFalse(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, 40)
James E. Blair1774dd52017-02-03 10:52:32 -0800267 nodes = job.nodeset.getNodes()
268 self.assertEqual(len(nodes), 1)
James E. Blair16d96a02017-06-08 11:32:56 -0700269 self.assertEqual(nodes[0].label, 'new')
James E. Blaira7f51ca2017-02-07 16:01:26 -0800270 self.assertEqual([x.path for x in job.pre_run],
Tobias Henkel165450e2017-06-26 22:53:45 +0200271 ['base-pre',
272 'py27-pre'])
James E. Blaira7f51ca2017-02-07 16:01:26 -0800273 self.assertEqual([x.path for x in job.post_run],
Tobias Henkel165450e2017-06-26 22:53:45 +0200274 ['py27-post',
275 'base-post'])
James E. Blaira7f51ca2017-02-07 16:01:26 -0800276 self.assertEqual([x.path for x in job.run],
277 ['playbooks/python27',
278 'playbooks/base'])
James E. Blair83005782015-12-11 14:46:03 -0800279
James E. Blair1774dd52017-02-03 10:52:32 -0800280 # Test diablo
James E. Blair83005782015-12-11 14:46:03 -0800281 change.branch = 'stable/diablo'
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700282 item = queue.enqueueChange(change)
283 item.current_build_set.layout = layout
James E. Blair83005782015-12-11 14:46:03 -0800284
285 self.assertTrue(base.changeMatches(change))
286 self.assertTrue(python27.changeMatches(change))
287 self.assertTrue(python27diablo.changeMatches(change))
James E. Blaira7f51ca2017-02-07 16:01:26 -0800288 self.assertFalse(python27essex.changeMatches(change))
James E. Blair83005782015-12-11 14:46:03 -0800289
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200290 item.freezeJobGraph()
James E. Blair83005782015-12-11 14:46:03 -0800291 self.assertEqual(len(item.getJobs()), 1)
292 job = item.getJobs()[0]
293 self.assertEqual(job.name, 'python27')
294 self.assertEqual(job.timeout, 50)
James E. Blair1774dd52017-02-03 10:52:32 -0800295 nodes = job.nodeset.getNodes()
296 self.assertEqual(len(nodes), 1)
James E. Blair16d96a02017-06-08 11:32:56 -0700297 self.assertEqual(nodes[0].label, 'old')
James E. Blaira7f51ca2017-02-07 16:01:26 -0800298 self.assertEqual([x.path for x in job.pre_run],
Tobias Henkel165450e2017-06-26 22:53:45 +0200299 ['base-pre',
300 'py27-pre',
301 'py27-diablo-pre'])
James E. Blaira7f51ca2017-02-07 16:01:26 -0800302 self.assertEqual([x.path for x in job.post_run],
Tobias Henkel165450e2017-06-26 22:53:45 +0200303 ['py27-diablo-post',
304 'py27-post',
305 'base-post'])
James E. Blaira7f51ca2017-02-07 16:01:26 -0800306 self.assertEqual([x.path for x in job.run],
Tobias Henkel165450e2017-06-26 22:53:45 +0200307 ['py27-diablo']),
James E. Blaira7f51ca2017-02-07 16:01:26 -0800308
309 # Test essex
310 change.branch = 'stable/essex'
311 item = queue.enqueueChange(change)
312 item.current_build_set.layout = layout
313
314 self.assertTrue(base.changeMatches(change))
315 self.assertTrue(python27.changeMatches(change))
316 self.assertFalse(python27diablo.changeMatches(change))
317 self.assertTrue(python27essex.changeMatches(change))
318
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200319 item.freezeJobGraph()
James E. Blaira7f51ca2017-02-07 16:01:26 -0800320 self.assertEqual(len(item.getJobs()), 1)
321 job = item.getJobs()[0]
322 self.assertEqual(job.name, 'python27')
323 self.assertEqual([x.path for x in job.pre_run],
Tobias Henkel165450e2017-06-26 22:53:45 +0200324 ['base-pre',
325 'py27-pre',
326 'py27-essex-pre'])
James E. Blaira7f51ca2017-02-07 16:01:26 -0800327 self.assertEqual([x.path for x in job.post_run],
Tobias Henkel165450e2017-06-26 22:53:45 +0200328 ['py27-essex-post',
329 'py27-post',
330 'base-post'])
James E. Blaira7f51ca2017-02-07 16:01:26 -0800331 self.assertEqual([x.path for x in job.run],
332 ['playbooks/python27',
333 'playbooks/base'])
James E. Blairce8a2132016-05-19 15:21:52 -0700334
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000335 def test_job_auth_inheritance(self):
James E. Blair6459db12017-06-29 14:57:20 -0700336 tenant = self.tenant
337 layout = self.layout
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000338
James E. Blair01f83b72017-03-15 13:03:40 -0700339 conf = yaml.safe_load('''
340- secret:
341 name: pypi-credentials
342 data:
343 username: test-username
James E. Blair717e8e92017-03-17 11:03:27 -0700344 password: !encrypted/pkcs1-oaep |
James E. Blair01f83b72017-03-15 13:03:40 -0700345 BFhtdnm8uXx7kn79RFL/zJywmzLkT1GY78P3bOtp4WghUFWobkifSu7ZpaV4NeO0s71YUsi1wGZZ
346 L0LveZjUN0t6OU1VZKSG8R5Ly7urjaSo1pPVIq5Rtt/H7W14Lecd+cUeKb4joeusC9drN3AA8a4o
347 ykcVpt1wVqUnTbMGC9ARMCQP6eopcs1l7tzMseprW4RDNhIuz3CRgd0QBMPl6VDoFgBPB8vxtJw+
348 3m0rqBYZCLZgCXekqlny8s2s92nJMuUABbJOEcDRarzibDsSXsfJt1y+5n7yOURsC7lovMg4GF/v
349 Cl/0YMKjBO5bpv9EM5fToeKYyPGSKQoHOnCYceb3cAVcv5UawcCic8XjhEhp4K7WPdYf2HVAC/qt
350 xhbpjTxG4U5Q/SoppOJ60WqEkQvbXs6n5Dvy7xmph6GWmU/bAv3eUK3pdD3xa2Ue1lHWz3U+rsYr
351 aI+AKYsMYx3RBlfAmCeC1ve2BXPrqnOo7G8tnUvfdYPbK4Aakk0ds/AVqFHEZN+S6hRBmBjLaRFW
352 Z3QSO1NjbBxWnaHKZYT7nkrJm8AMCgZU0ZArFLpaufKCeiK5ECSsDxic4FIsY1OkWT42qEUfL0Wd
353 +150AKGNZpPJnnP3QYY4W/MWcKH/zdO400+zWN52WevbSqZy90tqKDJrBkMl1ydqbuw1E4ZHvIs=
354''')[0]['secret']
355
356 conf['_source_context'] = self.context
357 conf['_start_mark'] = self.start_mark
358
359 secret = configloader.SecretParser.fromYaml(layout, conf)
360 layout.addSecret(secret)
361
James E. Blair6459db12017-06-29 14:57:20 -0700362 base = configloader.JobParser.fromYaml(self.tenant, self.layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800363 '_source_context': self.context,
364 '_start_mark': self.start_mark,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000365 'name': 'base',
366 'timeout': 30,
367 })
368 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800369 pypi_upload_without_inherit = configloader.JobParser.fromYaml(
370 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800371 '_source_context': self.context,
372 '_start_mark': self.start_mark,
James E. Blair5ac93842017-01-20 06:47:34 -0800373 'name': 'pypi-upload-without-inherit',
374 'parent': 'base',
375 'timeout': 40,
376 'auth': {
377 'secrets': [
378 'pypi-credentials',
379 ]
380 }
381 })
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000382 layout.addJob(pypi_upload_without_inherit)
James E. Blair5ac93842017-01-20 06:47:34 -0800383 pypi_upload_with_inherit = configloader.JobParser.fromYaml(
384 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800385 '_source_context': self.context,
386 '_start_mark': self.start_mark,
James E. Blair5ac93842017-01-20 06:47:34 -0800387 'name': 'pypi-upload-with-inherit',
388 'parent': 'base',
389 'timeout': 40,
390 'auth': {
391 'inherit': True,
392 'secrets': [
393 'pypi-credentials',
394 ]
395 }
396 })
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000397 layout.addJob(pypi_upload_with_inherit)
398 pypi_upload_with_inherit_false = configloader.JobParser.fromYaml(
James E. Blair5ac93842017-01-20 06:47:34 -0800399 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800400 '_source_context': self.context,
401 '_start_mark': self.start_mark,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000402 'name': 'pypi-upload-with-inherit-false',
403 'parent': 'base',
404 'timeout': 40,
405 'auth': {
406 'inherit': False,
Ricardo Carrillo Cruz12c892b2016-11-18 15:35:49 +0000407 'secrets': [
408 'pypi-credentials',
409 ]
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000410 }
411 })
412 layout.addJob(pypi_upload_with_inherit_false)
James E. Blair5ac93842017-01-20 06:47:34 -0800413 in_repo_job_without_inherit = configloader.JobParser.fromYaml(
414 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800415 '_source_context': self.context,
416 '_start_mark': self.start_mark,
James E. Blair5ac93842017-01-20 06:47:34 -0800417 'name': 'in-repo-job-without-inherit',
418 'parent': 'pypi-upload-without-inherit',
419 })
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000420 layout.addJob(in_repo_job_without_inherit)
James E. Blair5ac93842017-01-20 06:47:34 -0800421 in_repo_job_with_inherit = configloader.JobParser.fromYaml(
422 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800423 '_source_context': self.context,
424 '_start_mark': self.start_mark,
James E. Blair5ac93842017-01-20 06:47:34 -0800425 'name': 'in-repo-job-with-inherit',
426 'parent': 'pypi-upload-with-inherit',
427 })
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000428 layout.addJob(in_repo_job_with_inherit)
429 in_repo_job_with_inherit_false = configloader.JobParser.fromYaml(
James E. Blair5ac93842017-01-20 06:47:34 -0800430 tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800431 '_source_context': self.context,
432 '_start_mark': self.start_mark,
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000433 'name': 'in-repo-job-with-inherit-false',
434 'parent': 'pypi-upload-with-inherit-false',
435 })
436 layout.addJob(in_repo_job_with_inherit_false)
437
Monty Taylor38b553a2017-06-05 13:06:10 -0500438 self.assertIsNone(in_repo_job_without_inherit.auth)
James E. Blair8525e2b2017-03-15 14:05:47 -0700439 self.assertEqual(1, len(in_repo_job_with_inherit.auth.secrets))
440 self.assertEqual(in_repo_job_with_inherit.auth.secrets[0].name,
441 'pypi-credentials')
Monty Taylor38b553a2017-06-05 13:06:10 -0500442 self.assertIsNone(in_repo_job_with_inherit_false.auth)
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000443
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700444 def test_job_inheritance_job_tree(self):
James E. Blair5ac93842017-01-20 06:47:34 -0800445 tenant = model.Tenant('tenant')
James E. Blair6459db12017-06-29 14:57:20 -0700446 layout = model.Layout(tenant)
James E. Blair08d9b782017-06-29 14:22:48 -0700447 tpc = model.TenantProjectConfig(self.project)
448 tenant.addUntrustedProject(tpc)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700449
450 pipeline = model.Pipeline('gate', layout)
451 layout.addPipeline(pipeline)
452 queue = model.ChangeQueue(pipeline)
453
James E. Blair5ac93842017-01-20 06:47:34 -0800454 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800455 '_source_context': self.context,
456 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700457 'name': 'base',
458 'timeout': 30,
459 })
460 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800461 python27 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800462 '_source_context': self.context,
463 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700464 'name': 'python27',
465 'parent': 'base',
466 'timeout': 40,
467 })
468 layout.addJob(python27)
James E. Blair5ac93842017-01-20 06:47:34 -0800469 python27diablo = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800470 '_source_context': self.context,
471 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700472 'name': 'python27',
473 'branches': [
474 'stable/diablo'
475 ],
476 'timeout': 50,
477 })
478 layout.addJob(python27diablo)
479
James E. Blairff555742017-02-19 11:34:27 -0800480 project_config = configloader.ProjectParser.fromYaml(tenant, layout, [{
James E. Blairec7ff302017-03-04 07:31:32 -0800481 '_source_context': self.context,
482 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700483 'name': 'project',
484 'gate': {
485 'jobs': [
486 {'python27': {'timeout': 70}}
487 ]
488 }
James E. Blairff555742017-02-19 11:34:27 -0800489 }])
James E. Blairf59f3cf2017-02-19 14:50:26 -0800490 layout.addProjectConfig(project_config)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700491
James E. Blairec7ff302017-03-04 07:31:32 -0800492 change = model.Change(self.project)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700493 change.branch = 'master'
494 item = queue.enqueueChange(change)
495 item.current_build_set.layout = layout
496
497 self.assertTrue(base.changeMatches(change))
498 self.assertTrue(python27.changeMatches(change))
499 self.assertFalse(python27diablo.changeMatches(change))
500
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200501 item.freezeJobGraph()
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700502 self.assertEqual(len(item.getJobs()), 1)
503 job = item.getJobs()[0]
504 self.assertEqual(job.name, 'python27')
505 self.assertEqual(job.timeout, 70)
506
507 change.branch = 'stable/diablo'
508 item = queue.enqueueChange(change)
509 item.current_build_set.layout = layout
510
511 self.assertTrue(base.changeMatches(change))
512 self.assertTrue(python27.changeMatches(change))
513 self.assertTrue(python27diablo.changeMatches(change))
514
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200515 item.freezeJobGraph()
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700516 self.assertEqual(len(item.getJobs()), 1)
517 job = item.getJobs()[0]
518 self.assertEqual(job.name, 'python27')
519 self.assertEqual(job.timeout, 70)
520
Clint Byrum85493602016-11-18 11:59:47 -0800521 def test_inheritance_keeps_matchers(self):
James E. Blair5ac93842017-01-20 06:47:34 -0800522 tenant = model.Tenant('tenant')
James E. Blair6459db12017-06-29 14:57:20 -0700523 layout = model.Layout(tenant)
Clint Byrum85493602016-11-18 11:59:47 -0800524
525 pipeline = model.Pipeline('gate', layout)
526 layout.addPipeline(pipeline)
527 queue = model.ChangeQueue(pipeline)
James E. Blair0a899752017-03-29 13:22:16 -0700528 project = model.Project('project', self.source)
James E. Blair08d9b782017-06-29 14:22:48 -0700529 tpc = model.TenantProjectConfig(project)
530 tenant.addUntrustedProject(tpc)
Clint Byrum85493602016-11-18 11:59:47 -0800531
James E. Blair5ac93842017-01-20 06:47:34 -0800532 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800533 '_source_context': self.context,
534 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800535 'name': 'base',
536 'timeout': 30,
537 })
538 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800539 python27 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800540 '_source_context': self.context,
541 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800542 'name': 'python27',
543 'parent': 'base',
544 'timeout': 40,
545 'irrelevant-files': ['^ignored-file$'],
546 })
547 layout.addJob(python27)
548
James E. Blairff555742017-02-19 11:34:27 -0800549 project_config = configloader.ProjectParser.fromYaml(tenant, layout, [{
James E. Blairec7ff302017-03-04 07:31:32 -0800550 '_source_context': self.context,
551 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800552 'name': 'project',
553 'gate': {
554 'jobs': [
555 'python27',
556 ]
557 }
James E. Blairff555742017-02-19 11:34:27 -0800558 }])
James E. Blairf59f3cf2017-02-19 14:50:26 -0800559 layout.addProjectConfig(project_config)
Clint Byrum85493602016-11-18 11:59:47 -0800560
561 change = model.Change(project)
562 change.branch = 'master'
563 change.files = ['/COMMIT_MSG', 'ignored-file']
564 item = queue.enqueueChange(change)
565 item.current_build_set.layout = layout
566
567 self.assertTrue(base.changeMatches(change))
568 self.assertFalse(python27.changeMatches(change))
569
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200570 item.freezeJobGraph()
Clint Byrum85493602016-11-18 11:59:47 -0800571 self.assertEqual([], item.getJobs())
572
James E. Blair4317e9f2016-07-15 10:05:47 -0700573 def test_job_source_project(self):
James E. Blair6459db12017-06-29 14:57:20 -0700574 tenant = self.tenant
575 layout = self.layout
James E. Blair0a899752017-03-29 13:22:16 -0700576 base_project = model.Project('base_project', self.source)
James E. Blair6f140c72017-03-03 10:32:07 -0800577 base_context = model.SourceContext(base_project, 'master',
578 'test', True)
James E. Blair6459db12017-06-29 14:57:20 -0700579 tpc = model.TenantProjectConfig(base_project)
580 tenant.addUntrustedProject(tpc)
James E. Blaircdab2032017-02-01 09:09:29 -0800581
James E. Blair5ac93842017-01-20 06:47:34 -0800582 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800583 '_source_context': base_context,
James E. Blairec7ff302017-03-04 07:31:32 -0800584 '_start_mark': self.start_mark,
James E. Blair4317e9f2016-07-15 10:05:47 -0700585 'name': 'base',
586 })
587 layout.addJob(base)
588
James E. Blair0a899752017-03-29 13:22:16 -0700589 other_project = model.Project('other_project', self.source)
James E. Blair6f140c72017-03-03 10:32:07 -0800590 other_context = model.SourceContext(other_project, 'master',
591 'test', True)
James E. Blair6459db12017-06-29 14:57:20 -0700592 tpc = model.TenantProjectConfig(other_project)
593 tenant.addUntrustedProject(tpc)
James E. Blair5ac93842017-01-20 06:47:34 -0800594 base2 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800595 '_source_context': other_context,
James E. Blairec7ff302017-03-04 07:31:32 -0800596 '_start_mark': self.start_mark,
James E. Blair4317e9f2016-07-15 10:05:47 -0700597 'name': 'base',
598 })
599 with testtools.ExpectedException(
600 Exception,
601 "Job base in other_project is not permitted "
602 "to shadow job base in base_project"):
603 layout.addJob(base2)
604
James E. Blairb3f5db12017-03-17 12:57:39 -0700605 def test_job_allowed_projects(self):
606 job = configloader.JobParser.fromYaml(self.tenant, self.layout, {
607 '_source_context': self.context,
608 '_start_mark': self.start_mark,
609 'name': 'job',
610 'allowed-projects': ['project'],
611 })
612 self.layout.addJob(job)
613
James E. Blair0a899752017-03-29 13:22:16 -0700614 project2 = model.Project('project2', self.source)
James E. Blair08d9b782017-06-29 14:22:48 -0700615 tpc2 = model.TenantProjectConfig(project2)
616 self.tenant.addUntrustedProject(tpc2)
James E. Blairb3f5db12017-03-17 12:57:39 -0700617 context2 = model.SourceContext(project2, 'master',
618 'test', True)
619
620 project2_config = configloader.ProjectParser.fromYaml(
621 self.tenant, self.layout, [{
622 '_source_context': context2,
623 '_start_mark': self.start_mark,
624 'name': 'project2',
625 'gate': {
626 'jobs': [
627 'job'
628 ]
629 }
630 }]
631 )
632 self.layout.addProjectConfig(project2_config)
633
634 change = model.Change(project2)
635 # Test master
636 change.branch = 'master'
637 item = self.queue.enqueueChange(change)
638 item.current_build_set.layout = self.layout
639 with testtools.ExpectedException(
640 Exception,
641 "Project project2 is not allowed to run job job"):
642 item.freezeJobGraph()
643
James E. Blaird2348362017-03-17 13:59:35 -0700644 def test_job_pipeline_allow_secrets(self):
645 self.pipeline.allow_secrets = False
646 job = configloader.JobParser.fromYaml(self.tenant, self.layout, {
647 '_source_context': self.context,
648 '_start_mark': self.start_mark,
649 'name': 'job',
650 })
651 auth = model.AuthContext()
652 auth.secrets.append('foo')
653 job.auth = auth
654
655 self.layout.addJob(job)
656
657 project_config = configloader.ProjectParser.fromYaml(
658 self.tenant, self.layout, [{
659 '_source_context': self.context,
660 '_start_mark': self.start_mark,
661 'name': 'project',
662 'gate': {
663 'jobs': [
664 'job'
665 ]
666 }
667 }]
668 )
669 self.layout.addProjectConfig(project_config)
670
671 change = model.Change(self.project)
672 # Test master
673 change.branch = 'master'
674 item = self.queue.enqueueChange(change)
675 item.current_build_set.layout = self.layout
676 with testtools.ExpectedException(
677 Exception,
678 "Pipeline gate does not allow jobs with secrets"):
679 item.freezeJobGraph()
680
James E. Blairce8a2132016-05-19 15:21:52 -0700681
682class TestJobTimeData(BaseTestCase):
683 def setUp(self):
684 super(TestJobTimeData, self).setUp()
685 self.tmp_root = self.useFixture(fixtures.TempDir(
686 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
687 ).path
688
689 def test_empty_timedata(self):
690 path = os.path.join(self.tmp_root, 'job-name')
691 self.assertFalse(os.path.exists(path))
692 self.assertFalse(os.path.exists(path + '.tmp'))
693 td = model.JobTimeData(path)
694 self.assertEqual(td.success_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
695 self.assertEqual(td.failure_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
696 self.assertEqual(td.results, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
697
698 def test_save_reload(self):
699 path = os.path.join(self.tmp_root, 'job-name')
700 self.assertFalse(os.path.exists(path))
701 self.assertFalse(os.path.exists(path + '.tmp'))
702 td = model.JobTimeData(path)
703 self.assertEqual(td.success_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
704 self.assertEqual(td.failure_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
705 self.assertEqual(td.results, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
706 success_times = []
707 failure_times = []
708 results = []
709 for x in range(10):
710 success_times.append(int(random.random() * 1000))
711 failure_times.append(int(random.random() * 1000))
712 results.append(0)
713 results.append(1)
714 random.shuffle(results)
715 s = f = 0
716 for result in results:
717 if result:
718 td.add(failure_times[f], 'FAILURE')
719 f += 1
720 else:
721 td.add(success_times[s], 'SUCCESS')
722 s += 1
723 self.assertEqual(td.success_times, success_times)
724 self.assertEqual(td.failure_times, failure_times)
725 self.assertEqual(td.results, results[10:])
726 td.save()
727 self.assertTrue(os.path.exists(path))
728 self.assertFalse(os.path.exists(path + '.tmp'))
729 td = model.JobTimeData(path)
730 td.load()
731 self.assertEqual(td.success_times, success_times)
732 self.assertEqual(td.failure_times, failure_times)
733 self.assertEqual(td.results, results[10:])
734
735
736class TestTimeDataBase(BaseTestCase):
737 def setUp(self):
738 super(TestTimeDataBase, self).setUp()
739 self.tmp_root = self.useFixture(fixtures.TempDir(
740 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
741 ).path
742 self.db = model.TimeDataBase(self.tmp_root)
743
744 def test_timedatabase(self):
745 self.assertEqual(self.db.getEstimatedTime('job-name'), 0)
746 self.db.update('job-name', 50, 'SUCCESS')
747 self.assertEqual(self.db.getEstimatedTime('job-name'), 50)
748 self.db.update('job-name', 100, 'SUCCESS')
749 self.assertEqual(self.db.getEstimatedTime('job-name'), 75)
750 for x in range(10):
751 self.db.update('job-name', 100, 'SUCCESS')
752 self.assertEqual(self.db.getEstimatedTime('job-name'), 100)
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200753
754
755class TestGraph(BaseTestCase):
756 def test_job_graph_disallows_multiple_jobs_with_same_name(self):
757 graph = model.JobGraph()
758 job1 = model.Job('job')
759 job2 = model.Job('job')
760 graph.addJob(job1)
761 with testtools.ExpectedException(Exception,
762 "Job job already added"):
763 graph.addJob(job2)
764
765 def test_job_graph_disallows_circular_dependencies(self):
766 graph = model.JobGraph()
767 jobs = [model.Job('job%d' % i) for i in range(0, 10)]
768 prevjob = None
769 for j in jobs[:3]:
770 if prevjob:
771 j.dependencies = frozenset([prevjob.name])
772 graph.addJob(j)
773 prevjob = j
774 # 0 triggers 1 triggers 2 triggers 3...
775
776 # Cannot depend on itself
777 with testtools.ExpectedException(
778 Exception,
779 "Dependency cycle detected in job jobX"):
780 j = model.Job('jobX')
781 j.dependencies = frozenset([j.name])
782 graph.addJob(j)
783
784 # Disallow circular dependencies
785 with testtools.ExpectedException(
786 Exception,
787 "Dependency cycle detected in job job3"):
788 jobs[4].dependencies = frozenset([jobs[3].name])
789 graph.addJob(jobs[4])
790 jobs[3].dependencies = frozenset([jobs[4].name])
791 graph.addJob(jobs[3])
792
793 jobs[5].dependencies = frozenset([jobs[4].name])
794 graph.addJob(jobs[5])
795
796 with testtools.ExpectedException(
797 Exception,
798 "Dependency cycle detected in job job3"):
799 jobs[3].dependencies = frozenset([jobs[5].name])
800 graph.addJob(jobs[3])
801
802 jobs[3].dependencies = frozenset([jobs[2].name])
803 graph.addJob(jobs[3])
804 jobs[6].dependencies = frozenset([jobs[2].name])
805 graph.addJob(jobs[6])
James E. Blairc2a54fd2017-03-29 15:19:26 -0700806
807
808class TestTenant(BaseTestCase):
809 def test_add_project(self):
810 tenant = model.Tenant('tenant')
811 connection1 = Dummy(connection_name='dummy_connection1')
812 source1 = Dummy(canonical_hostname='git1.example.com',
813 name='dummy', # TODOv3(jeblair): remove
814 connection=connection1)
815
816 source1_project1 = model.Project('project1', source1)
James E. Blair08d9b782017-06-29 14:22:48 -0700817 source1_project1_tpc = model.TenantProjectConfig(source1_project1)
818 tenant.addConfigProject(source1_project1_tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700819 d = {'project1':
820 {'git1.example.com': source1_project1}}
821 self.assertEqual(d, tenant.projects)
822 self.assertEqual((True, source1_project1),
823 tenant.getProject('project1'))
824 self.assertEqual((True, source1_project1),
825 tenant.getProject('git1.example.com/project1'))
826
827 source1_project2 = model.Project('project2', source1)
James E. Blair08d9b782017-06-29 14:22:48 -0700828 tpc = model.TenantProjectConfig(source1_project2)
829 tenant.addUntrustedProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700830 d = {'project1':
831 {'git1.example.com': source1_project1},
832 'project2':
833 {'git1.example.com': source1_project2}}
834 self.assertEqual(d, tenant.projects)
835 self.assertEqual((False, source1_project2),
836 tenant.getProject('project2'))
837 self.assertEqual((False, source1_project2),
838 tenant.getProject('git1.example.com/project2'))
839
840 connection2 = Dummy(connection_name='dummy_connection2')
841 source2 = Dummy(canonical_hostname='git2.example.com',
842 name='dummy', # TODOv3(jeblair): remove
843 connection=connection2)
844
845 source2_project1 = model.Project('project1', source2)
James E. Blair08d9b782017-06-29 14:22:48 -0700846 tpc = model.TenantProjectConfig(source2_project1)
847 tenant.addUntrustedProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700848 d = {'project1':
849 {'git1.example.com': source1_project1,
850 'git2.example.com': source2_project1},
851 'project2':
852 {'git1.example.com': source1_project2}}
853 self.assertEqual(d, tenant.projects)
854 with testtools.ExpectedException(
855 Exception,
856 "Project name 'project1' is ambiguous"):
857 tenant.getProject('project1')
858 self.assertEqual((False, source1_project2),
859 tenant.getProject('project2'))
860 self.assertEqual((True, source1_project1),
861 tenant.getProject('git1.example.com/project1'))
862 self.assertEqual((False, source2_project1),
863 tenant.getProject('git2.example.com/project1'))
864
865 source2_project2 = model.Project('project2', source2)
James E. Blair08d9b782017-06-29 14:22:48 -0700866 tpc = model.TenantProjectConfig(source2_project2)
867 tenant.addConfigProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700868 d = {'project1':
869 {'git1.example.com': source1_project1,
870 'git2.example.com': source2_project1},
871 'project2':
872 {'git1.example.com': source1_project2,
873 'git2.example.com': source2_project2}}
874 self.assertEqual(d, tenant.projects)
875 with testtools.ExpectedException(
876 Exception,
877 "Project name 'project1' is ambiguous"):
878 tenant.getProject('project1')
879 with testtools.ExpectedException(
880 Exception,
881 "Project name 'project2' is ambiguous"):
882 tenant.getProject('project2')
883 self.assertEqual((True, source1_project1),
884 tenant.getProject('git1.example.com/project1'))
885 self.assertEqual((False, source2_project1),
886 tenant.getProject('git2.example.com/project1'))
887 self.assertEqual((False, source1_project2),
888 tenant.getProject('git1.example.com/project2'))
889 self.assertEqual((True, source2_project2),
890 tenant.getProject('git2.example.com/project2'))
891
892 source1_project2b = model.Project('subpath/project2', source1)
James E. Blair08d9b782017-06-29 14:22:48 -0700893 tpc = model.TenantProjectConfig(source1_project2b)
894 tenant.addConfigProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700895 d = {'project1':
896 {'git1.example.com': source1_project1,
897 'git2.example.com': source2_project1},
898 'project2':
899 {'git1.example.com': source1_project2,
900 'git2.example.com': source2_project2},
901 'subpath/project2':
902 {'git1.example.com': source1_project2b}}
903 self.assertEqual(d, tenant.projects)
904 self.assertEqual((False, source1_project2),
905 tenant.getProject('git1.example.com/project2'))
906 self.assertEqual((True, source2_project2),
907 tenant.getProject('git2.example.com/project2'))
908 self.assertEqual((True, source1_project2b),
909 tenant.getProject('subpath/project2'))
910 self.assertEqual(
911 (True, source1_project2b),
912 tenant.getProject('git1.example.com/subpath/project2'))
913
914 source2_project2b = model.Project('subpath/project2', source2)
James E. Blair08d9b782017-06-29 14:22:48 -0700915 tpc = model.TenantProjectConfig(source2_project2b)
916 tenant.addConfigProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700917 d = {'project1':
918 {'git1.example.com': source1_project1,
919 'git2.example.com': source2_project1},
920 'project2':
921 {'git1.example.com': source1_project2,
922 'git2.example.com': source2_project2},
923 'subpath/project2':
924 {'git1.example.com': source1_project2b,
925 'git2.example.com': source2_project2b}}
926 self.assertEqual(d, tenant.projects)
927 self.assertEqual((False, source1_project2),
928 tenant.getProject('git1.example.com/project2'))
929 self.assertEqual((True, source2_project2),
930 tenant.getProject('git2.example.com/project2'))
931 with testtools.ExpectedException(
932 Exception,
933 "Project name 'subpath/project2' is ambiguous"):
934 tenant.getProject('subpath/project2')
935 self.assertEqual(
936 (True, source1_project2b),
937 tenant.getProject('git1.example.com/subpath/project2'))
938 self.assertEqual(
939 (True, source2_project2b),
940 tenant.getProject('git2.example.com/subpath/project2'))
941
942 with testtools.ExpectedException(
943 Exception,
944 "Project project1 is already in project index"):
James E. Blair08d9b782017-06-29 14:22:48 -0700945 tenant._addProject(source1_project1_tpc)