runbot/runbot_merge/tests/test_disabled_branch.py

172 lines
6.3 KiB
Python
Raw Permalink Normal View History

import pytest
from utils import seen, Commit, pr_page, to_pr
pytestmark = pytest.mark.defaultstatuses
def test_existing_pr_disabled_branch(env, project, repo, config, users, page):
""" PRs to disabled branches are ignored, but what if the PR exists *before*
the branch is disabled?
"""
# run crons from template to clean up the queue before possibly creating
# new work
assert env['base'].run_crons()
project.write({'branch_ids': [
(1, project.branch_ids.id, {'sequence': 0}),
(0, 0, {'name': 'other', 'sequence': 1}),
(0, 0, {'name': 'other2', 'sequence': 2}),
]})
with repo:
[m] = repo.make_commits(None, Commit('root', tree={'a': '1'}), ref='heads/master')
[ot] = repo.make_commits(m, Commit('other', tree={'b': '1'}), ref='heads/other')
repo.make_commits(m, Commit('other2', tree={'c': '1'}), ref='heads/other2')
[c] = repo.make_commits(ot, Commit('wheee', tree={'b': '2'}))
pr = repo.make_pr(title="title", body='body', target='other', head=c)
repo.post_status(c, 'success')
pr.post_comment('hansen r+', config['role_reviewer']['token'])
env.run_crons()
pr_id = to_pr(env, pr)
branch_id = pr_id.target
assert pr_id.staging_id
staging_id = branch_id.active_staging_id
assert staging_id == pr_id.staging_id
[IMP] *: crons tests running for better triggered compatibility Mergebot / forwardport crons need to run in a specific ordering in order to flow into one another correctly. The default ordering being unspecified, it was not possible to use the normal cron runner (instead of the external driver running crons in sequence one at a time). This can be fixed by setting *sequences* on crons, as the cron runner (`_process_jobs`) will use that order to acquire and run crons. Also override `_process_jobs` however: the built-in cron runner fetches a static list of ready crons, then runs that. This is fine for normal situation where the cron runner runs in a loop anyway but it's any issue for the tests, as we expect that cron A can trigger cron B, and we want cron B to run *right now* even if it hadn't been triggered before cron A ran. We can replace `_process_job` with a cut down version which does that (cut down because we don't need most of the error handling / resilience, there's no concurrent workers, there's no module being installed, versions must match, ...). This allows e.g. the cron propagating commit statuses to trigger the staging cron, and both will run within the same `run_crons` session. Something I didn't touch is that `_process_jobs` internally creates completely new environments so there is no way to pass context into the cron jobs anymore (whereas it works for `method_direct_trigger`), this means the context values have to be shunted elsewhere for that purpose which is gross. But even though I'm replacing `_process_jobs`, this seems a bit too much of a change in cron execution semantics. So left it out. While at it tho, silence the spammy `py.warnings` stuff I can't do much about.
2024-07-30 14:21:05 +07:00
# staging of `pr` should have generated a staging branch
_ = repo.get_ref('heads/staging.other')
# disable branch "other"
branch_id.active = False
env.run_crons()
[IMP] *: crons tests running for better triggered compatibility Mergebot / forwardport crons need to run in a specific ordering in order to flow into one another correctly. The default ordering being unspecified, it was not possible to use the normal cron runner (instead of the external driver running crons in sequence one at a time). This can be fixed by setting *sequences* on crons, as the cron runner (`_process_jobs`) will use that order to acquire and run crons. Also override `_process_jobs` however: the built-in cron runner fetches a static list of ready crons, then runs that. This is fine for normal situation where the cron runner runs in a loop anyway but it's any issue for the tests, as we expect that cron A can trigger cron B, and we want cron B to run *right now* even if it hadn't been triggered before cron A ran. We can replace `_process_job` with a cut down version which does that (cut down because we don't need most of the error handling / resilience, there's no concurrent workers, there's no module being installed, versions must match, ...). This allows e.g. the cron propagating commit statuses to trigger the staging cron, and both will run within the same `run_crons` session. Something I didn't touch is that `_process_jobs` internally creates completely new environments so there is no way to pass context into the cron jobs anymore (whereas it works for `method_direct_trigger`), this means the context values have to be shunted elsewhere for that purpose which is gross. But even though I'm replacing `_process_jobs`, this seems a bit too much of a change in cron execution semantics. So left it out. While at it tho, silence the spammy `py.warnings` stuff I can't do much about.
2024-07-30 14:21:05 +07:00
# triggered cleanup should have deleted the staging for the disabled `other`
# target branch
with pytest.raises(AssertionError, match=r'Not Found'):
repo.get_ref('heads/staging.other')
# the PR should not have been closed implicitly
assert pr_id.state == 'ready'
# but it should be unstaged
assert not pr_id.staging_id
assert not branch_id.active_staging_id
assert staging_id.state == 'cancelled', \
"closing the PRs should have canceled the staging"
assert staging_id.reason == "Target branch deactivated by 'admin'."
p = pr_page(page, pr)
[target] = p.cssselect('table tr.bg-info')
assert 'inactive' in target.classes
assert target[0].text_content() == "other"
env.run_crons()
assert pr.comments == [
(users['reviewer'], "hansen r+"),
seen(env, pr, users),
(users['user'], "@%(user)s @%(reviewer)s the target branch 'other' has been disabled, you may want to close this PR." % users),
]
with repo:
[c2] = repo.make_commits(ot, Commit('wheee', tree={'b': '3'}), ref=pr.ref, make=False)
env.run_crons()
assert pr.comments[3] == (
users['user'],
"This PR targets the disabled branch {repository}:{target}, it needs to be retargeted before it can be merged.".format(
repository=repo.name,
target="other",
)
)
assert pr_id.head == c2, "pr should be aware of its update"
with repo:
pr.base = 'other2'
repo.post_status(c2, 'success')
pr.post_comment('hansen rebase-ff r+', config['role_reviewer']['token'])
env.run_crons()
assert pr.comments[4:] == [
(users['reviewer'], 'hansen rebase-ff r+'),
(users['user'], "Merge method set to rebase and fast-forward."),
]
assert pr_id.state == 'ready'
assert pr_id.target == env['runbot_merge.branch'].search([('name', '=', 'other2')])
assert pr_id.staging_id
# staging of `pr` should have generated a staging branch
[IMP] *: crons tests running for better triggered compatibility Mergebot / forwardport crons need to run in a specific ordering in order to flow into one another correctly. The default ordering being unspecified, it was not possible to use the normal cron runner (instead of the external driver running crons in sequence one at a time). This can be fixed by setting *sequences* on crons, as the cron runner (`_process_jobs`) will use that order to acquire and run crons. Also override `_process_jobs` however: the built-in cron runner fetches a static list of ready crons, then runs that. This is fine for normal situation where the cron runner runs in a loop anyway but it's any issue for the tests, as we expect that cron A can trigger cron B, and we want cron B to run *right now* even if it hadn't been triggered before cron A ran. We can replace `_process_job` with a cut down version which does that (cut down because we don't need most of the error handling / resilience, there's no concurrent workers, there's no module being installed, versions must match, ...). This allows e.g. the cron propagating commit statuses to trigger the staging cron, and both will run within the same `run_crons` session. Something I didn't touch is that `_process_jobs` internally creates completely new environments so there is no way to pass context into the cron jobs anymore (whereas it works for `method_direct_trigger`), this means the context values have to be shunted elsewhere for that purpose which is gross. But even though I'm replacing `_process_jobs`, this seems a bit too much of a change in cron execution semantics. So left it out. While at it tho, silence the spammy `py.warnings` stuff I can't do much about.
2024-07-30 14:21:05 +07:00
_ = repo.get_ref('heads/staging.other2')
def test_new_pr_no_branch(env, project, repo, users):
""" A new PR to an *unknown* branch should be ignored and warn
"""
with repo:
[m] = repo.make_commits(None, Commit('root', tree={'a': '1'}), ref='heads/master')
[ot] = repo.make_commits(m, Commit('other', tree={'b': '1'}), ref='heads/other')
[c] = repo.make_commits(ot, Commit('wheee', tree={'b': '2'}))
pr = repo.make_pr(title="title", body='body', target='other', head=c)
env.run_crons()
# the PR should not have been created in the backend
with pytest.raises(TimeoutError):
to_pr(env, pr, attempts=1)
assert pr.comments == [
(users['user'], "This PR targets the un-managed branch %s:other, it needs to be retargeted before it can be merged." % repo.name),
]
def test_new_pr_disabled_branch(env, project, repo, users):
""" A new PR to a *disabled* branch should be accepted (rather than ignored)
but should warn
"""
project.write({'branch_ids': [(0, 0, {'name': 'other', 'active': False})]})
with repo:
[m] = repo.make_commits(None, Commit('root', tree={'a': '1'}), ref='heads/master')
[ot] = repo.make_commits(m, Commit('other', tree={'b': '1'}), ref='heads/other')
[c] = repo.make_commits(ot, Commit('wheee', tree={'b': '2'}))
pr = repo.make_pr(title="title", body='body', target='other', head=c)
env.run_crons()
pr_id = to_pr(env, pr)
assert pr_id, "the PR should have been created in the backend"
assert pr_id.state == 'opened'
assert pr.comments == [
(users['user'], "This PR targets the disabled branch %s:other, it needs to be retargeted before it can be merged." % repo.name),
seen(env, pr, users),
]
def test_review_disabled_branch(env, project, repo, users, config):
with repo:
[m] = repo.make_commits(None, Commit("init", tree={'m': 'm'}), ref='heads/master')
[c] = repo.make_commits(m, Commit('pr', tree={'m': 'n'}))
pr = repo.make_pr(target="master", head=c)
env.run_crons()
target = project.branch_ids
target.active = False
env.run_crons()
with repo:
pr.post_comment("A normal comment", config['role_other']['token'])
with repo:
pr.post_comment("hansen r+", config['role_reviewer']['token'])
env.run_crons()
assert pr.comments == [
seen(env, pr, users),
(users['user'], "@{user} the target branch {target!r} has been disabled, you may want to close this PR.".format(
**users,
target=target.name,
)),
(users['other'], "A normal comment"),
(users['reviewer'], "hansen r+"),
(users['user'], "This PR targets the disabled branch {repository}:{target}, it needs to be retargeted before it can be merged.".format(
repository=repo.name,
target=target.name,
)),
]