blob: 784fcb3cf46a2773b2d71d9b2597e1886be6de34 [file] [log] [blame]
Maru Newby3fe5f852015-01-13 04:22:14 +00001# Copyright 2015 Red Hat, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
Joshua Hesketh0aa7e8b2016-07-14 00:12:25 +100015
James E. Blairce8a2132016-05-19 15:21:52 -070016import os
17import random
18
19import fixtures
James E. Blair4317e9f2016-07-15 10:05:47 -070020import testtools
James E. Blairce8a2132016-05-19 15:21:52 -070021
Maru Newby3fe5f852015-01-13 04:22:14 +000022from zuul import model
James E. Blair83005782015-12-11 14:46:03 -080023from zuul import configloader
James E. Blairbf1a4f22017-03-17 10:59:37 -070024from zuul.lib import encryption
Clint Byrum88fdfde2017-03-28 16:22:42 -070025from zuul.lib import yamlutil as yaml
Maru Newby3fe5f852015-01-13 04:22:14 +000026
James E. Blair18f86a32017-03-15 14:43:26 -070027from tests.base import BaseTestCase, FIXTURE_DIR
Maru Newby3fe5f852015-01-13 04:22:14 +000028
29
James E. Blair0a899752017-03-29 13:22:16 -070030class Dummy(object):
31 def __init__(self, **kw):
32 for k, v in kw.items():
33 setattr(self, k, v)
James E. Blairb3f5db12017-03-17 12:57:39 -070034
35
Maru Newby3fe5f852015-01-13 04:22:14 +000036class TestJob(BaseTestCase):
James E. Blaira7f51ca2017-02-07 16:01:26 -080037 def setUp(self):
38 super(TestJob, self).setUp()
James E. Blair0a899752017-03-29 13:22:16 -070039 self.connection = Dummy(connection_name='dummy_connection')
40 self.source = Dummy(canonical_hostname='git.example.com',
James E. Blair0a899752017-03-29 13:22:16 -070041 connection=self.connection)
James E. Blairb3f5db12017-03-17 12:57:39 -070042 self.tenant = model.Tenant('tenant')
James E. Blair6459db12017-06-29 14:57:20 -070043 self.layout = model.Layout(self.tenant)
James E. Blair0a899752017-03-29 13:22:16 -070044 self.project = model.Project('project', self.source)
James E. Blair08d9b782017-06-29 14:22:48 -070045 self.tpc = model.TenantProjectConfig(self.project)
46 self.tenant.addUntrustedProject(self.tpc)
James E. Blairb3f5db12017-03-17 12:57:39 -070047 self.pipeline = model.Pipeline('gate', self.layout)
48 self.layout.addPipeline(self.pipeline)
49 self.queue = model.ChangeQueue(self.pipeline)
50
James E. Blair18f86a32017-03-15 14:43:26 -070051 private_key_file = os.path.join(FIXTURE_DIR, 'private.pem')
52 with open(private_key_file, "rb") as f:
James E. Blairbf1a4f22017-03-17 10:59:37 -070053 self.project.private_key, self.project.public_key = \
54 encryption.deserialize_rsa_keypair(f.read())
James E. Blair6f140c72017-03-03 10:32:07 -080055 self.context = model.SourceContext(self.project, 'master',
56 'test', True)
James E. Blair892cca62017-08-09 11:36:58 -070057 self.untrusted_context = model.SourceContext(self.project, 'master',
58 'test', False)
James E. Blair1cebebf2017-07-14 11:39:03 -070059 m = yaml.Mark('name', 0, 0, 0, '', 0)
60 self.start_mark = configloader.ZuulMark(m, m, '')
James E. Blaira7f51ca2017-02-07 16:01:26 -080061
Maru Newby3fe5f852015-01-13 04:22:14 +000062 @property
63 def job(self):
James E. Blair5ac93842017-01-20 06:47:34 -080064 tenant = model.Tenant('tenant')
James E. Blair6459db12017-06-29 14:57:20 -070065 layout = model.Layout(tenant)
James E. Blair5ac93842017-01-20 06:47:34 -080066 job = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -080067 '_source_context': self.context,
68 '_start_mark': self.start_mark,
James E. Blair83005782015-12-11 14:46:03 -080069 'name': 'job',
James E. Blair2bab6e72017-08-07 09:52:45 -070070 'parent': None,
James E. Blair83005782015-12-11 14:46:03 -080071 'irrelevant-files': [
72 '^docs/.*$'
73 ]})
Maru Newby3fe5f852015-01-13 04:22:14 +000074 return job
75
76 def test_change_matches_returns_false_for_matched_skip_if(self):
77 change = model.Change('project')
Alexander Evseevdbe6fab2015-11-19 12:46:34 +030078 change.files = ['/COMMIT_MSG', 'docs/foo']
Maru Newby3fe5f852015-01-13 04:22:14 +000079 self.assertFalse(self.job.changeMatches(change))
80
Jan Hruban570d01c2016-03-10 21:51:32 +010081 def test_change_matches_returns_false_for_single_matched_skip_if(self):
82 change = model.Change('project')
83 change.files = ['docs/foo']
84 self.assertFalse(self.job.changeMatches(change))
85
Maru Newby3fe5f852015-01-13 04:22:14 +000086 def test_change_matches_returns_true_for_unmatched_skip_if(self):
87 change = model.Change('project')
Alexander Evseevdbe6fab2015-11-19 12:46:34 +030088 change.files = ['/COMMIT_MSG', 'foo']
Maru Newby3fe5f852015-01-13 04:22:14 +000089 self.assertTrue(self.job.changeMatches(change))
90
Jan Hruban570d01c2016-03-10 21:51:32 +010091 def test_change_matches_returns_true_for_single_unmatched_skip_if(self):
92 change = model.Change('project')
93 change.files = ['foo']
94 self.assertTrue(self.job.changeMatches(change))
95
Maru Newby79427a42015-02-17 17:54:45 +000096 def test_job_sets_defaults_for_boolean_attributes(self):
James E. Blair83005782015-12-11 14:46:03 -080097 self.assertIsNotNone(self.job.voting)
98
James E. Blaira7f51ca2017-02-07 16:01:26 -080099 def test_job_variants(self):
100 # This simulates freezing a job.
101
James E. Blair892cca62017-08-09 11:36:58 -0700102 secrets = ['foo']
103 py27_pre = model.PlaybookContext(self.context, 'py27-pre', [], secrets)
104 py27_run = model.PlaybookContext(self.context, 'py27-run', [], secrets)
105 py27_post = model.PlaybookContext(self.context, 'py27-post', [],
106 secrets)
James E. Blaira7f51ca2017-02-07 16:01:26 -0800107
108 py27 = model.Job('py27')
109 py27.timeout = 30
110 py27.pre_run = [py27_pre]
111 py27.run = [py27_run]
112 py27.post_run = [py27_post]
James E. Blaira7f51ca2017-02-07 16:01:26 -0800113
114 job = py27.copy()
115 self.assertEqual(30, job.timeout)
116
117 # Apply the diablo variant
118 diablo = model.Job('py27')
119 diablo.timeout = 40
120 job.applyVariant(diablo)
121
122 self.assertEqual(40, job.timeout)
123 self.assertEqual(['py27-pre'],
124 [x.path for x in job.pre_run])
125 self.assertEqual(['py27-run'],
126 [x.path for x in job.run])
127 self.assertEqual(['py27-post'],
128 [x.path for x in job.post_run])
James E. Blair892cca62017-08-09 11:36:58 -0700129 self.assertEqual(secrets, job.pre_run[0].secrets)
130 self.assertEqual(secrets, job.run[0].secrets)
131 self.assertEqual(secrets, job.post_run[0].secrets)
James E. Blaira7f51ca2017-02-07 16:01:26 -0800132
133 # Set the job to final for the following checks
134 job.final = True
135 self.assertTrue(job.voting)
136
137 good_final = model.Job('py27')
138 good_final.voting = False
139 job.applyVariant(good_final)
140 self.assertFalse(job.voting)
141
142 bad_final = model.Job('py27')
143 bad_final.timeout = 600
144 with testtools.ExpectedException(
145 Exception,
146 "Unable to modify final job"):
147 job.applyVariant(bad_final)
148
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700149 def test_job_inheritance_job_tree(self):
James E. Blair5ac93842017-01-20 06:47:34 -0800150 tenant = model.Tenant('tenant')
James E. Blair6459db12017-06-29 14:57:20 -0700151 layout = model.Layout(tenant)
James E. Blairaf8b2082017-10-03 15:38:27 -0700152
James E. Blair08d9b782017-06-29 14:22:48 -0700153 tpc = model.TenantProjectConfig(self.project)
154 tenant.addUntrustedProject(tpc)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700155
156 pipeline = model.Pipeline('gate', layout)
157 layout.addPipeline(pipeline)
158 queue = model.ChangeQueue(pipeline)
159
James E. Blair5ac93842017-01-20 06:47:34 -0800160 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800161 '_source_context': self.context,
162 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700163 'name': 'base',
James E. Blair2bab6e72017-08-07 09:52:45 -0700164 'parent': None,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700165 'timeout': 30,
166 })
167 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800168 python27 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800169 '_source_context': self.context,
170 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700171 'name': 'python27',
172 'parent': 'base',
173 'timeout': 40,
174 })
175 layout.addJob(python27)
James E. Blair5ac93842017-01-20 06:47:34 -0800176 python27diablo = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800177 '_source_context': self.context,
178 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700179 'name': 'python27',
180 'branches': [
181 'stable/diablo'
182 ],
183 'timeout': 50,
184 })
185 layout.addJob(python27diablo)
186
James E. Blairaf8b2082017-10-03 15:38:27 -0700187 project_template_parser = configloader.ProjectTemplateParser(
188 tenant, layout)
189 project_parser = configloader.ProjectParser(
190 tenant, layout, project_template_parser)
191 project_config = project_parser.fromYaml([{
James E. Blairec7ff302017-03-04 07:31:32 -0800192 '_source_context': self.context,
193 '_start_mark': self.start_mark,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700194 'name': 'project',
195 'gate': {
196 'jobs': [
James E. Blair00292672017-10-26 15:29:39 -0700197 {'python27': {'timeout': 70,
198 'run': 'playbooks/python27.yaml'}}
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700199 ]
200 }
James E. Blairff555742017-02-19 11:34:27 -0800201 }])
James E. Blairf59f3cf2017-02-19 14:50:26 -0800202 layout.addProjectConfig(project_config)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700203
James E. Blairec7ff302017-03-04 07:31:32 -0800204 change = model.Change(self.project)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700205 change.branch = 'master'
206 item = queue.enqueueChange(change)
James E. Blair29a24fd2017-10-02 15:04:56 -0700207 item.layout = layout
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700208
209 self.assertTrue(base.changeMatches(change))
210 self.assertTrue(python27.changeMatches(change))
211 self.assertFalse(python27diablo.changeMatches(change))
212
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200213 item.freezeJobGraph()
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700214 self.assertEqual(len(item.getJobs()), 1)
215 job = item.getJobs()[0]
216 self.assertEqual(job.name, 'python27')
217 self.assertEqual(job.timeout, 70)
218
219 change.branch = 'stable/diablo'
220 item = queue.enqueueChange(change)
James E. Blair29a24fd2017-10-02 15:04:56 -0700221 item.layout = layout
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700222
223 self.assertTrue(base.changeMatches(change))
224 self.assertTrue(python27.changeMatches(change))
225 self.assertTrue(python27diablo.changeMatches(change))
226
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200227 item.freezeJobGraph()
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700228 self.assertEqual(len(item.getJobs()), 1)
229 job = item.getJobs()[0]
230 self.assertEqual(job.name, 'python27')
231 self.assertEqual(job.timeout, 70)
232
Clint Byrum85493602016-11-18 11:59:47 -0800233 def test_inheritance_keeps_matchers(self):
James E. Blair5ac93842017-01-20 06:47:34 -0800234 tenant = model.Tenant('tenant')
James E. Blair6459db12017-06-29 14:57:20 -0700235 layout = model.Layout(tenant)
Clint Byrum85493602016-11-18 11:59:47 -0800236
237 pipeline = model.Pipeline('gate', layout)
238 layout.addPipeline(pipeline)
239 queue = model.ChangeQueue(pipeline)
James E. Blair0a899752017-03-29 13:22:16 -0700240 project = model.Project('project', self.source)
James E. Blair08d9b782017-06-29 14:22:48 -0700241 tpc = model.TenantProjectConfig(project)
242 tenant.addUntrustedProject(tpc)
Clint Byrum85493602016-11-18 11:59:47 -0800243
James E. Blair5ac93842017-01-20 06:47:34 -0800244 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800245 '_source_context': self.context,
246 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800247 'name': 'base',
James E. Blair2bab6e72017-08-07 09:52:45 -0700248 'parent': None,
Clint Byrum85493602016-11-18 11:59:47 -0800249 'timeout': 30,
250 })
251 layout.addJob(base)
James E. Blair5ac93842017-01-20 06:47:34 -0800252 python27 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blairec7ff302017-03-04 07:31:32 -0800253 '_source_context': self.context,
254 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800255 'name': 'python27',
256 'parent': 'base',
257 'timeout': 40,
258 'irrelevant-files': ['^ignored-file$'],
259 })
260 layout.addJob(python27)
261
James E. Blairaf8b2082017-10-03 15:38:27 -0700262 project_template_parser = configloader.ProjectTemplateParser(
263 tenant, layout)
264 project_parser = configloader.ProjectParser(
265 tenant, layout, project_template_parser)
266 project_config = project_parser.fromYaml([{
James E. Blairec7ff302017-03-04 07:31:32 -0800267 '_source_context': self.context,
268 '_start_mark': self.start_mark,
Clint Byrum85493602016-11-18 11:59:47 -0800269 'name': 'project',
270 'gate': {
271 'jobs': [
272 'python27',
273 ]
274 }
James E. Blairff555742017-02-19 11:34:27 -0800275 }])
James E. Blairf59f3cf2017-02-19 14:50:26 -0800276 layout.addProjectConfig(project_config)
Clint Byrum85493602016-11-18 11:59:47 -0800277
278 change = model.Change(project)
279 change.branch = 'master'
280 change.files = ['/COMMIT_MSG', 'ignored-file']
281 item = queue.enqueueChange(change)
James E. Blair29a24fd2017-10-02 15:04:56 -0700282 item.layout = layout
Clint Byrum85493602016-11-18 11:59:47 -0800283
284 self.assertTrue(base.changeMatches(change))
285 self.assertFalse(python27.changeMatches(change))
286
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200287 item.freezeJobGraph()
Clint Byrum85493602016-11-18 11:59:47 -0800288 self.assertEqual([], item.getJobs())
289
James E. Blair4317e9f2016-07-15 10:05:47 -0700290 def test_job_source_project(self):
James E. Blair6459db12017-06-29 14:57:20 -0700291 tenant = self.tenant
292 layout = self.layout
James E. Blair0a899752017-03-29 13:22:16 -0700293 base_project = model.Project('base_project', self.source)
James E. Blair6f140c72017-03-03 10:32:07 -0800294 base_context = model.SourceContext(base_project, 'master',
295 'test', True)
James E. Blair6459db12017-06-29 14:57:20 -0700296 tpc = model.TenantProjectConfig(base_project)
297 tenant.addUntrustedProject(tpc)
James E. Blaircdab2032017-02-01 09:09:29 -0800298
James E. Blair5ac93842017-01-20 06:47:34 -0800299 base = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800300 '_source_context': base_context,
James E. Blairec7ff302017-03-04 07:31:32 -0800301 '_start_mark': self.start_mark,
James E. Blair2bab6e72017-08-07 09:52:45 -0700302 'parent': None,
James E. Blair4317e9f2016-07-15 10:05:47 -0700303 'name': 'base',
304 })
305 layout.addJob(base)
306
James E. Blair0a899752017-03-29 13:22:16 -0700307 other_project = model.Project('other_project', self.source)
James E. Blair6f140c72017-03-03 10:32:07 -0800308 other_context = model.SourceContext(other_project, 'master',
309 'test', True)
James E. Blair6459db12017-06-29 14:57:20 -0700310 tpc = model.TenantProjectConfig(other_project)
311 tenant.addUntrustedProject(tpc)
James E. Blair5ac93842017-01-20 06:47:34 -0800312 base2 = configloader.JobParser.fromYaml(tenant, layout, {
James E. Blaircdab2032017-02-01 09:09:29 -0800313 '_source_context': other_context,
James E. Blairec7ff302017-03-04 07:31:32 -0800314 '_start_mark': self.start_mark,
James E. Blair4317e9f2016-07-15 10:05:47 -0700315 'name': 'base',
316 })
317 with testtools.ExpectedException(
318 Exception,
319 "Job base in other_project is not permitted "
320 "to shadow job base in base_project"):
321 layout.addJob(base2)
322
James E. Blairb3f5db12017-03-17 12:57:39 -0700323 def test_job_allowed_projects(self):
324 job = configloader.JobParser.fromYaml(self.tenant, self.layout, {
325 '_source_context': self.context,
326 '_start_mark': self.start_mark,
327 'name': 'job',
James E. Blair2bab6e72017-08-07 09:52:45 -0700328 'parent': None,
James E. Blairb3f5db12017-03-17 12:57:39 -0700329 'allowed-projects': ['project'],
330 })
331 self.layout.addJob(job)
332
James E. Blair0a899752017-03-29 13:22:16 -0700333 project2 = model.Project('project2', self.source)
James E. Blair08d9b782017-06-29 14:22:48 -0700334 tpc2 = model.TenantProjectConfig(project2)
335 self.tenant.addUntrustedProject(tpc2)
James E. Blairb3f5db12017-03-17 12:57:39 -0700336 context2 = model.SourceContext(project2, 'master',
337 'test', True)
338
James E. Blairaf8b2082017-10-03 15:38:27 -0700339 project_template_parser = configloader.ProjectTemplateParser(
340 self.tenant, self.layout)
341 project_parser = configloader.ProjectParser(
342 self.tenant, self.layout, project_template_parser)
343 project2_config = project_parser.fromYaml(
344 [{
James E. Blairb3f5db12017-03-17 12:57:39 -0700345 '_source_context': context2,
346 '_start_mark': self.start_mark,
347 'name': 'project2',
348 'gate': {
349 'jobs': [
350 'job'
351 ]
352 }
353 }]
354 )
355 self.layout.addProjectConfig(project2_config)
356
357 change = model.Change(project2)
358 # Test master
359 change.branch = 'master'
360 item = self.queue.enqueueChange(change)
James E. Blair29a24fd2017-10-02 15:04:56 -0700361 item.layout = self.layout
James E. Blairb3f5db12017-03-17 12:57:39 -0700362 with testtools.ExpectedException(
363 Exception,
364 "Project project2 is not allowed to run job job"):
365 item.freezeJobGraph()
366
James E. Blair8eb564a2017-08-10 09:21:41 -0700367 def test_job_pipeline_allow_untrusted_secrets(self):
368 self.pipeline.post_review = False
James E. Blaird2348362017-03-17 13:59:35 -0700369 job = configloader.JobParser.fromYaml(self.tenant, self.layout, {
370 '_source_context': self.context,
371 '_start_mark': self.start_mark,
372 'name': 'job',
James E. Blair2bab6e72017-08-07 09:52:45 -0700373 'parent': None,
James E. Blaird2348362017-03-17 13:59:35 -0700374 })
James E. Blair8eb564a2017-08-10 09:21:41 -0700375 job.post_review = True
James E. Blaird2348362017-03-17 13:59:35 -0700376
377 self.layout.addJob(job)
378
James E. Blairaf8b2082017-10-03 15:38:27 -0700379 project_template_parser = configloader.ProjectTemplateParser(
380 self.tenant, self.layout)
381 project_parser = configloader.ProjectParser(
382 self.tenant, self.layout, project_template_parser)
383 project_config = project_parser.fromYaml(
384 [{
James E. Blaird2348362017-03-17 13:59:35 -0700385 '_source_context': self.context,
386 '_start_mark': self.start_mark,
387 'name': 'project',
388 'gate': {
389 'jobs': [
390 'job'
391 ]
392 }
393 }]
394 )
395 self.layout.addProjectConfig(project_config)
396
397 change = model.Change(self.project)
398 # Test master
399 change.branch = 'master'
400 item = self.queue.enqueueChange(change)
James E. Blair29a24fd2017-10-02 15:04:56 -0700401 item.layout = self.layout
James E. Blaird2348362017-03-17 13:59:35 -0700402 with testtools.ExpectedException(
403 Exception,
James E. Blair8eb564a2017-08-10 09:21:41 -0700404 "Pre-review pipeline gate does not allow post-review job"):
James E. Blaird2348362017-03-17 13:59:35 -0700405 item.freezeJobGraph()
406
James E. Blairce8a2132016-05-19 15:21:52 -0700407
408class TestJobTimeData(BaseTestCase):
409 def setUp(self):
410 super(TestJobTimeData, self).setUp()
411 self.tmp_root = self.useFixture(fixtures.TempDir(
412 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
413 ).path
414
415 def test_empty_timedata(self):
416 path = os.path.join(self.tmp_root, 'job-name')
417 self.assertFalse(os.path.exists(path))
418 self.assertFalse(os.path.exists(path + '.tmp'))
419 td = model.JobTimeData(path)
420 self.assertEqual(td.success_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
421 self.assertEqual(td.failure_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
422 self.assertEqual(td.results, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
423
424 def test_save_reload(self):
425 path = os.path.join(self.tmp_root, 'job-name')
426 self.assertFalse(os.path.exists(path))
427 self.assertFalse(os.path.exists(path + '.tmp'))
428 td = model.JobTimeData(path)
429 self.assertEqual(td.success_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
430 self.assertEqual(td.failure_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
431 self.assertEqual(td.results, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
432 success_times = []
433 failure_times = []
434 results = []
435 for x in range(10):
436 success_times.append(int(random.random() * 1000))
437 failure_times.append(int(random.random() * 1000))
438 results.append(0)
439 results.append(1)
440 random.shuffle(results)
441 s = f = 0
442 for result in results:
443 if result:
444 td.add(failure_times[f], 'FAILURE')
445 f += 1
446 else:
447 td.add(success_times[s], 'SUCCESS')
448 s += 1
449 self.assertEqual(td.success_times, success_times)
450 self.assertEqual(td.failure_times, failure_times)
451 self.assertEqual(td.results, results[10:])
452 td.save()
453 self.assertTrue(os.path.exists(path))
454 self.assertFalse(os.path.exists(path + '.tmp'))
455 td = model.JobTimeData(path)
456 td.load()
457 self.assertEqual(td.success_times, success_times)
458 self.assertEqual(td.failure_times, failure_times)
459 self.assertEqual(td.results, results[10:])
460
461
462class TestTimeDataBase(BaseTestCase):
463 def setUp(self):
464 super(TestTimeDataBase, self).setUp()
465 self.tmp_root = self.useFixture(fixtures.TempDir(
466 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
467 ).path
468 self.db = model.TimeDataBase(self.tmp_root)
469
470 def test_timedatabase(self):
James E. Blairae0f23c2017-09-13 10:55:15 -0600471 pipeline = Dummy(layout=Dummy(tenant=Dummy(name='test-tenant')))
472 change = Dummy(project=Dummy(canonical_name='git.example.com/foo/bar'))
473 job = Dummy(name='job-name')
474 item = Dummy(pipeline=pipeline,
475 change=change)
476 build = Dummy(build_set=Dummy(item=item),
477 job=job)
478
479 self.assertEqual(self.db.getEstimatedTime(build), 0)
480 self.db.update(build, 50, 'SUCCESS')
481 self.assertEqual(self.db.getEstimatedTime(build), 50)
482 self.db.update(build, 100, 'SUCCESS')
483 self.assertEqual(self.db.getEstimatedTime(build), 75)
James E. Blairce8a2132016-05-19 15:21:52 -0700484 for x in range(10):
James E. Blairae0f23c2017-09-13 10:55:15 -0600485 self.db.update(build, 100, 'SUCCESS')
486 self.assertEqual(self.db.getEstimatedTime(build), 100)
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200487
488
489class TestGraph(BaseTestCase):
490 def test_job_graph_disallows_multiple_jobs_with_same_name(self):
491 graph = model.JobGraph()
492 job1 = model.Job('job')
493 job2 = model.Job('job')
494 graph.addJob(job1)
495 with testtools.ExpectedException(Exception,
496 "Job job already added"):
497 graph.addJob(job2)
498
499 def test_job_graph_disallows_circular_dependencies(self):
500 graph = model.JobGraph()
501 jobs = [model.Job('job%d' % i) for i in range(0, 10)]
502 prevjob = None
503 for j in jobs[:3]:
504 if prevjob:
505 j.dependencies = frozenset([prevjob.name])
506 graph.addJob(j)
507 prevjob = j
508 # 0 triggers 1 triggers 2 triggers 3...
509
510 # Cannot depend on itself
511 with testtools.ExpectedException(
512 Exception,
513 "Dependency cycle detected in job jobX"):
514 j = model.Job('jobX')
515 j.dependencies = frozenset([j.name])
516 graph.addJob(j)
517
518 # Disallow circular dependencies
519 with testtools.ExpectedException(
520 Exception,
521 "Dependency cycle detected in job job3"):
522 jobs[4].dependencies = frozenset([jobs[3].name])
523 graph.addJob(jobs[4])
524 jobs[3].dependencies = frozenset([jobs[4].name])
525 graph.addJob(jobs[3])
526
527 jobs[5].dependencies = frozenset([jobs[4].name])
528 graph.addJob(jobs[5])
529
530 with testtools.ExpectedException(
531 Exception,
532 "Dependency cycle detected in job job3"):
533 jobs[3].dependencies = frozenset([jobs[5].name])
534 graph.addJob(jobs[3])
535
536 jobs[3].dependencies = frozenset([jobs[2].name])
537 graph.addJob(jobs[3])
538 jobs[6].dependencies = frozenset([jobs[2].name])
539 graph.addJob(jobs[6])
James E. Blairc2a54fd2017-03-29 15:19:26 -0700540
541
542class TestTenant(BaseTestCase):
543 def test_add_project(self):
544 tenant = model.Tenant('tenant')
545 connection1 = Dummy(connection_name='dummy_connection1')
546 source1 = Dummy(canonical_hostname='git1.example.com',
547 name='dummy', # TODOv3(jeblair): remove
548 connection=connection1)
549
550 source1_project1 = model.Project('project1', source1)
James E. Blair08d9b782017-06-29 14:22:48 -0700551 source1_project1_tpc = model.TenantProjectConfig(source1_project1)
552 tenant.addConfigProject(source1_project1_tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700553 d = {'project1':
554 {'git1.example.com': source1_project1}}
555 self.assertEqual(d, tenant.projects)
556 self.assertEqual((True, source1_project1),
557 tenant.getProject('project1'))
558 self.assertEqual((True, source1_project1),
559 tenant.getProject('git1.example.com/project1'))
560
561 source1_project2 = model.Project('project2', source1)
James E. Blair08d9b782017-06-29 14:22:48 -0700562 tpc = model.TenantProjectConfig(source1_project2)
563 tenant.addUntrustedProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700564 d = {'project1':
565 {'git1.example.com': source1_project1},
566 'project2':
567 {'git1.example.com': source1_project2}}
568 self.assertEqual(d, tenant.projects)
569 self.assertEqual((False, source1_project2),
570 tenant.getProject('project2'))
571 self.assertEqual((False, source1_project2),
572 tenant.getProject('git1.example.com/project2'))
573
574 connection2 = Dummy(connection_name='dummy_connection2')
575 source2 = Dummy(canonical_hostname='git2.example.com',
576 name='dummy', # TODOv3(jeblair): remove
577 connection=connection2)
578
579 source2_project1 = model.Project('project1', source2)
James E. Blair08d9b782017-06-29 14:22:48 -0700580 tpc = model.TenantProjectConfig(source2_project1)
581 tenant.addUntrustedProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700582 d = {'project1':
583 {'git1.example.com': source1_project1,
584 'git2.example.com': source2_project1},
585 'project2':
586 {'git1.example.com': source1_project2}}
587 self.assertEqual(d, tenant.projects)
588 with testtools.ExpectedException(
589 Exception,
590 "Project name 'project1' is ambiguous"):
591 tenant.getProject('project1')
592 self.assertEqual((False, source1_project2),
593 tenant.getProject('project2'))
594 self.assertEqual((True, source1_project1),
595 tenant.getProject('git1.example.com/project1'))
596 self.assertEqual((False, source2_project1),
597 tenant.getProject('git2.example.com/project1'))
598
599 source2_project2 = model.Project('project2', source2)
James E. Blair08d9b782017-06-29 14:22:48 -0700600 tpc = model.TenantProjectConfig(source2_project2)
601 tenant.addConfigProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700602 d = {'project1':
603 {'git1.example.com': source1_project1,
604 'git2.example.com': source2_project1},
605 'project2':
606 {'git1.example.com': source1_project2,
607 'git2.example.com': source2_project2}}
608 self.assertEqual(d, tenant.projects)
609 with testtools.ExpectedException(
610 Exception,
611 "Project name 'project1' is ambiguous"):
612 tenant.getProject('project1')
613 with testtools.ExpectedException(
614 Exception,
615 "Project name 'project2' is ambiguous"):
616 tenant.getProject('project2')
617 self.assertEqual((True, source1_project1),
618 tenant.getProject('git1.example.com/project1'))
619 self.assertEqual((False, source2_project1),
620 tenant.getProject('git2.example.com/project1'))
621 self.assertEqual((False, source1_project2),
622 tenant.getProject('git1.example.com/project2'))
623 self.assertEqual((True, source2_project2),
624 tenant.getProject('git2.example.com/project2'))
625
626 source1_project2b = model.Project('subpath/project2', source1)
James E. Blair08d9b782017-06-29 14:22:48 -0700627 tpc = model.TenantProjectConfig(source1_project2b)
628 tenant.addConfigProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700629 d = {'project1':
630 {'git1.example.com': source1_project1,
631 'git2.example.com': source2_project1},
632 'project2':
633 {'git1.example.com': source1_project2,
634 'git2.example.com': source2_project2},
635 'subpath/project2':
636 {'git1.example.com': source1_project2b}}
637 self.assertEqual(d, tenant.projects)
638 self.assertEqual((False, source1_project2),
639 tenant.getProject('git1.example.com/project2'))
640 self.assertEqual((True, source2_project2),
641 tenant.getProject('git2.example.com/project2'))
642 self.assertEqual((True, source1_project2b),
643 tenant.getProject('subpath/project2'))
644 self.assertEqual(
645 (True, source1_project2b),
646 tenant.getProject('git1.example.com/subpath/project2'))
647
648 source2_project2b = model.Project('subpath/project2', source2)
James E. Blair08d9b782017-06-29 14:22:48 -0700649 tpc = model.TenantProjectConfig(source2_project2b)
650 tenant.addConfigProject(tpc)
James E. Blairc2a54fd2017-03-29 15:19:26 -0700651 d = {'project1':
652 {'git1.example.com': source1_project1,
653 'git2.example.com': source2_project1},
654 'project2':
655 {'git1.example.com': source1_project2,
656 'git2.example.com': source2_project2},
657 'subpath/project2':
658 {'git1.example.com': source1_project2b,
659 'git2.example.com': source2_project2b}}
660 self.assertEqual(d, tenant.projects)
661 self.assertEqual((False, source1_project2),
662 tenant.getProject('git1.example.com/project2'))
663 self.assertEqual((True, source2_project2),
664 tenant.getProject('git2.example.com/project2'))
665 with testtools.ExpectedException(
666 Exception,
667 "Project name 'subpath/project2' is ambiguous"):
668 tenant.getProject('subpath/project2')
669 self.assertEqual(
670 (True, source1_project2b),
671 tenant.getProject('git1.example.com/subpath/project2'))
672 self.assertEqual(
673 (True, source2_project2b),
674 tenant.getProject('git2.example.com/subpath/project2'))
675
676 with testtools.ExpectedException(
677 Exception,
678 "Project project1 is already in project index"):
James E. Blair08d9b782017-06-29 14:22:48 -0700679 tenant._addProject(source1_project1_tpc)