blob: a3de474d46f341e5a9f50bfdde91e03884c5103b [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. Blair7e9cc272018-02-15 14:13:46 -080066 job_parser = configloader.JobParser(tenant, layout)
67 job = job_parser.fromYaml({
James E. Blairec7ff302017-03-04 07:31:32 -080068 '_source_context': self.context,
69 '_start_mark': self.start_mark,
James E. Blair83005782015-12-11 14:46:03 -080070 'name': 'job',
James E. Blair2bab6e72017-08-07 09:52:45 -070071 'parent': None,
James E. Blair83005782015-12-11 14:46:03 -080072 'irrelevant-files': [
73 '^docs/.*$'
74 ]})
Maru Newby3fe5f852015-01-13 04:22:14 +000075 return job
76
77 def test_change_matches_returns_false_for_matched_skip_if(self):
78 change = model.Change('project')
Alexander Evseevdbe6fab2015-11-19 12:46:34 +030079 change.files = ['/COMMIT_MSG', 'docs/foo']
Maru Newby3fe5f852015-01-13 04:22:14 +000080 self.assertFalse(self.job.changeMatches(change))
81
Jan Hruban570d01c2016-03-10 21:51:32 +010082 def test_change_matches_returns_false_for_single_matched_skip_if(self):
83 change = model.Change('project')
84 change.files = ['docs/foo']
85 self.assertFalse(self.job.changeMatches(change))
86
Maru Newby3fe5f852015-01-13 04:22:14 +000087 def test_change_matches_returns_true_for_unmatched_skip_if(self):
88 change = model.Change('project')
Alexander Evseevdbe6fab2015-11-19 12:46:34 +030089 change.files = ['/COMMIT_MSG', 'foo']
Maru Newby3fe5f852015-01-13 04:22:14 +000090 self.assertTrue(self.job.changeMatches(change))
91
Jan Hruban570d01c2016-03-10 21:51:32 +010092 def test_change_matches_returns_true_for_single_unmatched_skip_if(self):
93 change = model.Change('project')
94 change.files = ['foo']
95 self.assertTrue(self.job.changeMatches(change))
96
Maru Newby79427a42015-02-17 17:54:45 +000097 def test_job_sets_defaults_for_boolean_attributes(self):
James E. Blair83005782015-12-11 14:46:03 -080098 self.assertIsNotNone(self.job.voting)
99
James E. Blaira7f51ca2017-02-07 16:01:26 -0800100 def test_job_variants(self):
101 # This simulates freezing a job.
102
James E. Blair892cca62017-08-09 11:36:58 -0700103 secrets = ['foo']
104 py27_pre = model.PlaybookContext(self.context, 'py27-pre', [], secrets)
105 py27_run = model.PlaybookContext(self.context, 'py27-run', [], secrets)
106 py27_post = model.PlaybookContext(self.context, 'py27-post', [],
107 secrets)
James E. Blaira7f51ca2017-02-07 16:01:26 -0800108
109 py27 = model.Job('py27')
110 py27.timeout = 30
111 py27.pre_run = [py27_pre]
112 py27.run = [py27_run]
113 py27.post_run = [py27_post]
James E. Blaira7f51ca2017-02-07 16:01:26 -0800114
115 job = py27.copy()
116 self.assertEqual(30, job.timeout)
117
118 # Apply the diablo variant
119 diablo = model.Job('py27')
120 diablo.timeout = 40
121 job.applyVariant(diablo)
122
123 self.assertEqual(40, job.timeout)
124 self.assertEqual(['py27-pre'],
125 [x.path for x in job.pre_run])
126 self.assertEqual(['py27-run'],
127 [x.path for x in job.run])
128 self.assertEqual(['py27-post'],
129 [x.path for x in job.post_run])
James E. Blair892cca62017-08-09 11:36:58 -0700130 self.assertEqual(secrets, job.pre_run[0].secrets)
131 self.assertEqual(secrets, job.run[0].secrets)
132 self.assertEqual(secrets, job.post_run[0].secrets)
James E. Blaira7f51ca2017-02-07 16:01:26 -0800133
134 # Set the job to final for the following checks
135 job.final = True
136 self.assertTrue(job.voting)
137
138 good_final = model.Job('py27')
139 good_final.voting = False
140 job.applyVariant(good_final)
141 self.assertFalse(job.voting)
142
143 bad_final = model.Job('py27')
144 bad_final.timeout = 600
145 with testtools.ExpectedException(
146 Exception,
147 "Unable to modify final job"):
148 job.applyVariant(bad_final)
149
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700150 def test_job_inheritance_job_tree(self):
James E. Blair5ac93842017-01-20 06:47:34 -0800151 tenant = model.Tenant('tenant')
James E. Blair6459db12017-06-29 14:57:20 -0700152 layout = model.Layout(tenant)
James E. Blairaf8b2082017-10-03 15:38:27 -0700153
James E. Blair08d9b782017-06-29 14:22:48 -0700154 tpc = model.TenantProjectConfig(self.project)
155 tenant.addUntrustedProject(tpc)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700156
157 pipeline = model.Pipeline('gate', layout)
158 layout.addPipeline(pipeline)
159 queue = model.ChangeQueue(pipeline)
160
James E. Blair7e9cc272018-02-15 14:13:46 -0800161 job_parser = configloader.JobParser(tenant, layout)
162 base = job_parser.fromYaml({
James E. Blairec7ff302017-03-04 07:31:32 -0800163 '_source_context': self.context,
164 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700165 'name': 'base',
James E. Blair2bab6e72017-08-07 09:52:45 -0700166 'parent': None,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700167 'timeout': 30,
168 })
169 layout.addJob(base)
James E. Blair7e9cc272018-02-15 14:13:46 -0800170 python27 = job_parser.fromYaml({
James E. Blairec7ff302017-03-04 07:31:32 -0800171 '_source_context': self.context,
172 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700173 'name': 'python27',
174 'parent': 'base',
175 'timeout': 40,
176 })
177 layout.addJob(python27)
James E. Blair7e9cc272018-02-15 14:13:46 -0800178 python27diablo = job_parser.fromYaml({
James E. Blairec7ff302017-03-04 07:31:32 -0800179 '_source_context': self.context,
180 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700181 'name': 'python27',
182 'branches': [
183 'stable/diablo'
184 ],
185 'timeout': 50,
186 })
187 layout.addJob(python27diablo)
188
James E. Blairaf8b2082017-10-03 15:38:27 -0700189 project_template_parser = configloader.ProjectTemplateParser(
190 tenant, layout)
191 project_parser = configloader.ProjectParser(
192 tenant, layout, project_template_parser)
193 project_config = project_parser.fromYaml([{
James E. Blairec7ff302017-03-04 07:31:32 -0800194 '_source_context': self.context,
195 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700196 'name': 'project',
197 'gate': {
198 'jobs': [
James E. Blair00292672017-10-26 15:29:39 -0700199 {'python27': {'timeout': 70,
200 'run': 'playbooks/python27.yaml'}}
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700201 ]
202 }
James E. Blairff555742017-02-19 11:34:27 -0800203 }])
James E. Blairf59f3cf2017-02-19 14:50:26 -0800204 layout.addProjectConfig(project_config)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700205
James E. Blairec7ff302017-03-04 07:31:32 -0800206 change = model.Change(self.project)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700207 change.branch = 'master'
208 item = queue.enqueueChange(change)
James E. Blair29a24fd2017-10-02 15:04:56 -0700209 item.layout = layout
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700210
211 self.assertTrue(base.changeMatches(change))
212 self.assertTrue(python27.changeMatches(change))
213 self.assertFalse(python27diablo.changeMatches(change))
214
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200215 item.freezeJobGraph()
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700216 self.assertEqual(len(item.getJobs()), 1)
217 job = item.getJobs()[0]
218 self.assertEqual(job.name, 'python27')
219 self.assertEqual(job.timeout, 70)
220
221 change.branch = 'stable/diablo'
222 item = queue.enqueueChange(change)
James E. Blair29a24fd2017-10-02 15:04:56 -0700223 item.layout = layout
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700224
225 self.assertTrue(base.changeMatches(change))
226 self.assertTrue(python27.changeMatches(change))
227 self.assertTrue(python27diablo.changeMatches(change))
228
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200229 item.freezeJobGraph()
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700230 self.assertEqual(len(item.getJobs()), 1)
231 job = item.getJobs()[0]
232 self.assertEqual(job.name, 'python27')
233 self.assertEqual(job.timeout, 70)
234
Clint Byrum85493602016-11-18 11:59:47 -0800235 def test_inheritance_keeps_matchers(self):
James E. Blair5ac93842017-01-20 06:47:34 -0800236 tenant = model.Tenant('tenant')
James E. Blair6459db12017-06-29 14:57:20 -0700237 layout = model.Layout(tenant)
Clint Byrum85493602016-11-18 11:59:47 -0800238
239 pipeline = model.Pipeline('gate', layout)
240 layout.addPipeline(pipeline)
241 queue = model.ChangeQueue(pipeline)
James E. Blair0a899752017-03-29 13:22:16 -0700242 project = model.Project('project', self.source)
James E. Blair08d9b782017-06-29 14:22:48 -0700243 tpc = model.TenantProjectConfig(project)
244 tenant.addUntrustedProject(tpc)
Clint Byrum85493602016-11-18 11:59:47 -0800245
James E. Blair7e9cc272018-02-15 14:13:46 -0800246 job_parser = configloader.JobParser(tenant, layout)
247 base = job_parser.fromYaml({
James E. Blairec7ff302017-03-04 07:31:32 -0800248 '_source_context': self.context,
249 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800250 'name': 'base',
James E. Blair2bab6e72017-08-07 09:52:45 -0700251 'parent': None,
Clint Byrum85493602016-11-18 11:59:47 -0800252 'timeout': 30,
253 })
254 layout.addJob(base)
James E. Blair7e9cc272018-02-15 14:13:46 -0800255 python27 = job_parser.fromYaml({
James E. Blairec7ff302017-03-04 07:31:32 -0800256 '_source_context': self.context,
257 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800258 'name': 'python27',
259 'parent': 'base',
260 'timeout': 40,
261 'irrelevant-files': ['^ignored-file$'],
262 })
263 layout.addJob(python27)
264
James E. Blairaf8b2082017-10-03 15:38:27 -0700265 project_template_parser = configloader.ProjectTemplateParser(
266 tenant, layout)
267 project_parser = configloader.ProjectParser(
268 tenant, layout, project_template_parser)
269 project_config = project_parser.fromYaml([{
James E. Blairec7ff302017-03-04 07:31:32 -0800270 '_source_context': self.context,
271 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800272 'name': 'project',
273 'gate': {
274 'jobs': [
275 'python27',
276 ]
277 }
James E. Blairff555742017-02-19 11:34:27 -0800278 }])
James E. Blairf59f3cf2017-02-19 14:50:26 -0800279 layout.addProjectConfig(project_config)
Clint Byrum85493602016-11-18 11:59:47 -0800280
281 change = model.Change(project)
282 change.branch = 'master'
283 change.files = ['/COMMIT_MSG', 'ignored-file']
284 item = queue.enqueueChange(change)
James E. Blair29a24fd2017-10-02 15:04:56 -0700285 item.layout = layout
Clint Byrum85493602016-11-18 11:59:47 -0800286
287 self.assertTrue(base.changeMatches(change))
288 self.assertFalse(python27.changeMatches(change))
289
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200290 item.freezeJobGraph()
Clint Byrum85493602016-11-18 11:59:47 -0800291 self.assertEqual([], item.getJobs())
292
James E. Blair4317e9f2016-07-15 10:05:47 -0700293 def test_job_source_project(self):
James E. Blair6459db12017-06-29 14:57:20 -0700294 tenant = self.tenant
295 layout = self.layout
James E. Blair0a899752017-03-29 13:22:16 -0700296 base_project = model.Project('base_project', self.source)
James E. Blair6f140c72017-03-03 10:32:07 -0800297 base_context = model.SourceContext(base_project, 'master',
298 'test', True)
James E. Blair6459db12017-06-29 14:57:20 -0700299 tpc = model.TenantProjectConfig(base_project)
300 tenant.addUntrustedProject(tpc)
James E. Blaircdab2032017-02-01 09:09:29 -0800301
James E. Blair7e9cc272018-02-15 14:13:46 -0800302 job_parser = configloader.JobParser(tenant, layout)
303 base = job_parser.fromYaml({
James E. Blaircdab2032017-02-01 09:09:29 -0800304 '_source_context': base_context,
James E. Blairec7ff302017-03-04 07:31:32 -0800305 '_start_mark': self.start_mark,
James E. Blair2bab6e72017-08-07 09:52:45 -0700306 'parent': None,
James E. Blair4317e9f2016-07-15 10:05:47 -0700307 'name': 'base',
308 })
309 layout.addJob(base)
310
James E. Blair0a899752017-03-29 13:22:16 -0700311 other_project = model.Project('other_project', self.source)
James E. Blair6f140c72017-03-03 10:32:07 -0800312 other_context = model.SourceContext(other_project, 'master',
313 'test', True)
James E. Blair6459db12017-06-29 14:57:20 -0700314 tpc = model.TenantProjectConfig(other_project)
315 tenant.addUntrustedProject(tpc)
James E. Blair7e9cc272018-02-15 14:13:46 -0800316 base2 = job_parser.fromYaml({
James E. Blaircdab2032017-02-01 09:09:29 -0800317 '_source_context': other_context,
James E. Blairec7ff302017-03-04 07:31:32 -0800318 '_start_mark': self.start_mark,
James E. Blair4317e9f2016-07-15 10:05:47 -0700319 'name': 'base',
320 })
321 with testtools.ExpectedException(
322 Exception,
323 "Job base in other_project is not permitted "
324 "to shadow job base in base_project"):
325 layout.addJob(base2)
326
James E. Blair8eb564a2017-08-10 09:21:41 -0700327 def test_job_pipeline_allow_untrusted_secrets(self):
328 self.pipeline.post_review = False
James E. Blair7e9cc272018-02-15 14:13:46 -0800329 job_parser = configloader.JobParser(self.tenant, self.layout)
330 job = job_parser.fromYaml({
James E. Blaird2348362017-03-17 13:59:35 -0700331 '_source_context': self.context,
332 '_start_mark': self.start_mark,
333 'name': 'job',
James E. Blair2bab6e72017-08-07 09:52:45 -0700334 'parent': None,
James E. Blaird2348362017-03-17 13:59:35 -0700335 })
James E. Blair8eb564a2017-08-10 09:21:41 -0700336 job.post_review = True
James E. Blaird2348362017-03-17 13:59:35 -0700337
338 self.layout.addJob(job)
339
James E. Blairaf8b2082017-10-03 15:38:27 -0700340 project_template_parser = configloader.ProjectTemplateParser(
341 self.tenant, self.layout)
342 project_parser = configloader.ProjectParser(
343 self.tenant, self.layout, project_template_parser)
344 project_config = project_parser.fromYaml(
345 [{
James E. Blaird2348362017-03-17 13:59:35 -0700346 '_source_context': self.context,
347 '_start_mark': self.start_mark,
348 'name': 'project',
349 'gate': {
350 'jobs': [
351 'job'
352 ]
353 }
354 }]
355 )
356 self.layout.addProjectConfig(project_config)
357
358 change = model.Change(self.project)
359 # Test master
360 change.branch = 'master'
361 item = self.queue.enqueueChange(change)
James E. Blair29a24fd2017-10-02 15:04:56 -0700362 item.layout = self.layout
James E. Blaird2348362017-03-17 13:59:35 -0700363 with testtools.ExpectedException(
364 Exception,
James E. Blair8eb564a2017-08-10 09:21:41 -0700365 "Pre-review pipeline gate does not allow post-review job"):
James E. Blaird2348362017-03-17 13:59:35 -0700366 item.freezeJobGraph()
367
James E. Blairce8a2132016-05-19 15:21:52 -0700368
369class TestJobTimeData(BaseTestCase):
370 def setUp(self):
371 super(TestJobTimeData, self).setUp()
372 self.tmp_root = self.useFixture(fixtures.TempDir(
373 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
374 ).path
375
376 def test_empty_timedata(self):
377 path = os.path.join(self.tmp_root, 'job-name')
378 self.assertFalse(os.path.exists(path))
379 self.assertFalse(os.path.exists(path + '.tmp'))
380 td = model.JobTimeData(path)
381 self.assertEqual(td.success_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
382 self.assertEqual(td.failure_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
383 self.assertEqual(td.results, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
384
385 def test_save_reload(self):
386 path = os.path.join(self.tmp_root, 'job-name')
387 self.assertFalse(os.path.exists(path))
388 self.assertFalse(os.path.exists(path + '.tmp'))
389 td = model.JobTimeData(path)
390 self.assertEqual(td.success_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
391 self.assertEqual(td.failure_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
392 self.assertEqual(td.results, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
393 success_times = []
394 failure_times = []
395 results = []
396 for x in range(10):
397 success_times.append(int(random.random() * 1000))
398 failure_times.append(int(random.random() * 1000))
399 results.append(0)
400 results.append(1)
401 random.shuffle(results)
402 s = f = 0
403 for result in results:
404 if result:
405 td.add(failure_times[f], 'FAILURE')
406 f += 1
407 else:
408 td.add(success_times[s], 'SUCCESS')
409 s += 1
410 self.assertEqual(td.success_times, success_times)
411 self.assertEqual(td.failure_times, failure_times)
412 self.assertEqual(td.results, results[10:])
413 td.save()
414 self.assertTrue(os.path.exists(path))
415 self.assertFalse(os.path.exists(path + '.tmp'))
416 td = model.JobTimeData(path)
417 td.load()
418 self.assertEqual(td.success_times, success_times)
419 self.assertEqual(td.failure_times, failure_times)
420 self.assertEqual(td.results, results[10:])
421
422
423class TestTimeDataBase(BaseTestCase):
424 def setUp(self):
425 super(TestTimeDataBase, self).setUp()
426 self.tmp_root = self.useFixture(fixtures.TempDir(
427 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
428 ).path
429 self.db = model.TimeDataBase(self.tmp_root)
430
431 def test_timedatabase(self):
James E. Blairae0f23c2017-09-13 10:55:15 -0600432 pipeline = Dummy(layout=Dummy(tenant=Dummy(name='test-tenant')))
433 change = Dummy(project=Dummy(canonical_name='git.example.com/foo/bar'))
434 job = Dummy(name='job-name')
435 item = Dummy(pipeline=pipeline,
436 change=change)
437 build = Dummy(build_set=Dummy(item=item),
438 job=job)
439
440 self.assertEqual(self.db.getEstimatedTime(build), 0)
441 self.db.update(build, 50, 'SUCCESS')
442 self.assertEqual(self.db.getEstimatedTime(build), 50)
443 self.db.update(build, 100, 'SUCCESS')
444 self.assertEqual(self.db.getEstimatedTime(build), 75)
James E. Blairce8a2132016-05-19 15:21:52 -0700445 for x in range(10):
James E. Blairae0f23c2017-09-13 10:55:15 -0600446 self.db.update(build, 100, 'SUCCESS')
447 self.assertEqual(self.db.getEstimatedTime(build), 100)
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200448
449
450class TestGraph(BaseTestCase):
451 def test_job_graph_disallows_multiple_jobs_with_same_name(self):
452 graph = model.JobGraph()
453 job1 = model.Job('job')
454 job2 = model.Job('job')
455 graph.addJob(job1)
456 with testtools.ExpectedException(Exception,
457 "Job job already added"):
458 graph.addJob(job2)
459
460 def test_job_graph_disallows_circular_dependencies(self):
461 graph = model.JobGraph()
462 jobs = [model.Job('job%d' % i) for i in range(0, 10)]
463 prevjob = None
464 for j in jobs[:3]:
465 if prevjob:
466 j.dependencies = frozenset([prevjob.name])
467 graph.addJob(j)
468 prevjob = j
469 # 0 triggers 1 triggers 2 triggers 3...
470
471 # Cannot depend on itself
472 with testtools.ExpectedException(
473 Exception,
474 "Dependency cycle detected in job jobX"):
475 j = model.Job('jobX')
476 j.dependencies = frozenset([j.name])
477 graph.addJob(j)
478
479 # Disallow circular dependencies
480 with testtools.ExpectedException(
481 Exception,
482 "Dependency cycle detected in job job3"):
483 jobs[4].dependencies = frozenset([jobs[3].name])
484 graph.addJob(jobs[4])
485 jobs[3].dependencies = frozenset([jobs[4].name])
486 graph.addJob(jobs[3])
487
488 jobs[5].dependencies = frozenset([jobs[4].name])
489 graph.addJob(jobs[5])
490
491 with testtools.ExpectedException(
492 Exception,
493 "Dependency cycle detected in job job3"):
494 jobs[3].dependencies = frozenset([jobs[5].name])
495 graph.addJob(jobs[3])
496
497 jobs[3].dependencies = frozenset([jobs[2].name])
498 graph.addJob(jobs[3])
499 jobs[6].dependencies = frozenset([jobs[2].name])
500 graph.addJob(jobs[6])
James E. Blairc2a54fd2017-03-29 15:19:26 -0700501
502
503class TestTenant(BaseTestCase):
504 def test_add_project(self):
505 tenant = model.Tenant('tenant')
506 connection1 = Dummy(connection_name='dummy_connection1')
507 source1 = Dummy(canonical_hostname='git1.example.com',
508 name='dummy', # TODOv3(jeblair): remove
509 connection=connection1)
510
511 source1_project1 = model.Project('project1', source1)
James E. Blair08d9b782017-06-29 14:22:48 -0700512 source1_project1_tpc = model.TenantProjectConfig(source1_project1)
513 tenant.addConfigProject(source1_project1_tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700514 d = {'project1':
515 {'git1.example.com': source1_project1}}
516 self.assertEqual(d, tenant.projects)
517 self.assertEqual((True, source1_project1),
518 tenant.getProject('project1'))
519 self.assertEqual((True, source1_project1),
520 tenant.getProject('git1.example.com/project1'))
521
522 source1_project2 = model.Project('project2', source1)
James E. Blair08d9b782017-06-29 14:22:48 -0700523 tpc = model.TenantProjectConfig(source1_project2)
524 tenant.addUntrustedProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700525 d = {'project1':
526 {'git1.example.com': source1_project1},
527 'project2':
528 {'git1.example.com': source1_project2}}
529 self.assertEqual(d, tenant.projects)
530 self.assertEqual((False, source1_project2),
531 tenant.getProject('project2'))
532 self.assertEqual((False, source1_project2),
533 tenant.getProject('git1.example.com/project2'))
534
535 connection2 = Dummy(connection_name='dummy_connection2')
536 source2 = Dummy(canonical_hostname='git2.example.com',
537 name='dummy', # TODOv3(jeblair): remove
538 connection=connection2)
539
540 source2_project1 = model.Project('project1', source2)
James E. Blair08d9b782017-06-29 14:22:48 -0700541 tpc = model.TenantProjectConfig(source2_project1)
542 tenant.addUntrustedProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700543 d = {'project1':
544 {'git1.example.com': source1_project1,
545 'git2.example.com': source2_project1},
546 'project2':
547 {'git1.example.com': source1_project2}}
548 self.assertEqual(d, tenant.projects)
549 with testtools.ExpectedException(
550 Exception,
551 "Project name 'project1' is ambiguous"):
552 tenant.getProject('project1')
553 self.assertEqual((False, source1_project2),
554 tenant.getProject('project2'))
555 self.assertEqual((True, source1_project1),
556 tenant.getProject('git1.example.com/project1'))
557 self.assertEqual((False, source2_project1),
558 tenant.getProject('git2.example.com/project1'))
559
560 source2_project2 = model.Project('project2', source2)
James E. Blair08d9b782017-06-29 14:22:48 -0700561 tpc = model.TenantProjectConfig(source2_project2)
562 tenant.addConfigProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700563 d = {'project1':
564 {'git1.example.com': source1_project1,
565 'git2.example.com': source2_project1},
566 'project2':
567 {'git1.example.com': source1_project2,
568 'git2.example.com': source2_project2}}
569 self.assertEqual(d, tenant.projects)
570 with testtools.ExpectedException(
571 Exception,
572 "Project name 'project1' is ambiguous"):
573 tenant.getProject('project1')
574 with testtools.ExpectedException(
575 Exception,
576 "Project name 'project2' is ambiguous"):
577 tenant.getProject('project2')
578 self.assertEqual((True, source1_project1),
579 tenant.getProject('git1.example.com/project1'))
580 self.assertEqual((False, source2_project1),
581 tenant.getProject('git2.example.com/project1'))
582 self.assertEqual((False, source1_project2),
583 tenant.getProject('git1.example.com/project2'))
584 self.assertEqual((True, source2_project2),
585 tenant.getProject('git2.example.com/project2'))
586
587 source1_project2b = model.Project('subpath/project2', source1)
James E. Blair08d9b782017-06-29 14:22:48 -0700588 tpc = model.TenantProjectConfig(source1_project2b)
589 tenant.addConfigProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700590 d = {'project1':
591 {'git1.example.com': source1_project1,
592 'git2.example.com': source2_project1},
593 'project2':
594 {'git1.example.com': source1_project2,
595 'git2.example.com': source2_project2},
596 'subpath/project2':
597 {'git1.example.com': source1_project2b}}
598 self.assertEqual(d, tenant.projects)
599 self.assertEqual((False, source1_project2),
600 tenant.getProject('git1.example.com/project2'))
601 self.assertEqual((True, source2_project2),
602 tenant.getProject('git2.example.com/project2'))
603 self.assertEqual((True, source1_project2b),
604 tenant.getProject('subpath/project2'))
605 self.assertEqual(
606 (True, source1_project2b),
607 tenant.getProject('git1.example.com/subpath/project2'))
608
609 source2_project2b = model.Project('subpath/project2', source2)
James E. Blair08d9b782017-06-29 14:22:48 -0700610 tpc = model.TenantProjectConfig(source2_project2b)
611 tenant.addConfigProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700612 d = {'project1':
613 {'git1.example.com': source1_project1,
614 'git2.example.com': source2_project1},
615 'project2':
616 {'git1.example.com': source1_project2,
617 'git2.example.com': source2_project2},
618 'subpath/project2':
619 {'git1.example.com': source1_project2b,
620 'git2.example.com': source2_project2b}}
621 self.assertEqual(d, tenant.projects)
622 self.assertEqual((False, source1_project2),
623 tenant.getProject('git1.example.com/project2'))
624 self.assertEqual((True, source2_project2),
625 tenant.getProject('git2.example.com/project2'))
626 with testtools.ExpectedException(
627 Exception,
628 "Project name 'subpath/project2' is ambiguous"):
629 tenant.getProject('subpath/project2')
630 self.assertEqual(
631 (True, source1_project2b),
632 tenant.getProject('git1.example.com/subpath/project2'))
633 self.assertEqual(
634 (True, source2_project2b),
635 tenant.getProject('git2.example.com/subpath/project2'))
636
637 with testtools.ExpectedException(
638 Exception,
639 "Project project1 is already in project index"):
James E. Blair08d9b782017-06-29 14:22:48 -0700640 tenant._addProject(source1_project1_tpc)