Compare commits

..

1 Commits

Author SHA256 Message Date
Andrii Nikitin
ab486d1a0b t: split common logic from the merge tests
All checks were successful
Integration tests / t (pull_request) Successful in 10m51s
This way it should be easier to reuse it in the further tests
2026-03-09 14:57:59 +01:00
7 changed files with 89 additions and 230 deletions

View File

@@ -1,8 +1,4 @@
MODULES := devel-importer utils/hujson utils/maintainer-update gitea-events-rabbitmq-publisher gitea_status_proxy group-review obs-forward-bot obs-staging-bot obs-status-service workflow-direct workflow-pr
.PHONY: build $(MODULES)
build: $(MODULES)
$(MODULES):
go build -C $@ -buildmode=pie
build:
for m in $(MODULES); do go build -C $$m -buildmode=pie || exit 1 ; done

View File

@@ -221,14 +221,11 @@ type Gitea interface {
GetPullRequests(org, project string) ([]*models.PullRequest, error)
GetCurrentUser() (*models.User, error)
SetCacheTTL(ttl time.Duration)
GetCacheTTL() time.Duration
}
type GiteaTransport struct {
transport *transport.Runtime
client *apiclient.GiteaAPI
cacheTTL time.Duration
}
func AllocateGiteaTransport(giteaUrl string) Gitea {
@@ -243,19 +240,10 @@ func AllocateGiteaTransport(giteaUrl string) Gitea {
r.transport.DefaultAuthentication = transport.BearerToken(giteaToken)
r.client = apiclient.New(r.transport, nil)
r.cacheTTL = time.Second * 5
return &r
}
func (gitea *GiteaTransport) SetCacheTTL(ttl time.Duration) {
gitea.cacheTTL = ttl
}
func (gitea *GiteaTransport) GetCacheTTL() time.Duration {
return gitea.cacheTTL
}
func (gitea *GiteaTransport) FetchMaintainershipFile(org, repo, branch string) ([]byte, string, error) {
return gitea.GetRepositoryFileContent(org, repo, branch, MaintainershipFile)
}
@@ -880,8 +868,8 @@ func (gitea *GiteaTransport) GetTimeline(org, repo string, idx int64) ([]*models
LastCachedTime = TimelineCache.data[0].Updated
}
// cache data
if TimelineCache.lastCheck.Add(gitea.cacheTTL).Compare(time.Now()) > 0 {
// cache data for 5 seconds
if TimelineCache.lastCheck.Add(time.Second*5).Compare(time.Now()) > 0 {
giteaTimelineCacheMutex.RUnlock()
return TimelineCache.data, nil
}

View File

@@ -2718,44 +2718,6 @@ func (c *MockGiteaFetchMaintainershipFileCall) DoAndReturn(f func(string, string
return c
}
// GetCacheTTL mocks base method.
func (m *MockGitea) GetCacheTTL() time.Duration {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetCacheTTL")
ret0, _ := ret[0].(time.Duration)
return ret0
}
// GetCacheTTL indicates an expected call of GetCacheTTL.
func (mr *MockGiteaMockRecorder) GetCacheTTL() *MockGiteaGetCacheTTLCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCacheTTL", reflect.TypeOf((*MockGitea)(nil).GetCacheTTL))
return &MockGiteaGetCacheTTLCall{Call: call}
}
// MockGiteaGetCacheTTLCall wrap *gomock.Call
type MockGiteaGetCacheTTLCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaGetCacheTTLCall) Return(arg0 time.Duration) *MockGiteaGetCacheTTLCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaGetCacheTTLCall) Do(f func() time.Duration) *MockGiteaGetCacheTTLCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaGetCacheTTLCall) DoAndReturn(f func() time.Duration) *MockGiteaGetCacheTTLCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// GetCommit mocks base method.
func (m *MockGitea) GetCommit(org, repo, sha string) (*models.Commit, error) {
m.ctrl.T.Helper()
@@ -3617,42 +3579,6 @@ func (c *MockGiteaResetTimelineCacheCall) DoAndReturn(f func(string, string, int
return c
}
// SetCacheTTL mocks base method.
func (m *MockGitea) SetCacheTTL(ttl time.Duration) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetCacheTTL", ttl)
}
// SetCacheTTL indicates an expected call of SetCacheTTL.
func (mr *MockGiteaMockRecorder) SetCacheTTL(ttl any) *MockGiteaSetCacheTTLCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCacheTTL", reflect.TypeOf((*MockGitea)(nil).SetCacheTTL), ttl)
return &MockGiteaSetCacheTTLCall{Call: call}
}
// MockGiteaSetCacheTTLCall wrap *gomock.Call
type MockGiteaSetCacheTTLCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaSetCacheTTLCall) Return() *MockGiteaSetCacheTTLCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaSetCacheTTLCall) Do(f func(time.Duration)) *MockGiteaSetCacheTTLCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaSetCacheTTLCall) DoAndReturn(f func(time.Duration)) *MockGiteaSetCacheTTLCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// SetCommitStatus mocks base method.
func (m *MockGitea) SetCommitStatus(org, repo, hash string, status *models.CommitStatus) (*models.CommitStatus, error) {
m.ctrl.T.Helper()

View File

@@ -100,7 +100,6 @@ services:
- ./workflow-pr/workflow-pr.json:/etc/workflow-pr.json:ro,z
- ./workflow-pr-repos:/var/lib/workflow-pr/repos:Z
command: [
"-check-on-start",
"-debug",
"-gitea-url", "http://gitea-test:3000",
"-url", "amqps://rabbitmq-test:5671",

View File

@@ -532,21 +532,20 @@ index 00000000..{pkg_b_sha}
return response.json()
def approve_requested_reviews(self, repo_full_name: str, pr_number: int):
vprint(f"--- Checking for REQUEST_REVIEW state in {repo_full_name} PR #{pr_number} ---")
reviews = self.list_reviews(repo_full_name, pr_number)
requested_reviews = [r for r in reviews if r["state"] == "REQUEST_REVIEW"]
if not requested_reviews:
vprint(f"No reviews in REQUEST_REVIEW state found for {repo_full_name} PR #{pr_number}")
return
vprint(f"--- Found {len(requested_reviews)} REQUEST_REVIEW state(s) in {repo_full_name} PR #{pr_number} ---")
admin_token = self.headers["Authorization"].split(" ")[1]
for r in requested_reviews:
reviewer_username = r["user"]["login"]
vprint(f"Reacting on REQUEST_REVIEW for user {reviewer_username} by approving...")
vprint(f"Approving requested review for user {reviewer_username}...")
reviewer_client = GiteaAPIClient(base_url=self.base_url, token=admin_token, sudo=reviewer_username)
time.sleep(1) # give a chance to avoid possible concurrency issues with reviews request/approval
time.sleep(0.5) # reduced from 1s
reviewer_client.create_review(repo_full_name, pr_number, event="APPROVED", body="Approving requested review")
def wait_for_project_pr(self, package_pr_repo, package_pr_number, project_pr_repo="myproducts/mySLFO", timeout=60):
@@ -569,7 +568,7 @@ index 00000000..{pkg_b_sha}
package_merged = False
project_merged = False
for i in range(timeout):
for i in range(int(timeout / 0.5)): # Poll with 0.5s interval
self.approve_requested_reviews(package_pr_repo, package_pr_number)
self.approve_requested_reviews(project_pr_repo, project_pr_number)
@@ -588,5 +587,5 @@ index 00000000..{pkg_b_sha}
if package_merged and project_merged:
return True, True
time.sleep(1)
time.sleep(0.5)
return package_merged, project_merged

View File

@@ -4,6 +4,80 @@ import time
from pathlib import Path
from tests.lib.common_test_utils import GiteaAPIClient
def wait_for_manual_merge_approvals(gitea_env, staging_bot_client, package_pr_number, project_pr_number):
"""
Wait for required approvals on both package and project PRs for manual merge tests.
"""
print("Waiting for required review requests and approving them...")
# Expected reviewers based on manual-merge branch config and pkgA maintainership
mandatory_reviewers = {"usera", "userb"}
maintainers = {"ownerA", "ownerX", "ownerY"}
# ManualMergeOnly still requires regular reviews to be satisfied.
# We poll until required reviewers have approved.
all_approved = False
approved_reviewers = set()
for _ in range(60): # Poll for up to 60 seconds (1s interval)
# Trigger approvals for whatever is already requested
gitea_env.approve_requested_reviews("mypool/pkgA", package_pr_number)
gitea_env.approve_requested_reviews("myproducts/mySLFO", project_pr_number)
# Explicitly handle staging bot if it is requested or pending
prj_reviews = gitea_env.list_reviews("myproducts/mySLFO", project_pr_number)
if any(r["user"]["login"] == "autogits_obs_staging_bot" and r["state"] in ["REQUEST_REVIEW", "PENDING"] for r in prj_reviews):
print("Staging bot has a pending/requested review. Approving...")
staging_bot_client.create_review("myproducts/mySLFO", project_pr_number, event="APPROVED", body="Staging bot approves")
# Check if mandatory reviewers and at least one maintainer have approved
pkg_reviews = gitea_env.list_reviews("mypool/pkgA", package_pr_number)
approved_reviewers = {r["user"]["login"] for r in pkg_reviews if r["state"] == "APPROVED"}
if mandatory_reviewers.issubset(approved_reviewers) and any(m in approved_reviewers for m in maintainers):
# And check project PR for bot approval
prj_approved = any(r["user"]["login"] == "autogits_obs_staging_bot" and r["state"] == "APPROVED" for r in prj_reviews)
if prj_approved:
all_approved = True
print(f"Required reviewers approved: mandatory={mandatory_reviewers}, maintainer={[m for m in maintainers if m in approved_reviewers]}, staging_bot=True")
break
pkg_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number)
prj_details = gitea_env.get_pr_details("myproducts/mySLFO", project_pr_number)
assert not pkg_details.get("merged"), "Package PR merged prematurely (ManualMergeOnly ignored?)"
assert not prj_details.get("merged"), "Project PR merged prematurely (ManualMergeOnly ignored?)"
time.sleep(1)
assert all_approved, f"Timed out waiting for required approvals. Mandatory: {mandatory_reviewers}, Maintainers: {maintainers}. Current approved: {approved_reviewers}"
print("Both PRs have all required approvals but are not merged (as expected with ManualMergeOnly).")
def wait_for_merge_status(gitea_env, package_pr_number, project_pr_number, timeout_seconds=20):
"""
Poll for PR merge status on both package and project PRs.
"""
print("Polling for PR merge status...")
package_merged = False
project_merged = False
for i in range(int(timeout_seconds / 0.5)): # Poll with 0.5s interval
if not package_merged:
pkg_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number)
if pkg_details.get("merged"):
package_merged = True
print(f"Package PR mypool/pkgA#{package_pr_number} merged.")
if not project_merged:
prj_details = gitea_env.get_pr_details("myproducts/mySLFO", project_pr_number)
if prj_details.get("merged"):
project_merged = True
print(f"Project PR myproducts/mySLFO#{project_pr_number} merged.")
if package_merged and project_merged:
break
time.sleep(0.5)
return package_merged, project_merged
@pytest.mark.t001
def test_001_automerge(automerge_env, test_user_client):
"""
@@ -62,74 +136,14 @@ index 00000000..473a0f4c
print(f"Found project PR: myproducts/mySLFO#{project_pr_number}")
# 3. Approve reviews and verify NOT merged
print("Waiting for required review requests and approving them...")
# Expected reviewers based on manual-merge branch config and pkgA maintainership
mandatory_reviewers = {"usera", "userb"}
maintainers = {"ownerA", "ownerX", "ownerY"}
# ManualMergeOnly still requires regular reviews to be satisfied.
# We poll until required reviewers have approved.
all_approved = False
for _ in range(30):
# Trigger approvals for whatever is already requested
gitea_env.approve_requested_reviews("mypool/pkgA", package_pr_number)
gitea_env.approve_requested_reviews("myproducts/mySLFO", project_pr_number)
# Explicitly handle staging bot if it is requested or pending
prj_reviews = gitea_env.list_reviews("myproducts/mySLFO", project_pr_number)
if any(r["user"]["login"] == "autogits_obs_staging_bot" and r["state"] in ["REQUEST_REVIEW", "PENDING"] for r in prj_reviews):
print("Staging bot has a pending/requested review. Approving...")
staging_bot_client.create_review("myproducts/mySLFO", project_pr_number, event="APPROVED", body="Staging bot approves")
# Check if mandatory reviewers and at least one maintainer have approved
pkg_reviews = gitea_env.list_reviews("mypool/pkgA", package_pr_number)
approved_reviewers = {r["user"]["login"] for r in pkg_reviews if r["state"] == "APPROVED"}
if mandatory_reviewers.issubset(approved_reviewers) and any(m in approved_reviewers for m in maintainers):
# And check project PR for bot approval
prj_approved = any(r["user"]["login"] == "autogits_obs_staging_bot" and r["state"] == "APPROVED" for r in prj_reviews)
if prj_approved:
all_approved = True
print(f"Required reviewers approved: mandatory={mandatory_reviewers}, maintainer={[m for m in maintainers if m in approved_reviewers]}, staging_bot=True")
break
pkg_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number)
prj_details = gitea_env.get_pr_details("myproducts/mySLFO", project_pr_number)
assert not pkg_details.get("merged"), "Package PR merged prematurely (ManualMergeOnly ignored?)"
assert not prj_details.get("merged"), "Project PR merged prematurely (ManualMergeOnly ignored?)"
time.sleep(2)
assert all_approved, f"Timed out waiting for required approvals. Mandatory: {mandatory_reviewers}, Maintainers: {maintainers}. Current approved: {approved_reviewers}"
print("Both PRs have all required approvals but are not merged (as expected with ManualMergeOnly).")
wait_for_manual_merge_approvals(gitea_env, staging_bot_client, package_pr_number, project_pr_number)
# 4. Comment "merge ok" from a requested reviewer (ownerA)
print("Commenting 'merge ok' on package PR from a maintainer...")
ownerA_client.create_issue_comment("mypool/pkgA", package_pr_number, "merge ok")
# 5. Verify both PRs are merged
print("Polling for PR merge status...")
package_merged = False
project_merged = False
for i in range(20): # Poll for up to 20 seconds
if not package_merged:
pkg_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number)
if pkg_details.get("merged"):
package_merged = True
print(f"Package PR mypool/pkgA#{package_pr_number} merged.")
if not project_merged:
prj_details = gitea_env.get_pr_details("myproducts/mySLFO", project_pr_number)
if prj_details.get("merged"):
project_merged = True
print(f"Project PR myproducts/mySLFO#{project_pr_number} merged.")
if package_merged and project_merged:
break
time.sleep(1)
package_merged, project_merged = wait_for_merge_status(gitea_env, package_pr_number, project_pr_number)
assert package_merged, f"Package PR mypool/pkgA#{package_pr_number} was not merged after 'merge ok'."
assert project_merged, f"Project PR myproducts/mySLFO#{project_pr_number} was not merged after 'merge ok'."
@@ -162,74 +176,14 @@ index 00000000..473a0f4c
print(f"Found project PR: myproducts/mySLFO#{project_pr_number}")
# 3. Approve reviews and verify NOT merged
print("Waiting for required review requests and approving them...")
# Expected reviewers based on manual-merge branch config and pkgA maintainership
mandatory_reviewers = {"usera", "userb"}
maintainers = {"ownerA", "ownerX", "ownerY"}
# ManualMergeOnly still requires regular reviews to be satisfied.
# We poll until required reviewers have approved.
all_approved = False
for _ in range(30):
# Trigger approvals for whatever is already requested
gitea_env.approve_requested_reviews("mypool/pkgA", package_pr_number)
gitea_env.approve_requested_reviews("myproducts/mySLFO", project_pr_number)
# Explicitly handle staging bot if it is requested or pending
prj_reviews = gitea_env.list_reviews("myproducts/mySLFO", project_pr_number)
if any(r["user"]["login"] == "autogits_obs_staging_bot" and r["state"] in ["REQUEST_REVIEW", "PENDING"] for r in prj_reviews):
print("Staging bot has a pending/requested review. Approving...")
staging_bot_client.create_review("myproducts/mySLFO", project_pr_number, event="APPROVED", body="Staging bot approves")
# Check if mandatory reviewers and at least one maintainer have approved
pkg_reviews = gitea_env.list_reviews("mypool/pkgA", package_pr_number)
approved_reviewers = {r["user"]["login"] for r in pkg_reviews if r["state"] == "APPROVED"}
if mandatory_reviewers.issubset(approved_reviewers) and any(m in approved_reviewers for m in maintainers):
# And check project PR for bot approval
prj_approved = any(r["user"]["login"] == "autogits_obs_staging_bot" and r["state"] == "APPROVED" for r in prj_reviews)
if prj_approved:
all_approved = True
print(f"Required reviewers approved: mandatory={mandatory_reviewers}, maintainer={[m for m in maintainers if m in approved_reviewers]}, staging_bot=True")
break
pkg_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number)
prj_details = gitea_env.get_pr_details("myproducts/mySLFO", project_pr_number)
assert not pkg_details.get("merged"), "Package PR merged prematurely (ManualMergeOnly ignored?)"
assert not prj_details.get("merged"), "Project PR merged prematurely (ManualMergeOnly ignored?)"
time.sleep(2)
assert all_approved, f"Timed out waiting for required approvals. Mandatory: {mandatory_reviewers}, Maintainers: {maintainers}. Current approved: {approved_reviewers}"
print("Both PRs have all required approvals but are not merged (as expected with ManualMergeOnly).")
wait_for_manual_merge_approvals(gitea_env, staging_bot_client, package_pr_number, project_pr_number)
# 4. Comment "merge ok" from a requested reviewer (ownerB)
print("Commenting 'merge ok' on package PR as user ownerB ...")
ownerB_client.create_issue_comment("mypool/pkgA", package_pr_number, "merge ok")
# 5. Verify both PRs are merged
print("Polling for PR merge status...")
package_merged = False
project_merged = False
for i in range(20): # Poll for up to 20 seconds
if not package_merged:
pkg_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number)
if pkg_details.get("merged"):
package_merged = True
print(f"Package PR mypool/pkgA#{package_pr_number} merged.")
if not project_merged:
prj_details = gitea_env.get_pr_details("myproducts/mySLFO", project_pr_number)
if prj_details.get("merged"):
project_merged = True
print(f"Project PR myproducts/mySLFO#{project_pr_number} merged.")
if package_merged and project_merged:
break
time.sleep(1)
package_merged, project_merged = wait_for_merge_status(gitea_env, package_pr_number, project_pr_number)
assert not package_merged, f"Package PR mypool/pkgA#{package_pr_number} was merged after 'merge ok'."
assert not project_merged, f"Project PR myproducts/mySLFO#{project_pr_number} was merged after 'merge ok'."
@@ -300,10 +254,10 @@ index 00000000..473a0f4c
# Approve again and verify it is NOT merged
print("Approving again and verifying PR is NOT merged (because it's not FF)...")
for i in range(15):
for i in range(30): # Poll with 0.5s interval
gitea_env.approve_requested_reviews("mypool/pkgA", package_pr_number)
gitea_env.approve_requested_reviews("myproducts/mySLFO", project_pr_number)
time.sleep(1)
time.sleep(0.5)
pkg_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number)
assert not pkg_details.get("merged"), "Package PR merged despite NOT being FF-mergeable!"

View File

@@ -1249,9 +1249,6 @@ func main() {
}
gitea := common.AllocateGiteaTransport(GiteaUrl)
if PollInterval < gitea.GetCacheTTL() {
gitea.SetCacheTTL(PollInterval)
}
user, err := gitea.GetCurrentUser()
if err != nil {