workflow-pr: more unit tests

This commit is contained in:
Adam Majer 2024-11-25 17:02:48 +01:00
parent b96c4d26ca
commit e56f444960
3 changed files with 200 additions and 39 deletions

View File

@ -65,8 +65,6 @@ func updateOrCreatePRBranch(req *common.PullRequestWebhookEvent, git *common.Git
} }
var DebugMode bool var DebugMode bool
var checkOnStart bool
var checkInterval time.Duration
func main() { func main() {
if err := common.RequireGiteaSecretToken(); err != nil { if err := common.RequireGiteaSecretToken(); err != nil {
@ -80,12 +78,10 @@ func main() {
giteaHost := flag.String("gitea", "src.opensuse.org", "Gitea instance") giteaHost := flag.String("gitea", "src.opensuse.org", "Gitea instance")
rabbitUrl := flag.String("url", "amqps://rabbit.opensuse.org", "URL for RabbitMQ instance") rabbitUrl := flag.String("url", "amqps://rabbit.opensuse.org", "URL for RabbitMQ instance")
flag.BoolVar(&DebugMode, "debug", false, "Extra debugging information") flag.BoolVar(&DebugMode, "debug", false, "Extra debugging information")
flag.BoolVar(&checkOnStart, "check-on-start", false, "Check all repositories for consistency on start, without delays") checkOnStart := flag.Bool("check-on-start", false, "Check all repositories for consistency on start, without delays")
checkIntervalHours := flag.Float64("check-interval", 5, "Check interval (+-random delay) for repositories for consitency, in hours") checkIntervalHours := flag.Float64("check-interval", 5, "Check interval (+-random delay) for repositories for consitency, in hours")
flag.Parse() flag.Parse()
checkInterval = time.Duration(*checkIntervalHours) * time.Hour
if len(*workflowConfig) == 0 { if len(*workflowConfig) == 0 {
log.Fatalln("No configuratio file specified. Aborting") log.Fatalln("No configuratio file specified. Aborting")
} }
@ -115,23 +111,20 @@ func main() {
} }
} }
gitea := common.AllocateGiteaTransport(*giteaHost)
checker := &DefaultStateChecker {
gitea: common.AllocateGiteaTransport(*giteaHost),
git: &common.GitHandlerImpl{},
}
req.Synced = &PullRequestSynced{ req.Synced = &PullRequestSynced{
gitea: checker.gitea, gitea: gitea,
} }
req.Opened = &PullRequestOpened{ req.Opened = &PullRequestOpened{
gitea: checker.gitea, gitea: gitea,
} }
req.Closed = &PullRequestClosed{ req.Closed = &PullRequestClosed{
gitea: checker.gitea, gitea: gitea,
} }
go checker.consistencyCheckProcess(req) checker := CreateDefaultStateChecker(*checkOnStart, req, gitea, time.Duration(*checkIntervalHours)*time.Hour)
go checker.ConsistencyCheckProcess()
var defs common.ListenDefinitions var defs common.ListenDefinitions

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"log" "log"
"math/rand" "math/rand"
@ -11,20 +12,48 @@ import (
"src.opensuse.org/autogits/common" "src.opensuse.org/autogits/common"
) )
//go:generate mockgen -source=repo_check.go -destination=mock/repo_check.go -typed
type StateChecker interface { type StateChecker interface {
VerifyProjectState(orgName string, configs []*common.AutogitConfig, idx int) error
CheckRepos() error
ConsistencyCheckProcess() error
} }
type DefaultStateChecker struct { type DefaultStateChecker struct {
processor *RequestProcessor exitCheckLoop bool
checkOnStart bool
checkInterval time.Duration
gitea common.Gitea gitea common.Gitea
git common.GitHandlerGenerator git common.GitHandlerGenerator
processor *RequestProcessor
i StateChecker
} }
func (s *DefaultStateChecker) verifyProjectState(processor *RequestProcessor, git *common.GitHandler, orgName string, config *common.AutogitConfig, configs []*common.AutogitConfig) error { func CreateDefaultStateChecker(checkOnStart bool, processor *RequestProcessor, gitea common.Gitea, interval time.Duration) *DefaultStateChecker {
var s = &DefaultStateChecker{
git: &common.GitHandlerImpl{},
gitea: gitea,
checkInterval: interval,
checkOnStart: checkOnStart,
processor: processor,
}
s.i = s
return s
}
func (s *DefaultStateChecker) VerifyProjectState(orgName string, configs []*common.AutogitConfig, idx int) error {
org := common.Organization{ org := common.Organization{
Username: orgName, Username: orgName,
} }
git, err := s.git.CreateGitHandler(GitAuthor, GitEmail, AppName)
if err != nil {
return fmt.Errorf("Cannot create git handler: %w", err)
}
config := configs[idx]
repo, err := s.gitea.CreateRepositoryIfNotExist(git, org, config.GitProjectName) repo, err := s.gitea.CreateRepositoryIfNotExist(git, org, config.GitProjectName)
if err != nil { if err != nil {
return fmt.Errorf("Error fetching or creating '%s/%s' -- aborting verifyProjectState(). Err: %w", orgName, config.GitProjectName, err) return fmt.Errorf("Error fetching or creating '%s/%s' -- aborting verifyProjectState(). Err: %w", orgName, config.GitProjectName, err)
@ -120,43 +149,45 @@ nextSubmodule:
return nil return nil
} }
func (s *DefaultStateChecker) checkRepos(processor *RequestProcessor) { func (s *DefaultStateChecker) CheckRepos() error {
for org, configs := range processor.configuredRepos { errorList := make([]error, 0, 10)
for _, config := range configs {
if checkInterval > 0 { for org, configs := range s.processor.configuredRepos {
sleepInterval := checkInterval - checkInterval/2 + time.Duration(rand.Int63n(int64(checkInterval))) for configIdx, config := range configs {
if s.checkInterval > 0 {
sleepInterval := (s.checkInterval - s.checkInterval/2) + time.Duration(rand.Int63n(int64(s.checkInterval)))
log.Println(" - sleep interval", sleepInterval, "until next check") log.Println(" - sleep interval", sleepInterval, "until next check")
time.Sleep(sleepInterval) time.Sleep(sleepInterval)
} }
log.Printf(" ++ starting verification, org: `%s` config: `%s`\n", org, config.GitProjectName) log.Printf(" ++ starting verification, org: `%s` config: `%s`\n", org, config.GitProjectName)
git, err := s.git.CreateGitHandler(GitAuthor, GitEmail, AppName) if err := s.i.VerifyProjectState(org, configs, configIdx); err != nil {
if err != nil {
log.Println("Faield to allocate GitHandler:", err)
return
}
if !DebugMode {
defer git.Close()
}
if err := s.verifyProjectState(processor, git, org, config, configs); err != nil {
log.Printf(" *** verification failed, org: `%s`, err: %#v\n", org, err) log.Printf(" *** verification failed, org: `%s`, err: %#v\n", org, err)
errorList = append(errorList, err)
} }
log.Printf(" ++ verification complete, org: `%s` config: `%s`\n", org, config.GitProjectName) log.Printf(" ++ verification complete, org: `%s` config: `%s`\n", org, config.GitProjectName)
} }
} }
return errors.Join(errorList...)
} }
func (s *DefaultStateChecker) consistencyCheckProcess(processor *RequestProcessor) { func (s *DefaultStateChecker) ConsistencyCheckProcess() error {
if checkOnStart { if s.checkOnStart {
savedCheckInterval := checkInterval savedCheckInterval := s.checkInterval
checkInterval = 0 s.checkInterval = 0
log.Println("== Startup consistency check begin...") log.Println("== Startup consistency check begin...")
s.checkRepos(processor) s.i.CheckRepos()
log.Println("== Startup consistency check done...") log.Println("== Startup consistency check done...")
checkInterval = savedCheckInterval s.checkInterval = savedCheckInterval
} }
for { for {
s.checkRepos(processor) if s.exitCheckLoop {
break
}
s.i.CheckRepos()
} }
return nil
} }

View File

@ -0,0 +1,137 @@
package main
import (
"bytes"
"errors"
"log"
"testing"
"go.uber.org/mock/gomock"
"src.opensuse.org/autogits/common"
mock_common "src.opensuse.org/autogits/common/mock"
mock_main "src.opensuse.org/workflow-pr/mock"
)
func TestRepoCheck(t *testing.T) {
var logBuf bytes.Buffer
oldOut := log.Writer()
log.SetOutput(&logBuf)
defer log.SetOutput(oldOut)
t.Run("Consistency Check On Start", func(t *testing.T) {
c := CreateDefaultStateChecker(true, nil, nil, 100)
ctl := gomock.NewController(t)
state := mock_main.NewMockStateChecker(ctl)
c.i = state
state.EXPECT().CheckRepos().Do(func() error {
// only checkOnStart has checkInterval = 0
if c.checkInterval != 0 {
t.Fail()
}
c.exitCheckLoop = true
return nil
})
c.ConsistencyCheckProcess()
if c.checkInterval != 100 {
t.Fail()
}
})
t.Run("No consistency Check On Start", func(t *testing.T) {
c := CreateDefaultStateChecker(true, nil, nil, 100)
ctl := gomock.NewController(t)
state := mock_main.NewMockStateChecker(ctl)
c.i = state
nCalls := 10
state.EXPECT().CheckRepos().Do(func() error {
// only checkOnStart has checkInterval = 0
if c.checkInterval != 100 {
t.Fail()
}
nCalls--
if nCalls == 0 {
c.exitCheckLoop = true
}
return nil
}).Times(nCalls)
c.checkOnStart = false
c.ConsistencyCheckProcess()
})
t.Run("CheckRepos() calls CheckProjectState() for each project", func(t *testing.T) {
ctl := gomock.NewController(t)
state := mock_main.NewMockStateChecker(ctl)
gitea := mock_common.NewMockGitea(ctl)
config1 := &common.AutogitConfig{
GitProjectName: "git_repo1",
Organization: "repo1_org",
}
config2 := &common.AutogitConfig{
GitProjectName: "git_repo2",
Organization: "repo2_org",
}
config3 := &common.AutogitConfig{
GitProjectName: "git_repo3",
Organization: "repo3_org",
}
configs := &RequestProcessor{
configuredRepos: map[string][]*common.AutogitConfig{
"repo1_org": []*common.AutogitConfig{config1},
"repo2_org": []*common.AutogitConfig{config2},
"repo3_org": []*common.AutogitConfig{config3},
},
}
r := configs.configuredRepos
c := CreateDefaultStateChecker(true, configs, gitea, 100)
c.i = state
state.EXPECT().VerifyProjectState("repo1_org", r["repo1_org"], 0)
state.EXPECT().VerifyProjectState("repo2_org", r["repo2_org"], 0)
state.EXPECT().VerifyProjectState("repo3_org", r["repo3_org"], 0)
if err := c.CheckRepos(); err != nil {
t.Error(err)
}
})
t.Run("CheckRepos errors", func(t *testing.T) {
ctl := gomock.NewController(t)
state := mock_main.NewMockStateChecker(ctl)
gitea := mock_common.NewMockGitea(ctl)
git := mock_common.NewMockGitHandlerGenerator(ctl)
config1 := &common.AutogitConfig{
GitProjectName: "git_repo1",
Organization: "repo1_org",
}
configs := &RequestProcessor{
configuredRepos: map[string][]*common.AutogitConfig{
"repo1_org": []*common.AutogitConfig{config1},
},
}
//r := configs.configuredRepos
c := CreateDefaultStateChecker(true, configs, gitea, 100)
c.i = state
c.git = git
err := errors.New("test error")
state.EXPECT().VerifyProjectState("repo1_org", gomock.Any(), 0).Return(err)
r := c.CheckRepos()
if !errors.Is(r, err) {
t.Error(err)
}
})
}