blob: ae404168ccc4f822455e4ec33e0f9c23dc1d98e7 [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
Maru Newby3fe5f852015-01-13 04:22:14 +000024
25from tests.base import BaseTestCase
26
27
28class TestJob(BaseTestCase):
29
30 @property
31 def job(self):
James E. Blair83005782015-12-11 14:46:03 -080032 layout = model.Layout()
33 job = configloader.JobParser.fromYaml(layout, {
34 'name': 'job',
35 'irrelevant-files': [
36 '^docs/.*$'
37 ]})
Maru Newby3fe5f852015-01-13 04:22:14 +000038 return job
39
40 def test_change_matches_returns_false_for_matched_skip_if(self):
41 change = model.Change('project')
Alexander Evseevdbe6fab2015-11-19 12:46:34 +030042 change.files = ['/COMMIT_MSG', 'docs/foo']
Maru Newby3fe5f852015-01-13 04:22:14 +000043 self.assertFalse(self.job.changeMatches(change))
44
45 def test_change_matches_returns_true_for_unmatched_skip_if(self):
46 change = model.Change('project')
Alexander Evseevdbe6fab2015-11-19 12:46:34 +030047 change.files = ['/COMMIT_MSG', 'foo']
Maru Newby3fe5f852015-01-13 04:22:14 +000048 self.assertTrue(self.job.changeMatches(change))
49
Maru Newby79427a42015-02-17 17:54:45 +000050 def test_job_sets_defaults_for_boolean_attributes(self):
James E. Blair83005782015-12-11 14:46:03 -080051 self.assertIsNotNone(self.job.voting)
52
53 def test_job_inheritance(self):
54 layout = model.Layout()
James E. Blair8b1dc3f2016-07-05 16:49:00 -070055
56 pipeline = model.Pipeline('gate', layout)
57 layout.addPipeline(pipeline)
58 queue = model.ChangeQueue(pipeline)
James E. Blairc73c73a2017-01-20 15:15:15 -080059 project = model.Project('project', None)
James E. Blair8b1dc3f2016-07-05 16:49:00 -070060
James E. Blair83005782015-12-11 14:46:03 -080061 base = configloader.JobParser.fromYaml(layout, {
James E. Blair4317e9f2016-07-15 10:05:47 -070062 '_source_project': project,
James E. Blair83005782015-12-11 14:46:03 -080063 'name': 'base',
64 'timeout': 30,
James E. Blair1774dd52017-02-03 10:52:32 -080065 'nodes': [{
66 'name': 'controller',
67 'image': 'base',
68 }],
James E. Blair83005782015-12-11 14:46:03 -080069 })
70 layout.addJob(base)
71 python27 = configloader.JobParser.fromYaml(layout, {
James E. Blair4317e9f2016-07-15 10:05:47 -070072 '_source_project': project,
James E. Blair83005782015-12-11 14:46:03 -080073 'name': 'python27',
74 'parent': 'base',
James E. Blair1774dd52017-02-03 10:52:32 -080075 'nodes': [{
76 'name': 'controller',
77 'image': 'new',
78 }],
James E. Blair83005782015-12-11 14:46:03 -080079 'timeout': 40,
80 })
81 layout.addJob(python27)
82 python27diablo = configloader.JobParser.fromYaml(layout, {
James E. Blair4317e9f2016-07-15 10:05:47 -070083 '_source_project': project,
James E. Blair83005782015-12-11 14:46:03 -080084 'name': 'python27',
85 'branches': [
86 'stable/diablo'
87 ],
James E. Blair1774dd52017-02-03 10:52:32 -080088 'nodes': [{
89 'name': 'controller',
90 'image': 'old',
91 }],
James E. Blair83005782015-12-11 14:46:03 -080092 'timeout': 50,
93 })
94 layout.addJob(python27diablo)
95
James E. Blair8b1dc3f2016-07-05 16:49:00 -070096 project_config = configloader.ProjectParser.fromYaml(layout, {
97 'name': 'project',
98 'gate': {
99 'jobs': [
100 'python27'
101 ]
102 }
103 })
104 layout.addProjectConfig(project_config, update_pipeline=False)
James E. Blair83005782015-12-11 14:46:03 -0800105
James E. Blair83005782015-12-11 14:46:03 -0800106 change = model.Change(project)
James E. Blair1774dd52017-02-03 10:52:32 -0800107 # Test master
James E. Blair83005782015-12-11 14:46:03 -0800108 change.branch = 'master'
109 item = queue.enqueueChange(change)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700110 item.current_build_set.layout = layout
James E. Blair83005782015-12-11 14:46:03 -0800111
112 self.assertTrue(base.changeMatches(change))
113 self.assertTrue(python27.changeMatches(change))
114 self.assertFalse(python27diablo.changeMatches(change))
115
116 item.freezeJobTree()
117 self.assertEqual(len(item.getJobs()), 1)
118 job = item.getJobs()[0]
119 self.assertEqual(job.name, 'python27')
120 self.assertEqual(job.timeout, 40)
James E. Blair1774dd52017-02-03 10:52:32 -0800121 nodes = job.nodeset.getNodes()
122 self.assertEqual(len(nodes), 1)
123 self.assertEqual(nodes[0].image, 'new')
James E. Blair83005782015-12-11 14:46:03 -0800124
James E. Blair1774dd52017-02-03 10:52:32 -0800125 # Test diablo
James E. Blair83005782015-12-11 14:46:03 -0800126 change.branch = 'stable/diablo'
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700127 item = queue.enqueueChange(change)
128 item.current_build_set.layout = layout
James E. Blair83005782015-12-11 14:46:03 -0800129
130 self.assertTrue(base.changeMatches(change))
131 self.assertTrue(python27.changeMatches(change))
132 self.assertTrue(python27diablo.changeMatches(change))
133
134 item.freezeJobTree()
135 self.assertEqual(len(item.getJobs()), 1)
136 job = item.getJobs()[0]
137 self.assertEqual(job.name, 'python27')
138 self.assertEqual(job.timeout, 50)
James E. Blair1774dd52017-02-03 10:52:32 -0800139 nodes = job.nodeset.getNodes()
140 self.assertEqual(len(nodes), 1)
141 self.assertEqual(nodes[0].image, 'old')
James E. Blairce8a2132016-05-19 15:21:52 -0700142
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000143 def test_job_auth_inheritance(self):
144 layout = model.Layout()
James E. Blairc73c73a2017-01-20 15:15:15 -0800145 project = model.Project('project', None)
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000146
147 base = configloader.JobParser.fromYaml(layout, {
148 '_source_project': project,
149 'name': 'base',
150 'timeout': 30,
151 })
152 layout.addJob(base)
153 pypi_upload_without_inherit = configloader.JobParser.fromYaml(layout, {
154 '_source_project': project,
155 'name': 'pypi-upload-without-inherit',
156 'parent': 'base',
157 'timeout': 40,
158 'auth': {
Ricardo Carrillo Cruz12c892b2016-11-18 15:35:49 +0000159 'secrets': [
160 'pypi-credentials',
161 ]
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000162 }
163 })
164 layout.addJob(pypi_upload_without_inherit)
165 pypi_upload_with_inherit = configloader.JobParser.fromYaml(layout, {
166 '_source_project': project,
167 'name': 'pypi-upload-with-inherit',
168 'parent': 'base',
169 'timeout': 40,
170 'auth': {
171 'inherit': True,
Ricardo Carrillo Cruz12c892b2016-11-18 15:35:49 +0000172 'secrets': [
173 'pypi-credentials',
174 ]
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000175 }
176 })
177 layout.addJob(pypi_upload_with_inherit)
178 pypi_upload_with_inherit_false = configloader.JobParser.fromYaml(
179 layout, {
180 '_source_project': project,
181 'name': 'pypi-upload-with-inherit-false',
182 'parent': 'base',
183 'timeout': 40,
184 'auth': {
185 'inherit': False,
Ricardo Carrillo Cruz12c892b2016-11-18 15:35:49 +0000186 'secrets': [
187 'pypi-credentials',
188 ]
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000189 }
190 })
191 layout.addJob(pypi_upload_with_inherit_false)
192 in_repo_job_without_inherit = configloader.JobParser.fromYaml(layout, {
193 '_source_project': project,
194 'name': 'in-repo-job-without-inherit',
195 'parent': 'pypi-upload-without-inherit',
196 })
197 layout.addJob(in_repo_job_without_inherit)
198 in_repo_job_with_inherit = configloader.JobParser.fromYaml(layout, {
199 '_source_project': project,
200 'name': 'in-repo-job-with-inherit',
201 'parent': 'pypi-upload-with-inherit',
202 })
203 layout.addJob(in_repo_job_with_inherit)
204 in_repo_job_with_inherit_false = configloader.JobParser.fromYaml(
205 layout, {
206 '_source_project': project,
207 'name': 'in-repo-job-with-inherit-false',
208 'parent': 'pypi-upload-with-inherit-false',
209 })
210 layout.addJob(in_repo_job_with_inherit_false)
211
212 self.assertNotIn('auth', in_repo_job_without_inherit.auth)
Ricardo Carrillo Cruz12c892b2016-11-18 15:35:49 +0000213 self.assertIn('secrets', in_repo_job_with_inherit.auth)
214 self.assertEquals(in_repo_job_with_inherit.auth['secrets'],
215 ['pypi-credentials'])
Ricardo Carrillo Cruz4e94f612016-07-25 16:11:56 +0000216 self.assertNotIn('auth', in_repo_job_with_inherit_false.auth)
217
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700218 def test_job_inheritance_job_tree(self):
219 layout = model.Layout()
220
221 pipeline = model.Pipeline('gate', layout)
222 layout.addPipeline(pipeline)
223 queue = model.ChangeQueue(pipeline)
James E. Blairc73c73a2017-01-20 15:15:15 -0800224 project = model.Project('project', None)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700225
226 base = configloader.JobParser.fromYaml(layout, {
James E. Blair4317e9f2016-07-15 10:05:47 -0700227 '_source_project': project,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700228 'name': 'base',
229 'timeout': 30,
230 })
231 layout.addJob(base)
232 python27 = configloader.JobParser.fromYaml(layout, {
James E. Blair4317e9f2016-07-15 10:05:47 -0700233 '_source_project': project,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700234 'name': 'python27',
235 'parent': 'base',
236 'timeout': 40,
237 })
238 layout.addJob(python27)
239 python27diablo = configloader.JobParser.fromYaml(layout, {
James E. Blair4317e9f2016-07-15 10:05:47 -0700240 '_source_project': project,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700241 'name': 'python27',
242 'branches': [
243 'stable/diablo'
244 ],
245 'timeout': 50,
246 })
247 layout.addJob(python27diablo)
248
249 project_config = configloader.ProjectParser.fromYaml(layout, {
250 'name': 'project',
251 'gate': {
252 'jobs': [
253 {'python27': {'timeout': 70}}
254 ]
255 }
256 })
257 layout.addProjectConfig(project_config, update_pipeline=False)
258
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700259 change = model.Change(project)
260 change.branch = 'master'
261 item = queue.enqueueChange(change)
262 item.current_build_set.layout = layout
263
264 self.assertTrue(base.changeMatches(change))
265 self.assertTrue(python27.changeMatches(change))
266 self.assertFalse(python27diablo.changeMatches(change))
267
268 item.freezeJobTree()
269 self.assertEqual(len(item.getJobs()), 1)
270 job = item.getJobs()[0]
271 self.assertEqual(job.name, 'python27')
272 self.assertEqual(job.timeout, 70)
273
274 change.branch = 'stable/diablo'
275 item = queue.enqueueChange(change)
276 item.current_build_set.layout = layout
277
278 self.assertTrue(base.changeMatches(change))
279 self.assertTrue(python27.changeMatches(change))
280 self.assertTrue(python27diablo.changeMatches(change))
281
282 item.freezeJobTree()
283 self.assertEqual(len(item.getJobs()), 1)
284 job = item.getJobs()[0]
285 self.assertEqual(job.name, 'python27')
286 self.assertEqual(job.timeout, 70)
287
Clint Byrum85493602016-11-18 11:59:47 -0800288 def test_inheritance_keeps_matchers(self):
289 layout = model.Layout()
290
291 pipeline = model.Pipeline('gate', layout)
292 layout.addPipeline(pipeline)
293 queue = model.ChangeQueue(pipeline)
James E. Blairc73c73a2017-01-20 15:15:15 -0800294 project = model.Project('project', None)
Clint Byrum85493602016-11-18 11:59:47 -0800295
296 base = configloader.JobParser.fromYaml(layout, {
297 '_source_project': project,
298 'name': 'base',
299 'timeout': 30,
300 })
301 layout.addJob(base)
302 python27 = configloader.JobParser.fromYaml(layout, {
303 '_source_project': project,
304 'name': 'python27',
305 'parent': 'base',
306 'timeout': 40,
307 'irrelevant-files': ['^ignored-file$'],
308 })
309 layout.addJob(python27)
310
311 project_config = configloader.ProjectParser.fromYaml(layout, {
312 'name': 'project',
313 'gate': {
314 'jobs': [
315 'python27',
316 ]
317 }
318 })
319 layout.addProjectConfig(project_config, update_pipeline=False)
320
321 change = model.Change(project)
322 change.branch = 'master'
323 change.files = ['/COMMIT_MSG', 'ignored-file']
324 item = queue.enqueueChange(change)
325 item.current_build_set.layout = layout
326
327 self.assertTrue(base.changeMatches(change))
328 self.assertFalse(python27.changeMatches(change))
329
330 item.freezeJobTree()
331 self.assertEqual([], item.getJobs())
332
James E. Blair4317e9f2016-07-15 10:05:47 -0700333 def test_job_source_project(self):
334 layout = model.Layout()
James E. Blairc73c73a2017-01-20 15:15:15 -0800335 base_project = model.Project('base_project', None)
James E. Blair4317e9f2016-07-15 10:05:47 -0700336 base = configloader.JobParser.fromYaml(layout, {
337 '_source_project': base_project,
338 'name': 'base',
339 })
340 layout.addJob(base)
341
James E. Blairc73c73a2017-01-20 15:15:15 -0800342 other_project = model.Project('other_project', None)
James E. Blair4317e9f2016-07-15 10:05:47 -0700343 base2 = configloader.JobParser.fromYaml(layout, {
344 '_source_project': other_project,
345 'name': 'base',
346 })
347 with testtools.ExpectedException(
348 Exception,
349 "Job base in other_project is not permitted "
350 "to shadow job base in base_project"):
351 layout.addJob(base2)
352
James E. Blairce8a2132016-05-19 15:21:52 -0700353
354class TestJobTimeData(BaseTestCase):
355 def setUp(self):
356 super(TestJobTimeData, self).setUp()
357 self.tmp_root = self.useFixture(fixtures.TempDir(
358 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
359 ).path
360
361 def test_empty_timedata(self):
362 path = os.path.join(self.tmp_root, 'job-name')
363 self.assertFalse(os.path.exists(path))
364 self.assertFalse(os.path.exists(path + '.tmp'))
365 td = model.JobTimeData(path)
366 self.assertEqual(td.success_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
367 self.assertEqual(td.failure_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
368 self.assertEqual(td.results, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
369
370 def test_save_reload(self):
371 path = os.path.join(self.tmp_root, 'job-name')
372 self.assertFalse(os.path.exists(path))
373 self.assertFalse(os.path.exists(path + '.tmp'))
374 td = model.JobTimeData(path)
375 self.assertEqual(td.success_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
376 self.assertEqual(td.failure_times, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
377 self.assertEqual(td.results, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
378 success_times = []
379 failure_times = []
380 results = []
381 for x in range(10):
382 success_times.append(int(random.random() * 1000))
383 failure_times.append(int(random.random() * 1000))
384 results.append(0)
385 results.append(1)
386 random.shuffle(results)
387 s = f = 0
388 for result in results:
389 if result:
390 td.add(failure_times[f], 'FAILURE')
391 f += 1
392 else:
393 td.add(success_times[s], 'SUCCESS')
394 s += 1
395 self.assertEqual(td.success_times, success_times)
396 self.assertEqual(td.failure_times, failure_times)
397 self.assertEqual(td.results, results[10:])
398 td.save()
399 self.assertTrue(os.path.exists(path))
400 self.assertFalse(os.path.exists(path + '.tmp'))
401 td = model.JobTimeData(path)
402 td.load()
403 self.assertEqual(td.success_times, success_times)
404 self.assertEqual(td.failure_times, failure_times)
405 self.assertEqual(td.results, results[10:])
406
407
408class TestTimeDataBase(BaseTestCase):
409 def setUp(self):
410 super(TestTimeDataBase, self).setUp()
411 self.tmp_root = self.useFixture(fixtures.TempDir(
412 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
413 ).path
414 self.db = model.TimeDataBase(self.tmp_root)
415
416 def test_timedatabase(self):
417 self.assertEqual(self.db.getEstimatedTime('job-name'), 0)
418 self.db.update('job-name', 50, 'SUCCESS')
419 self.assertEqual(self.db.getEstimatedTime('job-name'), 50)
420 self.db.update('job-name', 100, 'SUCCESS')
421 self.assertEqual(self.db.getEstimatedTime('job-name'), 75)
422 for x in range(10):
423 self.db.update('job-name', 100, 'SUCCESS')
424 self.assertEqual(self.db.getEstimatedTime('job-name'), 100)