The Gatekeeper, or a project gating system
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

647 lines
25 KiB

# Copyright 2015 BMW Car IT GmbH
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import textwrap
from unittest import mock
import tests.base
from tests.base import (
BaseTestCase, ZuulTestCase, AnsibleZuulTestCase,
simple_layout)
from zuul.driver.gerrit import GerritDriver
from zuul.driver.gerrit.gerritconnection import GerritConnection
FIXTURE_DIR = os.path.join(tests.base.FIXTURE_DIR, 'gerrit')
def read_fixture(file):
with open('%s/%s' % (FIXTURE_DIR, file), 'r') as fixturefile:
lines = fixturefile.readlines()
command = lines[0].replace('\n', '')
value = ''.join(lines[1:])
return command, value
def read_fixtures(files):
calls = []
values = []
for fixture_file in files:
command, value = read_fixture(fixture_file)
calls.append(mock.call(command))
values.append([value, ''])
return calls, values
class TestGerrit(BaseTestCase):
@mock.patch('zuul.driver.gerrit.gerritconnection.GerritConnection._ssh')
def run_query(self, files, expected_patches, _ssh_mock):
gerrit_config = {
'user': 'gerrit',
'server': 'localhost',
}
driver = GerritDriver()
gerrit = GerritConnection(driver, 'review_gerrit', gerrit_config)
calls, values = read_fixtures(files)
_ssh_mock.side_effect = values
result = gerrit.simpleQuery('project:zuul/zuul')
_ssh_mock.assert_has_calls(calls)
self.assertEqual(len(calls), _ssh_mock.call_count,
'_ssh should be called %d times' % len(calls))
self.assertIsNotNone(result, 'Result is not none')
self.assertEqual(len(result), expected_patches,
'There must be %d patches.' % expected_patches)
def test_simple_query_pagination_new(self):
files = ['simple_query_pagination_new_1',
'simple_query_pagination_new_2']
expected_patches = 5
self.run_query(files, expected_patches)
def test_simple_query_pagination_old(self):
files = ['simple_query_pagination_old_1',
'simple_query_pagination_old_2',
'simple_query_pagination_old_3']
expected_patches = 5
self.run_query(files, expected_patches)
def test_ref_name_check_rules(self):
# See man git-check-ref-format for the rules referenced here
test_strings = [
('refs/heads/normal', True),
('refs/heads/.bad', False), # rule 1
('refs/heads/bad.lock', False), # rule 1
('refs/heads/good.locked', True),
('refs/heads/go.od', True),
('refs/heads//bad', False), # rule 6
('refs/heads/b?d', False), # rule 5
('refs/heads/b[d', False), # rule 5
('refs/heads/b..ad', False), # rule 3
('bad', False), # rule 2
('refs/heads/\nbad', False), # rule 4
('/refs/heads/bad', False), # rule 6
('refs/heads/bad/', False), # rule 6
('refs/heads/bad.', False), # rule 7
('.refs/heads/bad', False), # rule 1
('refs/he@{ads/bad', False), # rule 8
('@', False), # rule 9
('refs\\heads/bad', False) # rule 10
]
for ref, accepted in test_strings:
self.assertEqual(
accepted,
GerritConnection._checkRefFormat(ref),
ref + ' shall be ' + ('accepted' if accepted else 'rejected'))
def test_getGitURL(self):
gerrit_config = {
'user': 'gerrit',
'server': 'localhost',
'password': '1/badpassword',
}
# The 1/ in the password ensures we test the url encoding
# path; this is the format of password we get from
# googlesource.com.
driver = GerritDriver()
gerrit = GerritConnection(driver, 'review_gerrit', gerrit_config)
project = gerrit.source.getProject('org/project')
url = gerrit.source.getGitUrl(project)
self.assertEqual(
'https://gerrit:1%2Fbadpassword@localhost/org/project',
url)
class TestGerritWeb(ZuulTestCase):
config_file = 'zuul-gerrit-web.conf'
tenant_config_file = 'config/single-tenant/main.yaml'
def test_jobs_executed(self):
"Test that jobs are executed and a change is merged"
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
A.addApproval('Code-Review', 2)
self.fake_gerrit.addEvent(A.addApproval('Approved', 1))
self.waitUntilSettled()
self.assertEqual(self.getJobFromHistory('project-merge').result,
'SUCCESS')
self.assertEqual(self.getJobFromHistory('project-test1').result,
'SUCCESS')
self.assertEqual(self.getJobFromHistory('project-test2').result,
'SUCCESS')
self.assertEqual(A.data['status'], 'MERGED')
self.assertEqual(A.reported, 2)
self.assertEqual(self.getJobFromHistory('project-test1').node,
'label1')
self.assertEqual(self.getJobFromHistory('project-test2').node,
'label1')
def test_dynamic_line_comment(self):
in_repo_conf = textwrap.dedent(
"""
- job:
name: garbage-job
garbage: True
""")
file_dict = {'.zuul.yaml': in_repo_conf}
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A',
files=file_dict)
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
self.assertEqual(A.patchsets[0]['approvals'][0]['value'], "-1")
self.assertEqual(A.patchsets[0]['approvals'][0]['__tag'],
"autogenerated:zuul:check")
self.assertIn('Zuul encountered a syntax error',
A.messages[0])
comments = sorted(A.comments, key=lambda x: x['line'])
self.assertEqual(comments[0],
{'file': '.zuul.yaml',
'line': 4,
'message': "extra keys not allowed @ "
"data['garbage']",
'range': {'end_character': 0,
'end_line': 4,
'start_character': 2,
'start_line': 2},
'reviewer': {'email': 'zuul@example.com',
'name': 'Zuul',
'username': 'jenkins'}}
)
def test_dependent_dynamic_line_comment(self):
in_repo_conf = textwrap.dedent(
"""
- job:
name: garbage-job
garbage: True
""")
file_dict = {'.zuul.yaml': in_repo_conf}
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A',
files=file_dict)
B = self.fake_gerrit.addFakeChange('org/project1', 'master', 'B')
B.data['commitMessage'] = '%s\n\nDepends-On: %s\n' % (
B.subject, A.data['id'])
self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
self.assertEqual(B.patchsets[0]['approvals'][0]['value'], "-1")
self.assertIn('This change depends on a change '
'with an invalid configuration',
B.messages[0])
self.assertEqual(B.comments, [])
@simple_layout('layouts/single-file-matcher.yaml')
def test_single_file(self):
# HTTP requests don't return a commit_msg entry in the files
# list, but the rest of zuul always expects one. This test
# returns a single file to exercise the single-file code path
# in the files matcher.
files = {'README': 'please!\n'}
change = self.fake_gerrit.addFakeChange('org/project',
'master',
'test irrelevant-files',
files=files)
self.fake_gerrit.addEvent(change.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
tested_change_ids = [x.changes[0] for x in self.history
if x.name == 'project-test-irrelevant-files']
self.assertEqual([], tested_change_ids)
class TestFileComments(AnsibleZuulTestCase):
config_file = 'zuul-gerrit-web.conf'
tenant_config_file = 'config/gerrit-file-comments/main.yaml'
def test_file_comments(self):
A = self.fake_gerrit.addFakeChange(
'org/project', 'master', 'A',
files={'path/to/file.py': 'test1',
'otherfile.txt': 'test2',
})
A.addApproval('Code-Review', 2)
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
self.assertEqual(self.getJobFromHistory('file-comments').result,
'SUCCESS')
self.assertEqual(self.getJobFromHistory('file-comments-error').result,
'SUCCESS')
self.assertEqual(len(A.comments), 6)
comments = sorted(A.comments, key=lambda x: (x['file'], x['line']))
self.assertEqual(
comments[0],
{
'file': 'otherfile.txt',
'line': 21,
'message': 'This is a much longer message.\n\n'
'With multiple paragraphs.\n',
'reviewer': {
'email': 'zuul@example.com',
'name': 'Zuul',
'username': 'jenkins'
},
},
)
self.assertEqual(
comments[1],
{
"file": "path/to/file.py",
"line": 2,
"message": "levels are ignored by gerrit",
"reviewer": {
"email": "zuul@example.com",
"name": "Zuul",
"username": "jenkins",
},
},
)
self.assertEqual(
comments[2],
{
"file": "path/to/file.py",
"line": 21,
"message": (
"A second zuul return value using the same file should not"
"\noverride the first result, but both should be merged.\n"
),
"reviewer": {
"email": "zuul@example.com",
"name": "Zuul",
"username": "jenkins",
},
},
)
self.assertEqual(
comments[3],
{
'file': 'path/to/file.py',
'line': 42,
'message': 'line too long',
'reviewer': {
'email': 'zuul@example.com',
'name': 'Zuul',
'username': 'jenkins'
},
},
)
self.assertEqual(
comments[4],
{
"file": "path/to/file.py",
"line": 42,
"message": (
"A second comment applied to the same line in the same "
"file\nshould also be added to the result.\n"
),
"reviewer": {
"email": "zuul@example.com",
"name": "Zuul",
"username": "jenkins",
}
}
)
self.assertEqual(comments[5],
{'file': 'path/to/file.py',
'line': 82,
'message': 'line too short',
'reviewer': {'email': 'zuul@example.com',
'name': 'Zuul',
'username': 'jenkins'}}
)
self.assertIn('expected a dictionary', A.messages[0],
"A should have a validation error reported")
self.assertIn('invalid file missingfile.txt', A.messages[0],
"A should have file error reported")
class TestChecksApi(ZuulTestCase):
config_file = 'zuul-gerrit-web.conf'
@simple_layout('layouts/gerrit-checks.yaml')
def test_check_pipeline(self):
B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
A.setDependsOn(B, 1)
A.setCheck('zuul:check', reset=True)
self.waitForPoll('gerrit')
self.waitUntilSettled()
self.assertEqual(A.checks_history[0]['zuul:check']['state'],
'NOT_STARTED')
self.assertEqual(A.checks_history[1]['zuul:check']['state'],
'SCHEDULED')
self.assertEqual(
A.checks_history[1]['zuul:check']['url'],
'http://zuul.example.com/t/tenant-one/status/change/2,1')
self.assertEqual(A.checks_history[2]['zuul:check']['state'],
'RUNNING')
self.assertEqual(
A.checks_history[2]['zuul:check']['url'],
'http://zuul.example.com/t/tenant-one/status/change/2,1')
self.assertEqual(A.checks_history[3]['zuul:check']['state'],
'SUCCESSFUL')
self.assertTrue(
A.checks_history[3]['zuul:check']['url'].startswith(
'http://zuul.example.com/t/tenant-one/buildset/'))
self.assertEqual(len(A.checks_history), 4)
self.assertTrue(isinstance(
A.checks_history[3]['zuul:check']['started'], str))
self.assertTrue(isinstance(
A.checks_history[3]['zuul:check']['finished'], str))
self.assertTrue(
A.checks_history[3]['zuul:check']['finished'] >
A.checks_history[3]['zuul:check']['started'])
self.assertEqual(A.checks_history[3]['zuul:check']['message'],
'Change passed all voting jobs')
self.assertHistory([
dict(name='test-job', result='SUCCESS', changes='1,1 2,1')])
self.assertEqual(A.reported, 0, "no messages should be reported")
self.assertEqual(A.messages, [], "no messages should be reported")
# Make sure B was never updated
self.assertEqual(len(B.checks_history), 0)
@simple_layout('layouts/gerrit-checks.yaml')
def test_gate_pipeline(self):
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
A.addApproval('Code-Review', 2)
A.addApproval('Approved', 1)
A.setCheck('zuul:gate', reset=True)
self.waitForPoll('gerrit')
self.waitUntilSettled()
self.assertEqual(A.checks_history[0]['zuul:gate']['state'],
'NOT_STARTED')
self.assertEqual(A.checks_history[1]['zuul:gate']['state'],
'SCHEDULED')
self.assertEqual(A.checks_history[2]['zuul:gate']['state'],
'RUNNING')
self.assertEqual(A.checks_history[3]['zuul:gate']['state'],
'SUCCESSFUL')
self.assertEqual(len(A.checks_history), 4)
self.assertHistory([
dict(name='test-job', result='SUCCESS', changes='1,1')])
self.assertEqual(A.data['status'], 'MERGED')
self.assertEqual(A.reported, 2,
"start and success messages should be reported")
@simple_layout('layouts/gerrit-checks-scheme.yaml')
def test_check_pipeline_scheme(self):
self.fake_gerrit.addFakeChecker(uuid='zuul_check:abcd',
repository='org/project',
status='ENABLED')
self.scheds.execute(lambda app: app.sched.reconfigure(app.config))
self.waitUntilSettled()
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
A.setCheck('zuul_check:abcd', reset=True)
self.waitForPoll('gerrit')
self.waitUntilSettled()
self.assertEqual(A.checks_history[0]['zuul_check:abcd']['state'],
'NOT_STARTED')
self.assertEqual(A.checks_history[1]['zuul_check:abcd']['state'],
'SCHEDULED')
self.assertEqual(A.checks_history[2]['zuul_check:abcd']['state'],
'RUNNING')
self.assertEqual(A.checks_history[3]['zuul_check:abcd']['state'],
'SUCCESSFUL')
self.assertEqual(len(A.checks_history), 4)
self.assertHistory([
dict(name='test-job', result='SUCCESS', changes='1,1')])
@simple_layout('layouts/gerrit-checks-nojobs.yaml')
def test_no_jobs(self):
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
A.setCheck('zuul:check', reset=True)
self.waitForPoll('gerrit')
self.waitUntilSettled()
self.assertEqual(A.checks_history[0]['zuul:check']['state'],
'NOT_STARTED')
self.assertEqual(A.checks_history[1]['zuul:check']['state'],
'SCHEDULED')
self.assertEqual(A.checks_history[2]['zuul:check']['state'],
'NOT_RELEVANT')
self.assertEqual(len(A.checks_history), 3)
self.assertEqual(A.data['status'], 'NEW')
@simple_layout('layouts/gerrit-checks.yaml')
def test_config_error(self):
# Test that line comments are reported on config errors
in_repo_conf = textwrap.dedent(
"""
- project:
check:
jobs:
- bad-job
""")
file_dict = {'.zuul.yaml': in_repo_conf}
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A',
files=file_dict)
A.setCheck('zuul:check', reset=True)
self.waitForPoll('gerrit')
self.waitUntilSettled()
self.assertEqual(A.checks_history[0]['zuul:check']['state'],
'NOT_STARTED')
self.assertEqual(A.checks_history[1]['zuul:check']['state'],
'SCHEDULED')
self.assertEqual(A.checks_history[2]['zuul:check']['state'],
'FAILED')
self.assertEqual(len(A.checks_history), 3)
comments = sorted(A.comments, key=lambda x: x['line'])
self.assertEqual(comments[0],
{'file': '.zuul.yaml',
'line': 5,
'message': 'Job bad-job not defined',
'range': {'end_character': 0,
'end_line': 5,
'start_character': 2,
'start_line': 2},
'reviewer': {'email': 'zuul@example.com',
'name': 'Zuul',
'username': 'jenkins'}}
)
self.assertEqual(A.reported, 0, "no messages should be reported")
self.assertEqual(A.messages, [], "no messages should be reported")
@simple_layout('layouts/gerrit-checks.yaml')
def test_new_patchset(self):
self.executor_server.hold_jobs_in_build = True
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
A.setCheck('zuul:check', reset=True)
self.waitForPoll('gerrit')
self.waitUntilSettled()
self.assertEqual(A.checks_history[0]['zuul:check']['state'],
'NOT_STARTED')
self.assertEqual(A.checks_history[1]['zuul:check']['state'],
'SCHEDULED')
self.assertEqual(A.checks_history[2]['zuul:check']['state'],
'RUNNING')
self.assertEqual(len(A.checks_history), 3)
A.addPatchset()
A.setCheck('zuul:check', reset=True)
self.waitForPoll('gerrit')
self.waitUntilSettled()
self.executor_server.hold_jobs_in_build = False
self.executor_server.release()
self.waitUntilSettled()
print(A.checks_history)
self.assertEqual(A.checks_history[3]['zuul:check']['state'],
'NOT_STARTED')
self.assertEqual(A.checks_history[4]['zuul:check']['state'],
'SCHEDULED')
self.assertEqual(A.checks_history[5]['zuul:check']['state'],
'RUNNING')
self.assertEqual(A.checks_history[6]['zuul:check']['state'],
'SUCCESSFUL')
self.assertEqual(len(A.checks_history), 7)
self.assertHistory([
dict(name='test-job', result='ABORTED', changes='1,1'),
dict(name='test-job', result='SUCCESS', changes='1,2'),
], ordered=False)
class TestPolling(ZuulTestCase):
config_file = 'zuul-gerrit-no-stream.conf'
@simple_layout('layouts/gerrit-checks.yaml')
def test_config_update(self):
# Test that the config is updated via polling when a change
# merges without stream-events enabled.
in_repo_conf = textwrap.dedent(
"""
- job:
name: test-job2
parent: test-job
- project:
check:
jobs:
- test-job2
""")
file_dict = {'.zuul.yaml': in_repo_conf}
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A',
files=file_dict)
A.setMerged()
self.waitForPoll('gerrit')
B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
B.setCheck('zuul:check', reset=True)
self.waitForPoll('gerrit')
self.waitUntilSettled()
self.assertEqual(B.checks_history[0]['zuul:check']['state'],
'NOT_STARTED')
self.assertEqual(B.checks_history[1]['zuul:check']['state'],
'SCHEDULED')
self.assertEqual(B.checks_history[2]['zuul:check']['state'],
'RUNNING')
self.assertEqual(B.checks_history[3]['zuul:check']['state'],
'SUCCESSFUL')
self.assertEqual(len(B.checks_history), 4)
self.assertHistory([
dict(name='test-job', result='SUCCESS', changes='2,1'),
dict(name='test-job2', result='SUCCESS', changes='2,1'),
], ordered=False)
@simple_layout('layouts/gerrit-poll-post.yaml')
def test_post(self):
# Test that ref-updated events trigger post jobs.
self.waitUntilSettled()
# Wait for an initial poll to get the original sha.
self.waitForPoll('gerrit-ref')
# Merge a change.
self.create_commit('org/project')
# Wait for the job to run.
self.waitForPoll('gerrit-ref')
self.waitUntilSettled()
self.assertHistory([
dict(name='post-job', result='SUCCESS'),
])
@simple_layout('layouts/gerrit-poll-post.yaml')
def test_tag(self):
# Test that ref-updated events trigger post jobs.
self.waitUntilSettled()
# Wait for an initial poll to get the original sha.
self.waitForPoll('gerrit-ref')
# Merge a change.
self.fake_gerrit.addFakeTag('org/project', 'master', 'foo')
# Wait for the job to run.
self.waitForPoll('gerrit-ref')
self.waitUntilSettled()
self.assertHistory([
dict(name='tag-job', result='SUCCESS'),
])
class TestWrongConnection(ZuulTestCase):
config_file = 'zuul-connections-multiple-gerrits.conf'
tenant_config_file = 'config/wrong-connection-in-pipeline/main.yaml'
def test_wrong_connection(self):
# Test if the wrong connection is configured in a gate pipeline
# Our system has two gerrits, and we have configured a gate
# pipeline to trigger on the "review_gerrit" connection, but
# report (and merge) via "another_gerrit".
A = self.fake_review_gerrit.addFakeChange('org/project', 'master', 'A')
A.addApproval('Code-Review', 2)
self.fake_review_gerrit.addEvent(A.addApproval('Approved', 1))
self.waitUntilSettled()
B = self.fake_review_gerrit.addFakeChange('org/project', 'master', 'B')
# Let's try this as if the change was merged (say, via another tenant).
B.setMerged()
B.addApproval('Code-Review', 2)
self.fake_review_gerrit.addEvent(B.addApproval('Approved', 1))
self.waitUntilSettled()
self.assertEqual(A.reported, 0)
self.assertEqual(B.reported, 0)
self.assertHistory([
dict(name='test-job', result='SUCCESS', changes='1,1'),
dict(name='test-job', result='SUCCESS', changes='2,1'),
], ordered=False)