harness-drone/trigger/cron/cron_test.go
Marko Gaćeša ea6566b059
fixed: graceful shutdown (#3083)
* fixed: graceful shutdown
* updated test for cron scheduler shutdown
2021-05-27 13:52:09 +01:00

489 lines
15 KiB
Go

// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
// +build !oss
package cron
import (
"context"
"database/sql"
"io/ioutil"
"testing"
"time"
"github.com/drone/drone/core"
"github.com/drone/drone/mock"
"github.com/golang/mock/gomock"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/go-multierror"
"github.com/sirupsen/logrus"
)
func init() {
logrus.SetOutput(ioutil.Discard)
}
// TODO(bradrydzewski) test disabled cron jobs are skipped
// TODO(bradrydzewski) test to ensure panic does not exit program
func TestCron(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
checkBuild := func(_ context.Context, _ *core.Repository, hook *core.Hook) {
ignoreHookFields := cmpopts.IgnoreFields(core.Hook{},
"Source", "Before")
if diff := cmp.Diff(hook, dummyHook, ignoreHookFields); diff != "" {
t.Errorf(diff)
}
}
before := time.Now().Unix()
checkCron := func(_ context.Context, cron *core.Cron) {
if got, want := cron.Prev, int64(2000000000); got != want {
t.Errorf("Expect Next copied to Prev")
}
if before > cron.Next {
t.Errorf("Expect Next is set to unix timestamp")
}
}
mockTriggerer := mock.NewMockTriggerer(controller)
mockTriggerer.EXPECT().Trigger(gomock.Any(), dummyRepo, gomock.Any()).Do(checkBuild)
mockRepos := mock.NewMockRepositoryStore(controller)
mockRepos.EXPECT().Find(gomock.Any(), dummyCron.RepoID).Return(dummyRepo, nil)
mockCrons := mock.NewMockCronStore(controller)
mockCrons.EXPECT().Ready(gomock.Any(), gomock.Any()).Return(dummyCronList, nil)
mockCrons.EXPECT().Update(gomock.Any(), dummyCron).Do(checkCron)
mockUsers := mock.NewMockUserStore(controller)
mockUsers.EXPECT().Find(gomock.Any(), dummyRepo.UserID).Return(dummyUser, nil)
mockCommits := mock.NewMockCommitService(controller)
mockCommits.EXPECT().FindRef(gomock.Any(), dummyUser, dummyRepo.Slug, dummyRepo.Branch).Return(dummyCommit, nil)
s := Scheduler{
commits: mockCommits,
cron: mockCrons,
repos: mockRepos,
users: mockUsers,
trigger: mockTriggerer,
}
err := s.run(noContext)
if err != nil {
t.Error(err)
}
}
func TestCron_Cancel(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
s := new(Scheduler)
err := s.Start(ctx, time.Minute)
if err != nil {
t.Errorf("Expect cron scheduler exits when context is canceled")
}
}
// This unit tests demonstrates that if an error is encountered
// when returning a list of ready cronjobs, the process exits
// immediately with an error message.
func TestCron_ErrorList(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockCrons := mock.NewMockCronStore(controller)
mockCrons.EXPECT().Ready(gomock.Any(), gomock.Any()).Return(dummyCronList, sql.ErrNoRows)
s := Scheduler{
commits: nil,
cron: mockCrons,
repos: nil,
trigger: nil,
users: nil,
}
err := s.run(noContext)
if err == nil {
t.Errorf("Want error when the select cron query fails")
}
}
// This unit tests demonstrates that if an error is encountered
// when parsing a cronjob, the system will continue processing
// cron jobs and return an aggregated list of errors.
func TestCron_ErrorCronParse(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockTriggerer := mock.NewMockTriggerer(controller)
mockTriggerer.EXPECT().Trigger(gomock.Any(), dummyRepo, gomock.Any()).Return(nil, nil).Times(1)
mockRepos := mock.NewMockRepositoryStore(controller)
mockRepos.EXPECT().Find(gomock.Any(), dummyCron.RepoID).Return(dummyRepo, nil).Times(1)
mockCrons := mock.NewMockCronStore(controller)
mockCrons.EXPECT().Ready(gomock.Any(), gomock.Any()).Return(dummyCronListInvalid, nil)
mockCrons.EXPECT().Update(gomock.Any(), dummyCron).Times(1)
mockUsers := mock.NewMockUserStore(controller)
mockUsers.EXPECT().Find(gomock.Any(), dummyRepo.UserID).Return(dummyUser, nil).Times(1)
mockCommits := mock.NewMockCommitService(controller)
mockCommits.EXPECT().FindRef(gomock.Any(), dummyUser, dummyRepo.Slug, dummyRepo.Branch).Return(dummyCommit, nil).Times(1)
s := Scheduler{
commits: mockCommits,
cron: mockCrons,
repos: mockRepos,
users: mockUsers,
trigger: mockTriggerer,
}
err := s.run(noContext)
merr := err.(*multierror.Error)
if got, want := len(merr.Errors), 1; got != want {
t.Errorf("Want %d errors, got %d", want, got)
}
}
// This unit tests demonstrates that if an error is encountered
// when finding the associated cron repository, the system will
// continue processing cron jobs and return an aggregated list of
// errors.
func TestCron_ErrorFindRepo(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockTriggerer := mock.NewMockTriggerer(controller)
mockTriggerer.EXPECT().Trigger(gomock.Any(), dummyRepo, gomock.Any()).Return(nil, nil).Times(1)
mockRepos := mock.NewMockRepositoryStore(controller)
mockRepos.EXPECT().Find(gomock.Any(), dummyCron.RepoID).Return(dummyRepo, nil)
mockRepos.EXPECT().Find(gomock.Any(), dummyCron.RepoID).Return(nil, sql.ErrNoRows)
mockCrons := mock.NewMockCronStore(controller)
mockCrons.EXPECT().Ready(gomock.Any(), gomock.Any()).Return(dummyCronListMultiple, nil)
mockCrons.EXPECT().Update(gomock.Any(), dummyCron).Times(2)
mockUsers := mock.NewMockUserStore(controller)
mockUsers.EXPECT().Find(gomock.Any(), dummyRepo.UserID).Return(dummyUser, nil).Times(1)
mockCommits := mock.NewMockCommitService(controller)
mockCommits.EXPECT().FindRef(gomock.Any(), dummyUser, dummyRepo.Slug, dummyRepo.Branch).Return(dummyCommit, nil).Times(1)
s := Scheduler{
commits: mockCommits,
cron: mockCrons,
repos: mockRepos,
users: mockUsers,
trigger: mockTriggerer,
}
err := s.run(noContext)
merr := err.(*multierror.Error)
if got, want := len(merr.Errors), 1; got != want {
t.Errorf("Want %d errors, got %d", want, got)
}
}
// This unit tests demonstrates that if an error is encountered
// when updating the next cron execution time, the system will
// continue processing cron jobs and return an aggregated list
// of errors.
func TestCron_ErrorUpdateCron(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockTriggerer := mock.NewMockTriggerer(controller)
mockTriggerer.EXPECT().Trigger(gomock.Any(), dummyRepo, gomock.Any()).Return(nil, nil).Times(1)
mockRepos := mock.NewMockRepositoryStore(controller)
mockRepos.EXPECT().Find(gomock.Any(), dummyCron.RepoID).Return(dummyRepo, nil).Times(1)
mockCrons := mock.NewMockCronStore(controller)
mockCrons.EXPECT().Ready(gomock.Any(), gomock.Any()).Return(dummyCronListMultiple, nil)
mockCrons.EXPECT().Update(gomock.Any(), dummyCron).Return(nil)
mockCrons.EXPECT().Update(gomock.Any(), dummyCron).Return(sql.ErrNoRows)
mockUsers := mock.NewMockUserStore(controller)
mockUsers.EXPECT().Find(gomock.Any(), dummyRepo.UserID).Return(dummyUser, nil).Times(1)
mockCommits := mock.NewMockCommitService(controller)
mockCommits.EXPECT().FindRef(gomock.Any(), dummyUser, dummyRepo.Slug, dummyRepo.Branch).Return(dummyCommit, nil).Times(1)
s := Scheduler{
commits: mockCommits,
cron: mockCrons,
repos: mockRepos,
users: mockUsers,
trigger: mockTriggerer,
}
err := s.run(noContext)
merr := err.(*multierror.Error)
if got, want := len(merr.Errors), 1; got != want {
t.Errorf("Want %d errors, got %d", want, got)
}
if got, want := merr.Errors[0], sql.ErrNoRows; got != want {
t.Errorf("Want error %v, got %v", want, got)
}
}
// This unit tests demonstrates that if an error is encountered
// when finding the repository owner in the database, the system
// will continue processing cron jobs and return an aggregated
// list of errors.
func TestCron_ErrorFindUser(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockTriggerer := mock.NewMockTriggerer(controller)
mockTriggerer.EXPECT().Trigger(gomock.Any(), dummyRepo, gomock.Any()).Return(nil, nil).Times(1)
mockRepos := mock.NewMockRepositoryStore(controller)
mockRepos.EXPECT().Find(gomock.Any(), dummyCron.RepoID).Return(dummyRepo, nil).Times(2)
mockCrons := mock.NewMockCronStore(controller)
mockCrons.EXPECT().Ready(gomock.Any(), gomock.Any()).Return(dummyCronListMultiple, nil)
mockCrons.EXPECT().Update(gomock.Any(), dummyCron).Times(2)
mockUsers := mock.NewMockUserStore(controller)
mockUsers.EXPECT().Find(gomock.Any(), dummyRepo.UserID).Return(dummyUser, nil).Times(1)
mockUsers.EXPECT().Find(gomock.Any(), dummyRepo.UserID).Return(nil, sql.ErrNoRows).Times(1)
mockCommits := mock.NewMockCommitService(controller)
mockCommits.EXPECT().FindRef(gomock.Any(), dummyUser, dummyRepo.Slug, dummyRepo.Branch).Return(dummyCommit, nil).Times(1)
s := Scheduler{
commits: mockCommits,
cron: mockCrons,
repos: mockRepos,
users: mockUsers,
trigger: mockTriggerer,
}
err := s.run(noContext)
merr := err.(*multierror.Error)
if got, want := len(merr.Errors), 1; got != want {
t.Errorf("Want %d errors, got %d", want, got)
}
if got, want := merr.Errors[0], sql.ErrNoRows; got != want {
t.Errorf("Want error %v, got %v", want, got)
}
}
// This unit tests demonstrates that if an error is encountered
// when communicating with the source code management system, the
// system will continue processing cron jobs and return an aggregated
// list of errors.
func TestCron_ErrorFindCommit(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockTriggerer := mock.NewMockTriggerer(controller)
mockTriggerer.EXPECT().Trigger(gomock.Any(), dummyRepo, gomock.Any()).Return(nil, nil).Times(1)
mockRepos := mock.NewMockRepositoryStore(controller)
mockRepos.EXPECT().Find(gomock.Any(), dummyCron.RepoID).Return(dummyRepo, nil).Times(2)
mockCrons := mock.NewMockCronStore(controller)
mockCrons.EXPECT().Ready(gomock.Any(), gomock.Any()).Return(dummyCronListMultiple, nil)
mockCrons.EXPECT().Update(gomock.Any(), dummyCron).Times(2)
mockUsers := mock.NewMockUserStore(controller)
mockUsers.EXPECT().Find(gomock.Any(), dummyRepo.UserID).Return(dummyUser, nil).Times(2)
mockCommits := mock.NewMockCommitService(controller)
mockCommits.EXPECT().FindRef(gomock.Any(), dummyUser, dummyRepo.Slug, dummyRepo.Branch).Return(dummyCommit, nil).Times(1)
mockCommits.EXPECT().FindRef(gomock.Any(), dummyUser, dummyRepo.Slug, dummyRepo.Branch).Return(nil, sql.ErrNoRows).Times(1)
s := Scheduler{
commits: mockCommits,
cron: mockCrons,
repos: mockRepos,
users: mockUsers,
trigger: mockTriggerer,
}
err := s.run(noContext)
merr := err.(*multierror.Error)
if got, want := len(merr.Errors), 1; got != want {
t.Errorf("Want %d errors, got %d", want, got)
}
if got, want := merr.Errors[0], sql.ErrNoRows; got != want {
t.Errorf("Want error %v, got %v", want, got)
}
}
// This unit tests demonstrates that if an error is encountered
// when triggering a build, the system will continue processing
// cron jobs and return an aggregated list of errors.
func TestCron_ErrorTrigger(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockTriggerer := mock.NewMockTriggerer(controller)
mockTriggerer.EXPECT().Trigger(gomock.Any(), dummyRepo, gomock.Any()).Return(nil, sql.ErrNoRows)
mockTriggerer.EXPECT().Trigger(gomock.Any(), dummyRepo, gomock.Any()).Return(nil, nil)
mockRepos := mock.NewMockRepositoryStore(controller)
mockRepos.EXPECT().Find(gomock.Any(), dummyCron.RepoID).Return(dummyRepo, nil).Times(2)
mockCrons := mock.NewMockCronStore(controller)
mockCrons.EXPECT().Ready(gomock.Any(), gomock.Any()).Return(dummyCronListMultiple, nil)
mockCrons.EXPECT().Update(gomock.Any(), dummyCron).Times(2)
mockUsers := mock.NewMockUserStore(controller)
mockUsers.EXPECT().Find(gomock.Any(), dummyRepo.UserID).Return(dummyUser, nil).Times(2)
mockCommits := mock.NewMockCommitService(controller)
mockCommits.EXPECT().FindRef(gomock.Any(), dummyUser, dummyRepo.Slug, dummyRepo.Branch).Return(dummyCommit, nil).Times(2)
s := Scheduler{
commits: mockCommits,
cron: mockCrons,
repos: mockRepos,
users: mockUsers,
trigger: mockTriggerer,
}
err := s.run(noContext)
merr := err.(*multierror.Error)
if got, want := len(merr.Errors), 1; got != want {
t.Errorf("Want %d errors, got %d", want, got)
}
if got, want := merr.Errors[0], sql.ErrNoRows; got != want {
t.Errorf("Want error %v, got %v", want, got)
}
}
var (
noContext = context.Background()
dummyUser = &core.User{
Login: "octocat",
}
dummyBuild = &core.Build{
Number: dummyRepo.Counter,
RepoID: dummyRepo.ID,
Status: core.StatusPending,
Event: core.EventCron,
Link: "https://github.com/octocat/Hello-World/commit/7fd1a60b01f91b314f59955a4e4d4e80d8edf11d",
Timestamp: 1299283200,
Message: "first commit",
Before: "553c2077f0edc3d5dc5d17262f6aa498e69d6f8e",
After: "7fd1a60b01f91b314f59955a4e4d4e80d8edf11d",
Ref: "refs/heads/master",
Source: "master",
Target: "master",
Author: "octocat",
AuthorName: "The Octocat",
AuthorEmail: "octocat@hello-world.com",
AuthorAvatar: "https://avatars3.githubusercontent.com/u/583231",
Sender: "octocat",
}
dummyRepo = &core.Repository{
ID: 1,
UID: "1296269",
UserID: 2,
Namespace: "octocat",
Name: "Hello-World",
Slug: "octocat/Hello-World",
SCM: "git",
HTTPURL: "https://github.com/octocat/Hello-World.git",
SSHURL: "git@github.com:octocat/Hello-World.git",
Link: "https://github.com/octocat/Hello-World",
Branch: "master",
Private: false,
Visibility: core.VisibilityPublic,
Active: true,
Counter: 42,
Signer: "g9dMChy22QutQM5lrpbe0yCR3f15t1gv",
Secret: "g9dMChy22QutQM5lrpbe0yCR3f15t1gv",
}
dummyCron = &core.Cron{
RepoID: dummyRepo.ID,
Name: "nightly",
Expr: "0 0 * * *",
Next: 2000000000,
Prev: 1000000000,
Branch: "master",
}
dummyCronInvalid = &core.Cron{
RepoID: dummyRepo.ID,
Name: "nightly",
Expr: "A B C D E",
Next: 2000000000,
Prev: 1000000000,
Branch: "master",
}
dummyCronList = []*core.Cron{
dummyCron,
}
dummyCronListMultiple = []*core.Cron{
dummyCron,
dummyCron,
}
dummyCronListInvalid = []*core.Cron{
dummyCronInvalid,
dummyCron,
}
dummyHook = &core.Hook{
Event: core.EventCron,
Link: "https://github.com/octocat/Hello-World/commit/7fd1a60b01f91b314f59955a4e4d4e80d8edf11d",
Timestamp: 1299283200,
Message: "first commit",
Before: "553c2077f0edc3d5dc5d17262f6aa498e69d6f8e",
After: "7fd1a60b01f91b314f59955a4e4d4e80d8edf11d",
Ref: "refs/heads/master",
Source: "master",
Target: "master",
Author: "octocat",
AuthorName: "The Octocat",
AuthorEmail: "octocat@hello-world.com",
AuthorAvatar: "https://avatars3.githubusercontent.com/u/583231",
Sender: "octocat",
Cron: "nightly",
Trigger: "@cron",
}
dummyCommit = &core.Commit{
Sha: dummyHook.After,
Message: dummyHook.Message,
Link: dummyHook.Link,
Committer: &core.Committer{
Name: dummyHook.AuthorName,
Email: dummyHook.AuthorEmail,
Login: dummyHook.Author,
Avatar: dummyHook.AuthorAvatar,
Date: dummyHook.Timestamp,
},
Author: &core.Committer{
Name: dummyHook.AuthorName,
Email: dummyHook.AuthorEmail,
Login: dummyHook.Author,
Avatar: dummyHook.AuthorAvatar,
Date: dummyHook.Timestamp,
},
}
ignoreBuildFields = cmpopts.IgnoreFields(core.Build{},
"Created", "Updated")
)