10 Commits

Author SHA256 Message Date
3112ed7f97 Use our IBS branch for packaging
Some checks failed
go-generate-check / go-generate-check (pull_request) Successful in 38s
Integration tests / t (pull_request) Failing after 9m40s
Need to use it from internal resource policy wise, but will be mirrored outside.
2026-02-26 11:12:30 +01:00
4dd54b5db1 staging: Hardcode currently used user of IBS
We should consolidate to a nicer user name...
2026-02-26 11:12:29 +01:00
dde0e04a03 Always handle build results as building when dirty flag is set 2026-02-26 11:12:29 +01:00
346c6d836a staging: Fix service file to work with obs ssh connections
We need a fixed user for that
2026-02-26 11:12:29 +01:00
cc37fa73bd Add an "lfs fsck" check after submodule update
to avoid merging pull requests where lfs objects are not correctly
registered. Can happen when user has not installed lfs for example.
2026-02-26 11:12:29 +01:00
7740021192 Disable temporary comment adding in case of lacking permissions 2026-02-26 11:12:29 +01:00
6b76c3efc0 pr: only update PR if elided title not changed
Gitea trims long titles so we need to compare if the trimmed length
is same, not entire string that will always differ.
2026-02-26 11:12:29 +01:00
d04b67fde7 Support remote source in pull requests
This requires write permission by maintainer there
2026-02-26 11:12:29 +01:00
93812e34d9 workflow-direct: use relative path when adding a submodule
This solves the issue of using the right credentials based on the
main repo.
Also it allows to rename the organisation.
2026-02-26 11:12:29 +01:00
439d1baf64 Fix build status check when package source is in subdir
We must not use the upper directory name as part of the package name.
2026-02-26 11:12:29 +01:00
29 changed files with 438 additions and 2564 deletions

View File

@@ -22,6 +22,8 @@ Release: 0
Summary: GitWorkflow utilities
License: GPL-2.0-or-later
URL: https://src.opensuse.org/adamm/autogits
#!RemoteAsset: git+https://src.suse.de/adrianSuSE/autogits#ibs_state
Source0: %name-%version.tar.xz
BuildRequires: git
BuildRequires: systemd-rpm-macros
BuildRequires: go

View File

@@ -83,260 +83,3 @@ func (c *MockObsStatusFetcherWithStateBuildStatusWithStateCall) DoAndReturn(f fu
c.Call = c.Call.DoAndReturn(f)
return c
}
// MockObsClientInterface is a mock of ObsClientInterface interface.
type MockObsClientInterface struct {
ctrl *gomock.Controller
recorder *MockObsClientInterfaceMockRecorder
isgomock struct{}
}
// MockObsClientInterfaceMockRecorder is the mock recorder for MockObsClientInterface.
type MockObsClientInterfaceMockRecorder struct {
mock *MockObsClientInterface
}
// NewMockObsClientInterface creates a new mock instance.
func NewMockObsClientInterface(ctrl *gomock.Controller) *MockObsClientInterface {
mock := &MockObsClientInterface{ctrl: ctrl}
mock.recorder = &MockObsClientInterfaceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockObsClientInterface) EXPECT() *MockObsClientInterfaceMockRecorder {
return m.recorder
}
// BuildStatus mocks base method.
func (m *MockObsClientInterface) BuildStatus(project string, packages ...string) (*common.BuildResultList, error) {
m.ctrl.T.Helper()
varargs := []any{project}
for _, a := range packages {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "BuildStatus", varargs...)
ret0, _ := ret[0].(*common.BuildResultList)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BuildStatus indicates an expected call of BuildStatus.
func (mr *MockObsClientInterfaceMockRecorder) BuildStatus(project any, packages ...any) *MockObsClientInterfaceBuildStatusCall {
mr.mock.ctrl.T.Helper()
varargs := append([]any{project}, packages...)
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildStatus", reflect.TypeOf((*MockObsClientInterface)(nil).BuildStatus), varargs...)
return &MockObsClientInterfaceBuildStatusCall{Call: call}
}
// MockObsClientInterfaceBuildStatusCall wrap *gomock.Call
type MockObsClientInterfaceBuildStatusCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockObsClientInterfaceBuildStatusCall) Return(arg0 *common.BuildResultList, arg1 error) *MockObsClientInterfaceBuildStatusCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockObsClientInterfaceBuildStatusCall) Do(f func(string, ...string) (*common.BuildResultList, error)) *MockObsClientInterfaceBuildStatusCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockObsClientInterfaceBuildStatusCall) DoAndReturn(f func(string, ...string) (*common.BuildResultList, error)) *MockObsClientInterfaceBuildStatusCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// DeleteProject mocks base method.
func (m *MockObsClientInterface) DeleteProject(project string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteProject", project)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteProject indicates an expected call of DeleteProject.
func (mr *MockObsClientInterfaceMockRecorder) DeleteProject(project any) *MockObsClientInterfaceDeleteProjectCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteProject", reflect.TypeOf((*MockObsClientInterface)(nil).DeleteProject), project)
return &MockObsClientInterfaceDeleteProjectCall{Call: call}
}
// MockObsClientInterfaceDeleteProjectCall wrap *gomock.Call
type MockObsClientInterfaceDeleteProjectCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockObsClientInterfaceDeleteProjectCall) Return(arg0 error) *MockObsClientInterfaceDeleteProjectCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockObsClientInterfaceDeleteProjectCall) Do(f func(string) error) *MockObsClientInterfaceDeleteProjectCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockObsClientInterfaceDeleteProjectCall) DoAndReturn(f func(string) error) *MockObsClientInterfaceDeleteProjectCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// GetHomeProject mocks base method.
func (m *MockObsClientInterface) GetHomeProject() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetHomeProject")
ret0, _ := ret[0].(string)
return ret0
}
// GetHomeProject indicates an expected call of GetHomeProject.
func (mr *MockObsClientInterfaceMockRecorder) GetHomeProject() *MockObsClientInterfaceGetHomeProjectCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHomeProject", reflect.TypeOf((*MockObsClientInterface)(nil).GetHomeProject))
return &MockObsClientInterfaceGetHomeProjectCall{Call: call}
}
// MockObsClientInterfaceGetHomeProjectCall wrap *gomock.Call
type MockObsClientInterfaceGetHomeProjectCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockObsClientInterfaceGetHomeProjectCall) Return(arg0 string) *MockObsClientInterfaceGetHomeProjectCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockObsClientInterfaceGetHomeProjectCall) Do(f func() string) *MockObsClientInterfaceGetHomeProjectCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockObsClientInterfaceGetHomeProjectCall) DoAndReturn(f func() string) *MockObsClientInterfaceGetHomeProjectCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// GetProjectMeta mocks base method.
func (m *MockObsClientInterface) GetProjectMeta(project string) (*common.ProjectMeta, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetProjectMeta", project)
ret0, _ := ret[0].(*common.ProjectMeta)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetProjectMeta indicates an expected call of GetProjectMeta.
func (mr *MockObsClientInterfaceMockRecorder) GetProjectMeta(project any) *MockObsClientInterfaceGetProjectMetaCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProjectMeta", reflect.TypeOf((*MockObsClientInterface)(nil).GetProjectMeta), project)
return &MockObsClientInterfaceGetProjectMetaCall{Call: call}
}
// MockObsClientInterfaceGetProjectMetaCall wrap *gomock.Call
type MockObsClientInterfaceGetProjectMetaCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockObsClientInterfaceGetProjectMetaCall) Return(arg0 *common.ProjectMeta, arg1 error) *MockObsClientInterfaceGetProjectMetaCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockObsClientInterfaceGetProjectMetaCall) Do(f func(string) (*common.ProjectMeta, error)) *MockObsClientInterfaceGetProjectMetaCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockObsClientInterfaceGetProjectMetaCall) DoAndReturn(f func(string) (*common.ProjectMeta, error)) *MockObsClientInterfaceGetProjectMetaCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// SetHomeProject mocks base method.
func (m *MockObsClientInterface) SetHomeProject(project string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetHomeProject", project)
}
// SetHomeProject indicates an expected call of SetHomeProject.
func (mr *MockObsClientInterfaceMockRecorder) SetHomeProject(project any) *MockObsClientInterfaceSetHomeProjectCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetHomeProject", reflect.TypeOf((*MockObsClientInterface)(nil).SetHomeProject), project)
return &MockObsClientInterfaceSetHomeProjectCall{Call: call}
}
// MockObsClientInterfaceSetHomeProjectCall wrap *gomock.Call
type MockObsClientInterfaceSetHomeProjectCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockObsClientInterfaceSetHomeProjectCall) Return() *MockObsClientInterfaceSetHomeProjectCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockObsClientInterfaceSetHomeProjectCall) Do(f func(string)) *MockObsClientInterfaceSetHomeProjectCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockObsClientInterfaceSetHomeProjectCall) DoAndReturn(f func(string)) *MockObsClientInterfaceSetHomeProjectCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// SetProjectMeta mocks base method.
func (m *MockObsClientInterface) SetProjectMeta(meta *common.ProjectMeta) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetProjectMeta", meta)
ret0, _ := ret[0].(error)
return ret0
}
// SetProjectMeta indicates an expected call of SetProjectMeta.
func (mr *MockObsClientInterfaceMockRecorder) SetProjectMeta(meta any) *MockObsClientInterfaceSetProjectMetaCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetProjectMeta", reflect.TypeOf((*MockObsClientInterface)(nil).SetProjectMeta), meta)
return &MockObsClientInterfaceSetProjectMetaCall{Call: call}
}
// MockObsClientInterfaceSetProjectMetaCall wrap *gomock.Call
type MockObsClientInterfaceSetProjectMetaCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockObsClientInterfaceSetProjectMetaCall) Return(arg0 error) *MockObsClientInterfaceSetProjectMetaCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockObsClientInterfaceSetProjectMetaCall) Do(f func(*common.ProjectMeta) error) *MockObsClientInterfaceSetProjectMetaCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockObsClientInterfaceSetProjectMetaCall) DoAndReturn(f func(*common.ProjectMeta) error) *MockObsClientInterfaceSetProjectMetaCall {
c.Call = c.Call.DoAndReturn(f)
return c
}

View File

