
This allows users to create pipeline triggers that provide expanded debug information in reports. This includes not only the debug info currently available with the project-pipeline-config "debug" flag (which includes decisions about running jobs), but additionally includes information about why the change may not have been enqueued (and in these cases, we will exceptionally report a non-enqueued item in order to get this information to the user). The intended workflow is: 1) User approves change 2) User is baffled by why change does not appear in pipeline 3) User leaves a comment with "gate debug" or a similar label/tag/etc 4) Zuul reports with a comment indicating why it was not enqueued (depends on abandoned change, etc). To make this more useful, the pipeline manager will now add several more bits of information to the "warnings" list for non-enqueue conditions. Also, the canMerge methods of the connections may now return a FalseWithReason so their reasoning may end up in these messages. Tests of this are added to each of the 4 code-review systems since the attribute is set on the trigger itself, which is driver-specific. These tests add coverage of the parsing and also exercise the canMerge method. The Gerrit driver test includes several more variations to exercise each of the new points in the pipeline manager that can report a non-enqueued item. While the git, timer, and zuul drivers also support this for completeness, testing is not added to them since it is unlikely to be used. Change-Id: Ifa59406bda40c2ede98ba275894b4bd05663cd6c
1412 lines
55 KiB
Python
1412 lines
55 KiB
Python
# Copyright 2019 Red Hat
|
|
# Copyright 2022 Acme Gating, LLC
|
|
#
|
|
# 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 re
|
|
import os
|
|
import git
|
|
import yaml
|
|
import socket
|
|
import time
|
|
|
|
from zuul.lib import strings
|
|
from zuul.zk.layout import LayoutState
|
|
from zuul.zk.change_cache import ChangeKey
|
|
|
|
from tests.base import (
|
|
ZuulTestCase,
|
|
ZuulWebFixture,
|
|
simple_layout,
|
|
skipIfMultiScheduler,
|
|
)
|
|
from tests.util import random_sha1
|
|
|
|
from testtools.matchers import MatchesRegex
|
|
|
|
EMPTY_LAYOUT_STATE = LayoutState("", "", 0, None, {}, -1)
|
|
|
|
|
|
class TestGitlabWebhook(ZuulTestCase):
|
|
config_file = 'zuul-gitlab-driver.conf'
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
|
|
# Start the web server
|
|
self.web = self.useFixture(
|
|
ZuulWebFixture(self.config, self.test_config,
|
|
self.additional_event_queues, self.upstream_root,
|
|
self.poller_events,
|
|
self.git_url_with_auth, self.addCleanup,
|
|
self.test_root))
|
|
|
|
host = '127.0.0.1'
|
|
# Wait until web server is started
|
|
while True:
|
|
port = self.web.port
|
|
try:
|
|
with socket.create_connection((host, port)):
|
|
break
|
|
except ConnectionRefusedError:
|
|
pass
|
|
|
|
self.fake_gitlab.setZuulWebPort(port)
|
|
|
|
def tearDown(self):
|
|
super(TestGitlabWebhook, self).tearDown()
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_webhook(self):
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project', 'master', 'A')
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent(),
|
|
use_zuulweb=False,
|
|
project='org/project')
|
|
self.waitUntilSettled()
|
|
|
|
self.assertEqual('SUCCESS',
|
|
self.getJobFromHistory('project-test1').result)
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_webhook_via_zuulweb(self):
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project', 'master', 'A')
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent(),
|
|
use_zuulweb=True,
|
|
project='org/project')
|
|
self.waitUntilSettled()
|
|
|
|
self.assertEqual('SUCCESS',
|
|
self.getJobFromHistory('project-test1').result)
|
|
|
|
|
|
class TestGitlabDriver(ZuulTestCase):
|
|
config_file = 'zuul-gitlab-driver.conf'
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_merge_request_opened(self):
|
|
|
|
description = "This is the\nMR description."
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project', 'master', 'A', description=description)
|
|
self.fake_gitlab.emitEvent(
|
|
A.getMergeRequestOpenedEvent(), project='org/project')
|
|
self.waitUntilSettled()
|
|
|
|
self.assertEqual('SUCCESS',
|
|
self.getJobFromHistory('project-test1').result)
|
|
|
|
self.assertEqual('SUCCESS',
|
|
self.getJobFromHistory('project-test2').result)
|
|
|
|
job = self.getJobFromHistory('project-test2')
|
|
zuulvars = job.parameters['zuul']
|
|
self.assertEqual(str(A.number), zuulvars['change'])
|
|
self.assertEqual(str(A.sha), zuulvars['patchset'])
|
|
self.assertEqual(str(A.sha), zuulvars['commit_id'])
|
|
self.assertEqual('master', zuulvars['branch'])
|
|
self.assertEquals(f'{self.fake_gitlab._test_baseurl}/'
|
|
'org/project/merge_requests/1',
|
|
zuulvars['items'][0]['change_url'])
|
|
self.assertEqual(zuulvars["message"], strings.b64encode(description))
|
|
self.assertEqual(2, len(self.history))
|
|
self.assertEqual(2, len(A.notes))
|
|
self.assertEqual(
|
|
A.notes[0]['body'], "Starting check jobs.")
|
|
self.assertThat(
|
|
A.notes[1]['body'],
|
|
MatchesRegex(r'.*project-test1.*SUCCESS.*', re.DOTALL))
|
|
self.assertThat(
|
|
A.notes[1]['body'],
|
|
MatchesRegex(r'.*project-test2.*SUCCESS.*', re.DOTALL))
|
|
self.assertTrue(A.approved)
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_merge_request_opened_imcomplete(self):
|
|
|
|
now = time.monotonic()
|
|
complete_at = now + 3
|
|
with self.fake_gitlab.enable_delayed_complete_mr(complete_at):
|
|
description = "This is the\nMR description."
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project', 'master', 'A', description=description)
|
|
self.fake_gitlab.emitEvent(
|
|
A.getMergeRequestOpenedEvent(), project='org/project')
|
|
self.waitUntilSettled()
|
|
|
|
self.assertEqual('SUCCESS',
|
|
self.getJobFromHistory('project-test1').result)
|
|
|
|
self.assertEqual('SUCCESS',
|
|
self.getJobFromHistory('project-test2').result)
|
|
|
|
self.assertTrue(self.fake_gitlab._test_web_server.stats["get_mr"] > 2)
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_merge_request_updated(self):
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest('org/project', 'master', 'A')
|
|
mr_tip1_sha = A.sha
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
self.assertEqual(2, len(self.history))
|
|
self.assertHistory(
|
|
[
|
|
{'name': 'project-test1', 'changes': '1,%s' % mr_tip1_sha},
|
|
{'name': 'project-test2', 'changes': '1,%s' % mr_tip1_sha},
|
|
], ordered=False
|
|
)
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestUpdatedEvent())
|
|
mr_tip2_sha = A.sha
|
|
self.waitUntilSettled()
|
|
self.assertEqual(4, len(self.history))
|
|
self.assertHistory(
|
|
[
|
|
{'name': 'project-test1', 'changes': '1,%s' % mr_tip1_sha},
|
|
{'name': 'project-test2', 'changes': '1,%s' % mr_tip1_sha},
|
|
{'name': 'project-test1', 'changes': '1,%s' % mr_tip2_sha},
|
|
{'name': 'project-test2', 'changes': '1,%s' % mr_tip2_sha}
|
|
], ordered=False
|
|
)
|
|
|
|
# Adding or removing reviewer should not trigger a build
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestReviewersUpdatedEvent())
|
|
self.waitUntilSettled()
|
|
self.assertEqual(4, len(self.history))
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_merge_request_approved(self):
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest('org/project', 'master', 'A')
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestApprovedEvent())
|
|
self.waitUntilSettled()
|
|
self.assertEqual(1, len(self.history))
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestUnapprovedEvent())
|
|
self.waitUntilSettled()
|
|
self.assertEqual(2, len(self.history))
|
|
|
|
job = self.getJobFromHistory('project-test-approval')
|
|
zuulvars = job.parameters['zuul']
|
|
self.assertEqual('check-approval', zuulvars['pipeline'])
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_merge_request_updated_during_build(self):
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest('org/project', 'master', 'A')
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
old = A.sha
|
|
A.addCommit()
|
|
new = A.sha
|
|
self.assertNotEqual(old, new)
|
|
self.waitUntilSettled()
|
|
|
|
self.assertEqual(2, len(self.history))
|
|
# MR must not be approved: tested commit isn't current commit
|
|
self.assertFalse(A.approved)
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestUpdatedEvent())
|
|
self.waitUntilSettled()
|
|
|
|
self.assertEqual(4, len(self.history))
|
|
self.assertTrue(A.approved)
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_merge_request_labeled(self):
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest('org/project', 'master', 'A')
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestLabeledEvent(
|
|
add_labels=('label1', 'label2')))
|
|
self.waitUntilSettled()
|
|
self.assertEqual(0, len(self.history))
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestLabeledEvent(
|
|
add_labels=('gateit', )))
|
|
self.waitUntilSettled()
|
|
self.assertEqual(1, len(self.history))
|
|
|
|
A.labels = ['verified']
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestLabeledEvent(
|
|
remove_labels=('verified', )))
|
|
self.waitUntilSettled()
|
|
self.assertEqual(2, len(self.history))
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_merge_request_merged(self):
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest('org/project', 'master', 'A')
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestMergedEvent())
|
|
self.waitUntilSettled()
|
|
self.assertEqual(1, len(self.history))
|
|
self.assertHistory([{'name': 'project-promote'}])
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_merge_push_does_not_reconfigure(self):
|
|
# Test that the push event that follows a merge doesn't
|
|
# needlessly trigger reconfiguration.
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest('org/project', 'master', 'A')
|
|
|
|
state1 = self.scheds.first.sched.local_layout_state.get("tenant-one")
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestMergedEvent())
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestMergedPushEvent(
|
|
self.fake_gitlab))
|
|
self.waitUntilSettled()
|
|
self.assertEqual(2, len(self.history))
|
|
self.assertHistory([{'name': 'project-post-job'},
|
|
{'name': 'project-promote'}
|
|
], ordered=False)
|
|
state2 = self.scheds.first.sched.local_layout_state.get("tenant-one")
|
|
self.assertEqual(state1, state2)
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_merge_push_does_reconfigure(self):
|
|
# Test that the push event that follows a merge does
|
|
# trigger reconfiguration if .zuul.yaml is changed.
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest('org/project', 'master', 'A')
|
|
|
|
state1 = self.scheds.first.sched.local_layout_state.get("tenant-one")
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestMergedEvent())
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestMergedPushEvent(
|
|
self.fake_gitlab,
|
|
modified_files=['.zuul.yaml']))
|
|
self.waitUntilSettled()
|
|
self.assertEqual(2, len(self.history))
|
|
self.assertHistory([{'name': 'project-post-job'},
|
|
{'name': 'project-promote'}
|
|
], ordered=False)
|
|
state2 = self.scheds.first.sched.local_layout_state.get("tenant-one")
|
|
self.assertNotEqual(state1, state2)
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_merge_request_updated_builds_aborted(self):
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest('org/project', 'master', 'A')
|
|
mr_tip1_sha = A.sha
|
|
|
|
self.executor_server.hold_jobs_in_build = True
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestUpdatedEvent())
|
|
mr_tip2_sha = A.sha
|
|
self.waitUntilSettled()
|
|
|
|
self.executor_server.hold_jobs_in_build = False
|
|
self.executor_server.release()
|
|
self.waitUntilSettled()
|
|
|
|
self.assertHistory(
|
|
[
|
|
{'name': 'project-test1', 'result': 'ABORTED',
|
|
'changes': '1,%s' % mr_tip1_sha},
|
|
{'name': 'project-test2', 'result': 'ABORTED',
|
|
'changes': '1,%s' % mr_tip1_sha},
|
|
{'name': 'project-test1', 'changes': '1,%s' % mr_tip2_sha},
|
|
{'name': 'project-test2', 'changes': '1,%s' % mr_tip2_sha}
|
|
], ordered=False
|
|
)
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_dequeue_mr_abandoned(self):
|
|
self.executor_server.hold_jobs_in_build = True
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest('org/project', 'master', 'A')
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestClosedEvent())
|
|
self.waitUntilSettled()
|
|
|
|
self.executor_server.hold_jobs_in_build = False
|
|
self.executor_server.release()
|
|
self.waitUntilSettled()
|
|
|
|
self.assertEqual(2, len(self.history))
|
|
self.assertEqual(2, self.countJobResults(self.history, 'ABORTED'))
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_merge_request_commented(self):
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest('org/project', 'master', 'A')
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
self.assertEqual(2, len(self.history))
|
|
|
|
self.fake_gitlab.emitEvent(
|
|
A.getMergeRequestCommentedEvent('I like that change'))
|
|
self.waitUntilSettled()
|
|
self.assertEqual(2, len(self.history))
|
|
|
|
self.fake_gitlab.emitEvent(
|
|
A.getMergeRequestCommentedEvent('recheck'))
|
|
self.waitUntilSettled()
|
|
self.assertEqual(4, len(self.history))
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
@skipIfMultiScheduler()
|
|
# This test fails reproducibly with multiple schedulers because
|
|
# the fake_gitlab._test_baseurl used in the assertion for the
|
|
# change_url doesn't match.
|
|
# An explanation for this would be that each scheduler (and the
|
|
# test case itself) use different (fake) Gitlab connections.
|
|
# However, the interesting part is that only this test fails
|
|
# although there are other gitlab tests with a similar assertion.
|
|
# Apart from that I'm wondering why this test first fails with
|
|
# multiple schedulers as each scheduler should have a different
|
|
# gitlab connection than the test case itself.
|
|
def test_ref_updated(self):
|
|
|
|
event = self.fake_gitlab.getPushEvent('org/project')
|
|
expected_newrev = event[1]['after']
|
|
expected_oldrev = event[1]['before']
|
|
self.fake_gitlab.emitEvent(event)
|
|
self.waitUntilSettled()
|
|
self.assertEqual(1, len(self.history))
|
|
self.assertEqual(
|
|
'SUCCESS',
|
|
self.getJobFromHistory('project-post-job').result)
|
|
|
|
job = self.getJobFromHistory('project-post-job')
|
|
zuulvars = job.parameters['zuul']
|
|
self.assertEqual('refs/heads/master', zuulvars['ref'])
|
|
self.assertEqual('post', zuulvars['pipeline'])
|
|
self.assertEqual('project-post-job', zuulvars['job'])
|
|
self.assertEqual('master', zuulvars['branch'])
|
|
self.assertEqual(
|
|
f'{self.fake_gitlab._test_baseurl}/org/project/tree/'
|
|
f'{zuulvars["newrev"]}',
|
|
zuulvars['change_url'])
|
|
self.assertEqual(expected_newrev, zuulvars['newrev'])
|
|
self.assertEqual(expected_oldrev, zuulvars['oldrev'])
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_ref_created(self):
|
|
|
|
self.create_branch('org/project', 'stable-1.0')
|
|
path = os.path.join(self.upstream_root, 'org/project')
|
|
repo = git.Repo(path)
|
|
newrev = repo.commit('refs/heads/stable-1.0').hexsha
|
|
event = self.fake_gitlab.getPushEvent(
|
|
'org/project', branch='refs/heads/stable-1.0',
|
|
before='0' * 40, after=newrev)
|
|
old = self.scheds.first.sched.tenant_layout_state.get(
|
|
'tenant-one', EMPTY_LAYOUT_STATE)
|
|
self.fake_gitlab.emitEvent(event)
|
|
self.waitUntilSettled()
|
|
new = self.scheds.first.sched.tenant_layout_state.get(
|
|
'tenant-one', EMPTY_LAYOUT_STATE)
|
|
# New timestamp should be greater than the old timestamp
|
|
self.assertLess(old, new)
|
|
self.assertEqual(1, len(self.history))
|
|
self.assertEqual(
|
|
'SUCCESS',
|
|
self.getJobFromHistory('project-post-job').result)
|
|
job = self.getJobFromHistory('project-post-job')
|
|
zuulvars = job.parameters['zuul']
|
|
self.assertEqual('refs/heads/stable-1.0', zuulvars['ref'])
|
|
self.assertEqual('post', zuulvars['pipeline'])
|
|
self.assertEqual('project-post-job', zuulvars['job'])
|
|
self.assertEqual('stable-1.0', zuulvars['branch'])
|
|
self.assertEqual(newrev, zuulvars['newrev'])
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_ref_deleted(self):
|
|
|
|
event = self.fake_gitlab.getPushEvent(
|
|
'org/project', 'stable-1.0', after='0' * 40)
|
|
self.fake_gitlab.emitEvent(event)
|
|
self.waitUntilSettled()
|
|
self.assertEqual(0, len(self.history))
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_tag_created(self):
|
|
|
|
path = os.path.join(self.upstream_root, 'org/project')
|
|
repo = git.Repo(path)
|
|
repo.create_tag('1.0')
|
|
tagsha = repo.tags['1.0'].commit.hexsha
|
|
event = self.fake_gitlab.getGitTagEvent(
|
|
'org/project', '1.0', tagsha)
|
|
self.fake_gitlab.emitEvent(event)
|
|
self.waitUntilSettled()
|
|
self.assertEqual(1, len(self.history))
|
|
self.assertEqual(
|
|
'SUCCESS',
|
|
self.getJobFromHistory('project-tag-job').result)
|
|
job = self.getJobFromHistory('project-tag-job')
|
|
zuulvars = job.parameters['zuul']
|
|
self.assertEqual('refs/tags/1.0', zuulvars['ref'])
|
|
self.assertEqual('tag', zuulvars['pipeline'])
|
|
self.assertEqual('project-tag-job', zuulvars['job'])
|
|
self.assertEqual(tagsha, zuulvars['newrev'])
|
|
self.assertEqual(tagsha, zuulvars['commit_id'])
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_pull_request_with_dyn_reconf(self):
|
|
path = os.path.join(self.upstream_root, 'org/project')
|
|
zuul_yaml = [
|
|
{'job': {
|
|
'name': 'project-test3',
|
|
'run': 'job.yaml'
|
|
}},
|
|
{'project': {
|
|
'check': {
|
|
'jobs': [
|
|
'project-test3'
|
|
]
|
|
}
|
|
}}
|
|
]
|
|
playbook = "- hosts: all\n tasks: []"
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project', 'master', 'A',
|
|
base_sha=git.Repo(path).head.object.hexsha)
|
|
A.addCommit(
|
|
{'.zuul.yaml': yaml.dump(zuul_yaml),
|
|
'job.yaml': playbook}
|
|
)
|
|
A.addCommit({"dummy.file": ""})
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
|
|
self.assertEqual('SUCCESS',
|
|
self.getJobFromHistory('project-test1').result)
|
|
self.assertEqual('SUCCESS',
|
|
self.getJobFromHistory('project-test2').result)
|
|
self.assertEqual('SUCCESS',
|
|
self.getJobFromHistory('project-test3').result)
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_pull_request_with_dyn_reconf_alt(self):
|
|
with self.fake_gitlab.enable_uncomplete_mr():
|
|
zuul_yaml = [
|
|
{'job': {
|
|
'name': 'project-test3',
|
|
'run': 'job.yaml'
|
|
}},
|
|
{'project': {
|
|
'check': {
|
|
'jobs': [
|
|
'project-test3'
|
|
]
|
|
}
|
|
}}
|
|
]
|
|
playbook = "- hosts: all\n tasks: []"
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project', 'master', 'A')
|
|
A.addCommit(
|
|
{'.zuul.yaml': yaml.dump(zuul_yaml),
|
|
'job.yaml': playbook}
|
|
)
|
|
A.addCommit({"dummy.file": ""})
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
|
|
self.assertEqual('SUCCESS',
|
|
self.getJobFromHistory('project-test1').result)
|
|
self.assertEqual('SUCCESS',
|
|
self.getJobFromHistory('project-test2').result)
|
|
self.assertEqual('SUCCESS',
|
|
self.getJobFromHistory('project-test3').result)
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_ref_updated_and_tenant_reconfigure(self):
|
|
|
|
self.waitUntilSettled()
|
|
old = self.scheds.first.sched.tenant_layout_state.get(
|
|
'tenant-one', EMPTY_LAYOUT_STATE)
|
|
|
|
zuul_yaml = [
|
|
{'job': {
|
|
'name': 'project-post-job2',
|
|
'run': 'job.yaml'
|
|
}},
|
|
{'project': {
|
|
'post': {
|
|
'jobs': [
|
|
'project-post-job2'
|
|
]
|
|
}
|
|
}}
|
|
]
|
|
playbook = "- hosts: all\n tasks: []"
|
|
self.create_commit(
|
|
'org/project',
|
|
{'.zuul.yaml': yaml.dump(zuul_yaml),
|
|
'job.yaml': playbook},
|
|
message='Add InRepo configuration'
|
|
)
|
|
event = self.fake_gitlab.getPushEvent('org/project',
|
|
modified_files=['.zuul.yaml'])
|
|
self.fake_gitlab.emitEvent(event)
|
|
self.waitUntilSettled()
|
|
|
|
new = self.scheds.first.sched.tenant_layout_state.get(
|
|
'tenant-one', EMPTY_LAYOUT_STATE)
|
|
# New timestamp should be greater than the old timestamp
|
|
self.assertLess(old, new)
|
|
|
|
self.assertHistory(
|
|
[{'name': 'project-post-job'},
|
|
{'name': 'project-post-job2'},
|
|
], ordered=False
|
|
)
|
|
|
|
@simple_layout('layouts/crd-gitlab.yaml', driver='gitlab')
|
|
def test_crd_independent(self):
|
|
|
|
# Create a change in project1 that a project2 change will depend on
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project1', 'master', 'A')
|
|
|
|
# Create a commit in B that sets the dependency on A
|
|
msg = "Depends-On: %s" % A.url
|
|
B = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project2', 'master', 'B', description=msg)
|
|
|
|
# Make an event to re-use
|
|
self.fake_gitlab.emitEvent(B.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
|
|
# The changes for the job from project2 should include the project1
|
|
# MR content
|
|
changes = self.getJobFromHistory(
|
|
'project2-test', 'org/project2').changes
|
|
|
|
self.assertEqual(changes, "%s,%s %s,%s" % (A.number,
|
|
A.sha,
|
|
B.number,
|
|
B.sha))
|
|
|
|
# There should be no more changes in the queue
|
|
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
|
|
self.assertEqual(
|
|
len(tenant.layout.pipeline_managers['check'].state.queues), 0)
|
|
|
|
@simple_layout('layouts/requirements-gitlab.yaml', driver='gitlab')
|
|
def test_state_require(self):
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project1', 'master', 'A')
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
self.assertEqual(1, len(self.history))
|
|
|
|
# Close the MR
|
|
A.closeMergeRequest()
|
|
|
|
# A recheck will not trigger the job
|
|
self.fake_gitlab.emitEvent(
|
|
A.getMergeRequestCommentedEvent('recheck'))
|
|
self.waitUntilSettled()
|
|
self.assertEqual(1, len(self.history))
|
|
|
|
# Merge the MR
|
|
A.mergeMergeRequest()
|
|
|
|
# A recheck will not trigger the job
|
|
self.fake_gitlab.emitEvent(
|
|
A.getMergeRequestCommentedEvent('recheck'))
|
|
self.waitUntilSettled()
|
|
self.assertEqual(1, len(self.history))
|
|
|
|
# Re-open the MR
|
|
A.reopenMergeRequest()
|
|
|
|
# A recheck will trigger the job
|
|
self.fake_gitlab.emitEvent(
|
|
A.getMergeRequestCommentedEvent('recheck'))
|
|
self.waitUntilSettled()
|
|
self.assertEqual(2, len(self.history))
|
|
|
|
@simple_layout('layouts/requirements-gitlab.yaml', driver='gitlab')
|
|
def test_approval_require(self):
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project2', 'master', 'A')
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
self.assertEqual(0, len(self.history))
|
|
|
|
A.approved = True
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestUpdatedEvent())
|
|
self.waitUntilSettled()
|
|
self.assertEqual(1, len(self.history))
|
|
|
|
A.approved = False
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestUpdatedEvent())
|
|
self.waitUntilSettled()
|
|
self.assertEqual(1, len(self.history))
|
|
|
|
@simple_layout('layouts/requirements-gitlab.yaml', driver='gitlab')
|
|
def test_approval_require_community_edition(self):
|
|
|
|
with self.fake_gitlab.enable_community_edition():
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project2', 'master', 'A')
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
self.assertEqual(0, len(self.history))
|
|
|
|
A.approved = True
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestUpdatedEvent())
|
|
self.waitUntilSettled()
|
|
self.assertEqual(1, len(self.history))
|
|
|
|
A.approved = False
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestUpdatedEvent())
|
|
self.waitUntilSettled()
|
|
self.assertEqual(1, len(self.history))
|
|
|
|
@simple_layout('layouts/requirements-gitlab.yaml', driver='gitlab')
|
|
def test_label_require(self):
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project3', 'master', 'A')
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
self.assertEqual(0, len(self.history))
|
|
|
|
A.labels = ['gateit', 'prio:low']
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestUpdatedEvent())
|
|
self.waitUntilSettled()
|
|
self.assertEqual(0, len(self.history))
|
|
|
|
A.labels = ['gateit', 'prio:low', 'another_label']
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestUpdatedEvent())
|
|
self.waitUntilSettled()
|
|
self.assertEqual(1, len(self.history))
|
|
|
|
@simple_layout('layouts/gitlab-label-add-remove.yaml', driver='gitlab')
|
|
def test_label_add_remove(self):
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project1', 'master', 'A')
|
|
A.labels = ['removeme1', 'removeme2']
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
self.assertEqual(1, len(self.history))
|
|
self.assertEqual(set(A.labels), {'addme1', 'addme2'})
|
|
|
|
@simple_layout('layouts/merging-gitlab.yaml', driver='gitlab')
|
|
def test_merge_blocking_discussions(self):
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project2', 'master', 'A')
|
|
|
|
A.blocking_discussions_resolved = False
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
# canMerge is not validated
|
|
self.assertEqual(0, len(self.history))
|
|
|
|
A.blocking_discussions_resolved = True
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
# canMerge is validated
|
|
self.assertEqual(1, len(self.history))
|
|
|
|
self.assertEqual('SUCCESS',
|
|
self.getJobFromHistory('project-test').result)
|
|
self.assertEqual('merged', A.state)
|
|
|
|
@simple_layout('layouts/merging-gitlab.yaml', driver='gitlab')
|
|
def test_merge_action_in_independent(self):
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project1', 'master', 'A')
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
|
|
self.assertEqual(1, len(self.history))
|
|
self.assertEqual('SUCCESS',
|
|
self.getJobFromHistory('project-test').result)
|
|
self.assertEqual('merged', A.state)
|
|
|
|
@simple_layout('layouts/merging-gitlab.yaml', driver='gitlab')
|
|
def test_merge_action_in_dependent(self):
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project2', 'master', 'A')
|
|
A.merge_status = 'cannot_be_merged'
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
# canMerge is not validated
|
|
self.assertEqual(0, len(self.history))
|
|
|
|
# Set Merge request can be merged
|
|
A.merge_status = 'can_be_merged'
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestUpdatedEvent())
|
|
self.waitUntilSettled()
|
|
# canMerge is validated
|
|
self.assertEqual(1, len(self.history))
|
|
|
|
self.assertEqual('SUCCESS',
|
|
self.getJobFromHistory('project-test').result)
|
|
self.assertEqual('merged', A.state)
|
|
|
|
@simple_layout('layouts/merging-gitlab-squash-merge.yaml', driver='gitlab')
|
|
def test_merge_squash(self):
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project1', 'master', 'A')
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
# canMerge is validated
|
|
self.assertEqual(1, len(self.history))
|
|
|
|
self.assertEqual('SUCCESS',
|
|
self.getJobFromHistory('project-test').result)
|
|
self.assertEqual('merged', A.state)
|
|
self.assertTrue(A.squash_merge)
|
|
|
|
@simple_layout('layouts/crd-gitlab.yaml', driver='gitlab')
|
|
def test_crd_dependent(self):
|
|
|
|
# Create a change in project3 that a project4 change will depend on
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project3', 'master', 'A')
|
|
A.approved = True
|
|
|
|
# Create a change B that sets the dependency on A
|
|
msg = "Depends-On: %s" % A.url
|
|
B = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project4', 'master', 'B', description=msg)
|
|
|
|
# Emit B opened event
|
|
event = B.getMergeRequestOpenedEvent()
|
|
self.fake_gitlab.emitEvent(event)
|
|
self.waitUntilSettled()
|
|
|
|
# B cannot be merged (not approved)
|
|
self.assertEqual(0, len(self.history))
|
|
|
|
# Approve B
|
|
B.approved = True
|
|
# And send the event
|
|
self.fake_gitlab.emitEvent(event)
|
|
self.waitUntilSettled()
|
|
|
|
# The changes for the job from project4 should include the project3
|
|
# MR content
|
|
changes = self.getJobFromHistory(
|
|
'project4-test', 'org/project4').changes
|
|
|
|
self.assertEqual(changes, "%s,%s %s,%s" % (A.number,
|
|
A.sha,
|
|
B.number,
|
|
B.sha))
|
|
|
|
self.assertTrue(A.is_merged)
|
|
self.assertTrue(B.is_merged)
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_timer_event(self):
|
|
self.executor_server.hold_jobs_in_build = True
|
|
self.commitConfigUpdate('org/common-config',
|
|
'layouts/timer-gitlab.yaml')
|
|
self.scheds.execute(lambda app: app.sched.reconfigure(app.config))
|
|
time.sleep(2)
|
|
self.waitUntilSettled()
|
|
self.assertEqual(len(self.builds), 1)
|
|
self.executor_server.hold_jobs_in_build = False
|
|
# Stop queuing timer triggered jobs so that the assertions
|
|
# below don't race against more jobs being queued.
|
|
self.commitConfigUpdate('org/common-config',
|
|
'layouts/no-timer-gitlab.yaml')
|
|
self.scheds.execute(lambda app: app.sched.reconfigure(app.config))
|
|
self.waitUntilSettled()
|
|
# If APScheduler is in mid-event when we remove the job, we
|
|
# can end up with one more event firing, so give it an extra
|
|
# second to settle.
|
|
time.sleep(1)
|
|
self.waitUntilSettled()
|
|
self.executor_server.release()
|
|
self.waitUntilSettled()
|
|
self.assertHistory([
|
|
dict(name='project-bitrot', result='SUCCESS',
|
|
ref='refs/heads/master'),
|
|
], ordered=False)
|
|
|
|
@simple_layout('layouts/crd-gitlab.yaml', driver='gitlab')
|
|
def test_api_token(self):
|
|
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
|
|
_, project = tenant.getProject('org/project1')
|
|
|
|
project_git_url = self.fake_gitlab.real_getGitUrl(project)
|
|
# cloneurl created from config 'server' should be used
|
|
# without credentials
|
|
self.assertEqual(f"{self.fake_gitlab._test_baseurl}/org/project1.git",
|
|
project_git_url)
|
|
|
|
@simple_layout('layouts/crd-gitlab.yaml', driver='gitlab2')
|
|
def test_api_token_cloneurl(self):
|
|
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
|
|
_, project = tenant.getProject('org/project1')
|
|
|
|
project_git_url = self.fake_gitlab2.real_getGitUrl(project)
|
|
# cloneurl from config file should be used as it defines token name and
|
|
# secret
|
|
self.assertEqual("http://myusername:2222@gitlab/org/project1.git",
|
|
project_git_url)
|
|
|
|
@simple_layout('layouts/crd-gitlab.yaml', driver='gitlab3')
|
|
def test_api_token_name_cloneurl(self):
|
|
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
|
|
_, project = tenant.getProject('org/project1')
|
|
|
|
project_git_url = self.fake_gitlab3.real_getGitUrl(project)
|
|
# cloneurl from config file should be used as it defines token name and
|
|
# secret, even if token name and token secret are defined
|
|
self.assertEqual("http://myusername:2222@gitlabthree/org/project1.git",
|
|
project_git_url)
|
|
|
|
@simple_layout('layouts/crd-gitlab.yaml', driver='gitlab4')
|
|
def test_api_token_name(self):
|
|
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
|
|
_, project = tenant.getProject('org/project1')
|
|
|
|
project_git_url = self.fake_gitlab4.real_getGitUrl(project)
|
|
# cloneurl is not set, generate one from token name, token secret and
|
|
# server
|
|
self.assertEqual("http://tokenname4:444@localhost:"
|
|
f"{self.fake_gitlab4._test_web_server.port}"
|
|
"/org/project1.git",
|
|
project_git_url)
|
|
|
|
@simple_layout('layouts/crd-gitlab.yaml', driver='gitlab5')
|
|
def test_api_token_name_cloneurl_server(self):
|
|
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
|
|
_, project = tenant.getProject('org/project1')
|
|
|
|
project_git_url = self.fake_gitlab5.real_getGitUrl(project)
|
|
# cloneurl defines a url, without credentials. As token name is
|
|
# set, include token name and secret in cloneurl, 'server' is
|
|
# overwritten
|
|
self.assertEqual("http://tokenname5:555@gitlabfivvve/org/project1.git",
|
|
project_git_url)
|
|
|
|
@simple_layout('layouts/files-gitlab.yaml', driver='gitlab')
|
|
def test_changed_file_match_filter(self):
|
|
path = os.path.join(self.upstream_root, 'org/project')
|
|
base_sha = git.Repo(path).head.object.hexsha
|
|
|
|
files = {'{:03d}.txt'.format(n): 'test' for n in range(300)}
|
|
files["foobar-requires"] = "test"
|
|
files["to-be-removed"] = "test"
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project', 'master', 'A', files=files, base_sha=base_sha)
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
# project-test1 and project-test2 should be run
|
|
self.assertEqual(2, len(self.history))
|
|
|
|
@simple_layout('layouts/files-gitlab.yaml', driver='gitlab')
|
|
def test_changed_file_match_filter_refresh(self):
|
|
# Test that a refresh of a MR doesn't overwrite the change
|
|
# files list supplied by a zuul merger.
|
|
self.hold_merge_jobs_in_queue = True
|
|
path = os.path.join(self.upstream_root, 'org/project')
|
|
base_sha = git.Repo(path).head.object.hexsha
|
|
|
|
files = {}
|
|
# This files causes project-test1 to run
|
|
files["foobar-requires"] = "test"
|
|
# File "to-be-removed" is not included, so -test2 should not run.
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project', 'master', 'A', files=files, base_sha=base_sha)
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
|
|
for x in self.merger_api.all():
|
|
if x.job_type == 'fileschanges':
|
|
self.merger_api.release(x)
|
|
|
|
self.waitUntilSettled()
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestCommentedEvent(
|
|
'test comment'))
|
|
self.waitUntilSettled()
|
|
self.hold_merge_jobs_in_queue = False
|
|
self.merger_api.release()
|
|
self.waitUntilSettled()
|
|
self.assertHistory([
|
|
dict(name='project-test1', result='SUCCESS'),
|
|
])
|
|
|
|
@simple_layout('layouts/files-gitlab.yaml', driver='gitlab')
|
|
def test_changed_and_reverted_file_not_match_filter(self):
|
|
path = os.path.join(self.upstream_root, 'org/project')
|
|
base_sha = git.Repo(path).head.object.hexsha
|
|
|
|
files = {'{:03d}.txt'.format(n): 'test' for n in range(300)}
|
|
files["foobar-requires"] = "test"
|
|
files["to-be-removed"] = "test"
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project', 'master', 'A', files=files, base_sha=base_sha)
|
|
A.addCommit(delete_files=['to-be-removed'])
|
|
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
# Only project-test1 should be run, because the file to-be-removed
|
|
# is reverted and not in changed files to trigger project-test2
|
|
self.assertEqual(1, len(self.history))
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_update_change(self):
|
|
# This tests the updateChange code path when no event is
|
|
# present (ie, during a pipeline refresh).
|
|
conn = self.scheds.first.sched.connections.connections['gitlab']
|
|
conn.gl_client.get_mr_wait_factor = 0
|
|
with self.fake_gitlab.enable_uncomplete_mr():
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project', 'master', 'A')
|
|
change_key = ChangeKey(conn.connection_name, "org/project",
|
|
"MergeRequest", str(A.number), A.sha)
|
|
# diff_refs has not populated yet, so this call tests the
|
|
# without-event and without-diff_refs path.
|
|
change = conn.getChange(change_key)
|
|
self.assertIsNone(change.commit_id, None)
|
|
self.fake_gitlab.emitEvent(
|
|
A.getMergeRequestOpenedEvent(), project='org/project')
|
|
self.waitUntilSettled()
|
|
# The above emits an event, so we should have populated
|
|
# the change cache using the event info. This tests the
|
|
# with-event and without-diff_refs path.
|
|
change = conn.getChange(change_key)
|
|
self.assertEqual(change.commit_id, A.sha)
|
|
|
|
# We don't test with event and with diff_refs here, since
|
|
# having diff_refs supercedes using event data. Also, any
|
|
# other test in the class which does not set the uncomplete
|
|
# flag will be testing that path.
|
|
|
|
# Delete the change from the cache
|
|
conn._change_cache.delete(change_key)
|
|
# This will force the driver to fetch the change info without
|
|
# an event. This tests the without-event and with-diff_refs
|
|
# path
|
|
change = conn.getChange(change_key)
|
|
self.assertEqual(change.commit_id, A.sha)
|
|
|
|
@simple_layout("layouts/basic-gitlab.yaml", driver="gitlab")
|
|
def test_get_project_branch_sha(self):
|
|
# Exercise this method since it's only called from timer
|
|
# triggers
|
|
source = self.fake_gitlab.source
|
|
project = source.getProject('org/project')
|
|
self.assertIsNotNone(source.getProjectBranchSha(project, 'master'))
|
|
|
|
@simple_layout('layouts/gitlab-pipeline-trigger-debug.yaml',
|
|
driver='gitlab')
|
|
def test_gitlab_pipeline_trigger_debug(self):
|
|
# Test that we get debug info for a pipeline debug trigger
|
|
A = self.fake_gitlab.openFakeMergeRequest("org/project", "master", "A")
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
|
|
self.assertFalse(A.is_merged)
|
|
self.assertEqual(1, len(A.notes))
|
|
self.assertIn('Debug information:',
|
|
A.notes[0]['body'])
|
|
|
|
# Test that we get a reason from canMerge.
|
|
B = self.fake_gitlab.openFakeMergeRequest("org/project", "master", "B")
|
|
B.blocking_discussions_resolved = False
|
|
self.fake_gitlab.emitEvent(B.getMergeRequestLabeledEvent(
|
|
add_labels=('gateit', )))
|
|
self.waitUntilSettled()
|
|
|
|
self.assertFalse(B.is_merged)
|
|
self.assertEqual(1, len(B.notes))
|
|
self.assertIn("can not be merged due to: blocking discussions",
|
|
B.notes[0]['body'])
|
|
|
|
|
|
class TestGitlabUnprotectedBranches(ZuulTestCase):
|
|
config_file = 'zuul-gitlab-driver.conf'
|
|
tenant_config_file = 'config/unprotected-branches-gitlab/main.yaml'
|
|
|
|
@skipIfMultiScheduler()
|
|
# This test is failing with multiple schedulers depending on which
|
|
# scheduler did the tenant reconfiguration first. As the
|
|
# assertions are all done on the objects from scheduler-0 they
|
|
# will fail if scheduler-1 did the reconfig first.
|
|
# To make this work with multiple schedulers, we might want to wait
|
|
# until all schedulers completed their tenant reconfiguration.
|
|
def test_unprotected_branches(self):
|
|
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
|
|
layout = tenant.layout
|
|
|
|
# project1 should have parsed master
|
|
self.assertIn('project1-job', layout.jobs)
|
|
|
|
# project2 should have no parsed branch
|
|
self.assertNotIn('project2-job', layout.jobs)
|
|
|
|
# now enable branch protection and trigger reload
|
|
self.fake_gitlab.protectBranch('org', 'project2', 'master')
|
|
pevent = self.fake_gitlab.getPushEvent(project='org/project2')
|
|
self.fake_gitlab.emitEvent(pevent)
|
|
self.waitUntilSettled()
|
|
|
|
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
|
|
layout = tenant.layout
|
|
|
|
# project1 and project2 should have parsed master now
|
|
self.assertIn('project1-job', layout.jobs)
|
|
self.assertIn('project2-job', layout.jobs)
|
|
|
|
def test_filtered_branches_in_build(self):
|
|
"""
|
|
Tests unprotected branches are filtered in builds if excluded
|
|
"""
|
|
self.executor_server.keep_jobdir = True
|
|
|
|
# Enable branch protection on org/project2@master
|
|
self.create_branch('org/project2', 'feat-x')
|
|
self.fake_gitlab.protectBranch('org', 'project2', 'master',
|
|
protected=True)
|
|
|
|
# Enable branch protection on org/project3@stable. We'll use a MR on
|
|
# this branch as a depends-on to validate that the stable branch
|
|
# which is not protected in org/project2 is not filtered out.
|
|
self.create_branch('org/project3', 'stable')
|
|
self.fake_gitlab.protectBranch('org', 'project3', 'stable',
|
|
protected=True)
|
|
|
|
self.scheds.execute(lambda app: app.sched.reconfigure(app.config))
|
|
self.waitUntilSettled()
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest('org/project3', 'stable',
|
|
'A')
|
|
msg = "Depends-On: %s" % A.url
|
|
B = self.fake_gitlab.openFakeMergeRequest('org/project2', 'master',
|
|
'B', description=msg)
|
|
|
|
self.fake_gitlab.emitEvent(B.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
|
|
build = self.history[0]
|
|
path = os.path.join(
|
|
build.jobdir.src_root, 'gitlab', 'org/project2')
|
|
build_repo = git.Repo(path)
|
|
branches = [x.name for x in build_repo.branches]
|
|
self.assertNotIn('feat-x', branches)
|
|
|
|
self.assertHistory([
|
|
dict(name='used-job', result='SUCCESS',
|
|
changes="%s,%s %s,%s" % (A.number, A.sha,
|
|
B.number, B.sha)),
|
|
])
|
|
|
|
def test_unfiltered_branches_in_build(self):
|
|
"""
|
|
Tests unprotected branches are not filtered in builds if not excluded
|
|
"""
|
|
self.executor_server.keep_jobdir = True
|
|
|
|
# Enable branch protection on org/project1@master
|
|
self.create_branch('org/project1', 'feat-x')
|
|
self.fake_gitlab.protectBranch('org', 'project1', 'master',
|
|
protected=True)
|
|
self.scheds.execute(lambda app: app.sched.reconfigure(app.config))
|
|
self.waitUntilSettled()
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest('org/project1', 'master',
|
|
'A')
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
|
|
build = self.history[0]
|
|
path = os.path.join(
|
|
build.jobdir.src_root, 'gitlab', 'org/project1')
|
|
build_repo = git.Repo(path)
|
|
branches = [x.name for x in build_repo.branches]
|
|
self.assertIn('feat-x', branches)
|
|
|
|
self.assertHistory([
|
|
dict(name='project-test', result='SUCCESS',
|
|
changes="%s,%s" % (A.number, A.sha)),
|
|
])
|
|
|
|
def test_unprotected_push(self):
|
|
"""Test that unprotected pushes don't cause tenant reconfigurations"""
|
|
|
|
# Prepare repo with an initial commit
|
|
A = self.fake_gitlab.openFakeMergeRequest('org/project2', 'master',
|
|
'A')
|
|
|
|
zuul_yaml = [
|
|
{'job': {
|
|
'name': 'used-job2',
|
|
'run': 'playbooks/used-job.yaml'
|
|
}},
|
|
{'project': {
|
|
'check': {
|
|
'jobs': [
|
|
'used-job2'
|
|
]
|
|
}
|
|
}}
|
|
]
|
|
|
|
A.addCommit({'zuul.yaml': yaml.dump(zuul_yaml)})
|
|
A.mergeMergeRequest()
|
|
|
|
# Do a push on top of A
|
|
pevent = self.fake_gitlab.getPushEvent(project='org/project2',
|
|
before=A.sha,
|
|
branch='refs/heads/master')
|
|
|
|
# record previous tenant reconfiguration time, which may not be set
|
|
old = self.scheds.first.sched.tenant_layout_state.get(
|
|
'tenant-one', EMPTY_LAYOUT_STATE)
|
|
self.waitUntilSettled()
|
|
|
|
self.fake_gitlab.emitEvent(pevent)
|
|
self.waitUntilSettled()
|
|
new = self.scheds.first.sched.tenant_layout_state.get(
|
|
'tenant-one', EMPTY_LAYOUT_STATE)
|
|
|
|
# We don't expect a reconfiguration because the push was to an
|
|
# unprotected branch
|
|
self.assertEqual(old, new)
|
|
|
|
# now enable branch protection and trigger the push event again
|
|
self.fake_gitlab.protectBranch('org', 'project2', 'master')
|
|
|
|
self.fake_gitlab.emitEvent(pevent)
|
|
self.waitUntilSettled()
|
|
new = self.scheds.first.sched.tenant_layout_state.get(
|
|
'tenant-one', EMPTY_LAYOUT_STATE)
|
|
|
|
# We now expect that zuul reconfigured itself
|
|
self.assertLess(old, new)
|
|
|
|
def test_protected_branch_delete(self):
|
|
"""Test that protected branch deletes trigger a tenant reconfig"""
|
|
|
|
# Prepare repo with an initial commit and enable branch protection
|
|
self.fake_gitlab.protectBranch('org', 'project2', 'master')
|
|
self.fake_gitlab.emitEvent(
|
|
self.fake_gitlab.getPushEvent(
|
|
project='org/project2', branch='refs/heads/master'))
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest('org/project2', 'master',
|
|
'A')
|
|
A.mergeMergeRequest()
|
|
|
|
# add a spare branch so that the project is not empty after master gets
|
|
# deleted.
|
|
self.create_branch('org/project2', 'feat-x')
|
|
self.fake_gitlab.protectBranch('org', 'project2', 'feat-x',
|
|
protected=False)
|
|
self.fake_gitlab.emitEvent(
|
|
self.fake_gitlab.getPushEvent(
|
|
project='org/project2', branch='refs/heads/feat-x'))
|
|
self.waitUntilSettled()
|
|
|
|
# record previous tenant reconfiguration time, which may not be set
|
|
old = self.scheds.first.sched.tenant_layout_state.get(
|
|
'tenant-one', EMPTY_LAYOUT_STATE)
|
|
self.waitUntilSettled()
|
|
|
|
# Delete the branch
|
|
self.fake_gitlab.deleteBranch('org', 'project2', 'master')
|
|
|
|
pevent = self.fake_gitlab.getPushEvent(project='org/project2',
|
|
before=A.sha,
|
|
after='0' * 40,
|
|
branch='refs/heads/master')
|
|
|
|
self.fake_gitlab.emitEvent(pevent)
|
|
self.waitUntilSettled()
|
|
new = self.scheds.first.sched.tenant_layout_state.get(
|
|
'tenant-one', EMPTY_LAYOUT_STATE)
|
|
|
|
# We now expect that zuul reconfigured itself as we deleted a protected
|
|
# branch
|
|
self.assertLess(old, new)
|
|
|
|
# This test verifies that a PR is considered in case it was created for
|
|
# a branch just has been set to protected before a tenant reconfiguration
|
|
# took place.
|
|
def test_reconfigure_on_pr_to_new_protected_branch(self):
|
|
self.create_branch('org/project2', 'release')
|
|
self.create_branch('org/project2', 'feature')
|
|
|
|
self.fake_gitlab.protectBranch('org', 'project2', 'master')
|
|
self.fake_gitlab.protectBranch('org', 'project2', 'release',
|
|
protected=False)
|
|
self.fake_gitlab.protectBranch('org', 'project2', 'feature',
|
|
protected=False)
|
|
|
|
self.scheds.execute(lambda app: app.sched.reconfigure(app.config))
|
|
self.waitUntilSettled()
|
|
|
|
self.fake_gitlab.protectBranch('org', 'project2', 'release')
|
|
|
|
self.executor_server.hold_jobs_in_build = True
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project2', 'release', 'A')
|
|
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
|
self.waitUntilSettled()
|
|
|
|
self.executor_server.hold_jobs_in_build = False
|
|
self.executor_server.release()
|
|
self.waitUntilSettled()
|
|
|
|
self.assertEqual('SUCCESS',
|
|
self.getJobFromHistory('used-job').result)
|
|
|
|
job = self.getJobFromHistory('used-job')
|
|
zuulvars = job.parameters['zuul']
|
|
self.assertEqual(str(A.number), zuulvars['change'])
|
|
self.assertEqual(str(A.sha), zuulvars['patchset'])
|
|
self.assertEqual('release', zuulvars['branch'])
|
|
|
|
self.assertEqual(1, len(self.history))
|
|
|
|
def _test_push_event_reconfigure(self, project, branch,
|
|
expect_reconfigure=False,
|
|
old_sha=None, new_sha=None,
|
|
modified_files=None,
|
|
removed_files=None):
|
|
pevent = self.fake_gitlab.getPushEvent(
|
|
project=project,
|
|
branch='refs/heads/%s' % branch,
|
|
before=old_sha,
|
|
after=new_sha)
|
|
|
|
# record previous tenant reconfiguration time, which may not be set
|
|
old = self.scheds.first.sched.tenant_layout_state.get(
|
|
'tenant-one', EMPTY_LAYOUT_STATE)
|
|
self.waitUntilSettled()
|
|
|
|
self.fake_gitlab.emitEvent(pevent)
|
|
self.waitUntilSettled()
|
|
new = self.scheds.first.sched.tenant_layout_state.get(
|
|
'tenant-one', EMPTY_LAYOUT_STATE)
|
|
|
|
if expect_reconfigure:
|
|
# New timestamp should be greater than the old timestamp
|
|
self.assertLess(old, new)
|
|
else:
|
|
# Timestamps should be equal as no reconfiguration shall happen
|
|
self.assertEqual(old, new)
|
|
|
|
def test_push_event_reconfigure_complex_branch(self):
|
|
|
|
branch = 'feature/somefeature'
|
|
project = 'org/project2'
|
|
|
|
# prepare an existing branch
|
|
self.create_branch(project, branch)
|
|
|
|
self.fake_gitlab.protectBranch(*project.split('/'), branch,
|
|
protected=False)
|
|
|
|
self.fake_gitlab.emitEvent(
|
|
self.fake_gitlab.getPushEvent(
|
|
project,
|
|
branch='refs/heads/%s' % branch))
|
|
self.waitUntilSettled()
|
|
|
|
A = self.fake_gitlab.openFakeMergeRequest(project, branch, 'A')
|
|
old_sha = A.sha
|
|
A.mergeMergeRequest()
|
|
new_sha = random_sha1()
|
|
|
|
# branch is not protected, no reconfiguration even if config file
|
|
self._test_push_event_reconfigure(project, branch,
|
|
expect_reconfigure=False,
|
|
old_sha=old_sha,
|
|
new_sha=new_sha,
|
|
modified_files=['zuul.yaml'])
|
|
|
|
# branch is not protected: no reconfiguration
|
|
self.fake_gitlab.deleteBranch(*project.split('/'), branch)
|
|
|
|
self._test_push_event_reconfigure(project, branch,
|
|
expect_reconfigure=False,
|
|
old_sha=new_sha,
|
|
new_sha='0' * 40,
|
|
removed_files=['zuul.yaml'])
|
|
|
|
|
|
class TestGitlabDriverNoPool(ZuulTestCase):
|
|
config_file = 'zuul-gitlab-driver-no-pool.conf'
|
|
|
|
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
|
def test_merge_request_opened(self):
|
|
|
|
description = "This is the\nMR description."
|
|
A = self.fake_gitlab.openFakeMergeRequest(
|
|
'org/project', 'master', 'A', description=description)
|
|
self.fake_gitlab.emitEvent(
|
|
A.getMergeRequestOpenedEvent(), project='org/project')
|
|
self.waitUntilSettled()
|
|
|
|
self.assertEqual('SUCCESS',
|
|
self.getJobFromHistory('project-test1').result)
|
|
|
|
self.assertEqual('SUCCESS',
|
|
self.getJobFromHistory('project-test2').result)
|
|
|
|
job = self.getJobFromHistory('project-test2')
|
|
zuulvars = job.parameters['zuul']
|
|
self.assertEqual(str(A.number), zuulvars['change'])
|
|
self.assertEqual(str(A.sha), zuulvars['patchset'])
|
|
self.assertEqual('master', zuulvars['branch'])
|
|
self.assertEquals(f'{self.fake_gitlab._test_baseurl}/'
|
|
'org/project/merge_requests/1',
|
|
zuulvars['items'][0]['change_url'])
|
|
self.assertEqual(zuulvars["message"], strings.b64encode(description))
|
|
self.assertEqual(2, len(self.history))
|
|
self.assertEqual(2, len(A.notes))
|
|
self.assertEqual(
|
|
A.notes[0]['body'], "Starting check jobs.")
|
|
self.assertThat(
|
|
A.notes[1]['body'],
|
|
MatchesRegex(r'.*project-test1.*SUCCESS.*', re.DOTALL))
|
|
self.assertThat(
|
|
A.notes[1]['body'],
|
|
MatchesRegex(r'.*project-test2.*SUCCESS.*', re.DOTALL))
|
|
self.assertTrue(A.approved)
|