mirror of
https://github.com/odoo/runbot.git
synced 2025-03-15 15:35:46 +07:00

The `to_pr` helper was added a *while* ago to replace the pretty verbose process of looking a PR with the right number in the right repository after a `make_pr`. However while I did some ad-hoc replacement of existing `search` calls as I had to work with existing tests I never did a full search and replace to try and excise searches from the test suite. This is now done. I've undoubtedly missed a few, but a hundred-odd lines of codes have been simplified.
1269 lines
49 KiB
Python
1269 lines
49 KiB
Python
# -*- coding: utf-8 -*-
|
|
from datetime import datetime, timedelta
|
|
|
|
import pytest
|
|
|
|
from utils import seen, Commit, to_pr, make_basic, prevent_unstaging
|
|
|
|
|
|
def test_no_token(env, config, make_repo):
|
|
""" if there's no token on the repo, nothing should break though should
|
|
log
|
|
"""
|
|
# create project configured with remotes on the repo but no token
|
|
prod, _ = make_basic(env, config, make_repo, fp_token=False, fp_remote=True)
|
|
|
|
with prod:
|
|
prod.make_commits(
|
|
'a', Commit('c0', tree={'a': '0'}), ref='heads/abranch'
|
|
)
|
|
pr = prod.make_pr(target='a', head='abranch')
|
|
prod.post_status(pr.head, 'success', 'legal/cla')
|
|
prod.post_status(pr.head, 'success', 'ci/runbot')
|
|
pr.post_comment('hansen r+', config['role_reviewer']['token'])
|
|
|
|
env.run_crons()
|
|
with prod:
|
|
prod.post_status('staging.a', 'success', 'legal/cla')
|
|
prod.post_status('staging.a', 'success', 'ci/runbot')
|
|
|
|
# wanted to use capfd, however it's not compatible with the subprocess
|
|
# being created beforehand and server() depending on capfd() would remove
|
|
# all its output from the normal pytest capture (dumped on test failure)
|
|
#
|
|
# so I'd really have to hand-roll the entire thing by having server()
|
|
# pipe stdout/stderr to temp files, yield those temp files, and have the
|
|
# tests mess around with reading those files, and finally have the server
|
|
# dump the file contents back to the test runner's stdout/stderr on
|
|
# fixture teardown...
|
|
env.run_crons()
|
|
assert len(env['runbot_merge.pull_requests'].search([], order='number')) == 1,\
|
|
"should not have created forward port"
|
|
|
|
def test_remove_token(env, config, make_repo):
|
|
prod, _ = make_basic(env, config, make_repo)
|
|
env['runbot_merge.project'].search([]).fp_github_token = False
|
|
|
|
with prod:
|
|
prod.make_commits(
|
|
'a', Commit('c0', tree={'a': '0'}), ref='heads/abranch'
|
|
)
|
|
pr = prod.make_pr(target='a', head='abranch')
|
|
prod.post_status(pr.head, 'success', 'legal/cla')
|
|
prod.post_status(pr.head, 'success', 'ci/runbot')
|
|
pr.post_comment('hansen r+', config['role_reviewer']['token'])
|
|
|
|
env.run_crons()
|
|
with prod:
|
|
prod.post_status('staging.a', 'success', 'legal/cla')
|
|
prod.post_status('staging.a', 'success', 'ci/runbot')
|
|
|
|
env.run_crons()
|
|
assert len(env['runbot_merge.pull_requests'].search([], order='number')) == 1,\
|
|
"should not have created forward port"
|
|
|
|
def test_no_target(env, config, make_repo):
|
|
prod, _ = make_basic(env, config, make_repo, fp_remote=False)
|
|
|
|
with prod:
|
|
prod.make_commits(
|
|
'a', Commit('c0', tree={'a': '0'}), ref='heads/abranch'
|
|
)
|
|
pr = prod.make_pr(target='a', head='abranch')
|
|
prod.post_status(pr.head, 'success', 'legal/cla')
|
|
prod.post_status(pr.head, 'success', 'ci/runbot')
|
|
pr.post_comment('hansen r+', config['role_reviewer']['token'])
|
|
|
|
env.run_crons()
|
|
with prod:
|
|
prod.post_status('staging.a', 'success', 'legal/cla')
|
|
prod.post_status('staging.a', 'success', 'ci/runbot')
|
|
|
|
env.run_crons()
|
|
assert len(env['runbot_merge.pull_requests'].search([], order='number')) == 1,\
|
|
"should not have created forward port"
|
|
|
|
def test_failed_staging(env, config, make_repo):
|
|
prod, _ = make_basic(env, config, make_repo)
|
|
|
|
reviewer = config['role_reviewer']['token']
|
|
with prod:
|
|
prod.make_commits('a', Commit('c', tree={'a': '0'}), ref='heads/abranch')
|
|
pr1 = prod.make_pr(target='a', head='abranch')
|
|
prod.post_status(pr1.head, 'success', 'legal/cla')
|
|
prod.post_status(pr1.head, 'success', 'ci/runbot')
|
|
pr1.post_comment('hansen r+', reviewer)
|
|
env.run_crons()
|
|
with prod:
|
|
prod.post_status('staging.a', 'success', 'legal/cla')
|
|
prod.post_status('staging.a', 'success', 'ci/runbot')
|
|
env.run_crons()
|
|
|
|
pr1_id, pr2_id = env['runbot_merge.pull_requests'].search([], order='number')
|
|
assert pr2_id.parent_id == pr2_id.source_id == pr1_id
|
|
with prod:
|
|
prod.post_status(pr2_id.head, 'success', 'legal/cla')
|
|
prod.post_status(pr2_id.head, 'success', 'ci/runbot')
|
|
env.run_crons()
|
|
|
|
pr1_id, pr2_id, pr3_id = env['runbot_merge.pull_requests'].search([], order='number')
|
|
pr3 = prod.get_pr(pr3_id.number)
|
|
with prod:
|
|
prod.post_status(pr3_id.head, 'success', 'legal/cla')
|
|
prod.post_status(pr3_id.head, 'success', 'ci/runbot')
|
|
pr3.post_comment('hansen r+', reviewer)
|
|
env.run_crons()
|
|
|
|
prod.commit('staging.c')
|
|
|
|
with prod:
|
|
prod.post_status('staging.b', 'success', 'legal/cla')
|
|
prod.post_status('staging.b', 'success', 'ci/runbot')
|
|
prod.post_status('staging.c', 'failure', 'ci/runbot')
|
|
env.run_crons()
|
|
|
|
pr3_head = env['runbot_merge.commit'].search([('sha', '=', pr3_id.head)])
|
|
assert pr3_head
|
|
|
|
# send a new status to the PR, as if somebody had rebuilt it or something
|
|
with prod:
|
|
pr3.post_comment('hansen retry', reviewer)
|
|
prod.post_status(pr3_id.head, 'success', 'foo/bar')
|
|
prod.post_status(pr3_id.head, 'success', 'legal/cla')
|
|
assert pr3_head.to_check, "check that the commit was updated as to process"
|
|
env.run_crons()
|
|
assert not pr3_head.to_check, "check that the commit was processed"
|
|
assert pr3_id.state == 'ready'
|
|
assert pr3_id.staging_id
|
|
|
|
def test_fw_retry(env, config, make_repo, users):
|
|
prod, _ = make_basic(env, config, make_repo, statuses='default')
|
|
other_token = config['role_other']['token']
|
|
fork = prod.fork(token=other_token)
|
|
with prod, fork:
|
|
fork.make_commits('a', Commit('c', tree={'a': '0'}), ref='heads/abranch')
|
|
pr1 = prod.make_pr(
|
|
title="whatever",
|
|
target='a',
|
|
head=f'{fork.owner}:abranch',
|
|
token=other_token,
|
|
)
|
|
prod.post_status(pr1.head, 'success')
|
|
pr1.post_comment('hansen r+', config['role_reviewer']['token'])
|
|
env.run_crons()
|
|
|
|
other_partner = env['res.partner'].search([('github_login', '=', users['other'])])
|
|
assert len(other_partner) == 1
|
|
other_partner.email = "foo@example.com"
|
|
|
|
with prod:
|
|
prod.post_status('staging.a', 'success')
|
|
env.run_crons()
|
|
|
|
_pr1_id, pr2_id = env['runbot_merge.pull_requests'].search([], order='number')
|
|
pr2 = prod.get_pr(pr2_id.number)
|
|
with prod:
|
|
prod.post_status(pr2_id.head, 'success')
|
|
pr2.post_comment('hansen r+', other_token)
|
|
env.run_crons()
|
|
assert not pr2_id.blocked
|
|
|
|
with prod:
|
|
prod.post_status('staging.b', 'failure')
|
|
env.run_crons()
|
|
|
|
assert pr2_id.error
|
|
with prod:
|
|
pr2.post_comment('hansen r+', other_token)
|
|
env.run_crons()
|
|
assert pr2_id.state == 'error'
|
|
with prod:
|
|
pr2.post_comment('hansen retry', other_token)
|
|
env.run_crons()
|
|
assert pr2_id.state == 'ready'
|
|
|
|
assert pr2.comments == [
|
|
seen(env, pr2, users),
|
|
(users['user'], "This PR targets b and is part of the forward-port chain. Further PRs will be created up to c.\n\nMore info at https://github.com/odoo/odoo/wiki/Mergebot#forward-port\n"),
|
|
(users['other'], 'hansen r+'),
|
|
(users['user'], "@{other} @{reviewer} staging failed: default".format_map(users)),
|
|
|
|
(users['other'], 'hansen r+'),
|
|
(users['user'], "This PR is already reviewed, it's in error, you might want to `retry` it instead (if you have already confirmed the error is not legitimate)."),
|
|
|
|
(users['other'], 'hansen retry'),
|
|
]
|
|
|
|
class TestNotAllBranches:
|
|
""" Check that forward-ports don't behave completely insanely when not all
|
|
branches are supported on all repositories.
|
|
|
|
repo A branches a -> b -> c
|
|
a0 -> a1 -> a2 branch a
|
|
`-> a11 -> a22 branch b
|
|
`-> a111 branch c
|
|
repo B branches a -> c
|
|
b0 -> b1 -> b2 branch a
|
|
|
|
|
`-> b000 branch c
|
|
"""
|
|
@pytest.fixture
|
|
def repos(self, env, config, make_repo, setreviewers):
|
|
a = make_repo('A')
|
|
with a:
|
|
_, a_, _ = a.make_commits(
|
|
None,
|
|
Commit('a0', tree={'a': '0'}),
|
|
Commit('a1', tree={'a': '1'}),
|
|
Commit('a2', tree={'a': '2'}),
|
|
ref='heads/a'
|
|
)
|
|
b_, _ = a.make_commits(
|
|
a_,
|
|
Commit('a11', tree={'b': '11'}),
|
|
Commit('a22', tree={'b': '22'}),
|
|
ref='heads/b'
|
|
)
|
|
a.make_commits(b_, Commit('a111', tree={'c': '111'}), ref='heads/c')
|
|
a_dev = a.fork()
|
|
b = make_repo('B')
|
|
with b:
|
|
_, _a, _ = b.make_commits(
|
|
None,
|
|
Commit('b0', tree={'a': 'x'}),
|
|
Commit('b1', tree={'a': 'y'}),
|
|
Commit('b2', tree={'a': 'z'}),
|
|
ref='heads/a'
|
|
)
|
|
b.make_commits(_a, Commit('b000', tree={'c': 'x'}), ref='heads/c')
|
|
b_dev = b.fork()
|
|
|
|
project = env['runbot_merge.project'].create({
|
|
'name': 'proj',
|
|
'github_token': config['github']['token'],
|
|
'github_prefix': 'hansen',
|
|
'github_name': config['github']['name'],
|
|
'github_email': "foo@example.org",
|
|
'fp_github_token': config['github']['token'],
|
|
'fp_github_name': 'herbert',
|
|
'branch_ids': [
|
|
(0, 0, {'name': 'a', 'sequence': 2}),
|
|
(0, 0, {'name': 'b', 'sequence': 1}),
|
|
(0, 0, {'name': 'c', 'sequence': 0}),
|
|
]
|
|
})
|
|
repo_a = env['runbot_merge.repository'].create({
|
|
'project_id': project.id,
|
|
'name': a.name,
|
|
'required_statuses': 'ci/runbot',
|
|
'fp_remote_target': a_dev.name,
|
|
})
|
|
repo_b = env['runbot_merge.repository'].create({
|
|
'project_id': project.id,
|
|
'name': b.name,
|
|
'required_statuses': 'ci/runbot',
|
|
'fp_remote_target': b_dev.name,
|
|
'branch_filter': '[("name", "in", ["a", "c"])]',
|
|
})
|
|
setreviewers(repo_a, repo_b)
|
|
env['runbot_merge.events_sources'].create([{'repository': a.name}, {'repository': b.name}])
|
|
return project, a, a_dev, b, b_dev
|
|
|
|
def test_single_first(self, env, repos, config):
|
|
""" A merge in A.a should be forward-ported to A.b and A.c
|
|
"""
|
|
project, a, a_dev, b, _ = repos
|
|
with a, a_dev:
|
|
[c] = a_dev.make_commits('a', Commit('pr', tree={'pr': '1'}), ref='heads/change')
|
|
pr = a.make_pr(target='a', title="a pr", head=a_dev.owner + ':change')
|
|
a.post_status(c, 'success', 'ci/runbot')
|
|
pr.post_comment('hansen r+', config['role_reviewer']['token'])
|
|
p = to_pr(env, pr)
|
|
env.run_crons()
|
|
assert p.staging_id
|
|
with a, b:
|
|
for repo in a, b:
|
|
repo.post_status('staging.a', 'success', 'ci/runbot')
|
|
env.run_crons()
|
|
|
|
a_head = a.commit('a')
|
|
assert a_head.message.startswith('pr\n\n')
|
|
assert a.read_tree(a_head) == {'a': '2', 'pr': '1'}
|
|
|
|
pr0, pr1 = env['runbot_merge.pull_requests'].search([], order='number')
|
|
with a:
|
|
a.post_status(pr1.head, 'success', 'ci/runbot')
|
|
env.run_crons()
|
|
|
|
pr0, pr1, pr2 = env['runbot_merge.pull_requests'].search([], order='number')
|
|
with a:
|
|
a.post_status(pr2.head, 'success', 'ci/runbot')
|
|
a.get_pr(pr2.number).post_comment(
|
|
'hansen r+',
|
|
config['role_reviewer']['token'])
|
|
env.run_crons()
|
|
assert pr1.staging_id
|
|
assert pr2.staging_id
|
|
with a, b:
|
|
a.post_status('staging.b', 'success', 'ci/runbot')
|
|
a.post_status('staging.c', 'success', 'ci/runbot')
|
|
b.post_status('staging.c', 'success', 'ci/runbot')
|
|
env.run_crons()
|
|
|
|
assert pr0.state == 'merged'
|
|
assert pr1.state == 'merged'
|
|
assert pr2.state == 'merged'
|
|
assert a.read_tree(a.commit('b')) == {'a': '1', 'b': '22', 'pr': '1'}
|
|
assert a.read_tree(a.commit('c')) == {'a': '1', 'b': '11', 'c': '111', 'pr': '1'}
|
|
|
|
def test_single_second(self, env, repos, config):
|
|
""" A merge in B.a should "skip ahead" to B.c
|
|
"""
|
|
project, a, _, b, b_dev = repos
|
|
with b, b_dev:
|
|
[c] = b_dev.make_commits('a', Commit('pr', tree={'pr': '1'}), ref='heads/change')
|
|
pr = b.make_pr(target='a', title="a pr", head=b_dev.owner + ':change')
|
|
b.post_status(c, 'success', 'ci/runbot')
|
|
pr.post_comment('hansen r+', config['role_reviewer']['token'])
|
|
env.run_crons()
|
|
|
|
with a, b:
|
|
a.post_status('staging.a', 'success', 'ci/runbot')
|
|
b.post_status('staging.a', 'success', 'ci/runbot')
|
|
env.run_crons()
|
|
|
|
assert b.read_tree(b.commit('a')) == {'a': 'z', 'pr': '1'}
|
|
|
|
pr0, pr1 = env['runbot_merge.pull_requests'].search([], order='number')
|
|
with b:
|
|
b.post_status(pr1.head, 'success', 'ci/runbot')
|
|
b.get_pr(pr1.number).post_comment(
|
|
'hansen r+',
|
|
config['role_reviewer']['token'])
|
|
env.run_crons()
|
|
with a, b:
|
|
a.post_status('staging.c', 'success', 'ci/runbot')
|
|
b.post_status('staging.c', 'success', 'ci/runbot')
|
|
env.run_crons()
|
|
|
|
assert pr0.state == 'merged'
|
|
assert pr1.state == 'merged'
|
|
assert b.read_tree(b.commit('c')) == {'a': 'y', 'c': 'x', 'pr': '1'}
|
|
|
|
def test_both_first(self, env, repos, config, users):
|
|
""" A merge in A.a, B.a should... not be forward-ported at all?
|
|
"""
|
|
project, a, a_dev, b, b_dev = repos
|
|
with a, a_dev:
|
|
[c_a] = a_dev.make_commits('a', Commit('pr a', tree={'pr': 'a'}), ref='heads/change')
|
|
pr_a = a.make_pr(target='a', title='a pr', head=a_dev.owner + ':change')
|
|
a.post_status(c_a, 'success', 'ci/runbot')
|
|
pr_a.post_comment('hansen r+', config['role_reviewer']['token'])
|
|
with b, b_dev:
|
|
[c_b] = b_dev.make_commits('a', Commit('pr b', tree={'pr': 'b'}), ref='heads/change')
|
|
pr_b = b.make_pr(target='a', title='b pr', head=b_dev.owner + ':change')
|
|
b.post_status(c_b, 'success', 'ci/runbot')
|
|
pr_b.post_comment('hansen r+', config['role_reviewer']['token'])
|
|
env.run_crons()
|
|
|
|
with a, b:
|
|
for repo in a, b:
|
|
repo.post_status('staging.a', 'success', 'ci/runbot')
|
|
env.run_crons()
|
|
|
|
pr_a_id = to_pr(env, pr_a)
|
|
pr_b_id = to_pr(env, pr_b)
|
|
assert pr_a_id.state == pr_b_id.state == 'merged'
|
|
assert env['runbot_merge.pull_requests'].search([]) == pr_a_id | pr_b_id
|
|
# should have refused to create a forward port because the PRs have
|
|
# different next target
|
|
assert pr_a.comments == [
|
|
(users['reviewer'], 'hansen r+'),
|
|
seen(env, pr_a, users),
|
|
(users['user'], "@%s @%s this pull request can not be forward-ported:"
|
|
" next branch is 'b' but linked pull request %s "
|
|
"has a next branch 'c'." % (
|
|
users['user'], users['reviewer'], pr_b_id.display_name,
|
|
)),
|
|
]
|
|
assert pr_b.comments == [
|
|
(users['reviewer'], 'hansen r+'),
|
|
seen(env, pr_b, users),
|
|
(users['user'], "@%s @%s this pull request can not be forward-ported:"
|
|
" next branch is 'c' but linked pull request %s "
|
|
"has a next branch 'b'." % (
|
|
users['user'], users['reviewer'], pr_a_id.display_name,
|
|
)),
|
|
]
|
|
|
|
def test_new_intermediate_branch(env, config, make_repo):
|
|
""" In the case of a freeze / release a new intermediate branch appears in
|
|
the sequence. New or ongoing forward ports should pick it up just fine (as
|
|
the "next target" is decided when a PR is ported forward) however this is
|
|
an issue for existing yet-to-be-merged sequences e.g. given the branches
|
|
1.0, 2.0 and master, if a branch 3.0 is forked off from master and inserted
|
|
before it, we need to create a new *intermediate* forward port PR
|
|
"""
|
|
def validate(repo, commit):
|
|
repo.post_status(commit, 'success', 'ci/runbot')
|
|
repo.post_status(commit, 'success', 'legal/cla')
|
|
prod, _ = make_basic(env, config, make_repo)
|
|
prod2, _ = make_basic(env, config, make_repo)
|
|
project = env['runbot_merge.project'].search([])
|
|
assert len(project.repo_ids) == 2
|
|
|
|
original_c_tree = prod.read_tree(prod.commit('c'))
|
|
prs = []
|
|
with prod, prod2:
|
|
for i in ['0', '1']:
|
|
prod.make_commits('a', Commit(i, tree={i:i}), ref='heads/branch%s' % i)
|
|
pr = prod.make_pr(target='a', head='branch%s' % i)
|
|
prs.append(pr)
|
|
validate(prod, pr.head)
|
|
pr.post_comment('hansen r+', config['role_reviewer']['token'])
|
|
|
|
# also add a PR targeting b forward-ported to c, in order to check
|
|
# for an insertion right after the source, as well as have linked PRs in
|
|
# two different repos
|
|
prod.make_commits('b', Commit('x', tree={'x': 'x'}), ref='heads/branchx')
|
|
prod2.make_commits('b', Commit('x2', tree={'x': 'x2'}), ref='heads/branchx')
|
|
prx = prod.make_pr(target='b', head='branchx')
|
|
prx2 = prod2.make_pr(target='b', head='branchx')
|
|
validate(prod, prx.head)
|
|
validate(prod2, prx2.head)
|
|
prx.post_comment('hansen r+', config['role_reviewer']['token'])
|
|
prx2.post_comment('hansen r+', config['role_reviewer']['token'])
|
|
env.run_crons()
|
|
with prod, prod2:
|
|
for r in [prod, prod2]:
|
|
validate(r, 'staging.a')
|
|
validate(r, 'staging.b')
|
|
env.run_crons()
|
|
|
|
# should have merged pr1, pr2 and prx and created their forward ports, now
|
|
# validate pr0's FP so the c-targeted FP is created
|
|
PRs = env['runbot_merge.pull_requests']
|
|
pr0_id = to_pr(env, prs[0])
|
|
pr0_fp_id = PRs.search([
|
|
('source_id', '=', pr0_id.id),
|
|
])
|
|
assert pr0_fp_id
|
|
assert pr0_fp_id.target.name == 'b'
|
|
with prod:
|
|
validate(prod, pr0_fp_id.head)
|
|
env.run_crons()
|
|
assert pr0_fp_id.state == 'validated'
|
|
original0 = PRs.search([('parent_id', '=', pr0_fp_id.id)])
|
|
assert original0, "Could not find FP of PR0 to C"
|
|
assert original0.target.name == 'c'
|
|
|
|
pr1_id = to_pr(env, prs[1])
|
|
pr1_fp_id = PRs.search([('source_id', '=', pr1_id.id)])
|
|
assert pr1_fp_id.target.name == 'b'
|
|
|
|
# also check prx's fp
|
|
prx_id = to_pr(env, prx)
|
|
prx2_id = to_pr(env, prx2)
|
|
assert prx_id.label == prx2_id.label
|
|
|
|
prx_fp_id = PRs.search([('source_id', '=', prx_id.id)])
|
|
assert prx_fp_id
|
|
assert prx_fp_id.target.name == 'c'
|
|
prx2_fp_id = PRs.search([('source_id', '=', prx2_id.id)])
|
|
assert prx2_fp_id
|
|
assert prx2_fp_id.target.name == 'c'
|
|
assert prx_fp_id.label == prx2_fp_id.label,\
|
|
"ensure labels of PRs of same batch are the same"
|
|
|
|
# NOTE: the branch must be created on git(hub) first, probably
|
|
# create new branch forked from the "current master" (c)
|
|
c = prod.commit('c').id
|
|
with prod:
|
|
prod.make_ref('heads/new', c)
|
|
c2 = prod2.commit('c').id
|
|
with prod2:
|
|
prod2.make_ref('heads/new', c2)
|
|
currents = {branch.name: branch.id for branch in project.branch_ids}
|
|
# insert a branch between "b" and "c"
|
|
project.write({
|
|
'branch_ids': [
|
|
(1, currents['a'], {'sequence': 3}),
|
|
(1, currents['b'], {'sequence': 2, 'active': False}),
|
|
(1, currents['c'], {'sequence': 0})
|
|
]
|
|
})
|
|
env.run_crons()
|
|
project.write({
|
|
'branch_ids': [
|
|
(0, False, {'name': 'new', 'sequence': 1}),
|
|
]
|
|
})
|
|
env.run_crons()
|
|
|
|
assert pr0_fp_id.state == 'validated'
|
|
# created an intermediate PR for 0 and x
|
|
desc0 = PRs.search([('source_id', '=', pr0_id.id)])
|
|
new0 = desc0 - pr0_fp_id - original0
|
|
assert len(new0) == 1
|
|
assert new0.parent_id == pr0_fp_id
|
|
assert new0.target.name == 'new'
|
|
assert original0.parent_id == new0
|
|
|
|
descx = PRs.search([('source_id', '=', prx_id.id)])
|
|
newx = descx - prx_fp_id
|
|
assert len(newx) == 1
|
|
assert newx.parent_id == prx_id
|
|
assert newx.target.name == 'new'
|
|
assert prx_fp_id.parent_id == newx
|
|
|
|
descx2 = PRs.search([('source_id', '=', prx2_id.id)])
|
|
newx2 = descx2 - prx2_fp_id
|
|
assert len(newx2) == 1
|
|
assert newx2.parent_id == prx2_id
|
|
assert newx2.target.name == 'new'
|
|
assert prx2_fp_id.parent_id == newx2
|
|
|
|
assert newx.label == newx2.label
|
|
|
|
# created followups for 1
|
|
# earliest followup is followup from deactivating a branch, creates fp in
|
|
# n+1 = c (from b), then inserting a new branch between b and c should
|
|
# create a bridge forward port
|
|
_, pr1_c, pr1_new = PRs.search([('source_id', '=', pr1_id.id)], order='number')
|
|
assert pr1_c.target.name == 'c'
|
|
assert pr1_new.target.name == 'new'
|
|
assert pr1_c.parent_id == pr1_new
|
|
assert pr1_new.parent_id == pr1_fp_id
|
|
|
|
# ci on pr1/pr2 fp to b
|
|
sources = [to_pr(env, pr).id for pr in prs]
|
|
sources.append(prx_id.id)
|
|
sources.append(prx2_id.id)
|
|
|
|
def get_repo(pr):
|
|
if pr.repository.name == prod.name:
|
|
return prod
|
|
return prod2
|
|
# CI all the forward port PRs (shouldn't hurt to re-ci the forward port of
|
|
# prs[0] to b aka pr0_fp_id
|
|
fps = PRs.search([('source_id', 'in', sources), ('target.name', '=', ['new', 'c'])])
|
|
with prod, prod2:
|
|
for fp in fps:
|
|
validate(get_repo(fp), fp.head)
|
|
env.run_crons()
|
|
# now fps should be the last PR of each sequence, and thus r+-able (via
|
|
# fwbot so preceding PR is also r+'d)
|
|
with prod, prod2:
|
|
for pr in fps.filtered(lambda p: p.target.name == 'c'):
|
|
get_repo(pr).get_pr(pr.number).post_comment(
|
|
'hansen r+',
|
|
config['role_reviewer']['token'])
|
|
assert all(p.state == 'merged' for p in PRs.browse(sources)),\
|
|
"all sources should be merged"
|
|
assert all(p.state == 'ready' for p in PRs.search([('source_id', '!=', False), ('target.name', '!=', 'b')])), \
|
|
"All PRs except sources and prs on disabled branch should be ready"
|
|
env.run_crons()
|
|
|
|
assert len(env['runbot_merge.stagings'].search([])) == 2,\
|
|
"enabled branches should have been staged"
|
|
with prod, prod2:
|
|
for target in ['new', 'c']:
|
|
validate(prod, f'staging.{target}')
|
|
validate(prod2, f'staging.{target}')
|
|
env.run_crons()
|
|
assert all(p.state == 'merged' for p in PRs.search([('target.name', '!=', 'b')])), \
|
|
"All PRs except disabled branch should be merged now"
|
|
|
|
assert prod.read_tree(prod.commit('c')) == {
|
|
**original_c_tree,
|
|
'0': '0', '1': '1', # updates from PRs
|
|
'x': 'x',
|
|
}, "check that C got all the updates"
|
|
assert prod.read_tree(prod.commit('new')) == {
|
|
**original_c_tree,
|
|
'0': '0', '1': '1', # updates from PRs
|
|
'x': 'x',
|
|
}, "check that new got all the updates (should be in the same state as c really)"
|
|
|
|
def test_author_can_close_via_fwbot(env, config, make_repo):
|
|
prod, _ = make_basic(env, config, make_repo)
|
|
other_user = config['role_other']
|
|
other_token = other_user['token']
|
|
other = prod.fork(token=other_token)
|
|
|
|
with prod, other:
|
|
[c] = other.make_commits('a', Commit('c', tree={'0': '0'}), ref='heads/change')
|
|
pr = prod.make_pr(
|
|
target='a', title='my change',
|
|
head=other_user['user'] + ':change',
|
|
token=other_token
|
|
)
|
|
# should be able to close and open own PR
|
|
pr.close(other_token)
|
|
pr.open(other_token)
|
|
prod.post_status(c, 'success', 'legal/cla')
|
|
prod.post_status(c, 'success', 'ci/runbot')
|
|
pr.post_comment('hansen close', other_token)
|
|
pr.post_comment('hansen r+', config['role_reviewer']['token'])
|
|
env.run_crons()
|
|
assert pr.state == 'open'
|
|
|
|
with prod:
|
|
prod.post_status('staging.a', 'success', 'legal/cla')
|
|
prod.post_status('staging.a', 'success', 'ci/runbot')
|
|
env.run_crons()
|
|
|
|
pr0_id, pr1_id = env['runbot_merge.pull_requests'].search([], order='number')
|
|
assert pr0_id.number == pr.number
|
|
pr1 = prod.get_pr(pr1_id.number)
|
|
# `other` can't close fw PR directly, because that requires triage (and even
|
|
# write depending on account type) access to the repo, which an external
|
|
# contributor probably does not have
|
|
with prod, pytest.raises(Exception):
|
|
pr1.close(other_token)
|
|
# use can close via fwbot
|
|
with prod:
|
|
pr1.post_comment('hansen close', other_token)
|
|
env.run_crons()
|
|
assert pr1.state == 'closed'
|
|
assert pr1_id.state == 'closed'
|
|
|
|
def test_skip_ci_all(env, config, make_repo):
|
|
prod, _ = make_basic(env, config, make_repo)
|
|
|
|
with prod:
|
|
prod.make_commits('a', Commit('x', tree={'x': '0'}), ref='heads/change')
|
|
pr = prod.make_pr(target='a', head='change')
|
|
prod.post_status(pr.head, 'success', 'legal/cla')
|
|
prod.post_status(pr.head, 'success', 'ci/runbot')
|
|
pr.post_comment('hansen fw=skipci', config['role_reviewer']['token'])
|
|
pr.post_comment('hansen r+', config['role_reviewer']['token'])
|
|
env.run_crons()
|
|
assert to_pr(env, pr).batch_id.fw_policy == 'skipci'
|
|
|
|
with prod:
|
|
prod.post_status('staging.a', 'success', 'legal/cla')
|
|
prod.post_status('staging.a', 'success', 'ci/runbot')
|
|
env.run_crons()
|
|
|
|
# run cron a few more times for the fps
|
|
env.run_crons()
|
|
env.run_crons()
|
|
env.run_crons()
|
|
|
|
pr0_id, pr1_id, pr2_id = env['runbot_merge.pull_requests'].search([], order='number')
|
|
assert pr1_id.state == 'opened'
|
|
assert pr1_id.source_id == pr0_id
|
|
assert pr2_id.state == 'opened'
|
|
assert pr2_id.source_id == pr0_id
|
|
|
|
def test_skip_ci_next(env, config, make_repo):
|
|
prod, _ = make_basic(env, config, make_repo)
|
|
|
|
with prod:
|
|
prod.make_commits('a', Commit('x', tree={'x': '0'}), ref='heads/change')
|
|
pr = prod.make_pr(target='a', head='change')
|
|
prod.post_status(pr.head, 'success', 'legal/cla')
|
|
prod.post_status(pr.head, 'success', 'ci/runbot')
|
|
pr.post_comment('hansen r+', config['role_reviewer']['token'])
|
|
env.run_crons()
|
|
|
|
with prod:
|
|
prod.post_status('staging.a', 'success', 'legal/cla')
|
|
prod.post_status('staging.a', 'success', 'ci/runbot')
|
|
env.run_crons()
|
|
|
|
pr0_id, pr1_id = env['runbot_merge.pull_requests'].search([], order='number')
|
|
with prod:
|
|
prod.get_pr(pr1_id.number).post_comment(
|
|
'hansen fw=skipci',
|
|
config['role_reviewer']['token']
|
|
)
|
|
assert pr0_id.batch_id.fw_policy == 'skipci'
|
|
env.run_crons()
|
|
|
|
_, _, pr2_id = env['runbot_merge.pull_requests'].search([], order='number')
|
|
assert pr1_id.state == 'opened'
|
|
assert pr2_id.state == 'opened'
|
|
|
|
def test_retarget_after_freeze(env, config, make_repo, users):
|
|
"""Turns out it was possible to trip the forwardbot if you're a bit of a
|
|
dick: the forward port cron was not resilient to forward port failure in
|
|
case of filling in new branches (forward ports existing across a branch
|
|
insertion so the fwbot would have to "fill in" for the new branch).
|
|
|
|
But it turns out causing such failure is possible by e.g. regargeting the
|
|
latter port. In that case the reinsertion task should just do nothing, and
|
|
the retargeted PR should be forward-ported normally once merged.
|
|
"""
|
|
prod, _ = make_basic(env, config, make_repo)
|
|
project = env['runbot_merge.project'].search([])
|
|
with prod:
|
|
[c] = prod.make_commits('b', Commit('thing', tree={'x': '1'}), ref='heads/mypr')
|
|
pr = prod.make_pr(target='b', head='mypr')
|
|
prod.post_status(c, 'success', 'ci/runbot')
|
|
prod.post_status(c, 'success', 'legal/cla')
|
|
pr.post_comment('hansen r+', config['role_reviewer']['token'])
|
|
env.run_crons()
|
|
|
|
original_pr_id = to_pr(env, pr)
|
|
assert original_pr_id.state == 'ready'
|
|
assert original_pr_id.staging_id
|
|
|
|
with prod:
|
|
prod.post_status('staging.b', 'success', 'ci/runbot')
|
|
prod.post_status('staging.b', 'success', 'legal/cla')
|
|
env.run_crons()
|
|
# should have created a pr targeted to C
|
|
port_id = env['runbot_merge.pull_requests'].search([('state', 'not in', ('merged', 'closed'))])
|
|
assert len(port_id) == 1
|
|
assert port_id.target.name == 'c'
|
|
assert port_id.source_id == original_pr_id
|
|
assert port_id.parent_id == original_pr_id
|
|
|
|
branch_c, branch_b, branch_a = branches_before = project.branch_ids
|
|
assert [branch_a.name, branch_b.name, branch_c.name] == ['a', 'b', 'c']
|
|
# create branch so cron runs correctly
|
|
with prod: prod.make_ref('heads/bprime', prod.get_ref('c'))
|
|
project.write({
|
|
'branch_ids': [
|
|
(1, branch_c.id, {'sequence': 1}),
|
|
(0, 0, {'name': 'bprime', 'sequence': 2}),
|
|
(1, branch_b.id, {'sequence': 3}),
|
|
(1, branch_a.id, {'sequence': 4}),
|
|
]
|
|
})
|
|
new_branch = project.branch_ids - branches_before
|
|
assert new_branch.name == 'bprime'
|
|
|
|
# should have added a job for the new fp
|
|
job = env['forwardport.batches'].search([])
|
|
assert job
|
|
|
|
# fuck up yo life: retarget the existing FP PR to the new branch
|
|
port_pr = prod.get_pr(port_id.number)
|
|
with prod:
|
|
port_pr.base = 'bprime'
|
|
assert port_id.target == new_branch
|
|
|
|
env.run_crons(None)
|
|
assert not job.exists(), "job should have succeeded and apoptosed"
|
|
|
|
# since the PR was "already forward-ported" to the new branch it should not
|
|
# be touched
|
|
assert env['runbot_merge.pull_requests'].search([('state', 'not in', ('merged', 'closed'))]) == port_id
|
|
|
|
# merge the retargered PR
|
|
with prod:
|
|
prod.post_status(port_pr.head, 'success', 'ci/runbot')
|
|
prod.post_status(port_pr.head, 'success', 'legal/cla')
|
|
port_pr.post_comment('hansen r+', config['role_reviewer']['token'])
|
|
env.run_crons()
|
|
with prod:
|
|
prod.post_status('staging.bprime', 'success', 'ci/runbot')
|
|
prod.post_status('staging.bprime', 'success', 'legal/cla')
|
|
env.run_crons()
|
|
|
|
# #2 batch 6 (???)
|
|
assert port_id.state == 'merged'
|
|
|
|
new_pr_id = env['runbot_merge.pull_requests'].search([('state', 'not in', ('merged', 'closed'))])
|
|
assert len(new_pr_id) == 1
|
|
assert new_pr_id.parent_id == port_id
|
|
assert new_pr_id.target == branch_c
|
|
|
|
def test_approve_draft(env, config, make_repo, users):
|
|
prod, _ = make_basic(env, config, make_repo)
|
|
|
|
with prod:
|
|
prod.make_commits('a', Commit('x', tree={'x': '0'}), ref='heads/change')
|
|
pr = prod.make_pr(target='a', head='change', draft=True)
|
|
pr.post_comment('hansen r+', config['role_reviewer']['token'])
|
|
env.run_crons()
|
|
|
|
pr_id = to_pr(env, pr)
|
|
assert pr_id.state == 'opened'
|
|
assert pr.comments == [
|
|
(users['reviewer'], 'hansen r+'),
|
|
seen(env, pr, users),
|
|
(users['user'], f"@{users['reviewer']} draft PRs can not be approved."),
|
|
]
|
|
|
|
with prod:
|
|
pr.draft = False
|
|
assert pr.draft is False
|
|
with prod:
|
|
pr.post_comment('hansen r+', config['role_reviewer']['token'])
|
|
env.run_crons()
|
|
assert pr_id.state == 'approved'
|
|
|
|
def test_freeze(env, config, make_repo, users):
|
|
"""Freeze:
|
|
|
|
- should not forward-port the freeze PRs themselves
|
|
- unmerged forward ports need to be backfilled
|
|
- if the tip of the forward port is approved, the backfilled forward port
|
|
should also be
|
|
"""
|
|
prod, _ = make_basic(env, config, make_repo, statuses='default')
|
|
project = env['runbot_merge.project'].search([])
|
|
|
|
|
|
# branches here are "a" (older), "b", and "c" (master)
|
|
with prod:
|
|
[root, _] = prod.make_commits(
|
|
None,
|
|
Commit('base', tree={'version': '', 'f': '0'}),
|
|
Commit('release 1.0', tree={'version': '1.0'}),
|
|
ref='heads/b'
|
|
)
|
|
prod.make_commits(root, Commit('other', tree={'f': '1'}), ref='heads/c')
|
|
|
|
# region PR which is forward ported but the FPs are not merged (they are approved)
|
|
with prod:
|
|
prod.make_commits("a", Commit("stuff", tree={'x': '0'}), ref="heads/abranch")
|
|
p = prod.make_pr(target='a', head='abranch')
|
|
p.post_comment("hansen r+ fw=skipci", config['role_reviewer']['token'])
|
|
prod.post_status('abranch', 'success')
|
|
env.run_crons()
|
|
with prod:
|
|
prod.post_status('staging.a', 'success')
|
|
env.run_crons()
|
|
pr_a_id, pr_b_id, pr_c_id = pr_ids = env['runbot_merge.pull_requests'].search([], order='number')
|
|
assert len(pr_ids) == 3, \
|
|
"should have created two forward ports, one in b and one in c (/ master)"
|
|
# endregion
|
|
|
|
with prod:
|
|
prod.make_commits(
|
|
'c',
|
|
Commit('Release 1.1', tree={'version': '1.1'}),
|
|
ref='heads/release-1.1'
|
|
)
|
|
release = prod.make_pr(target='c', head='release-1.1')
|
|
env.run_crons()
|
|
|
|
# approve pr_c_id but don't actually merge it before freezing
|
|
with prod:
|
|
prod.post_status(pr_b_id.head, 'success')
|
|
prod.post_status(pr_c_id.head, 'success')
|
|
prod.get_pr(pr_c_id.number).post_comment('hansen r+', config['role_reviewer']['token'])
|
|
# review comment should be handled eagerly
|
|
assert pr_b_id.reviewed_by
|
|
assert pr_c_id.reviewed_by
|
|
|
|
w = project.action_prepare_freeze()
|
|
assert w['res_model'] == 'runbot_merge.project.freeze'
|
|
w_id = env[w['res_model']].browse([w['res_id']])
|
|
assert w_id.release_pr_ids.repository_id.name == prod.name
|
|
release_id = to_pr(env, release)
|
|
w_id.release_pr_ids.pr_id = release_id.id
|
|
|
|
assert not w_id.errors
|
|
w_id.action_freeze()
|
|
|
|
assert project.branch_ids.mapped('name') == ['c', 'post-b', 'b', 'a']
|
|
|
|
# re-enable forward-port cron after freeze
|
|
_, cron_id = env['ir.model.data'].check_object_reference('forwardport', 'port_forward', context={'active_test': False})
|
|
env['ir.cron'].browse([cron_id]).active = True
|
|
env.run_crons('forwardport.port_forward')
|
|
|
|
assert release_id.state == 'merged'
|
|
assert not env['runbot_merge.pull_requests'].search([
|
|
('source_id', '=', release_id.id),
|
|
]), "the release PRs should not be forward-ported"
|
|
|
|
assert env['runbot_merge.stagings'].search_count([]) == 2,\
|
|
"b and c forward ports should be staged since they were ready before freeze"
|
|
|
|
# an intermediate PR should have been created
|
|
pr_inserted = env['runbot_merge.pull_requests'].search([
|
|
('source_id', '=', pr_a_id.id),
|
|
('target.name', '=', 'post-b'),
|
|
])
|
|
assert pr_inserted, "an intermediate PR should have been reinsered in the sequence"
|
|
assert pr_c_id.parent_id == pr_inserted
|
|
assert pr_inserted.parent_id == pr_b_id
|
|
|
|
assert pr_inserted.reviewed_by == pr_c_id.reviewed_by,\
|
|
"review state should have been copied over from c (master)"
|
|
with prod:
|
|
prod.post_status(pr_inserted.head, 'success')
|
|
prod.post_status('staging.b', 'success')
|
|
prod.post_status('staging.c', 'success')
|
|
env.run_crons()
|
|
with prod:
|
|
prod.post_status('staging.post-b', 'success')
|
|
env.run_crons()
|
|
|
|
assert env['runbot_merge.pull_requests'].search_count([('state', '=', 'merged')]) \
|
|
== len(['release', 'initial', 'fw-b', 'fw-post-b', 'fw-c'])
|
|
|
|
|
|
@pytest.mark.expect_log_errors(reason="missing / invalid head causes an error to be logged")
|
|
def test_missing_magic_ref(env, config, make_repo):
|
|
"""There are cases where github fails to create / publish or fails to update
|
|
the magic refs in refs/pull/*.
|
|
|
|
In that case, pulling from the regular remote does not bring in the contents
|
|
of the PR we're trying to forward port, and the forward porting process
|
|
fails.
|
|
|
|
Emulate this behaviour by updating the PR with a commit which lives in the
|
|
repo but has no ref.
|
|
"""
|
|
prod, _ = make_basic(env, config, make_repo, statuses='default')
|
|
a_head = prod.commit('refs/heads/a')
|
|
with prod:
|
|
[c] = prod.make_commits(a_head.id, Commit('x', tree={'x': '0'}), ref='heads/change')
|
|
pr = prod.make_pr(target='a', head='change')
|
|
prod.post_status(c, 'success')
|
|
pr.post_comment('hansen r+', config['role_reviewer']['token'])
|
|
env.run_crons()
|
|
|
|
# create variant of pr head in fork, update PR with that commit as head so
|
|
# it's not found after a fetch, simulating an outdated or missing magic ref
|
|
pr_id = to_pr(env, pr)
|
|
assert pr_id.staging_id
|
|
|
|
with prevent_unstaging(pr_id.staging_id):
|
|
pr_id.head = '0'*40
|
|
with prod:
|
|
prod.post_status('staging.a', 'success')
|
|
env.run_crons()
|
|
|
|
assert not pr_id.staging_id
|
|
assert pr_id.state == 'merged'
|
|
|
|
# check that the fw failed
|
|
assert not env['runbot_merge.pull_requests'].search([('source_id', '=', pr_id.id)]),\
|
|
"forward port should not have been created"
|
|
# check that the batch is still here and targeted for the future
|
|
req = env['forwardport.batches'].search([])
|
|
assert len(req) == 1
|
|
assert req.retry_after > datetime.utcnow().isoformat(" ", "seconds")
|
|
# reset retry_after
|
|
req.retry_after = '1900-01-01 01:01:01'
|
|
|
|
# add a real commit
|
|
with prod:
|
|
[c2] = prod.make_commits(a_head.id, Commit('y', tree={'x': '0'}))
|
|
assert c2 != c
|
|
pr_id.head = c2
|
|
env.run_crons(None)
|
|
|
|
fp_id = env['runbot_merge.pull_requests'].search([('source_id', '=', pr_id.id)])
|
|
assert fp_id
|
|
# the cherrypick process fetches the commits on the PR in order to know
|
|
# what they are (rather than e.g. diff the HEAD it branch with the target)
|
|
# as a result it doesn't forwardport our fake, we'd have to reset the PR's
|
|
# branch for that to happen
|
|
|
|
def test_disable_branch_with_batches(env, config, make_repo, users):
|
|
"""We want to avoid losing pull requests, so when deactivating a branch,
|
|
if there are *forward port* batches targeting that branch which have not
|
|
been forward ported yet port them over, as if their source had been merged
|
|
after the branch was disabled (thus skipped over)
|
|
"""
|
|
repo, fork = make_basic(env, config, make_repo, statuses="default")
|
|
proj = env['runbot_merge.project'].search([])
|
|
branch_b = env['runbot_merge.branch'].search([('name', '=', 'b')])
|
|
assert branch_b
|
|
|
|
# region repo2 creation & setup
|
|
repo2 = make_repo('proj2')
|
|
with repo2:
|
|
[a, b, c] = repo2.make_commits(
|
|
None,
|
|
Commit("a", tree={"f": "a"}),
|
|
Commit("b", tree={"g": "b"}),
|
|
Commit("c", tree={"h": "c"}),
|
|
)
|
|
repo2.make_ref("heads/a", a)
|
|
repo2.make_ref("heads/b", b)
|
|
repo2.make_ref("heads/c", c)
|
|
fork2 = repo2.fork()
|
|
repo2_id = env['runbot_merge.repository'].create({
|
|
"project_id": proj.id,
|
|
"name": repo2.name,
|
|
"required_statuses": "default",
|
|
"fp_remote_target": fork2.name,
|
|
})
|
|
env['runbot_merge.events_sources'].create({'repository': repo2.name})
|
|
env['res.partner'].search([
|
|
('github_login', '=', config['role_reviewer']['user'])
|
|
]).write({
|
|
'review_rights': [(0, 0, {'repository_id': repo2_id.id, 'review': True})]
|
|
})
|
|
env['res.partner'].search([
|
|
('github_login', '=', config['role_self_reviewer']['user'])
|
|
]).write({
|
|
'review_rights': [(0, 0, {'repository_id': repo2_id.id, 'self_review': True})]
|
|
})
|
|
# endregion
|
|
|
|
# region set up forward ported batches
|
|
with repo, fork, repo2, fork2:
|
|
fork.make_commits("a", Commit("x", tree={"x": "1"}), ref="heads/x")
|
|
pr1_a = repo.make_pr(title="X", target="a", head=f"{fork.owner}:x")
|
|
pr1_a.post_comment("hansen r+", config['role_reviewer']['token'])
|
|
repo.post_status(pr1_a.head, "success")
|
|
|
|
fork2.make_commits("a", Commit("x", tree={"x": "1"}), ref="heads/x")
|
|
pr2_a = repo2.make_pr(title="X", target="a", head=f"{fork2.owner}:x")
|
|
pr2_a.post_comment("hansen r+", config['role_reviewer']['token'])
|
|
repo2.post_status(pr2_a.head, "success")
|
|
|
|
fork.make_commits("a", Commit("y", tree={"y": "1"}), ref="heads/y")
|
|
pr3_a = repo.make_pr(title="Y", target="a", head=f"{fork.owner}:y")
|
|
pr3_a.post_comment("hansen r+", config['role_reviewer']['token'])
|
|
repo.post_status(pr3_a.head, 'success')
|
|
# remove just pr2 from the forward ports (maybe?)
|
|
pr2_a_id = to_pr(env, pr2_a)
|
|
pr2_a_id.limit_id = branch_b.id
|
|
env.run_crons()
|
|
assert pr2_a_id.limit_id == branch_b
|
|
# endregion
|
|
|
|
with repo, repo2:
|
|
repo.post_status('staging.a', 'success')
|
|
repo2.post_status('staging.a', 'success')
|
|
env.run_crons()
|
|
|
|
PullRequests = env['runbot_merge.pull_requests']
|
|
pr1_b_id = PullRequests.search([('parent_id', '=', to_pr(env, pr1_a).id)])
|
|
pr2_b_id = PullRequests.search([('parent_id', '=', pr2_a_id.id)])
|
|
pr3_b_id = PullRequests.search([('parent_id', '=', to_pr(env, pr3_a).id)])
|
|
assert pr1_b_id.parent_id
|
|
assert pr1_b_id.state == 'opened'
|
|
assert pr2_b_id.parent_id
|
|
assert pr2_b_id.state == 'opened'
|
|
assert pr3_b_id.parent_id
|
|
assert pr3_b_id.state == 'opened'
|
|
# detach pr3 (?)
|
|
pr3_b_id.write({'parent_id': False, 'detach_reason': 'because'})
|
|
|
|
b_id = proj.branch_ids.filtered(lambda b: b.name == 'b')
|
|
proj.write({
|
|
'branch_ids': [(1, b_id.id, {'active': False})]
|
|
})
|
|
env.run_crons()
|
|
assert not b_id.active
|
|
# pr1_a, pr1_b, pr1_c, pr2_a, pr2_b, pr3_a, pr3_b, pr3_c
|
|
assert PullRequests.search_count([]) == 8, "should have ported pr1 and pr3 but not pr2"
|
|
assert PullRequests.search_count([('parent_id', '=', pr1_b_id.id)])
|
|
assert PullRequests.search_count([('parent_id', '=', pr3_b_id.id)])
|
|
|
|
assert repo.get_pr(pr1_b_id.number).comments == [
|
|
seen(env, repo.get_pr(pr1_b_id.number), users),
|
|
(users['user'], "This PR targets b and is part of the forward-port chain. Further PRs will be created up to c.\n\nMore info at https://github.com/odoo/odoo/wiki/Mergebot#forward-port\n"),
|
|
(users['user'], "@{user} @{reviewer} the target branch 'b' has been disabled, you may want to close this PR.\n\nAs this was not its limit, it will automatically be forward ported to the next active branch.".format_map(users)),
|
|
]
|
|
assert repo2.get_pr(pr2_b_id.number).comments == [
|
|
seen(env, repo2.get_pr(pr2_b_id.number), users),
|
|
(users['user'], """\
|
|
@{user} @{reviewer} this PR targets b and is the last of the forward-port chain.
|
|
|
|
To merge the full chain, use
|
|
> @hansen r+
|
|
|
|
More info at https://github.com/odoo/odoo/wiki/Mergebot#forward-port
|
|
""".format_map(users)),
|
|
(users['user'], "@{user} @{reviewer} the target branch 'b' has been disabled, you may want to close this PR.".format_map(users)),
|
|
]
|
|
|
|
def test_disable_multitudes(env, config, make_repo, users, setreviewers):
|
|
"""Ensure that deactivation ports can jump over other deactivated branches.
|
|
"""
|
|
# region setup
|
|
repo = make_repo("bob")
|
|
project = env['runbot_merge.project'].create({
|
|
"name": "bob",
|
|
"github_token": config['github']['token'],
|
|
"github_prefix": "hansen",
|
|
'github_name': config['github']['name'],
|
|
'github_email': "foo@example.org",
|
|
"fp_github_token": config['github']['token'],
|
|
"fp_github_name": "herbert",
|
|
"branch_ids": [
|
|
(0, 0, {'name': 'a', 'sequence': 90}),
|
|
(0, 0, {'name': 'b', 'sequence': 80}),
|
|
(0, 0, {'name': 'c', 'sequence': 70}),
|
|
(0, 0, {'name': 'd', 'sequence': 60}),
|
|
],
|
|
"repo_ids": [(0, 0, {
|
|
'name': repo.name,
|
|
'required_statuses': 'default',
|
|
'fp_remote_target': repo.name,
|
|
})],
|
|
})
|
|
setreviewers(project.repo_ids)
|
|
env['runbot_merge.events_sources'].create({'repository': repo.name})
|
|
|
|
with repo:
|
|
[a, b, c, d] = repo.make_commits(
|
|
None,
|
|
Commit("a", tree={"branch": "a"}),
|
|
Commit("b", tree={"branch": "b"}),
|
|
Commit("c", tree={"branch": "c"}),
|
|
Commit("d", tree={"branch": "d"}),
|
|
)
|
|
repo.make_ref("heads/a", a)
|
|
repo.make_ref("heads/b", b)
|
|
repo.make_ref("heads/c", c)
|
|
repo.make_ref("heads/d", d)
|
|
# endregion
|
|
|
|
with repo:
|
|
[a] = repo.make_commits("a", Commit("X", tree={"x": "1"}), ref="heads/x")
|
|
pra = repo.make_pr(target="a", head="x")
|
|
pra.post_comment("hansen r+", config['role_reviewer']['token'])
|
|
repo.post_status(a, "success")
|
|
env.run_crons()
|
|
|
|
with repo:
|
|
repo.post_status('staging.a', 'success')
|
|
env.run_crons()
|
|
|
|
pra_id = to_pr(env, pra)
|
|
assert pra_id.state == 'merged'
|
|
|
|
prb_id = env['runbot_merge.pull_requests'].search([('target.name', '=', 'b')])
|
|
assert prb_id.parent_id == pra_id
|
|
|
|
project.write({
|
|
'branch_ids': [
|
|
(1, b.id, {'active': False})
|
|
for b in env['runbot_merge.branch'].search([('name', 'in', ['b', 'c'])])
|
|
]
|
|
})
|
|
env.run_crons()
|
|
|
|
# should not have ported prb to the disabled branch c
|
|
assert not env['runbot_merge.pull_requests'].search([('target.name', '=', 'c')])
|
|
|
|
# should have ported prb to the active branch d
|
|
prd_id = env['runbot_merge.pull_requests'].search([('target.name', '=', 'd')])
|
|
assert prd_id
|
|
assert prd_id.parent_id == prb_id
|
|
|
|
prb = repo.get_pr(prb_id.number)
|
|
assert prb.comments == [
|
|
seen(env, prb, users),
|
|
(users['user'], 'This PR targets b and is part of the forward-port chain. Further PRs will be created up to d.\n\nMore info at https://github.com/odoo/odoo/wiki/Mergebot#forward-port\n'),
|
|
(users['user'], """\
|
|
@{user} @{reviewer} the target branch 'b' has been disabled, you may want to close this PR.
|
|
|
|
As this was not its limit, it will automatically be forward ported to the next active branch.\
|
|
""".format_map(users)),
|
|
]
|
|
prd = repo.get_pr(prd_id.number)
|
|
assert prd.comments == [
|
|
seen(env, prd, users),
|
|
(users['user'], """\
|
|
@{user} @{reviewer} this PR targets d and is the last of the forward-port chain.
|
|
|
|
To merge the full chain, use
|
|
> @hansen r+
|
|
|
|
More info at https://github.com/odoo/odoo/wiki/Mergebot#forward-port
|
|
""".format_map(users))
|
|
]
|
|
|
|
FMT = '%Y-%m-%d %H:%M:%S'
|
|
FAKE_PREV_WEEK = (datetime.now() + timedelta(days=1)).strftime(FMT)
|
|
def test_reminder_detached(env, config, make_repo, users):
|
|
"""On detached forward ports, both sides of the detachment should be notified.
|
|
"""
|
|
# region setup
|
|
prod, _ = make_basic(env, config, make_repo, statuses='default')
|
|
with prod:
|
|
prod.make_commits('a', Commit('c', tree={'x': '0'}), ref="heads/abranch")
|
|
pr_a = prod.make_pr(target='a', head='abranch')
|
|
prod.post_status('abranch', 'success')
|
|
pr_a.post_comment('hansen r+ fw=skipci', config['role_reviewer']['token'])
|
|
env.run_crons()
|
|
|
|
with prod:
|
|
prod.post_status('staging.a', 'success')
|
|
env.run_crons()
|
|
|
|
pr_a_id = to_pr(env, pr_a)
|
|
pr_b_id = env['runbot_merge.pull_requests'].search([
|
|
('target.name', '=', 'b'),
|
|
('parent_id', '=', pr_a_id.id),
|
|
])
|
|
assert pr_b_id
|
|
with prod:
|
|
prod.post_status(pr_b_id.head, 'success')
|
|
env.run_crons()
|
|
pr_c_id = env['runbot_merge.pull_requests'].search([
|
|
('target.name', '=', 'c'),
|
|
('parent_id', '=', pr_b_id.id),
|
|
])
|
|
assert pr_c_id
|
|
# endregion
|
|
|
|
pr_b = prod.get_pr(pr_b_id.number)
|
|
pr_c = prod.get_pr(pr_c_id.number)
|
|
|
|
# region sanity check
|
|
env.run_crons('forwardport.reminder', context={'forwardport_updated_before': FAKE_PREV_WEEK})
|
|
|
|
assert pr_b.comments == [
|
|
seen(env, pr_b, users),
|
|
(users['user'], """\
|
|
This PR targets b and is part of the forward-port chain. Further PRs will be created up to c.
|
|
|
|
More info at https://github.com/odoo/odoo/wiki/Mergebot#forward-port
|
|
""")], "the intermediate PR should not be reminded"
|
|
|
|
assert pr_c.comments == [
|
|
seen(env, pr_c, users),
|
|
(users['user'], """\
|
|
@%s @%s this PR targets c and is the last of the forward-port chain containing:
|
|
* %s
|
|
|
|
To merge the full chain, use
|
|
> @hansen r+
|
|
|
|
More info at https://github.com/odoo/odoo/wiki/Mergebot#forward-port
|
|
""" % (
|
|
users['user'], users['reviewer'],
|
|
pr_b_id.display_name,
|
|
)),
|
|
(users['user'], "@%s @%s this forward port of %s is awaiting action (not merged or closed)." % (
|
|
users['user'],
|
|
users['reviewer'],
|
|
pr_a_id.display_name,
|
|
))
|
|
], "the final PR should be reminded"
|
|
# endregion
|
|
|
|
# region check detached
|
|
pr_c_id.write({'parent_id': False, 'detach_reason': 'because'})
|
|
env.run_crons('forwardport.reminder', context={'forwardport_updated_before': FAKE_PREV_WEEK})
|
|
|
|
assert pr_b.comments[2:] == [
|
|
(users['user'], "@%s @%s child PR %s was modified / updated and has become a normal PR. This PR (and any of its parents) will need to be merged independently as approvals won't cross." % (
|
|
users['user'],
|
|
users['reviewer'],
|
|
pr_c_id.display_name,
|
|
)),
|
|
(users['user'], "@%s @%s this forward port of %s is awaiting action (not merged or closed)." % (
|
|
users['user'],
|
|
users['reviewer'],
|
|
pr_a_id.display_name,
|
|
))
|
|
], "the detached-from intermediate PR should now be reminded"
|
|
assert pr_c.comments[3:] == [
|
|
(users['user'], "@%(user)s @%(reviewer)s this PR was modified / updated and has become a normal PR. It must be merged directly." % users),
|
|
(users['user'], "@%s @%s this forward port of %s is awaiting action (not merged or closed)." % (
|
|
users['user'],
|
|
users['reviewer'],
|
|
pr_a_id.display_name,
|
|
))
|
|
], "the final forward port should be reminded as before"
|
|
# endregion
|