@@ -46,15 +46,6 @@ type ObsStatusFetcherWithState interface {
BuildStatusWithState(project string, opts *BuildResultOptions, packages ...string) (*BuildResultList, error)
}
type ObsClientInterface interface {
GetProjectMeta(project string) (*ProjectMeta, error)
SetProjectMeta(meta *ProjectMeta) error
DeleteProject(project string) error
BuildStatus(project string, packages ...string) (*BuildResultList, error)
GetHomeProject() string
SetHomeProject(project string)
}
type ObsClient struct {
baseUrl *url.URL
client *http.Client
@@ -66,14 +57,6 @@ type ObsClient struct {
HomeProject string
}
func (c *ObsClient) GetHomeProject() string {
return c.HomeProject
}
func (c *ObsClient) SetHomeProject(project string) {
c.HomeProject = project
}
func NewObsClient(host string) (*ObsClient, error) {
baseUrl, err := url.Parse(host)
if err != nil {

View File

@@ -468,7 +468,7 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData
LogError("Cannot fetch gita reaviews for PR:", err)
return false
}
r.SetRequiredReviewers(reviewers)
r.RequestedReviewers = reviewers
prjgit.Reviews = r
if prjgit.Reviews.IsManualMergeOK() {
is_manually_reviewed_ok = true
@@ -489,7 +489,7 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData
LogError("Cannot fetch gita reaviews for PR:", err)
return false
}
r.SetRequiredReviewers(reviewers)
r.RequestedReviewers = reviewers
pr.Reviews = r
if !pr.Reviews.IsManualMergeOK() {
LogInfo("Not approved manual merge. PR:", pr.PR.URL)
@@ -530,7 +530,7 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData
LogError("Cannot fetch gitea reaviews for PR:", err)
return false
}
r.SetRequiredReviewers(reviewers)
r.RequestedReviewers = reviewers
is_manually_reviewed_ok = r.IsApproved()
LogDebug("PR to", pr.PR.Base.Repo.Name, "reviewed?", is_manually_reviewed_ok)

View File

@@ -807,8 +807,9 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
},
Reviews: &common.PRReviews{
Reviews: []*models.PullReview{{State: common.ReviewStateRequestReview, User: &models.User{UserName: "m1"}}},
RequestedReviewers: []*models.TimelineComment{
Reviews: []*models.PullReview{{State: common.ReviewStateRequestReview, User: &models.User{UserName: "m1"}}},
RequestedReviewers: []string{"m1"},
FullTimeline: []*models.TimelineComment{
{User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "m1"}, Type: common.TimelineCommentType_ReviewRequested},
},
},
@@ -918,7 +919,8 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
},
Reviews: &common.PRReviews{
Reviews: []*models.PullReview{{State: common.ReviewStateRequestReview, User: &models.User{UserName: "reviewer"}}},
RequestedReviewers: []*models.TimelineComment{{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "reviewer"}}},
RequestedReviewers: []string{"reviewer"},
FullTimeline: []*models.TimelineComment{{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "reviewer"}}},
},
},
{
@@ -928,7 +930,8 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
},
Reviews: &common.PRReviews{
Reviews: []*models.PullReview{{State: common.ReviewStateRequestReview, User: &models.User{UserName: "reviewer"}}},
RequestedReviewers: []*models.TimelineComment{{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "reviewer"}}},
RequestedReviewers: []string{"reviewer"},
FullTimeline: []*models.TimelineComment{{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "reviewer"}}},
},
},
},
@@ -963,7 +966,8 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
{State: common.ReviewStateApproved, User: &models.User{UserName: "pkgmaintainer"}},
{State: common.ReviewStatePending, User: &models.User{UserName: "prjmaintainer"}},
},
RequestedReviewers: []*models.TimelineComment{
RequestedReviewers: []string{"user2", "pkgmaintainer", "prjmaintainer"},
FullTimeline: []*models.TimelineComment{
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "user2"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgmaintainer"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prjmaintainer"}},
@@ -981,7 +985,8 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
{State: common.ReviewStateRequestChanges, User: &models.User{UserName: "user1"}},
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "autogits_obs_staging_bot"}},
},
RequestedReviewers: []*models.TimelineComment{
RequestedReviewers: []string{"user1", "autogits_obs_staging_bot"},
FullTimeline: []*models.TimelineComment{
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "user1"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "autogits_obs_staging_bot"}},
},
@@ -1021,7 +1026,8 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
{State: common.ReviewStatePending, User: &models.User{UserName: "prj2"}},
{State: common.ReviewStatePending, User: &models.User{UserName: "someother"}},
},
RequestedReviewers: []*models.TimelineComment{
RequestedReviewers: []string{"user2", "pkgmaintainer", "prjmaintainer", "pkgm1", "pkgm2", "someother", "prj1", "prj2"},
FullTimeline: []*models.TimelineComment{
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgmaintainer"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prjmaintainer"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj1"}},
@@ -1044,7 +1050,8 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
{State: common.ReviewStatePending, User: &models.User{UserName: "prj1"}},
{State: common.ReviewStatePending, User: &models.User{UserName: "prj2"}},
},
RequestedReviewers: []*models.TimelineComment{
RequestedReviewers: []string{"user1", "autogits_obs_staging_bot", "prj1", "prj2"},
FullTimeline: []*models.TimelineComment{
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "autogits_obs_staging_bot"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj1"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj2"}},
@@ -1083,7 +1090,8 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "prj1"}},
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "someother"}},
},
RequestedReviewers: []*models.TimelineComment{
RequestedReviewers: []string{"user2", "pkgmaintainer", "prjmaintainer", "pkgm1", "someother", "prj1"},
FullTimeline: []*models.TimelineComment{
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgm1"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgmaintainer"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prjmaintainer"}},
@@ -1104,7 +1112,8 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "autogits_obs_staging_bot"}},
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "prj1"}},
},
RequestedReviewers: []*models.TimelineComment{
RequestedReviewers: []string{"user1", "autogits_obs_staging_bot", "prj1"},
FullTimeline: []*models.TimelineComment{
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "autogits_obs_staging_bot"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj1"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "!bot"}, Assignee: &models.User{UserName: "user1"}},
@@ -1190,9 +1199,6 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
test.prset.HasAutoStaging = !test.noAutoStaging
for idx, pr := range test.prset.PRs {
if pr.Reviews != nil {
pr.Reviews.SetRequiredReviewers(test.prset.Config.Reviewers)
}
missing, extra := test.prset.FindMissingAndExtraReviewers(test.maintainers, idx)
// avoid nil dereference below, by adding empty array elements

View File

@@ -8,28 +8,12 @@ import (
"src.opensuse.org/autogits/common/gitea-generated/models"
)
type ReviewInterface interface {
IsManualMergeOK() bool
IsApproved() bool
MisingReviews() []string
FindReviewRequester(reviewer string) *models.TimelineComment
HasPendingReviewBy(reviewer string) bool
IsReviewedBy(reviewer string) bool
IsReviewedByOneOf(reviewers ...string) bool
SetRequiredReviewers(reviewers []string)
}
type PRReviews struct {
Reviews []*models.PullReview
RequestedReviewers []*models.TimelineComment
RequestedReviewers []string
Comments []*models.TimelineComment
RequiredReviewers []string
}
func (r *PRReviews) SetRequiredReviewers(reviewers []string) {
r.RequiredReviewers = reviewers
FullTimeline []*models.TimelineComment
}
func FetchGiteaReviews(rf GiteaReviewTimelineFetcher, org, repo string, no int64) (*PRReviews, error) {
@@ -44,11 +28,11 @@ func FetchGiteaReviews(rf GiteaReviewTimelineFetcher, org, repo string, no int64
}
reviews := make([]*models.PullReview, 0, 10)
needNewReviews := []string{}
var comments []*models.TimelineComment
var foundUsers []string
alreadyHaveUserReview := func(user string) bool {
if slices.Contains(foundUsers, user) {
if slices.Contains(needNewReviews, user) {
return true
}
for _, r := range reviews {
@@ -64,24 +48,20 @@ func FetchGiteaReviews(rf GiteaReviewTimelineFetcher, org, repo string, no int64
LogDebug("Number of items in timeline:", len(timeline))
cutOffIdx := len(timeline)
var PendingRequestedReviews []*models.TimelineComment
for idx, item := range timeline {
if item.Type == TimelineCommentType_Review {
if item.Type == TimelineCommentType_Review || item.Type == TimelineCommentType_ReviewRequested {
for _, r := range rawReviews {
if r.ID == item.ReviewID && r.User != nil {
if r.ID == item.ReviewID {
if !alreadyHaveUserReview(r.User.UserName) {
if idx < cutOffIdx {
if item.Type == TimelineCommentType_Review && idx > cutOffIdx {
needNewReviews = append(needNewReviews, r.User.UserName)
} else {
reviews = append(reviews, r)
}
foundUsers = append(foundUsers, r.User.UserName)
}
break
}
}
} else if item.Type == TimelineCommentType_ReviewRequested && item.Assignee != nil && !alreadyHaveUserReview(item.Assignee.UserName) {
PendingRequestedReviews = append(PendingRequestedReviews, item)
} else if item.Type == TimelineCommentType_DismissReview && item.Assignee != nil && !alreadyHaveUserReview(item.Assignee.UserName) {
foundUsers = append(foundUsers, item.Assignee.UserName)
} else if item.Type == TimelineCommentType_Comment && cutOffIdx > idx {
comments = append(comments, item)
} else if item.Type == TimelineCommentType_PushPull && cutOffIdx == len(timeline) {
@@ -94,9 +74,9 @@ func FetchGiteaReviews(rf GiteaReviewTimelineFetcher, org, repo string, no int64
LogDebug("num comments:", len(comments), "timeline:", len(reviews))
return &PRReviews{
Reviews: reviews,
Comments: comments,
RequestedReviewers: PendingRequestedReviews,
Reviews: reviews,
Comments: comments,
FullTimeline: timeline,
}, nil
}
@@ -124,7 +104,7 @@ func (r *PRReviews) IsManualMergeOK() bool {
continue
}
LogDebug("comment:", c.User.UserName, c.Body)
if slices.Contains(r.RequiredReviewers, c.User.UserName) {
if slices.Contains(r.RequestedReviewers, c.User.UserName) {
if bodyCommandManualMergeOK(c.Body) {
return true
}
@@ -135,7 +115,7 @@ func (r *PRReviews) IsManualMergeOK() bool {
if c.Updated != c.Submitted {
continue
}
if slices.Contains(r.RequiredReviewers, c.User.UserName) {
if slices.Contains(r.RequestedReviewers, c.User.UserName) {
if bodyCommandManualMergeOK(c.Body) {
return true
}
@@ -151,7 +131,7 @@ func (r *PRReviews) IsApproved() bool {
}
goodReview := true
for _, reviewer := range r.RequiredReviewers {
for _, reviewer := range r.RequestedReviewers {
goodReview = false
for _, review := range r.Reviews {
if review.User.UserName == reviewer && review.State == ReviewStateApproved && !review.Stale && !review.Dismissed {
@@ -175,7 +155,7 @@ func (r *PRReviews) MissingReviews() []string {
return missing
}
for _, reviewer := range r.RequiredReviewers {
for _, reviewer := range r.RequestedReviewers {
if !r.IsReviewedBy(reviewer) {
missing = append(missing, reviewer)
}
@@ -188,11 +168,12 @@ func (r *PRReviews) FindReviewRequester(reviewer string) *models.TimelineComment
return nil
}
for _, t := range r.RequestedReviewers {
if t.Assignee.UserName == reviewer {
return t
for _, r := range r.FullTimeline {
if r.Type == TimelineCommentType_ReviewRequested && r.Assignee.UserName == reviewer {
return r
}
}
return nil
}
@@ -212,13 +193,6 @@ func (r *PRReviews) HasPendingReviewBy(reviewer string) bool {
}
}
// at this point, we do not have actual review by user. Check if we have a pending review
for _, t := range r.RequestedReviewers {
if t.Assignee != nil && t.Assignee.UserName == reviewer {
return true
}
}
return false
}

View File

@@ -137,61 +137,6 @@ func TestReviews(t *testing.T) {
isApproved: false,
isReviewedByTest1: true,
},
{
name: "Ghost user review",
reviews: []*models.PullReview{
{State: common.ReviewStateApproved, User: nil},
},
reviewers: []string{"user1"},
isApproved: false,
},
{
name: "ReviewRequested predates PushPull should be seen as pending",
reviews: []*models.PullReview{},
timeline: []*models.TimelineComment{
{Type: common.TimelineCommentType_PushPull},
{Type: common.TimelineCommentType_ReviewRequested, Assignee: &models.User{UserName: "user1"}},
},
reviewers: []string{"user1"},
isPendingByTest1: true,
},
{
name: "ReviewRequested postdates PushPull but blocked by older dismiss",
reviews: []*models.PullReview{},
timeline: []*models.TimelineComment{
{Type: common.TimelineCommentType_ReviewRequested, Assignee: &models.User{UserName: "user1"}},
{Type: common.TimelineCommentType_PushPull},
{Type: common.TimelineCommentType_ReviewDismissed, Assignee: &models.User{UserName: "user1"}},
},
reviewers: []string{"user1"},
isPendingByTest1: true,
},
{
name: "ReviewRequested predates PushPull should be seen as pending",
reviews: []*models.PullReview{
{ID: 101, State: common.ReviewStateRequestReview, User: &models.User{UserName: "user1"}},
},
timeline: []*models.TimelineComment{
{Type: common.TimelineCommentType_PushPull},
{Type: common.TimelineCommentType_ReviewRequested, Assignee: &models.User{UserName: "user1"}},
},
reviewers: []string{"user1"},
isPendingByTest1: true,
},
{
name: "Review requested, review, then push needs re-requesting",
reviews: []*models.PullReview{
{ID: 100, State: common.ReviewStateRequestChanges, User: &models.User{UserName: "user1"}},
},
timeline: []*models.TimelineComment{
{Type: common.TimelineCommentType_PushPull},
{Type: common.TimelineCommentType_Review, ReviewID: 100},
{Type: common.TimelineCommentType_ReviewRequested, Assignee: &models.User{UserName: "user1"}},
},
reviewers: []string{"user1"},
isReviewedByTest1: false, // Should be stale
isPendingByTest1: false, // Should be stale
},
}
for _, test := range tests {
@@ -213,7 +158,7 @@ func TestReviews(t *testing.T) {
}
return
}
reviews.SetRequiredReviewers(test.reviewers)
reviews.RequestedReviewers = test.reviewers
if r := reviews.IsApproved(); r != test.isApproved {
t.Fatal("Unexpected IsReviewed():", r, "vs. expected", test.isApproved)

View File

@@ -8,7 +8,6 @@ import (
)
const (
TimelineCommentType_ReviewDismissed = "dismiss_review"
TimelineCommentType_ReviewRequested = "review_request"
TimelineCommentType_Review = "review"
TimelineCommentType_PushPull = "pull_push"

View File

@@ -92,13 +92,10 @@ func ConnectToExchangeForPublish(host, username, password string) {
auth = username + ":" + password + "@"
}
connection, err := rabbitmq.DialConfig("amqps://"+auth+host, rabbitmq.Config{
Dial: rabbitmq.DefaultDial(10 * time.Second),
TLSClientConfig: &tls.Config{
ServerName: host,
},
connection, err := rabbitmq.DialTLS("amqps://"+auth+host, &tls.Config{
ServerName: host,
})
failOnError(err, "Cannot connect to "+host)
failOnError(err, "Cannot connect to rabbit.opensuse.org")
defer connection.Close()
ch, err := connection.Channel()

View File

@@ -2,7 +2,7 @@
<title>openSUSE Leap 16.0 based on SLFO</title>
<description>Leap 16.0 based on SLES 16.0 (specifically SLFO:1.2)</description>
<link project="openSUSE:Backports:SLE-16.0"/>
<scmsync>http://gitea-test:3000/myproducts/mySLFO#staging-main</scmsync>
<scmsync>http://gitea-test:3000/products/SLFO#main</scmsync>
<person userid="dimstar_suse" role="maintainer"/>
<person userid="lkocman-factory" role="maintainer"/>
<person userid="maxlin_factory" role="maintainer"/>

View File

@@ -8,7 +8,6 @@ services:
gitea:
build: ./gitea
container_name: gitea-test
init: true
environment:
- GITEA_WORK_DIR=/var/lib/gitea
networks:
@@ -28,7 +27,6 @@ services:
rabbitmq:
image: rabbitmq:3.13.7-management
container_name: rabbitmq-test
init: true
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "check_running", "-q"]
interval: 30s
@@ -57,7 +55,6 @@ services:
context: ..
dockerfile: integration/gitea-events-rabbitmq-publisher/Dockerfile${GIWTF_IMAGE_SUFFIX}
container_name: gitea-publisher
init: true
networks:
- gitea-network
depends_on:
@@ -78,7 +75,6 @@ services:
context: ..
dockerfile: integration/workflow-pr/Dockerfile${GIWTF_IMAGE_SUFFIX}
container_name: workflow-pr
init: true
networks:
- gitea-network
depends_on:
@@ -107,7 +103,6 @@ services:
mock-obs:
build: ./mock-obs
container_name: mock-obs
init: true
networks:
- gitea-network
ports:
@@ -121,7 +116,6 @@ services:
context: ..
dockerfile: integration/obs-staging-bot/Dockerfile${GIWTF_IMAGE_SUFFIX}
container_name: obs-staging-bot
init: true
networks:
- gitea-network
depends_on:

View File

@@ -59,18 +59,18 @@ The testing will be conducted in a dedicated test environment that mimics the pr
| **TC-SYNC-002** | P | **Update ProjectGit PR from PackageGit PR** | 1. Push a new commit to an existing PackageGit PR. | 1. The corresponding ProjectGit PR's head branch is updated with the new commit. | High |
| **TC-SYNC-003** | P | **WIP Flag Synchronization** | 1. Mark a PackageGit PR as "Work In Progress".<br>2. Remove the WIP flag from the PackageGit PR. | 1. The corresponding ProjectGit PR is also marked as "Work In Progress".<br>2. The WIP flag on the ProjectGit PR is removed. | Medium |
| **TC-SYNC-004** | - | **WIP Flag (multiple referenced package PRs)** | 1. Create a ProjectGit PR that references multiple PackageGit PRs.<br>2. Mark one of the PackageGit PRs as "Work In Progress".<br>3. Remove the "Work In Progress" flag from all PackageGit PRs. | 1. The ProjectGit PR is marked as "Work In Progress".<br>2. The "Work In Progress" flag is removed from the ProjectGit PR only after it has been removed from all associated PackageGit PRs. | Medium |
| **TC-SYNC-005** | S | **NoProjectGitPR = true, edits disabled** | 1. Set `NoProjectGitPR = true` in `workflow.config`.<br>2. Create a PackageGit PR without "Allow edits from maintainers" enabled. <br>3. Push a new commit to the PackageGit PR. | 1. No ProjectGit PR is created.<br>2. The bot adds a warning comment to the PackageGit PR explaining that it cannot update the PR. | High |
| **TC-SYNC-006** | S | **NoProjectGitPR = true, edits enabled** | 1. Set `NoProjectGitPR = true` in `workflow.config`.<br>2. Create a PackageGit PR with "Allow edits from maintainers" enabled.<br>3. Push a new commit to the PackageGit PR. | 1. No ProjectGit PR is created.<br>2. The submodule commit on the project PR is updated with the new commit from the PackageGit PR. | High |
| **TC-SYNC-005** | x | **NoProjectGitPR = true, edits disabled** | 1. Set `NoProjectGitPR = true` in `workflow.config`.<br>2. Create a PackageGit PR without "Allow edits from maintainers" enabled. <br>3. Push a new commit to the PackageGit PR. | 1. No ProjectGit PR is created.<br>2. The bot adds a warning comment to the PackageGit PR explaining that it cannot update the PR. | High |
| **TC-SYNC-006** | x | **NoProjectGitPR = true, edits enabled** | 1. Set `NoProjectGitPR = true` in `workflow.config`.<br>2. Create a PackageGit PR with "Allow edits from maintainers" enabled.<br>3. Push a new commit to the PackageGit PR. | 1. No ProjectGit PR is created.<br>2. The submodule commit on the project PR is updated with the new commit from the PackageGit PR. | High |
| **TC-COMMENT-001** | - | **Detect duplicate comments** | 1. Create a PackageGit PR.<br>2. Wait for the `workflow-pr` bot to act on the PR.<br>3. Edit the body of the PR to trigger the bot a second time. | 1. The bot should not post a duplicate comment. | High |
| **TC-REVIEW-001** | P | **Add mandatory reviewers** | 1. Create a new PackageGit PR. | 1. All mandatory reviewers are added to both the PackageGit and ProjectGit PRs. | High |
| **TC-REVIEW-002** | - | **Add advisory reviewers** | 1. Create a new PackageGit PR with advisory reviewers defined in the configuration. | 1. Advisory reviewers are added to the PR, but their approval is not required for merging. | Medium |
| **TC-REVIEW-003** | - | **Re-add reviewers** | 1. Push a new commit to a PackageGit PR after it has been approved. | 1. The original reviewers are re-added to the PR. | Medium |
| **TC-REVIEW-004** | P | **Package PR created by a maintainer** | 1. Create a PackageGit PR from the account of a package maintainer. | 1. No review is requested from other package maintainers. | High |
| **TC-REVIEW-004** | x | **Package PR created by a maintainer** | 1. Create a PackageGit PR from the account of a package maintainer. | 1. No review is requested from other package maintainers. | High |
| **TC-REVIEW-005** | P | **Package PR created by an external user (approve)** | 1. Create a PackageGit PR from the account of a user who is not a package maintainer.<br>2. One of the package maintainers approves the PR. | 1. All package maintainers are added as reviewers.<br>2. Once one maintainer approves the PR, the other maintainers are removed as reviewers. | High |
| **TC-REVIEW-006** | P | **Package PR created by an external user (reject)** | 1. Create a PackageGit PR from the account of a user who is not a package maintainer.<br>2. One of the package maintainers rejects the PR. | 1. All package maintainers are added as reviewers.<br>2. Once one maintainer rejects the PR, the other maintainers are removed as reviewers. | High |
| **TC-REVIEW-007** | P | **Package PR created by a maintainer with ReviewRequired=true** | 1. Set `ReviewRequired = true` in `workflow.config`.<br>2. Create a PackageGit PR from the account of a package maintainer. | 1. A review is requested from other package maintainers if available. | High |
| **TC-MERGE-001** | P | **Automatic Merge** | 1. Create a PackageGit PR.<br>2. Ensure all mandatory reviews are completed on both project and package PRs. | 1. The PR is automatically merged. | High |
| **TC-MERGE-002** | P | **ManualMergeOnly with Package Maintainer** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a package maintainer (or requested reviewer). | 1. The PR is merged. | High |
| **TC-MERGE-001** | x | **Automatic Merge** | 1. Create a PackageGit PR.<br>2. Ensure all mandatory reviews are completed on both project and package PRs. | 1. The PR is automatically merged. | High |
| **TC-MERGE-002** | - | **ManualMergeOnly with Package Maintainer** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a package maintainer for that package. | 1. The PR is merged. | High |
| **TC-MERGE-003** | - | **ManualMergeOnly with unauthorized user** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a user who is not a maintainer for that package. | 1. The PR is not merged. | High |
| **TC-MERGE-004** | - | **ManualMergeOnly with multiple packages** | 1. Create a ProjectGit PR that references multiple PackageGit PRs with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on each package PR from the account of a package maintainer. | 1. The PR is merged only after "merge ok" is commented on all associated PackageGit PRs. | High |
| **TC-MERGE-005** | - | **ManualMergeOnly with Project Maintainer** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a project maintainer. | 1. The PR is merged. | High |
@@ -84,7 +84,6 @@ The testing will be conducted in a dedicated test environment that mimics the pr
#### Legend:
* P = implemented and passing;
* S = skipped because is not implemented yet to save some execution time;
* x = likely implemented, but investigation is needed;
* X = implemented and likely to pass, but someteimes may fail, but troubleshooting is needed;
* - = test is not implemented

View File

@@ -13,9 +13,9 @@ from tests.lib.common_test_utils import GiteaAPIClient
BRANCH_CONFIG_COMMON = {
"workflow.config": {
"Workflows": ["pr"],
"Organization": "mypool",
"Organization": "pool",
"Reviewers": ["-autogits_obs_staging_bot"],
"GitProjectName": "myproducts/mySLFO#{branch}"
"GitProjectName": "products/SLFO#{branch}"
},
"_maintainership.json": {
"": ["ownerX", "ownerY"],
@@ -25,8 +25,7 @@ BRANCH_CONFIG_COMMON = {
}
BRANCH_CONFIG_CUSTOM = {
"main": {},
"staging-main": {
"main": {
"workflow.config": {
"ManualMergeProject": True
},
@@ -55,12 +54,6 @@ BRANCH_CONFIG_CUSTOM = {
"NoProjectGitPR": True
}
},
"manual-merge": {
"workflow.config": {
"ManualMergeOnly": True,
"Reviewers": ["+usera", "+userb", "-autogits_obs_staging_bot"]
}
},
"label-test": {
"workflow.config": {
"ManualMergeProject": True,
@@ -74,13 +67,6 @@ BRANCH_CONFIG_CUSTOM = {
}
}
# Global state to track created Gitea objects during a pytest run
_CREATED_ORGS = set()
_CREATED_REPOS = set()
_CREATED_USERS = set()
_CREATED_LABELS = set()
_ADDED_COLLABORATORS = set() # format: (org_repo, username)
def setup_users_from_config(client: GiteaAPIClient, wf: dict, mt: dict):
"""
Parses workflow.config and _maintainership.json, creates users, and adds them as collaborators.
@@ -101,27 +87,18 @@ def setup_users_from_config(client: GiteaAPIClient, wf: dict, mt: dict):
# Create all users
for username in all_users:
if username not in _CREATED_USERS:
client.create_user(username, "password123", f"{username}@example.com")
_CREATED_USERS.add(username)
if ("myproducts/mySLFO", username) not in _ADDED_COLLABORATORS:
client.add_collaborator("myproducts", "mySLFO", username, "write")
_ADDED_COLLABORATORS.add(("myproducts/mySLFO", username))
client.create_user(username, "password123", f"{username}@example.com")
client.add_collaborator("products", "SLFO", username, "write")
# Set specific repository permissions based on maintainership
for pkg, users in mt.items():
repo_name = pkg if pkg else None
for username in users:
if not repo_name:
for r in ["pkgA", "pkgB"]:
if (f"mypool/{r}", username) not in _ADDED_COLLABORATORS:
client.add_collaborator("mypool", r, username, "write")
_ADDED_COLLABORATORS.add((f"mypool/{r}", username))
client.add_collaborator("pool", "pkgA", username, "write")
client.add_collaborator("pool", "pkgB", username, "write")
else:
if (f"mypool/{repo_name}", username) not in _ADDED_COLLABORATORS:
client.add_collaborator("mypool", repo_name, username, "write")
_ADDED_COLLABORATORS.add((f"mypool/{repo_name}", username))
client.add_collaborator("pool", repo_name, username, "write")
def ensure_config_file(client: GiteaAPIClient, owner: str, repo: str, branch: str, file_name: str, expected_content_dict: dict):
"""
@@ -172,41 +149,33 @@ def gitea_env():
raise Exception("Gitea not available.")
print("--- Starting Gitea Global Setup ---")
for org in ["myproducts", "mypool"]:
if org not in _CREATED_ORGS:
client.create_org(org)
_CREATED_ORGS.add(org)
for org, repo in [("myproducts", "mySLFO"), ("mypool", "pkgA"), ("mypool", "pkgB")]:
if f"{org}/{repo}" not in _CREATED_REPOS:
client.create_repo(org, repo)
client.update_repo_settings(org, repo)
_CREATED_REPOS.add(f"{org}/{repo}")
client.create_org("products")
client.create_org("pool")
client.create_repo("products", "SLFO")
client.create_repo("pool", "pkgA")
client.create_repo("pool", "pkgB")
client.update_repo_settings("products", "SLFO")
client.update_repo_settings("pool", "pkgA")
client.update_repo_settings("pool", "pkgB")
# Create labels
for name, color in [("staging/Backlog", "#0000ff"), ("review/Pending", "#ffff00")]:
if ("myproducts/mySLFO", name) not in _CREATED_LABELS:
client.create_label("myproducts", "mySLFO", name, color=color)
_CREATED_LABELS.add(("myproducts/mySLFO", name))
client.create_label("products", "SLFO", "staging/Backlog", color="#0000ff")
client.create_label("products", "SLFO", "review/Pending", color="#ffff00")
# Submodules in mySLFO
client.add_submodules("myproducts", "mySLFO")
# Submodules in SLFO
client.add_submodules("products", "SLFO")
for repo_full, bot in [("myproducts/mySLFO", "autogits_obs_staging_bot"),
("myproducts/mySLFO", "workflow-pr"),
("mypool/pkgA", "workflow-pr"),
("mypool/pkgB", "workflow-pr")]:
if (repo_full, bot) not in _ADDED_COLLABORATORS:
org_part, repo_part = repo_full.split("/")
client.add_collaborator(org_part, repo_part, bot, "write")
_ADDED_COLLABORATORS.add((repo_full, bot))
client.add_collaborator("products", "SLFO", "autogits_obs_staging_bot", "write")
client.add_collaborator("products", "SLFO", "workflow-pr", "write")
client.add_collaborator("pool", "pkgA", "workflow-pr", "write")
client.add_collaborator("pool", "pkgB", "workflow-pr", "write")
restart_needed = False
# Setup all branches and configs
for branch_name, custom_configs in BRANCH_CONFIG_CUSTOM.items():
# Ensure branch exists in all 3 repos
for owner, repo in [("myproducts", "mySLFO"), ("mypool", "pkgA"), ("mypool", "pkgB")]:
for owner, repo in [("products", "SLFO"), ("pool", "pkgA"), ("pool", "pkgB")]:
if branch_name != "main":
try:
main_sha = client._request("GET", f"repos/{owner}/{repo}/branches/main").json()["commit"]["id"]
@@ -232,9 +201,9 @@ def gitea_env():
else:
merged_configs[file_name] = custom_content
# Ensure config files in myproducts/mySLFO
# Ensure config files in products/SLFO
for file_name, content_dict in merged_configs.items():
if ensure_config_file(client, "myproducts", "mySLFO", branch_name, file_name, content_dict):
if ensure_config_file(client, "products", "SLFO", branch_name, file_name, content_dict):
restart_needed = True
# Setup users (using configs from this branch)
@@ -249,35 +218,23 @@ def gitea_env():
@pytest.fixture(scope="session")
def automerge_env(gitea_env):
return gitea_env, "myproducts/mySLFO", "merge"
@pytest.fixture(scope="session")
def staging_main_env(gitea_env):
return gitea_env, "myproducts/mySLFO", "staging-main"
@pytest.fixture(scope="session")
def manual_merge_env(gitea_env):
return gitea_env, "myproducts/mySLFO", "manual-merge"
return gitea_env, "products/SLFO", "merge"
@pytest.fixture(scope="session")
def maintainer_env(gitea_env):
return gitea_env, "myproducts/mySLFO", "maintainer-merge"
return gitea_env, "products/SLFO", "maintainer-merge"
@pytest.fixture(scope="session")
def review_required_env(gitea_env):
return gitea_env, "myproducts/mySLFO", "review-required"
return gitea_env, "products/SLFO", "review-required"
@pytest.fixture(scope="session")
def no_project_git_pr_env(gitea_env):
return gitea_env, "myproducts/mySLFO", "dev"
return gitea_env, "products/SLFO", "dev"
@pytest.fixture(scope="session")
def label_env(gitea_env):
return gitea_env, "myproducts/mySLFO", "label-test"
@pytest.fixture(scope="session")
def usera_client(gitea_env):
return GiteaAPIClient(base_url=gitea_env.base_url, token=gitea_env.headers["Authorization"].split(" ")[1], sudo="usera")
return gitea_env, "products/SLFO", "label-test"
@pytest.fixture(scope="session")
def ownerA_client(gitea_env):
@@ -299,6 +256,6 @@ def staging_bot_client(gitea_env):
def test_user_client(gitea_env):
username = f"test-user-{int(time.time())}"
gitea_env.create_user(username, "password123", f"{username}@example.com")
gitea_env.add_collaborator("mypool", "pkgA", username, "write")
gitea_env.add_collaborator("myproducts", "mySLFO", username, "write")
gitea_env.add_collaborator("pool", "pkgA", username, "write")
gitea_env.add_collaborator("products", "SLFO", username, "write")
return GiteaAPIClient(base_url=gitea_env.base_url, token=gitea_env.headers["Authorization"].split(" ")[1], sudo=username)

View File

@@ -1,21 +1,21 @@
<resultlist state="0fef640bfb56c3e76fcfb698b19b59c0">
<result project="SUSE:SLFO:Main:PullRequest:1881" repository="standard" arch="aarch64" code="unpublished" state="unpublished">
<scmsync>https://src.suse.de/myproducts/mySLFO.git?onlybuild=openjpeg2#d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1</scmsync>
<scmsync>https://src.suse.de/products/SLFO.git?onlybuild=openjpeg2#d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1</scmsync>
<scminfo>d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1</scminfo>
<status package="openjpeg2" code="succeeded"/>
</result>
<result project="SUSE:SLFO:Main:PullRequest:1881" repository="standard" arch="ppc64le" code="unpublished" state="unpublished">
<scmsync>https://src.suse.de/myproducts/mySLFO.git?onlybuild=openjpeg2#d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1</scmsync>
<scmsync>https://src.suse.de/products/SLFO.git?onlybuild=openjpeg2#d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1</scmsync>
<scminfo>d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1</scminfo>
<status package="openjpeg2" code="succeeded"/>
</result>
<result project="SUSE:SLFO:Main:PullRequest:1881" repository="standard" arch="x86_64" code="unpublished" state="unpublished">
<scmsync>https://src.suse.de/myproducts/mySLFO.git?onlybuild=openjpeg2#d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1</scmsync>
<scmsync>https://src.suse.de/products/SLFO.git?onlybuild=openjpeg2#d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1</scmsync>
<scminfo>d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1</scminfo>
<status package="openjpeg2" code="succeeded"/>
</result>
<result project="SUSE:SLFO:Main:PullRequest:1881" repository="standard" arch="s390x" code="unpublished" state="unpublished">
<scmsync>https://src.suse.de/myproducts/mySLFO.git?onlybuild=openjpeg2#d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1</scmsync>
<scmsync>https://src.suse.de/products/SLFO.git?onlybuild=openjpeg2#d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1</scmsync>
<scminfo>d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1</scminfo>
<status package="openjpeg2" code="succeeded"/>
</result>

View File

@@ -2,7 +2,7 @@
<title>openSUSE Leap 16.0 based on SLFO</title>
<description>Leap 16.0 based on SLES 16.0 (specifically SLFO:1.2)</description>
<link project="openSUSE:Backports:SLE-16.0"/>
<scmsync>http://gitea-test:3000/myproducts/mySLFO#staging-main</scmsync>
<scmsync>http://gitea-test:3000/products/SLFO#main</scmsync>
<person userid="dimstar_suse" role="maintainer"/>
<person userid="lkocman-factory" role="maintainer"/>
<person userid="maxlin_factory" role="maintainer"/>
@@ -56,4 +56,4 @@
<arch>ppc64le</arch>
<arch>s390x</arch>
</repository>
</project>
</project>

View File

@@ -172,8 +172,8 @@ class GiteaAPIClient:
raise
# Get latest commit SHAs for the submodules
pkg_a_sha = self._request("GET", "repos/mypool/pkgA/branches/main").json()["commit"]["id"]
pkg_b_sha = self._request("GET", "repos/mypool/pkgB/branches/main").json()["commit"]["id"]
pkg_a_sha = self._request("GET", "repos/pool/pkgA/branches/main").json()["commit"]["id"]
pkg_b_sha = self._request("GET", "repos/pool/pkgB/branches/main").json()["commit"]["id"]
if not pkg_a_sha or not pkg_b_sha:
raise Exception("Error: Could not get submodule commit SHAs. Cannot apply patch.")
@@ -186,10 +186,10 @@ index 0000000..f1838bd
@@ -0,0 +1,6 @@
+[submodule "pkgA"]
+ path = pkgA
+ url = ../../mypool/pkgA.git
+ url = ../../pool/pkgA.git
+[submodule "pkgB"]
+ path = pkgB
+ url = ../../mypool/pkgB.git
+ url = ../../pool/pkgB.git
diff --git a/pkgA b/pkgA
new file mode 160000
index 0000000..{pkg_a_sha}
@@ -389,14 +389,6 @@ index 0000000..{pkg_b_sha}
response = self._request("PATCH", url, json=kwargs)
return response.json()
def create_issue_comment(self, repo_full_name: str, issue_number: int, body: str):
owner, repo = repo_full_name.split("/")
url = f"repos/{owner}/{repo}/issues/{issue_number}/comments"
data = {"body": body}
print(f"--- Creating comment on {repo_full_name} issue #{issue_number} ---")
response = self._request("POST", url, json=data)
return response.json()
def get_timeline_events(self, repo_full_name: str, pr_number: int):
owner, repo = repo_full_name.split("/")
url = f"repos/{owner}/{repo}/issues/{pr_number}/timeline"

View File

@@ -14,28 +14,27 @@ from tests.lib.common_test_utils import (
# =============================================================================
def test_pr_workflow_succeeded(staging_main_env, mock_build_result):
def test_pr_workflow_succeeded(gitea_env, mock_build_result):
"""End-to-end test for a successful PR workflow."""
gitea_env, test_full_repo_name, merge_branch_name = staging_main_env
diff = "diff --git a/test.txt b/test.txt\nnew file mode 100644\nindex 0000000..e69de29\n"
pr = gitea_env.create_gitea_pr("mypool/pkgA", diff, "Test PR - should succeed", False, base_branch=merge_branch_name)
pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR - should succeed", False)
initial_pr_number = pr["number"]
compose_dir = Path(__file__).parent.parent
forwarded_pr_number = None
print(
f"Polling mypool/pkgA PR #{initial_pr_number} timeline for forwarded PR event..."
f"Polling pool/pkgA PR #{initial_pr_number} timeline for forwarded PR event..."
)
for _ in range(20):
time.sleep(1)
timeline_events = gitea_env.get_timeline_events("mypool/pkgA", initial_pr_number)
timeline_events = gitea_env.get_timeline_events("pool/pkgA", initial_pr_number)
for event in timeline_events:
if event.get("type") == "pull_ref":
if not (ref_issue := event.get("ref_issue")):
continue
url_to_check = ref_issue.get("html_url", "")
match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check)
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
if match:
forwarded_pr_number = match.group(1)
break
@@ -44,13 +43,13 @@ def test_pr_workflow_succeeded(staging_main_env, mock_build_result):
assert (
forwarded_pr_number is not None
), "Workflow bot did not create a pull_ref event on the timeline."
print(f"Found forwarded PR: myproducts/mySLFO #{forwarded_pr_number}")
print(f"Found forwarded PR: products/SLFO #{forwarded_pr_number}")
print(f"Polling myproducts/mySLFO PR #{forwarded_pr_number} for reviewer assignment...")
print(f"Polling products/SLFO PR #{forwarded_pr_number} for reviewer assignment...")
reviewer_added = False
for _ in range(15):
time.sleep(1)
pr_details = gitea_env.get_pr_details("myproducts/mySLFO", forwarded_pr_number)
pr_details = gitea_env.get_pr_details("products/SLFO", forwarded_pr_number)
if any(
r.get("login") == "autogits_obs_staging_bot"
for r in pr_details.get("requested_reviewers", [])
@@ -70,11 +69,11 @@ def test_pr_workflow_succeeded(staging_main_env, mock_build_result):
capture_output=True,
)
print(f"Polling myproducts/mySLFO PR #{forwarded_pr_number} for final status...")
print(f"Polling products/SLFO PR #{forwarded_pr_number} for final status...")
status_comment_found = False
for _ in range(20):
time.sleep(1)
timeline_events = gitea_env.get_timeline_events("myproducts/mySLFO", forwarded_pr_number)
timeline_events = gitea_env.get_timeline_events("products/SLFO", forwarded_pr_number)
for event in timeline_events:
print(event.get("body", "not a body"))
if event.get("body") and "successful" in event["body"]:
@@ -85,28 +84,27 @@ def test_pr_workflow_succeeded(staging_main_env, mock_build_result):
assert status_comment_found, "Staging bot did not post a 'successful' comment."
def test_pr_workflow_failed(staging_main_env, mock_build_result):
def test_pr_workflow_failed(gitea_env, mock_build_result):
"""End-to-end test for a failed PR workflow."""
gitea_env, test_full_repo_name, merge_branch_name = staging_main_env
diff = "diff --git a/another_test.txt b/another_test.txt\nnew file mode 100644\nindex 0000000..e69de29\n"
pr = gitea_env.create_gitea_pr("mypool/pkgA", diff, "Test PR - should fail", False, base_branch=merge_branch_name)
pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR - should fail", False)
initial_pr_number = pr["number"]
compose_dir = Path(__file__).parent.parent
forwarded_pr_number = None
print(
f"Polling mypool/pkgA PR #{initial_pr_number} timeline for forwarded PR event..."
f"Polling pool/pkgA PR #{initial_pr_number} timeline for forwarded PR event..."
)
for _ in range(20):
time.sleep(1)
timeline_events = gitea_env.get_timeline_events("mypool/pkgA", initial_pr_number)
timeline_events = gitea_env.get_timeline_events("pool/pkgA", initial_pr_number)
for event in timeline_events:
if event.get("type") == "pull_ref":
if not (ref_issue := event.get("ref_issue")):
continue
url_to_check = ref_issue.get("html_url", "")
match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check)
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
if match:
forwarded_pr_number = match.group(1)
break
@@ -115,13 +113,13 @@ def test_pr_workflow_failed(staging_main_env, mock_build_result):
assert (
forwarded_pr_number is not None
), "Workflow bot did not create a pull_ref event on the timeline."
print(f"Found forwarded PR: myproducts/mySLFO #{forwarded_pr_number}")
print(f"Found forwarded PR: products/SLFO #{forwarded_pr_number}")
print(f"Polling myproducts/mySLFO PR #{forwarded_pr_number} for reviewer assignment...")
print(f"Polling products/SLFO PR #{forwarded_pr_number} for reviewer assignment...")
reviewer_added = False
for _ in range(15):
time.sleep(1)
pr_details = gitea_env.get_pr_details("myproducts/mySLFO", forwarded_pr_number)
pr_details = gitea_env.get_pr_details("products/SLFO", forwarded_pr_number)
if any(
r.get("login") == "autogits_obs_staging_bot"
for r in pr_details.get("requested_reviewers", [])
@@ -141,11 +139,11 @@ def test_pr_workflow_failed(staging_main_env, mock_build_result):
capture_output=True,
)
print(f"Polling myproducts/mySLFO PR #{forwarded_pr_number} for final status...")
print(f"Polling products/SLFO PR #{forwarded_pr_number} for final status...")
status_comment_found = False
for _ in range(20):
time.sleep(1)
timeline_events = gitea_env.get_timeline_events("myproducts/mySLFO", forwarded_pr_number)
timeline_events = gitea_env.get_timeline_events("products/SLFO", forwarded_pr_number)
for event in timeline_events:
if event.get("body") and "failed" in event["body"]:
status_comment_found = True

View File

@@ -29,23 +29,23 @@ def test_001_project_pr_labels(label_env, staging_bot_client):
new file mode 100644
index 0000000..e69de29
"""
print(f"--- Creating package PR in mypool/pkgA on branch {branch_name} ---")
package_pr = gitea_env.create_gitea_pr("mypool/pkgA", diff, "Test Labels Fixture", False, base_branch=branch_name)
print(f"--- Creating package PR in pool/pkgA on branch {branch_name} ---")
package_pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test Labels Fixture", False, base_branch=branch_name)
package_pr_number = package_pr["number"]
print(f"Created package PR mypool/pkgA#{package_pr_number}")
print(f"Created package PR pool/pkgA#{package_pr_number}")
# 2. Make sure the workflow-pr service created related project PR
project_pr_number = None
print(f"Polling mypool/pkgA PR #{package_pr_number} timeline for forwarded PR event...")
print(f"Polling pool/pkgA PR #{package_pr_number} timeline for forwarded PR event...")
for _ in range(40):
time.sleep(1)
timeline_events = gitea_env.get_timeline_events("mypool/pkgA", package_pr_number)
timeline_events = gitea_env.get_timeline_events("pool/pkgA", package_pr_number)
for event in timeline_events:
if event.get("type") == "pull_ref":
if not (ref_issue := event.get("ref_issue")):
continue
url_to_check = ref_issue.get("html_url", "")
match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check)
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
if match:
project_pr_number = int(match.group(1))
break
@@ -53,16 +53,16 @@ index 0000000..e69de29
break
assert project_pr_number is not None, "Workflow bot did not create a project PR."
print(f"Found project PR: myproducts/mySLFO#{project_pr_number}")
print(f"Found project PR: products/SLFO#{project_pr_number}")
# 3. Wait for the project PR to have the label "staging/Backlog"
print(f"Checking for 'staging/Backlog' label on project PR myproducts/mySLFO#{project_pr_number}...")
print(f"Checking for 'staging/Backlog' label on project PR products/SLFO#{project_pr_number}...")
backlog_label_found = False
expected_backlog_label = "staging/Backlog"
for _ in range(20):
project_pr_details = gitea_env.get_pr_details("myproducts/mySLFO", project_pr_number)
project_pr_details = gitea_env.get_pr_details("products/SLFO", project_pr_number)
labels = project_pr_details.get("labels", [])
label_names = [l["name"] for l in labels]
if expected_backlog_label in label_names:
@@ -70,21 +70,21 @@ index 0000000..e69de29
break
time.sleep(1)
assert backlog_label_found, f"Project PR myproducts/mySLFO#{project_pr_number} does not have the expected label '{expected_backlog_label}'."
print(f"Project PR myproducts/mySLFO#{project_pr_number} has the expected label '{expected_backlog_label}'.")
assert backlog_label_found, f"Project PR products/SLFO#{project_pr_number} does not have the expected label '{expected_backlog_label}'."
print(f"Project PR products/SLFO#{project_pr_number} has the expected label '{expected_backlog_label}'.")
# 4. Post approval from autogits_obs_staging_bot
print(f"--- Posting approval from autogits_obs_staging_bot on project PR myproducts/mySLFO#{project_pr_number} ---")
staging_bot_client.create_review("myproducts/mySLFO", project_pr_number, event="APPROVED", body="Staging OK")
print(f"--- Posting approval from autogits_obs_staging_bot on project PR products/SLFO#{project_pr_number} ---")
staging_bot_client.create_review("products/SLFO", project_pr_number, event="APPROVED", body="Staging OK")
# 5. Check that the project PR has the label "review/Pending"
print(f"Checking for 'review/Pending' label on project PR myproducts/mySLFO#{project_pr_number}...")
print(f"Checking for 'review/Pending' label on project PR products/SLFO#{project_pr_number}...")
pending_label_found = False
expected_pending_label = "review/Pending"
for _ in range(20):
project_pr_details = gitea_env.get_pr_details("myproducts/mySLFO", project_pr_number)
project_pr_details = gitea_env.get_pr_details("products/SLFO", project_pr_number)
labels = project_pr_details.get("labels", [])
label_names = [l["name"] for l in labels]
print(f"Current labels: {label_names}")
@@ -93,5 +93,5 @@ index 0000000..e69de29
break
time.sleep(1)
assert pending_label_found, f"Project PR myproducts/mySLFO#{project_pr_number} does not have the expected label '{expected_pending_label}'."
print(f"Project PR myproducts/mySLFO#{project_pr_number} has the expected label '{expected_pending_label}'.")
assert pending_label_found, f"Project PR products/SLFO#{project_pr_number} does not have the expected label '{expected_pending_label}'."
print(f"Project PR products/SLFO#{project_pr_number} has the expected label '{expected_pending_label}'.")

View File

@@ -5,37 +5,40 @@ from pathlib import Path
from tests.lib.common_test_utils import GiteaAPIClient
@pytest.mark.t001
@pytest.mark.xfail(reason="The bot sometimes re-request reviews despite having all the approvals")
def test_001_automerge(automerge_env, test_user_client):
"""
Test scenario TC-MERGE-001:
1. Create a PackageGit PR.
2. Ensure all mandatory reviews are completed on both project and package PRs.
3. Verify the PR is merged automatically.
Test scenario:
1. Setup custom workflow.config with mandatory reviewers (+usera, +userb).
2. Create a package PR in 'merge' branch.
3. Make sure the workflow-pr service created related project PR in 'merge' branch.
4. React on 'requested' reviews by approving them.
5. Make sure both PRs are merged automatically by the workflow-pr service.
"""
gitea_env, test_full_repo_name, merge_branch_name = automerge_env
# 1. Create a package PR
diff = """diff --git a/automerge_test.txt b/automerge_test.txt
diff = """diff --git a/merge_test_fixture.txt b/merge_test_fixture.txt
new file mode 100644
index 0000000..e69de29
"""
print(f"--- Creating package PR in mypool/pkgA on branch {merge_branch_name} ---")
package_pr = test_user_client.create_gitea_pr("mypool/pkgA", diff, "Test Automerge Fixture", False, base_branch=merge_branch_name)
print(f"--- Creating package PR in pool/pkgA on branch {merge_branch_name} ---")
package_pr = test_user_client.create_gitea_pr("pool/pkgA", diff, "Test Automerge Fixture", False, base_branch=merge_branch_name)
package_pr_number = package_pr["number"]
print(f"Created package PR mypool/pkgA#{package_pr_number}")
print(f"Created package PR pool/pkgA#{package_pr_number}")
# 2. Make sure the workflow-pr service created related project PR
project_pr_number = None
print(f"Polling mypool/pkgA PR #{package_pr_number} timeline for forwarded PR event...")
print(f"Polling pool/pkgA PR #{package_pr_number} timeline for forwarded PR event...")
for _ in range(40):
time.sleep(1)
timeline_events = gitea_env.get_timeline_events("mypool/pkgA", package_pr_number)
timeline_events = gitea_env.get_timeline_events("pool/pkgA", package_pr_number)
for event in timeline_events:
if event.get("type") == "pull_ref":
if not (ref_issue := event.get("ref_issue")):
continue
url_to_check = ref_issue.get("html_url", "")
match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check)
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
if match:
project_pr_number = int(match.group(1))
break
@@ -43,153 +46,37 @@ index 0000000..e69de29
break
assert project_pr_number is not None, "Workflow bot did not create a project PR."
print(f"Found project PR: myproducts/mySLFO#{project_pr_number}")
print(f"Found project PR: products/SLFO#{project_pr_number}")
# 3. Approve reviews and verify merged
print("Approving reviews and verifying both PRs are merged...")
# 4. Make sure both PRs are merged automatically by the workflow-pr service
print("Polling for PR merge status and reacting on REQUEST_REVIEW...")
package_merged = False
project_merged = False
for i in range(20): # Poll for up to 20 seconds
gitea_env.approve_requested_reviews("mypool/pkgA", package_pr_number)
gitea_env.approve_requested_reviews("myproducts/mySLFO", project_pr_number)
for i in range(15): # Poll for up to 15 seconds
# Package PR
if not package_merged:
pkg_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number)
pkg_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number)
if pkg_details.get("merged"):
package_merged = True
print(f"Package PR mypool/pkgA#{package_pr_number} merged.")
print(f"Package PR pool/pkgA#{package_pr_number} merged.")
else:
gitea_env.approve_requested_reviews("pool/pkgA", package_pr_number)
# Project PR
if not project_merged:
prj_details = gitea_env.get_pr_details("myproducts/mySLFO", project_pr_number)
prj_details = gitea_env.get_pr_details("products/SLFO", project_pr_number)
if prj_details.get("merged"):
project_merged = True
print(f"Project PR myproducts/mySLFO#{project_pr_number} merged.")
print(f"Project PR products/SLFO#{project_pr_number} merged.")
else:
gitea_env.approve_requested_reviews("products/SLFO", project_pr_number)
if package_merged and project_merged:
break
time.sleep(1)
assert package_merged, f"Package PR mypool/pkgA#{package_pr_number} was not merged automatically."
assert project_merged, f"Project PR myproducts/mySLFO#{project_pr_number} was not merged automatically."
assert package_merged, f"Package PR pool/pkgA#{package_pr_number} was not merged automatically."
assert project_merged, f"Project PR products/SLFO#{project_pr_number} was not merged automatically."
print("Both PRs merged successfully.")
@pytest.mark.t002
def test_002_manual_merge(manual_merge_env, test_user_client, usera_client, staging_bot_client):
"""
Test scenario TC-MERGE-002:
1. Create a PackageGit PR with ManualMergeOnly set to true.
2. Ensure all mandatory reviews are completed on both project and package PRs.
3. Comment "merge ok" on the package PR from the account of a requested reviewer.
4. Verify the PR is merged.
"""
gitea_env, test_full_repo_name, merge_branch_name = manual_merge_env
# 1. Create a package PR
diff = """diff --git a/manual_merge_test.txt b/manual_merge_test.txt
new file mode 100644
index 0000000..e69de29
"""
print(f"--- Creating package PR in mypool/pkgA on branch {merge_branch_name} ---")
package_pr = test_user_client.create_gitea_pr("mypool/pkgA", diff, "Test Manual Merge Fixture", False, base_branch=merge_branch_name)
package_pr_number = package_pr["number"]
print(f"Created package PR mypool/pkgA#{package_pr_number}")
# 2. Make sure the workflow-pr service created related project PR
project_pr_number = None
print(f"Polling mypool/pkgA PR #{package_pr_number} timeline for forwarded PR event...")
for _ in range(40):
time.sleep(1)
timeline_events = gitea_env.get_timeline_events("mypool/pkgA", package_pr_number)
for event in timeline_events:
if event.get("type") == "pull_ref":
if not (ref_issue := event.get("ref_issue")):
continue
url_to_check = ref_issue.get("html_url", "")
match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check)
if match:
project_pr_number = int(match.group(1))
break
if project_pr_number:
break
assert project_pr_number is not None, "Workflow bot did not create a project PR."
print(f"Found project PR: myproducts/mySLFO#{project_pr_number}")
# 3. Approve reviews and verify NOT merged
print("Waiting for all expected review requests and approving them...")
# Expected reviewers based on manual-merge branch config and pkgA maintainership
expected_reviewers = {"usera", "userb", "ownerA", "ownerX", "ownerY"}
# ManualMergeOnly still requires regular reviews to be satisfied.
# We poll until all expected reviewers are requested, then approve them.
all_requested = 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 all expected reviewers have at least one review record (any state)
pkg_reviews = gitea_env.list_reviews("mypool/pkgA", package_pr_number)
current_reviewers = {r["user"]["login"] for r in pkg_reviews}
if expected_reviewers.issubset(current_reviewers):
# Also ensure they are all approved (not just requested)
approved_reviewers = {r["user"]["login"] for r in pkg_reviews if r["state"] == "APPROVED"}
if expected_reviewers.issubset(approved_reviewers):
# 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_requested = True
print(f"All expected reviewers {expected_reviewers} and staging bot have approved.")
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_requested, f"Timed out waiting for all expected reviewers {expected_reviewers} to approve. Current: {current_reviewers}"
print("Both PRs have all required approvals but are not merged (as expected with ManualMergeOnly).")
# 4. Comment "merge ok" from a requested reviewer (usera)
print("Commenting 'merge ok' on package PR...")
usera_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)
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'."
print("Both PRs merged successfully after 'merge ok'.")

View File

@@ -15,24 +15,22 @@ def test_001_review_requests_matching_config(automerge_env, ownerA_client):
"""
gitea_env, test_full_repo_name, branch_name = automerge_env
# 1. Create a package PR for mypool/pkgB as ownerA
ts = int(time.time() * 1000)
filename = f"pkgB_test_{ts}.txt"
diff = f"""diff --git a/{filename} b/{filename}
# 1. Create a package PR for pool/pkgB as ownerA
diff = """diff --git a/pkgB_test_001.txt b/pkgB_test_001.txt
new file mode 100644
index 0000000..e69de29
"""
print(f"--- Creating package PR in mypool/pkgB on branch {branch_name} as ownerA ---")
package_pr = ownerA_client.create_gitea_pr("mypool/pkgB", diff, "Test Review Requests Config", True, base_branch=branch_name)
print(f"--- Creating package PR in pool/pkgB on branch {branch_name} as ownerA ---")
package_pr = ownerA_client.create_gitea_pr("pool/pkgB", diff, "Test Review Requests Config", True, base_branch=branch_name)
package_pr_number = package_pr["number"]
print(f"Created package PR mypool/pkgB#{package_pr_number}")
print(f"Created package PR pool/pkgB#{package_pr_number}")
# 2. Check that review requests came to ownerB, ownerBB, usera, and userb
print("Checking for review requests from maintainers and workflow.config...")
reviewers_requested = set()
expected_reviewers = {"ownerB", "ownerBB", "usera", "userb"}
for _ in range(30):
reviews = gitea_env.list_reviews("mypool/pkgB", package_pr_number)
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
if expected_reviewers.issubset(reviewers_requested):
break
@@ -45,6 +43,7 @@ index 0000000..e69de29
@pytest.mark.t004
@pytest.mark.xfail(reason="the bot sometimes re-requests review from autogits_obs_staging_bot despite having the approval")
def test_004_maintainer(maintainer_env, ownerA_client):
"""
Test scenario:
@@ -63,9 +62,9 @@ def test_004_maintainer(maintainer_env, ownerA_client):
# 0.1 Verify all users from config exist
print("--- Verifying all users from config exist ---")
import json
wf_file = gitea_env.get_file_info("myproducts", "mySLFO", "workflow.config", branch=branch_name)
wf_file = gitea_env.get_file_info("products", "SLFO", "workflow.config", branch=branch_name)
wf = json.loads(base64.b64decode(wf_file["content"]).decode("utf-8"))
mt_file = gitea_env.get_file_info("myproducts", "mySLFO", "_maintainership.json", branch=branch_name)
mt_file = gitea_env.get_file_info("products", "SLFO", "_maintainership.json", branch=branch_name)
mt = json.loads(base64.b64decode(mt_file["content"]).decode("utf-8"))
expected_users = set()
@@ -82,29 +81,27 @@ def test_004_maintainer(maintainer_env, ownerA_client):
print(f"Verified user exists: {username}")
# 1. Create a package PR as ownerA
ts = int(time.time() * 1000)
filename = f"maintainer_test_{ts}.txt"
diff = f"""diff --git a/{filename} b/{filename}
diff = """diff --git a/maintainer_test_fixture.txt b/maintainer_test_fixture.txt
new file mode 100644
index 0000000..e69de29
"""
print(f"--- Creating package PR in mypool/pkgA on branch {branch_name} as ownerA ---")
package_pr = ownerA_client.create_gitea_pr("mypool/pkgA", diff, "Test Maintainer Merge", True, base_branch=branch_name)
print(f"--- Creating package PR in pool/pkgA on branch {branch_name} as ownerA ---")
package_pr = ownerA_client.create_gitea_pr("pool/pkgA", diff, "Test Maintainer Merge", True, base_branch=branch_name)
package_pr_number = package_pr["number"]
print(f"Created package PR mypool/pkgA#{package_pr_number}")
print(f"Created package PR pool/pkgA#{package_pr_number}")
# 2. Make sure the workflow-pr service created related project PR
project_pr_number = None
print(f"Polling mypool/pkgA PR #{package_pr_number} timeline for forwarded PR event...")
print(f"Polling pool/pkgA PR #{package_pr_number} timeline for forwarded PR event...")
for _ in range(40):
time.sleep(1)
timeline_events = gitea_env.get_timeline_events("mypool/pkgA", package_pr_number)
timeline_events = gitea_env.get_timeline_events("pool/pkgA", package_pr_number)
for event in timeline_events:
if event.get("type") == "pull_ref":
if not (ref_issue := event.get("ref_issue")):
continue
url_to_check = ref_issue.get("html_url", "")
match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check)
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
if match:
project_pr_number = int(match.group(1))
break
@@ -112,7 +109,7 @@ index 0000000..e69de29
break
assert project_pr_number is not None, "Workflow bot did not create a project PR."
print(f"Found project PR: myproducts/mySLFO#{project_pr_number}")
print(f"Found project PR: products/SLFO#{project_pr_number}")
# 3. Make sure both PRs are merged automatically WITHOUT manual approvals
print("Polling for PR merge status (only bot approval allowed)...")
@@ -122,35 +119,35 @@ index 0000000..e69de29
for i in range(15): # Poll for up to 15 seconds
# Package PR
if not package_merged:
pkg_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number)
pkg_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number)
if pkg_details.get("merged"):
package_merged = True
print(f"Package PR mypool/pkgA#{package_pr_number} merged.")
print(f"Package PR pool/pkgA#{package_pr_number} merged.")
else:
# Approve ONLY bot if requested
reviews = gitea_env.list_reviews("mypool/pkgA", package_pr_number)
reviews = gitea_env.list_reviews("pool/pkgA", package_pr_number)
if any(r["state"] == "REQUEST_REVIEW" and r["user"]["login"] == "autogits_obs_staging_bot" for r in reviews):
gitea_env.approve_requested_reviews("mypool/pkgA", package_pr_number)
gitea_env.approve_requested_reviews("pool/pkgA", package_pr_number)
# Project PR
if not project_merged:
prj_details = gitea_env.get_pr_details("myproducts/mySLFO", project_pr_number)
prj_details = gitea_env.get_pr_details("products/SLFO", project_pr_number)
if prj_details.get("merged"):
project_merged = True
print(f"Project PR myproducts/mySLFO#{project_pr_number} merged.")
print(f"Project PR products/SLFO#{project_pr_number} merged.")
else:
# Approve ONLY bot if requested
reviews = gitea_env.list_reviews("myproducts/mySLFO", project_pr_number)
reviews = gitea_env.list_reviews("products/SLFO", project_pr_number)
if any(r["state"] == "REQUEST_REVIEW" and r["user"]["login"] == "autogits_obs_staging_bot" for r in reviews):
gitea_env.approve_requested_reviews("myproducts/mySLFO", project_pr_number)
gitea_env.approve_requested_reviews("products/SLFO", project_pr_number)
if package_merged and project_merged:
break
time.sleep(1)
assert package_merged, f"Package PR mypool/pkgA#{package_pr_number} was not merged automatically."
assert project_merged, f"Project PR myproducts/mySLFO#{project_pr_number} was not merged automatically."
assert package_merged, f"Package PR pool/pkgA#{package_pr_number} was not merged automatically."
assert project_merged, f"Project PR products/SLFO#{project_pr_number} was not merged automatically."
print("Both PRs merged successfully by maintainer rule.")
@@ -166,30 +163,28 @@ def test_005_any_maintainer_approval_sufficient(maintainer_env, ownerA_client, o
"""
gitea_env, test_full_repo_name, branch_name = maintainer_env
# 1. Create a package PR for mypool/pkgB as ownerA
ts = int(time.time() * 1000)
filename = f"pkgB_test_{ts}.txt"
diff = f"""diff --git a/{filename} b/{filename}
# 1. Create a package PR for pool/pkgB as ownerA
diff = """diff --git a/pkgB_test_fixture.txt b/pkgB_test_fixture.txt
new file mode 100644
index 0000000..e69de29
"""
print(f"--- Creating package PR in mypool/pkgB on branch {branch_name} as ownerA ---")
package_pr = ownerA_client.create_gitea_pr("mypool/pkgB", diff, "Test Single Maintainer Merge", True, base_branch=branch_name)
print(f"--- Creating package PR in pool/pkgB on branch {branch_name} as ownerA ---")
package_pr = ownerA_client.create_gitea_pr("pool/pkgB", diff, "Test Single Maintainer Merge", True, base_branch=branch_name)
package_pr_number = package_pr["number"]
print(f"Created package PR mypool/pkgB#{package_pr_number}")
print(f"Created package PR pool/pkgB#{package_pr_number}")
# 2. Make sure the workflow-pr service created related project PR
project_pr_number = None
print(f"Polling mypool/pkgB PR #{package_pr_number} timeline for forwarded PR event...")
print(f"Polling pool/pkgB PR #{package_pr_number} timeline for forwarded PR event...")
for _ in range(40):
time.sleep(1)
timeline_events = gitea_env.get_timeline_events("mypool/pkgB", package_pr_number)
timeline_events = gitea_env.get_timeline_events("pool/pkgB", package_pr_number)
for event in timeline_events:
if event.get("type") == "pull_ref":
if not (ref_issue := event.get("ref_issue")):
continue
url_to_check = ref_issue.get("html_url", "")
match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check)
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
if match:
project_pr_number = int(match.group(1))
break
@@ -197,13 +192,13 @@ index 0000000..e69de29
break
assert project_pr_number is not None, "Workflow bot did not create a project PR."
print(f"Found project PR: myproducts/mySLFO#{project_pr_number}")
print(f"Found project PR: products/SLFO#{project_pr_number}")
# 3. Check that review requests came to ownerB and ownerBB
print("Checking for review requests from ownerB and ownerBB...")
reviewers_requested = set()
for _ in range(20):
reviews = gitea_env.list_reviews("mypool/pkgB", package_pr_number)
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
if "ownerB" in reviewers_requested and "ownerBB" in reviewers_requested:
break
@@ -215,21 +210,41 @@ index 0000000..e69de29
# 4. ownerBB leaves review, ownerB does not.
print("ownerBB approving the PR...")
ownerBB_client.create_review("mypool/pkgB", package_pr_number, event="APPROVED", body="Approval from ownerBB")
ownerBB_client.create_review("pool/pkgB", package_pr_number, event="APPROVED", body="Approval from ownerBB")
# 5. Check that review request for ownerB is removed
print("Polling for ownerB review request removal...")
ownerB_removed = False
for _ in range(30):
time.sleep(1)
reviews = gitea_env.list_reviews("mypool/pkgB", package_pr_number)
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
if "ownerB" not in reviewers_requested:
ownerB_removed = True
break
# 5. Check that both PRs are merged automatically
print("Polling for PR merge status (only bot approval allowed for project PR)...")
package_merged = False
project_merged = False
assert ownerB_removed, f"ownerB review request was not removed after ownerBB approval. Current requested: {reviewers_requested}"
print("Confirmed: ownerB review request removed after single maintainer approval.")
for i in range(15): # Poll for up to 15 seconds
# Package PR
if not package_merged:
pkg_details = gitea_env.get_pr_details("pool/pkgB", package_pr_number)
if pkg_details.get("merged"):
package_merged = True
print(f"Package PR pool/pkgB#{package_pr_number} merged.")
# Project PR
if not project_merged:
prj_details = gitea_env.get_pr_details("products/SLFO", project_pr_number)
if prj_details.get("merged"):
project_merged = True
print(f"Project PR products/SLFO#{project_pr_number} merged.")
else:
# Approve ONLY bot if requested
reviews = gitea_env.list_reviews("products/SLFO", project_pr_number)
if any(r["state"] == "REQUEST_REVIEW" and r["user"]["login"] == "autogits_obs_staging_bot" for r in reviews):
gitea_env.approve_requested_reviews("products/SLFO", project_pr_number)
if package_merged and project_merged:
break
time.sleep(1)
assert package_merged, f"Package PR pool/pkgB#{package_pr_number} was not merged automatically."
assert project_merged, f"Project PR products/SLFO#{project_pr_number} was not merged automatically."
print("Both PRs merged successfully with only one maintainer approval.")
@pytest.mark.t006
@@ -244,39 +259,37 @@ def test_006_maintainer_rejection_removes_other_requests(maintainer_env, ownerA_
"""
gitea_env, test_full_repo_name, branch_name = maintainer_env
# 1. Create a package PR for mypool/pkgB as ownerA
ts = int(time.time() * 1000)
filename = f"pkgB_rejection_test_{ts}.txt"
diff = f"""diff --git a/{filename} b/{filename}
# 1. Create a package PR for pool/pkgB as ownerA
diff = """diff --git a/pkgB_rejection_test.txt b/pkgB_rejection_test.txt
new file mode 100644
index 0000000..e69de29
"""
print(f"--- Creating package PR in mypool/pkgB on branch {branch_name} as ownerA ---")
package_pr = ownerA_client.create_gitea_pr("mypool/pkgB", diff, "Test Maintainer Rejection", True, base_branch=branch_name)
print(f"--- Creating package PR in pool/pkgB on branch {branch_name} as ownerA ---")
package_pr = ownerA_client.create_gitea_pr("pool/pkgB", diff, "Test Maintainer Rejection", True, base_branch=branch_name)
package_pr_number = package_pr["number"]
print(f"Created package PR mypool/pkgB#{package_pr_number}")
print(f"Created package PR pool/pkgB#{package_pr_number}")
# 2. Check that review requests came to ownerB and ownerBB
print("Checking for review requests from ownerB and ownerBB...")
for _ in range(20):
reviews = gitea_env.list_reviews("mypool/pkgB", package_pr_number)
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
if "ownerB" in reviewers_requested and "ownerBB" in reviewers_requested:
break
time.sleep(1)
else:
reviews = gitea_env.list_reviews("mypool/pkgB", package_pr_number)
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
pytest.fail(f"ownerB and ownerBB were not both requested. Got: {reviewers_requested}")
# 3. ownerBB rejects the PR
print("ownerBB rejecting the PR...")
ownerBB_client.create_review("mypool/pkgB", package_pr_number, event="REQUEST_CHANGES", body="Rejecting from ownerBB")
ownerBB_client.create_review("pool/pkgB", package_pr_number, event="REQUEST_CHANGES", body="Rejecting from ownerBB")
# 4. Check that review request for ownerB is removed
print("Checking if ownerB's review request is removed...")
for _ in range(20):
reviews = gitea_env.list_reviews("mypool/pkgB", package_pr_number)
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
if "ownerB" not in reviewers_requested:
print("Confirmed: ownerB's review request was removed.")
@@ -304,30 +317,28 @@ def test_007_review_required_needs_all_approvals(review_required_env, ownerA_cli
ownerA_client._request("GET", "users/admin")
print(f"ownerA_client smoke test passed")
# 1. Create a package PR for mypool/pkgB as ownerA
ts = int(time.time() * 1000)
filename = f"pkgB_review_required_test_{ts}.txt"
diff = f"""diff --git a/{filename} b/{filename}
# 1. Create a package PR for pool/pkgB as ownerA
diff = """diff --git a/pkgB_review_required_test.txt b/pkgB_review_required_test.txt
new file mode 100644
index 0000000..e69de29
"""
print(f"--- Creating package PR in mypool/pkgB on branch {branch_name} as ownerA ---")
package_pr = ownerA_client.create_gitea_pr("mypool/pkgB", diff, "Test Review Required", True, base_branch=branch_name)
print(f"--- Creating package PR in pool/pkgB on branch {branch_name} as ownerA ---")
package_pr = ownerA_client.create_gitea_pr("pool/pkgB", diff, "Test Review Required", True, base_branch=branch_name)
package_pr_number = package_pr["number"]
print(f"Created package PR mypool/pkgB#{package_pr_number}")
print(f"Created package PR pool/pkgB#{package_pr_number}")
# 2. Make sure the workflow-pr service created related project PR
project_pr_number = None
print(f"Polling mypool/pkgB PR #{package_pr_number} timeline for forwarded PR event...")
print(f"Polling pool/pkgB PR #{package_pr_number} timeline for forwarded PR event...")
for _ in range(40):
time.sleep(1)
timeline_events = gitea_env.get_timeline_events("mypool/pkgB", package_pr_number)
timeline_events = gitea_env.get_timeline_events("pool/pkgB", package_pr_number)
for event in timeline_events:
if event.get("type") == "pull_ref":
if not (ref_issue := event.get("ref_issue")):
continue
url_to_check = ref_issue.get("html_url", "")
match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check)
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
if match:
project_pr_number = int(match.group(1))
break
@@ -335,38 +346,38 @@ index 0000000..e69de29
break
assert project_pr_number is not None, "Workflow bot did not create a project PR."
print(f"Found project PR: myproducts/mySLFO#{project_pr_number}")
print(f"Found project PR: products/SLFO#{project_pr_number}")
# 3. Check that review requests came to ownerB and ownerBB
print("Checking for review requests from ownerB and ownerBB...")
for _ in range(20):
reviews = gitea_env.list_reviews("mypool/pkgB", package_pr_number)
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
if "ownerB" in reviewers_requested and "ownerBB" in reviewers_requested:
break
time.sleep(1)
else:
reviews = gitea_env.list_reviews("mypool/pkgB", package_pr_number)
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
pytest.fail(f"ownerB and ownerBB were not both requested. Got: {reviewers_requested}")
# 4. ownerBB leaves review, ownerB does not.
print("ownerBB approving the PR...")
ownerBB_client.create_review("mypool/pkgB", package_pr_number, event="APPROVED", body="Approval from ownerBB")
ownerBB_client.create_review("pool/pkgB", package_pr_number, event="APPROVED", body="Approval from ownerBB")
# 5. Check that the PR is NOT merged automatically and ownerB request remains
print("Waiting to ensure PR is NOT merged and ownerB request remains...")
for i in range(10):
pkg_details = gitea_env.get_pr_details("mypool/pkgB", package_pr_number)
reviews = gitea_env.list_reviews("mypool/pkgB", package_pr_number)
pkg_details = gitea_env.get_pr_details("pool/pkgB", package_pr_number)
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
review_states = [(r["user"]["login"], r["state"]) for r in reviews]
print(f"Attempt {i+1}: Merged={pkg_details.get('merged')}, Reviews={review_states}")
time.sleep(2)
pkg_details = gitea_env.get_pr_details("mypool/pkgB", package_pr_number)
pkg_details = gitea_env.get_pr_details("pool/pkgB", package_pr_number)
assert not pkg_details.get("merged"), "Package PR was merged automatically but it should NOT have been (ReviewRequired=true)."
reviews = gitea_env.list_reviews("mypool/pkgB", package_pr_number)
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
assert "ownerB" in reviewers_requested, f"ownerB's review request was removed, but it should have remained. All reviews: {[(r['user']['login'], r['state']) for r in reviews]}"

View File

@@ -23,7 +23,7 @@ pytest.forwarded_pr_number = None
def test_001_project_pr(gitea_env):
"""Forwarded PR correct title"""
diff = "diff --git a/another_test.txt b/another_test.txt\nnew file mode 100644\nindex 0000000..e69de29\n"
pytest.pr = gitea_env.create_gitea_pr("mypool/pkgA", diff, "Test PR", False)
pytest.pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR", False)
pytest.initial_pr_number = pytest.pr["number"]
time.sleep(5) # Give Gitea some time to process the PR and make the timeline available
@@ -31,18 +31,18 @@ def test_001_project_pr(gitea_env):
pytest.forwarded_pr_number = None
print(
f"Polling mypool/pkgA PR #{pytest.initial_pr_number} timeline for forwarded PR event..."
f"Polling pool/pkgA PR #{pytest.initial_pr_number} timeline for forwarded PR event..."
)
# Instead of polling timeline, check if forwarded PR exists directly
for _ in range(20):
time.sleep(1)
timeline_events = gitea_env.get_timeline_events("mypool/pkgA", pytest.initial_pr_number)
timeline_events = gitea_env.get_timeline_events("pool/pkgA", pytest.initial_pr_number)
for event in timeline_events:
if event.get("type") == "pull_ref":
if not (ref_issue := event.get("ref_issue")):
continue
url_to_check = ref_issue.get("html_url", "")
match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check)
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
if match:
pytest.forwarded_pr_number = match.group(1)
break
@@ -51,7 +51,7 @@ def test_001_project_pr(gitea_env):
assert (
pytest.forwarded_pr_number is not None
), "Workflow bot did not create a forwarded PR."
pytest.pr_details = gitea_env.get_pr_details("myproducts/mySLFO", pytest.forwarded_pr_number)
pytest.pr_details = gitea_env.get_pr_details("products/SLFO", pytest.forwarded_pr_number)
assert (
pytest.pr_details["title"] == "Forwarded PRs: pkgA"
), "Forwarded PR correct title"
@@ -62,13 +62,13 @@ def test_001_project_pr(gitea_env):
def test_002_updated_project_pr(gitea_env):
"""Forwarded PR head is updated"""
diff = "diff --git a/another_test.txt b/another_test.txt\nnew file mode 100444\nindex 0000000..e69de21\n"
gitea_env.modify_gitea_pr("mypool/pkgA", pytest.initial_pr_number, diff, "Tweaks")
gitea_env.modify_gitea_pr("pool/pkgA", pytest.initial_pr_number, diff, "Tweaks")
sha_old = pytest.pr_details["head"]["sha"]
sha_changed = False
for _ in range(20):
time.sleep(1)
new_pr_details = gitea_env.get_pr_details("myproducts/mySLFO", pytest.forwarded_pr_number)
new_pr_details = gitea_env.get_pr_details("products/SLFO", pytest.forwarded_pr_number)
sha_new = new_pr_details["head"]["sha"]
if sha_new != sha_old:
print(f"Sha changed from {sha_old} to {sha_new}")
@@ -82,17 +82,17 @@ def test_002_updated_project_pr(gitea_env):
@pytest.mark.dependency(depends=["test_001_project_pr"])
def test_003_wip(gitea_env):
"""WIP flag set for PR"""
# 1. set WIP flag in PR f"mypool/pkgA#{pytest.initial_pr_number}"
initial_pr_details = gitea_env.get_pr_details("mypool/pkgA", pytest.initial_pr_number)
# 1. set WIP flag in PR f"pool/pkgA#{pytest.initial_pr_number}"
initial_pr_details = gitea_env.get_pr_details("pool/pkgA", pytest.initial_pr_number)
wip_title = "WIP: " + initial_pr_details["title"]
gitea_env.update_gitea_pr_properties("mypool/pkgA", pytest.initial_pr_number, title=wip_title)
# 2. in loop check whether WIP flag is set for PR f"myproducts/mySLFO #{pytest.forwarded_pr_number}"
gitea_env.update_gitea_pr_properties("pool/pkgA", pytest.initial_pr_number, title=wip_title)
# 2. in loop check whether WIP flag is set for PR f"products/SLFO #{pytest.forwarded_pr_number}"
wip_flag_set = False
for _ in range(20):
time.sleep(1)
forwarded_pr_details = gitea_env.get_pr_details(
"myproducts/mySLFO", pytest.forwarded_pr_number
"products/SLFO", pytest.forwarded_pr_number
)
if "WIP: " in forwarded_pr_details["title"]:
wip_flag_set = True
@@ -100,19 +100,19 @@ def test_003_wip(gitea_env):
assert wip_flag_set, "WIP flag was not set in the forwarded PR."
# Remove WIP flag from PR f"mypool/pkgA#{pytest.initial_pr_number}"
initial_pr_details = gitea_env.get_pr_details("mypool/pkgA", pytest.initial_pr_number)
# Remove WIP flag from PR f"pool/pkgA#{pytest.initial_pr_number}"
initial_pr_details = gitea_env.get_pr_details("pool/pkgA", pytest.initial_pr_number)
non_wip_title = initial_pr_details["title"].replace("WIP: ", "")
gitea_env.update_gitea_pr_properties(
"mypool/pkgA", pytest.initial_pr_number, title=non_wip_title
"pool/pkgA", pytest.initial_pr_number, title=non_wip_title
)
# In loop check whether WIP flag is removed for PR f"myproducts/mySLFO #{pytest.forwarded_pr_number}"
# In loop check whether WIP flag is removed for PR f"products/SLFO #{pytest.forwarded_pr_number}"
wip_flag_removed = False
for _ in range(20):
time.sleep(1)
forwarded_pr_details = gitea_env.get_pr_details(
"myproducts/mySLFO", pytest.forwarded_pr_number
"products/SLFO", pytest.forwarded_pr_number
)
if "WIP: " not in forwarded_pr_details["title"]:
wip_flag_removed = True
@@ -121,7 +121,7 @@ def test_003_wip(gitea_env):
@pytest.mark.t005
@pytest.mark.skip(reason="works only in ibs_state branch?")
@pytest.mark.xfail(reason="works only in ibs_state branch?")
@pytest.mark.dependency()
def test_005_NoProjectGitPR_edits_disabled(no_project_git_pr_env, test_user_client):
"""
@@ -139,7 +139,7 @@ index 0000000..e69de29
@@ -0,0 +1 @@
+Initial content
"""
package_pr = test_user_client.create_gitea_pr("mypool/pkgA", initial_diff, "Test PR for No Project PR, No Edits", False, base_branch=dev_branch_name)
package_pr = test_user_client.create_gitea_pr("pool/pkgA", initial_diff, "Test PR for No Project PR, No Edits", False, base_branch=dev_branch_name)
package_pr_number = package_pr["number"]
print(f"Created Package PR #{package_pr_number}")
@@ -147,29 +147,29 @@ index 0000000..e69de29
project_pr_created = False
for i in range(10): # Poll for some time
time.sleep(2)
timeline_events = gitea_env.get_timeline_events("mypool/pkgA", package_pr_number)
timeline_events = gitea_env.get_timeline_events("pool/pkgA", package_pr_number)
for event in timeline_events:
if event.get("type") == "pull_ref":
if not (ref_issue := event.get("ref_issue")):
continue
url_to_check = ref_issue.get("html_url", "")
match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check)
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
if match:
project_pr_created = True
break
if project_pr_created:
break
assert not project_pr_created, "Workflow bot unexpectedly created a Project PR in myproducts/mySLFO."
assert not project_pr_created, "Workflow bot unexpectedly created a Project PR in products/SLFO."
print("Verification complete: No Project PR was created by the bot.")
# 3. Manually create the Project PR
pkgA_main_sha = gitea_env._request("GET", f"repos/mypool/pkgA/branches/{dev_branch_name}").json()["commit"]["id"]
package_pr_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number)
pkgA_main_sha = gitea_env._request("GET", f"repos/pool/pkgA/branches/{dev_branch_name}").json()["commit"]["id"]
package_pr_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number)
pkgA_pr_head_sha = package_pr_details["head"]["sha"]
project_pr_title = "Forwarded PRs: pkgA (Manual)"
project_pr_body = f"Manual Project PR for NoProjectGitPR. \nPR: mypool/pkgA!{package_pr_number}"
project_pr_body = f"Manual Project PR for NoProjectGitPR. \nPR: pool/pkgA!{package_pr_number}"
project_pr_diff = f"""diff --git a/pkgA b/pkgA
index {pkgA_main_sha[:7]}..{pkgA_pr_head_sha[:7]} 160000
--- a/pkgA
@@ -199,14 +199,14 @@ index 0000000..e69de29
@@ -0,0 +1 @@
+Trigger content
"""
test_user_client.modify_gitea_pr("mypool/pkgA", package_pr_number, new_diff_content, "Trigger bot update")
test_user_client.modify_gitea_pr("pool/pkgA", package_pr_number, new_diff_content, "Trigger bot update")
# 5. Verify that the bot adds a warning comment because it cannot update the manual PR (edits disabled)
warning_found = False
print(f"Polling Package PR #{package_pr_number} for warning comment...")
for _ in range(20):
time.sleep(3)
comments = gitea_env.get_comments("mypool/pkgA", package_pr_number)
comments = gitea_env.get_comments("pool/pkgA", package_pr_number)
for comment in comments:
# According to test-plan.md, the warning explains that it cannot update the PR.
if "cannot update" in comment.get("body", "").lower():
@@ -221,7 +221,7 @@ index 0000000..e69de29
@pytest.mark.t006
@pytest.mark.skip(reason="works only in ibs_state branch?")
@pytest.mark.xfail(reason="works only in ibs_state branch?")
@pytest.mark.dependency()
def test_006_NoProjectGitPR_edits_enabled(no_project_git_pr_env, test_user_client):
"""
@@ -239,42 +239,42 @@ index 0000000..e69de29
@@ -0,0 +1 @@
+New feature content
"""
package_pr = test_user_client.create_gitea_pr("mypool/pkgA", diff, "Test PR for NoProjectGitPR", False, base_branch=dev_branch_name)
package_pr = test_user_client.create_gitea_pr("pool/pkgA", diff, "Test PR for NoProjectGitPR", False, base_branch=dev_branch_name)
package_pr_number = package_pr["number"]
# Enable "Allow edits from maintainers"
test_user_client.update_gitea_pr_properties("mypool/pkgA", package_pr_number, allow_maintainer_edit=True)
test_user_client.update_gitea_pr_properties("pool/pkgA", package_pr_number, allow_maintainer_edit=True)
print(f"Created Package PR #{package_pr_number} and enabled 'Allow edits from maintainers'.")
# Get SHAs needed for the manual Project PR diff
pkgA_main_sha = gitea_env._request("GET", f"repos/mypool/pkgA/branches/{dev_branch_name}").json()["commit"]["id"]
package_pr_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number)
pkgA_main_sha = gitea_env._request("GET", f"repos/pool/pkgA/branches/{dev_branch_name}").json()["commit"]["id"]
package_pr_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number)
pkgA_pr_head_sha = package_pr_details["head"]["sha"]
# 3. Assert that the workflow-pr bot did not create a Project PR in the myproducts/mySLFO repository
# 3. Assert that the workflow-pr bot did not create a Project PR in the products/SLFO repository
project_pr_created = False
for i in range(20): # Poll for a reasonable time
time.sleep(2) # Wait a bit longer to be sure
timeline_events = gitea_env.get_timeline_events("mypool/pkgA", package_pr_number)
timeline_events = gitea_env.get_timeline_events("pool/pkgA", package_pr_number)
for event in timeline_events:
if event.get("type") == "pull_ref":
if not (ref_issue := event.get("ref_issue")):
continue
url_to_check = ref_issue.get("html_url", "")
# Regex now searches for myproducts/mySLFO/pulls/(\d+)
match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check)
# Regex now searches for products/SLFO/pulls/(\d+)
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
if match:
project_pr_created = True
break
if project_pr_created:
break
assert not project_pr_created, "Workflow bot unexpectedly created a Project PR in myproducts/mySLFO."
print("Verification complete: No Project PR was created in myproducts/mySLFO as expected.")
assert not project_pr_created, "Workflow bot unexpectedly created a Project PR in products/SLFO."
print("Verification complete: No Project PR was created in products/SLFO as expected.")
# 1. Create that Project PR from the test code.
project_pr_title = "Forwarded PRs: pkgA"
project_pr_body = f"Test Project PR for NoProjectGitPR. \nPR: mypool/pkgA!{package_pr_number}"
project_pr_body = f"Test Project PR for NoProjectGitPR. \nPR: pool/pkgA!{package_pr_number}"
project_pr_diff = f"""diff --git a/pkgA b/pkgA
index {pkgA_main_sha[:7]}..{pkgA_pr_head_sha[:7]} 160000
--- a/pkgA
@@ -304,7 +304,7 @@ index 0000000..f587a12
@@ -0,0 +1 @@
+Another file content
"""
test_user_client.modify_gitea_pr("mypool/pkgA", package_pr_number, new_diff_content, "Add another file to Package PR")
test_user_client.modify_gitea_pr("pool/pkgA", package_pr_number, new_diff_content, "Add another file to Package PR")
print(f"Added new commit to Package PR #{package_pr_number}.")
time.sleep(5) # Give the bot time to react

View File

@@ -19,8 +19,8 @@ export GITEA_TOKEN
echo "GITEA_TOKEN exported (length: ${#GITEA_TOKEN})"
# Wait for the dummy data to be created by the gitea setup script
echo "Waiting for workflow.config in myproducts/mySLFO..."
API_URL="http://gitea-test:3000/api/v1/repos/myproducts/mySLFO/contents/workflow.config"
echo "Waiting for workflow.config in products/SLFO..."
API_URL="http://gitea-test:3000/api/v1/repos/products/SLFO/contents/workflow.config"
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token $GITEA_TOKEN" "$API_URL")
while [ "$HTTP_STATUS" != "200" ]; do

View File

@@ -1,10 +1,8 @@
[
"myproducts/mySLFO#main",
"myproducts/mySLFO#staging-main",
"myproducts/mySLFO#dev",
"myproducts/mySLFO#merge",
"myproducts/mySLFO#maintainer-merge",
"myproducts/mySLFO#review-required",
"myproducts/mySLFO#label-test",
"myproducts/mySLFO#manual-merge"
"products/SLFO#main",
"products/SLFO#dev",
"products/SLFO#merge",
"products/SLFO#maintainer-merge",
"products/SLFO#review-required",
"products/SLFO#label-test"
]

View File

@@ -27,6 +27,7 @@ import (
"net/url"
"os"
"path"
"path/filepath"
"regexp"
"runtime/debug"
"slices"
@@ -50,10 +51,6 @@ const (
var runId uint
var GitWorkTreeAllocate func(string, string, string) (common.GitHandlerGenerator, error) = func(basePath, gitAuthor, email string) (common.GitHandlerGenerator, error) {
return common.AllocateGitWorkTree(basePath, gitAuthor, email)
}
func FetchPrGit(git common.Git, pr *models.PullRequest) error {
// clone PR head via base (target) repo
cloneURL := pr.Base.Repo.CloneURL
@@ -131,6 +128,10 @@ func ProcessBuildStatus(project *common.BuildResultList) BuildStatusSummary {
found:
for j := 0; j < len(project.Result); j++ {
common.LogDebug(" found match for @ idx:", j)
if project.Result[i].Dirty {
// ignore possible temporary failures and wait for settling
return BuildStatusSummaryBuilding
}
res := ProcessRepoBuildStatus(project.Result[i].Status)
switch res {
case BuildStatusSummarySuccess:
@@ -148,9 +149,9 @@ func ProcessBuildStatus(project *common.BuildResultList) BuildStatusSummary {
func ProcessRepoBuildStatus(results []*common.PackageBuildStatus) (status BuildStatusSummary) {
PackageBuildStatusSorter := func(a, b *common.PackageBuildStatus) int {
return strings.Compare(a.Package, b.Package)
}
PackageBuildStatusSorter := func(a, b *common.PackageBuildStatus) int {
return strings.Compare(a.Package, b.Package)
}
common.LogDebug("******* RESULTS: ")
data, _ := xml.MarshalIndent(results, "", " ")
@@ -195,23 +196,24 @@ func GetPackageBuildStatus(project *common.BuildResultList, packageName string)
return true, BuildStatusSummaryUnknown // true for 'missing'
}
// Check for any unfinished builds
// Check for any failures
for _, pkgStatus := range packageStatuses {
res, ok := common.ObsBuildStatusDetails[pkgStatus.Code]
if !ok {
common.LogInfo("unknown package result code:", pkgStatus.Code, "for package:", pkgStatus.Package)
return false, BuildStatusSummaryUnknown
}
if !res.Finished {
return false, BuildStatusSummaryBuilding
if !res.Success {
return false, BuildStatusSummaryFailed
}
}
// Check for any failures
// Check for any unfinished builds
for _, pkgStatus := range packageStatuses {
res, _ := common.ObsBuildStatusDetails[pkgStatus.Code]
if !res.Success {
return false, BuildStatusSummaryFailed
// 'ok' is already checked in the loop above
if !res.Finished {
return false, BuildStatusSummaryBuilding
}
}
@@ -219,7 +221,7 @@ func GetPackageBuildStatus(project *common.BuildResultList, packageName string)
return false, BuildStatusSummarySuccess
}
func GenerateObsPrjMeta(obs common.ObsClientInterface, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingPrj, buildPrj string, stagingMasterPrj string) (*common.ProjectMeta, error) {
func GenerateObsPrjMeta(git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingPrj, buildPrj string, stagingMasterPrj string) (*common.ProjectMeta, error) {
common.LogDebug("repo content fetching ...")
err := FetchPrGit(git, pr)
if err != nil {
@@ -263,13 +265,13 @@ func GenerateObsPrjMeta(obs common.ObsClientInterface, git common.Git, gitea com
}
common.LogDebug("Trying first staging master project: ", stagingMasterPrj)
meta, err := obs.GetProjectMeta(stagingMasterPrj)
meta, err := ObsClient.GetProjectMeta(stagingMasterPrj)
if err == nil {
// success, so we use that staging master project as our build project
buildPrj = stagingMasterPrj
} else {
common.LogInfo("error fetching project meta for ", stagingMasterPrj, ". Fall Back to ", buildPrj)
meta, err = obs.GetProjectMeta(buildPrj)
meta, err = ObsClient.GetProjectMeta(buildPrj)
}
if err != nil {
common.LogError("error fetching project meta for", buildPrj, ". Err:", err)
@@ -333,10 +335,10 @@ func GenerateObsPrjMeta(obs common.ObsClientInterface, git common.Git, gitea com
// stagingProject:$buildProject
// ^- stagingProject:$buildProject:$subProjectName (based on templateProject)
func CreateQASubProject(obs common.ObsClientInterface, stagingConfig *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingProject, templateProject, subProjectName string, buildDisableRepos []string) error {
func CreateQASubProject(stagingConfig *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingProject, templateProject, subProjectName string, buildDisableRepos []string) error {
common.LogDebug("Setup QA sub projects")
common.LogDebug("reading templateProject ", templateProject)
templateMeta, err := obs.GetProjectMeta(templateProject)
templateMeta, err := ObsClient.GetProjectMeta(templateProject)
if err != nil {
common.LogError("error fetching template project meta for", templateProject, ":", err)
return err
@@ -346,10 +348,10 @@ func CreateQASubProject(obs common.ObsClientInterface, stagingConfig *common.Sta
templateMeta.Name = stagingProject + ":" + subProjectName
// freeze tag for now
if len(templateMeta.ScmSync) > 0 {
repository, err := url.Parse(templateMeta.ScmSync)
if err != nil {
panic(err)
}
repository, err := url.Parse(templateMeta.ScmSync)
if err != nil {
panic(err)
}
common.LogDebug("getting data for ", repository.EscapedPath())
split := strings.Split(repository.EscapedPath(), "/")
@@ -357,12 +359,12 @@ func CreateQASubProject(obs common.ObsClientInterface, stagingConfig *common.Sta
common.LogDebug("getting commit for ", org, " repo ", repo, " fragment ", repository.Fragment)
branch, err := gitea.GetCommit(org, repo, repository.Fragment)
if err != nil {
panic(err)
}
if err != nil {
panic(err)
}
// set expanded commit url
repository.Fragment = branch.SHA
repository.Fragment = branch.SHA
templateMeta.ScmSync = repository.String()
common.LogDebug("Setting scmsync url to ", templateMeta.ScmSync)
}
@@ -409,11 +411,11 @@ func CreateQASubProject(obs common.ObsClientInterface, stagingConfig *common.Sta
templateMeta.Repositories[idx].Paths[pidx].Project = templateMeta.Name
} else
// Check for path prefixes against a template project inside of template project area
if strings.HasPrefix(path.Project, stagingConfig.StagingProject+":") {
if strings.HasPrefix(path.Project, stagingConfig.StagingProject + ":") {
newProjectName := stagingProject
// find project name
for _, setup := range stagingConfig.QA {
if setup.Origin == path.Project {
if setup.Origin == path.Project {
common.LogDebug(" Match:", setup.Origin)
newProjectName = newProjectName + ":" + setup.Name
common.LogDebug(" New:", newProjectName)
@@ -421,14 +423,14 @@ func CreateQASubProject(obs common.ObsClientInterface, stagingConfig *common.Sta
}
}
templateMeta.Repositories[idx].Paths[pidx].Project = newProjectName
common.LogDebug(" Matched prefix")
common.LogDebug(" Matched prefix")
}
common.LogDebug(" Path using project ", templateMeta.Repositories[idx].Paths[pidx].Project)
}
}
if !IsDryRun {
err = obs.SetProjectMeta(templateMeta)
err = ObsClient.SetProjectMeta(templateMeta)
if err != nil {
common.LogError("cannot create project:", templateMeta.Name, err)
x, _ := xml.MarshalIndent(templateMeta, "", " ")
@@ -442,10 +444,10 @@ func CreateQASubProject(obs common.ObsClientInterface, stagingConfig *common.Sta
return nil
}
func StartOrUpdateBuild(obs common.ObsClientInterface, config *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest) (RequestModification, error) {
func StartOrUpdateBuild(config *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest) (RequestModification, error) {
common.LogDebug("fetching OBS project Meta")
obsPrProject := GetObsProjectAssociatedWithPr(config, obs.GetHomeProject(), pr)
meta, err := obs.GetProjectMeta(obsPrProject)
obsPrProject := GetObsProjectAssociatedWithPr(config, ObsClient.HomeProject, pr)
meta, err := ObsClient.GetProjectMeta(obsPrProject)
if err != nil {
common.LogError("error fetching project meta for", obsPrProject, ":", err)
return RequestModificationNoChange, err
@@ -470,7 +472,7 @@ func StartOrUpdateBuild(obs common.ObsClientInterface, config *common.StagingCon
if meta == nil {
// new build
common.LogDebug(" Staging master:", config.StagingProject)
meta, err = GenerateObsPrjMeta(obs, git, gitea, pr, obsPrProject, config.ObsProject, config.StagingProject)
meta, err = GenerateObsPrjMeta(git, gitea, pr, obsPrProject, config.ObsProject, config.StagingProject)
if err != nil {
return RequestModificationNoChange, err
}
@@ -482,7 +484,7 @@ func StartOrUpdateBuild(obs common.ObsClientInterface, config *common.StagingCon
common.LogDebug("Creating build project:")
common.LogDebug(" meta:", string(x))
} else {
err = obs.SetProjectMeta(meta)
err = ObsClient.SetProjectMeta(meta)
if err != nil {
x, _ := xml.MarshalIndent(meta, "", " ")
common.LogDebug(" meta:", string(x))
@@ -553,7 +555,7 @@ func ParseNotificationToPR(thread *models.NotificationThread) (org string, repo
return
}
func ProcessPullNotification(obs common.ObsClientInterface, gitea common.Gitea, thread *models.NotificationThread) {
func ProcessPullNotification(gitea common.Gitea, thread *models.NotificationThread) {
defer func() {
err := recover()
if err != nil {
@@ -569,7 +571,7 @@ func ProcessPullNotification(obs common.ObsClientInterface, gitea common.Gitea,
}
common.LogInfo("processing PR:", org, "/", repo, "#", num)
done, err := ProcessPullRequest(obs, gitea, org, repo, num)
done, err := ProcessPullRequest(gitea, org, repo, num)
if !IsDryRun && err == nil && done {
gitea.SetNotificationRead(thread.ID)
} else if err != nil {
@@ -579,7 +581,7 @@ func ProcessPullNotification(obs common.ObsClientInterface, gitea common.Gitea,
var CleanedUpIssues []int64 = []int64{}
func CleanupPullNotification(obs common.ObsClientInterface, gitea common.Gitea, thread *models.NotificationThread) (CleanupComplete bool) {
func CleanupPullNotification(gitea common.Gitea, thread *models.NotificationThread) (CleanupComplete bool) {
defer func() {
err := recover()
if err != nil {
@@ -646,8 +648,8 @@ func CleanupPullNotification(obs common.ObsClientInterface, gitea common.Gitea,
return false
}
stagingProject := GetObsProjectAssociatedWithPr(config, obs.GetHomeProject(), pr)
if prj, err := obs.GetProjectMeta(stagingProject); err != nil {
stagingProject := GetObsProjectAssociatedWithPr(config, ObsClient.HomeProject, pr)
if prj, err := ObsClient.GetProjectMeta(stagingProject); err != nil {
common.LogError("Failed fetching meta for project:", stagingProject, ". Not cleaning up")
return false
} else if prj == nil && err == nil {
@@ -661,13 +663,13 @@ func CleanupPullNotification(obs common.ObsClientInterface, gitea common.Gitea,
project := stagingProject + ":" + qa.Name
common.LogDebug("Cleaning up QA staging", project)
if !IsDryRun {
if err := obs.DeleteProject(project); err != nil {
if err := ObsClient.DeleteProject(project); err != nil {
common.LogError("Failed to cleanup QA staging", project, err)
}
}
}
if !IsDryRun {
if err := obs.DeleteProject(stagingProject); err != nil {
if err := ObsClient.DeleteProject(stagingProject); err != nil {
common.LogError("Failed to cleanup staging", stagingProject, err)
}
}
@@ -688,7 +690,7 @@ func SetStatus(gitea common.Gitea, org, repo, hash string, status *models.Commit
return err
}
func CommentPROnce(gitea common.Gitea, org string, repo string, prNum int64, msg string) {
func commentOnPackagePR(gitea common.Gitea, org string, repo string, prNum int64, msg string) {
if IsDryRun {
common.LogInfo("Would comment on package PR %s/%s#%d: %s", org, repo, prNum, msg)
return
@@ -700,18 +702,6 @@ func CommentPROnce(gitea common.Gitea, org string, repo string, prNum int64, msg
return
}
timeline, err := gitea.GetTimeline(org, repo, prNum)
if err != nil {
common.LogError("Failed to get timeline for PR %s/%s#%d: %v", org, repo, prNum, err)
return
}
for _, t := range timeline {
if t.User != nil && t.User.UserName == BotUser && t.Type == common.TimelineCommentType_Comment && t.Body == msg {
return
}
}
err = gitea.AddComment(pr, msg)
if err != nil {
common.LogError("Failed to comment on package PR %s/%s#%d: %v", org, repo, prNum, err)
@@ -719,21 +709,20 @@ func CommentPROnce(gitea common.Gitea, org string, repo string, prNum int64, msg
}
// Create and remove QA projects
func ProcessQaProjects(obs common.ObsClientInterface, stagingConfig *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingProject string) ([]string, string) {
func ProcessQaProjects(stagingConfig *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingProject string) []string {
usedQAprojects := make([]string, 0)
prLabelNames := make(map[string]int)
for _, label := range pr.Labels {
prLabelNames[label.Name] = 1
}
msg := ""
var qa_projects []string
for _, setup := range stagingConfig.QA {
QAproject := stagingProject + ":" + setup.Name
if len(setup.Label) > 0 {
if _, ok := prLabelNames[setup.Label]; !ok {
if !IsDryRun {
// blindly remove, will fail when not existing
obs.DeleteProject(QAproject)
ObsClient.DeleteProject(QAproject)
}
common.LogInfo("QA project ", setup.Name, "has no matching Label")
continue
@@ -742,25 +731,24 @@ func ProcessQaProjects(obs common.ObsClientInterface, stagingConfig *common.Stag
usedQAprojects = append(usedQAprojects, QAproject)
// check for existens first, no error, but no meta is a 404
if meta, err := obs.GetProjectMeta(QAproject); meta == nil && err == nil {
if meta, err := ObsClient.GetProjectMeta(QAproject); meta == nil && err == nil {
common.LogInfo("Create QA project ", QAproject)
CreateQASubProject(obs, stagingConfig, git, gitea, pr,
CreateQASubProject(stagingConfig, git, gitea, pr,
stagingProject,
setup.Origin,
setup.Name,
setup.BuildDisableRepos)
qa_projects = append(qa_projects, ObsWebHost+"/project/show/"+QAproject)
msg = msg + "QA Project added: " + ObsWebHost + "/project/show/" +
QAproject + "\n"
}
}
if len(qa_projects) > 0 {
msg = "Additional QA builds:\n" + strings.Join(qa_projects, "\n")
if len(msg) > 1 {
gitea.AddComment(pr, msg)
}
return usedQAprojects, msg
return usedQAprojects
}
func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org, repo string, id int64) (bool, error) {
func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, error) {
dir, err := os.MkdirTemp(os.TempDir(), BotName)
common.PanicOnError(err)
if IsDryRun {
@@ -769,7 +757,7 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
defer os.RemoveAll(dir)
}
gh, err := GitWorkTreeAllocate(dir, GitAuthor, "noaddress@suse.de")
gh, err := common.AllocateGitWorkTree(dir, GitAuthor, "noaddress@suse.de")
common.PanicOnError(err)
git, err := gh.CreateGitHandler(org)
@@ -814,7 +802,7 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
if err != nil {
common.LogError("Staging config", common.StagingConfigFile, "not found in PR to the project. Aborting.")
if !IsDryRun {
_, _ = gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Cannot find project config in PR: "+common.ProjectConfigFile)
_, err = gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Cannot find project config in PR: "+common.ProjectConfigFile)
}
return true, err
}
@@ -834,7 +822,7 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
return true, nil
}
meta, err := obs.GetProjectMeta(stagingConfig.ObsProject)
meta, err := ObsClient.GetProjectMeta(stagingConfig.ObsProject)
if err != nil || meta == nil {
common.LogError("Cannot find reference project meta:", stagingConfig.ObsProject, err)
if !IsDryRun && err == nil {
@@ -903,10 +891,13 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
if !stagingConfig.RebuildAll {
for pkg, headOid := range headSubmodules {
if baseOid, exists := baseSubmodules[pkg]; !exists || baseOid != headOid {
if pkg != "rpms" && pkg != "dependencies" {
_, spkg := filepath.Split(pkg)
if exists {
modifiedPackages = append(modifiedPackages, pkg)
modifiedPackages = append(modifiedPackages, spkg)
} else {
newPackages = append(newPackages, pkg)
newPackages = append(newPackages, spkg)
}
}
common.LogDebug(pkg, ":", baseOid, "->", headOid)
}
@@ -963,8 +954,8 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
}
common.LogDebug("ObsProject:", stagingConfig.ObsProject)
stagingProject := GetObsProjectAssociatedWithPr(stagingConfig, obs.GetHomeProject(), pr)
change, err := StartOrUpdateBuild(obs, stagingConfig, git, gitea, pr)
stagingProject := GetObsProjectAssociatedWithPr(stagingConfig, ObsClient.HomeProject, pr)
change, err := StartOrUpdateBuild(stagingConfig, git, gitea, pr)
status := &models.CommitStatus{
Context: BotName,
Description: "OBS Staging build",
@@ -995,8 +986,11 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
SetStatus(gitea, org, repo, pr.Head.Sha, status)
}
if change != RequestModificationNoChange && !IsDryRun {
gitea.AddComment(pr, msg)
}
stagingResult, err := obs.BuildStatus(stagingProject)
stagingResult, err := ObsClient.BuildStatus(stagingProject)
if err != nil {
common.LogError("failed fetching stage project status for", stagingProject, ":", err)
}
@@ -1004,14 +998,7 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
_, packagePRs := common.ExtractDescriptionAndPRs(bufio.NewScanner(strings.NewReader(pr.Body)))
// always update QA projects because Labels can change
qaProjects, qaProjectMsg := ProcessQaProjects(obs, stagingConfig, git, gitea, pr, stagingProject)
if change != RequestModificationNoChange && !IsDryRun {
if len(qaProjectMsg) > 0 {
msg += "\n" + qaProjectMsg
}
CommentPROnce(gitea, org, repo, id, msg)
}
qaProjects := ProcessQaProjects(stagingConfig, git, gitea, pr, stagingProject)
done := false
overallBuildStatus := ProcessBuildStatus(stagingResult)
@@ -1019,7 +1006,7 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
if len(qaProjects) > 0 && overallBuildStatus == BuildStatusSummarySuccess {
seperator := " in "
for _, qaProject := range qaProjects {
qaResult, err := obs.BuildStatus(qaProject)
qaResult, err := ObsClient.BuildStatus(qaProject)
if err != nil {
common.LogError("failed fetching stage project status for", qaProject, ":", err)
}
@@ -1039,6 +1026,7 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
}
}
}
switch overallBuildStatus {
case BuildStatusSummarySuccess:
status.Status = common.CommitStatus_Success
@@ -1079,7 +1067,7 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
default:
continue
}
CommentPROnce(gitea, packagePR.Org, packagePR.Repo, packagePR.Num, msg)
commentOnPackagePR(gitea, packagePR.Org, packagePR.Repo, packagePR.Num, msg)
}
if len(missingPkgs) > 0 {
@@ -1089,7 +1077,10 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
msg = msg + " - " + pkg + "\n"
}
common.LogInfo(msg)
CommentPROnce(gitea, org, repo, id, msg)
err := gitea.AddComment(pr, msg)
if err != nil {
common.LogError(err)
}
}
}
@@ -1108,7 +1099,8 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
return false, nil
}
func PollWorkNotifications(obs common.ObsClientInterface, gitea common.Gitea) {
func PollWorkNotifications(giteaUrl string) {
gitea := common.AllocateGiteaTransport(giteaUrl)
data, err := gitea.GetNotifications(common.GiteaNotificationType_Pull, nil)
if err != nil {
@@ -1124,7 +1116,7 @@ func PollWorkNotifications(obs common.ObsClientInterface, gitea common.Gitea) {
if !ListPullNotificationsOnly {
switch notification.Subject.Type {
case "Pull":
ProcessPullNotification(obs, gitea, notification)
ProcessPullNotification(gitea, notification)
default:
if !IsDryRun {
gitea.SetNotificationRead(notification.ID)
@@ -1147,7 +1139,7 @@ func PollWorkNotifications(obs common.ObsClientInterface, gitea common.Gitea) {
continue
}
cleanupFinished = CleanupPullNotification(obs, gitea, n) && cleanupFinished
cleanupFinished = CleanupPullNotification(gitea, n) && cleanupFinished
}
} else if err != nil {
common.LogError(err)
@@ -1161,8 +1153,7 @@ var ObsApiHost string
var ObsWebHost string
var IsDryRun bool
var ProcessPROnly string
var ObsClient common.ObsClientInterface
var BotUser string
var ObsClient *common.ObsClient
func ObsWebHostFromApiHost(apihost string) string {
u, err := url.Parse(apihost)
@@ -1227,18 +1218,9 @@ func main() {
}
if len(*buildRoot) > 0 {
ObsClient.SetHomeProject(*buildRoot)
ObsClient.HomeProject = *buildRoot
}
gitea := common.AllocateGiteaTransport(GiteaUrl)
user, err := gitea.GetCurrentUser()
if err != nil {
common.LogError("Cannot fetch current user:", err)
return
}
BotUser = user.UserName
if len(*ProcessPROnly) > 0 {
rx := regexp.MustCompile("^([^/#]+)/([^/#]+)#([0-9]+)$")
m := rx.FindStringSubmatch(*ProcessPROnly)
@@ -1247,14 +1229,15 @@ func main() {
return
}
gitea := common.AllocateGiteaTransport(GiteaUrl)
id, _ := strconv.ParseInt(m[3], 10, 64)
ProcessPullRequest(ObsClient, gitea, m[1], m[2], id)
ProcessPullRequest(gitea, m[1], m[2], id)
return
}
for {
PollWorkNotifications(ObsClient, gitea)
PollWorkNotifications(GiteaUrl)
common.LogInfo("Poll cycle finished")
time.Sleep(5 * time.Minute)
}

File diff suppressed because it is too large Load Diff

View File

@@ -6,9 +6,13 @@ After=network-online.target
Type=exec
ExecStart=/usr/bin/obs-staging-bot
EnvironmentFile=-/etc/default/obs-staging-bot.env
DynamicUser=yes
NoNewPrivileges=yes
ProtectSystem=strict
User=autogits_obs_staging_bot
Group=users
# This may work when not using ssh api connections:
#DynamicUser=yes
#NoNewPrivileges=yes
#ProtectSystem=strict
[Install]
WantedBy=multi-user.target

View File

@@ -123,7 +123,7 @@ func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, co
common.LogError(" - ", action.Repository.Name, "repo is not sha256. Ignoring.")
return
}
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", action.Repository.Clone_Url, action.Repository.Name))
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", "../" + action.Repository.Name, action.Repository.Name))
defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f")
branch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, action.Repository.Name), "branch", "--show-current"))
@@ -215,7 +215,7 @@ func processConfiguredPushAction(action *common.PushWebhookEvent, config *common
}
if stat, err := os.Stat(filepath.Join(git.GetPath(), gitPrj, action.Repository.Name)); err != nil {
git.GitExecOrPanic(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", action.Repository.Clone_Url, action.Repository.Name)
git.GitExecOrPanic(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", "../" + action.Repository.Name, action.Repository.Name)
common.LogDebug("Pushed to package that is not part of the project. Re-adding...", err)
} else if !stat.IsDir() {
common.LogError("Pushed to a package that is not a submodule but exists in the project. Ignoring.")
@@ -420,7 +420,7 @@ next_repo:
}
// add repository to git project
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", r.CloneURL, r.Name))
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", "../" + r.Name, r.Name))
curBranch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, r.Name), "branch", "--show-current"))
if branch != curBranch {

View File

@@ -180,6 +180,33 @@ func (pr *PRProcessor) SetSubmodulesToMatchPRSet(prset *common.PRSet) error {
}
updateSubmoduleInPR(submodulePath, prHead, git)
err := git.GitExec(path.Join(common.DefaultGitPrj, submodulePath), "lfs", "fetch")
common.LogError("lfs fetch err: ", err)
if err = git.GitExec(path.Join(common.DefaultGitPrj, submodulePath), "lfs", "fsck"); err != nil {
found_comment := false
timeline, terr := common.FetchTimelineSinceLastPush(Gitea, prHead, org, repo, idx)
if terr != nil {
common.LogError("lfs fsck error, but timeline fetch failed")
break
}
msgPrefix := "The LFS objects are broken!"
for _, t := range timeline {
if t.Type == common.TimelineCommentType_Comment && strings.HasPrefix(t.Body, msgPrefix) {
found_comment = true
common.LogError("lfs fsck Comment already found")
break
}
}
if !found_comment && !common.IsDryRun {
Gitea.AddComment(pr.PR, msgPrefix + " Please verify with 'git lfs fsck'")
}
common.LogError("lfs fsck failed with: ", err.Error())
return err
}
status, err := git.GitStatus(common.DefaultGitPrj)
common.LogDebug("status:", status)
common.LogDebug("submodule", repo, " hash:", id, " -> ", prHead)
@@ -311,14 +338,13 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
if PrjGitPR.PR.Base.RepoID != PrjGitPR.PR.Head.RepoID {
// permission check, if submission comes from foreign repo
if !PrjGitPR.PR.AllowMaintainerEdit {
// well, wrong place...
// common.LogError("Warning: source and target branch are in different repositories. We may not have the right permissions...")
// Gitea.AddComment(PrjGitPR.PR, "This PR does not allow maintainer changes, but referenced package branch has changed!")
common.LogError("Warning: source and target branch are in different repositories. We may not have the right permissions...")
// Gitea.AddComment(PrjGitPR.PR, "This PR does not allow maintainer changes, but referenced package branch has changed!")
return nil
}
}
// PrjGitPR.RemoteName, err = git.GitClone(common.DefaultGitPrj, prjGitPRbranch, PrjGit.SSHURL)
// PrjGitPR.RemoteName, err = git.GitClone(common.DefaultGitPrj, prjGitPRbranch, PrjGit.SSHURL)
PrjGitPR.RemoteName, err = git.GitClone(common.DefaultGitPrj, PrjGitPR.PR.Head.Ref, PrjGitPR.PR.Head.Repo.SSHURL)
common.PanicOnError(err)
git.GitExecOrPanic(common.DefaultGitPrj, "fetch", PrjGitPR.RemoteName, PrjGitBranch)
@@ -384,7 +410,7 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
}
return CurrentTitle == NewTitle
}
if PrjGitPR.PR.User.UserName == CurrentUser.UserName && (PrjGitPR.PR.Body != PrjGitBody || !isPrTitleSame(PrjGitPR.PR.Title, PrjGitTitle)) {
if !pr.config.NoProjectGitPR && PrjGitPR.PR.User.UserName == CurrentUser.UserName && (PrjGitPR.PR.Body != PrjGitBody || !isPrTitleSame(PrjGitPR.PR.Title, PrjGitTitle)) {
Gitea.UpdatePullRequest(PrjGit.Owner.UserName, PrjGit.Name, PrjGitPR.PR.Index, &models.EditPullRequestOption{
RemoveDeadline: true,
Title: PrjGitTitle,

View File

@@ -396,62 +396,7 @@ func TestUpdatePrjGitPR(t *testing.T) {
Name: "feature",
RepoID: 2, // Different RepoID
Sha: "sha1",
Repo: &models.Repository{
SSHURL: "url",
},
},
User: &models.User{UserName: "someone"},
Mergeable: true,
AllowMaintainerEdit: true,
},
},
{
PR: &models.PullRequest{
State: "open",
Base: &models.PRBranchInfo{
Name: "other",
Repo: &models.Repository{
Name: "other-pkg",
Owner: &models.User{UserName: "test-org"},
},
},
Head: &models.PRBranchInfo{
Sha: "other-sha",
},
},
},
},
}
mockGit.EXPECT().GitClone(common.DefaultGitPrj, gomock.Any(), gomock.Any()).Return("remote2", nil)
mockGit.EXPECT().GitExecOrPanic(common.DefaultGitPrj, "fetch", "remote2", "main")
mockGit.EXPECT().GitBranchHead(common.DefaultGitPrj, gomock.Any()).Return("sha1", nil).Times(2)
mockGit.EXPECT().GitSubmoduleList(common.DefaultGitPrj, "HEAD").Return(map[string]string{"other-pkg": "other-sha"}, nil)
err := processor.UpdatePrjGitPR(prset)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
})
t.Run("PR on another remote - not allowed", func(t *testing.T) {
prset := &common.PRSet{
Config: config,
PRs: []*common.PRInfo{
{
PR: &models.PullRequest{
Base: &models.PRBranchInfo{
Name: "main",
RepoID: 1,
Repo: &models.Repository{
Name: "test-prj",
Owner: &models.User{UserName: "test-org"},
},
},
Head: &models.PRBranchInfo{
Name: "feature",
RepoID: 2, // Different RepoID
},
AllowMaintainerEdit: false,
},
},
{
@@ -467,6 +412,10 @@ func TestUpdatePrjGitPR(t *testing.T) {
},
},
}
mockGit.EXPECT().GitClone(gomock.Any(), gomock.Any(), gomock.Any()).Return("remote2", nil)
mockGit.EXPECT().GitExecOrPanic(gomock.Any(), "fetch", "remote2", "sha1")
mockGit.EXPECT().GitExecOrPanic(gomock.Any(), "checkout", "sha1")
err := processor.UpdatePrjGitPR(prset)
if err != nil {
t.Errorf("Unexpected error: %v", err)
@@ -496,9 +445,6 @@ func TestUpdatePrjGitPR(t *testing.T) {
Name: "PR_branch",
RepoID: 1,
Sha: "old-head",
Repo: &models.Repository{
SSHURL: "url",
},
},
},
},
@@ -537,7 +483,6 @@ func TestUpdatePrjGitPR(t *testing.T) {
mockGit.EXPECT().GitStatus(gomock.Any()).Return(nil, nil).AnyTimes()
// UpdatePullRequest expectation
gitea.EXPECT().SetLabels(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
gitea.EXPECT().UpdatePullRequest(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
err := processor.UpdatePrjGitPR(prset)
@@ -569,9 +514,6 @@ func TestUpdatePrjGitPR(t *testing.T) {
Name: "PR_branch",
RepoID: 1,
Sha: "head",
Repo: &models.Repository{
SSHURL: "url",
},
},
},
},