489 lines
16 KiB
Go
489 lines
16 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 != context.Canceled {
|
|
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")
|
|
)
|