mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-23 22:27:38 -05:00
Git: More tests. Code refactor.
This commit is contained in:
parent
6f05794bb8
commit
6c6e0e3f73
6 changed files with 279 additions and 20 deletions
|
@ -2,7 +2,6 @@ package setup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
@ -22,22 +21,8 @@ func Git(c *Controller) (middleware.Middleware, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Startup = append(c.Startup, func() error {
|
c.Startup = append(c.Startup, func() error {
|
||||||
// Startup functions are blocking; start
|
// Start service routine in background
|
||||||
// service routine in background
|
git.Start(repo)
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
time.Sleep(repo.Interval)
|
|
||||||
|
|
||||||
err := repo.Pull()
|
|
||||||
if err != nil {
|
|
||||||
if git.Logger == nil {
|
|
||||||
log.Println(err)
|
|
||||||
} else {
|
|
||||||
git.Logger.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Do a pull right away to return error
|
// Do a pull right away to return error
|
||||||
return repo.Pull()
|
return repo.Pull()
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package setup
|
package setup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -13,18 +16,83 @@ func init() {
|
||||||
git.SetOS(gittest.FakeOS)
|
git.SetOS(gittest.FakeOS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func check(t *testing.T, err error) {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no errors, but got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGit(t *testing.T) {
|
func TestGit(t *testing.T) {
|
||||||
c := newTestController(`git git@github.com:mholt/caddy.git`)
|
c := newTestController(`git git@github.com:mholt/caddy.git`)
|
||||||
|
|
||||||
mid, err := Git(c)
|
mid, err := Git(c)
|
||||||
if err != nil {
|
check(t, err)
|
||||||
t.Errorf("Expected no errors, but got: %v", err)
|
|
||||||
}
|
|
||||||
if mid != nil {
|
if mid != nil {
|
||||||
t.Fatal("Git middleware is a background service and expected to be nil.")
|
t.Fatal("Git middleware is a background service and expected to be nil.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIntervals(t *testing.T) {
|
||||||
|
tests := []string{
|
||||||
|
`git git@github.com:user/repo { interval 10 }`,
|
||||||
|
`git git@github.com:user/repo { interval 5 }`,
|
||||||
|
`git git@github.com:user/repo { interval 2 }`,
|
||||||
|
`git git@github.com:user/repo { interval 1 }`,
|
||||||
|
`git git@github.com:user/repo { interval 6 }`,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
git.Logger = nil
|
||||||
|
|
||||||
|
c1 := newTestController(test)
|
||||||
|
repo, err := gitParse(c1)
|
||||||
|
check(t, err)
|
||||||
|
|
||||||
|
c2 := newTestController(test)
|
||||||
|
_, err = Git(c2)
|
||||||
|
check(t, err)
|
||||||
|
|
||||||
|
// start startup services
|
||||||
|
err = c2.Startup[0]()
|
||||||
|
check(t, err)
|
||||||
|
|
||||||
|
// wait for first background pull
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
|
||||||
|
// switch logger to test file
|
||||||
|
logFile := gittest.Open("file")
|
||||||
|
git.Logger = log.New(logFile, "", 0)
|
||||||
|
|
||||||
|
// sleep for the interval
|
||||||
|
time.Sleep(repo.Interval)
|
||||||
|
|
||||||
|
// get log output
|
||||||
|
out, err := ioutil.ReadAll(logFile)
|
||||||
|
check(t, err)
|
||||||
|
|
||||||
|
// if greater than minimum interval
|
||||||
|
if repo.Interval >= time.Second*5 {
|
||||||
|
expected := `https://github.com/user/repo.git pulled.
|
||||||
|
No new changes.`
|
||||||
|
|
||||||
|
// ensure pull is done by tracing the output
|
||||||
|
if expected != strings.TrimSpace(string(out)) {
|
||||||
|
t.Errorf("Test %v: Expected %v found %v", i, expected, string(out))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ensure pull is ignored by confirming no output
|
||||||
|
if string(out) != "" {
|
||||||
|
t.Errorf("Test %v: Expected no output but found %v", i, string(out))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop background thread monitor
|
||||||
|
git.Monitor.StopAndWait(repo.URL, 1)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestGitParse(t *testing.T) {
|
func TestGitParse(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
|
|
|
@ -33,6 +33,9 @@ var initMutex = sync.Mutex{}
|
||||||
// Logger is used to log errors; if nil, the default log.Logger is used.
|
// Logger is used to log errors; if nil, the default log.Logger is used.
|
||||||
var Logger *log.Logger
|
var Logger *log.Logger
|
||||||
|
|
||||||
|
// Monitor listens for halt signal to stop repositories from auto pulling.
|
||||||
|
var Monitor = &monitor{}
|
||||||
|
|
||||||
// logger is an helper function to retrieve the available logger
|
// logger is an helper function to retrieve the available logger
|
||||||
func logger() *log.Logger {
|
func logger() *log.Logger {
|
||||||
if Logger == nil {
|
if Logger == nil {
|
||||||
|
|
|
@ -139,6 +139,39 @@ Command echo Hello successful.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// timeout checks
|
||||||
|
timeoutTests := []struct {
|
||||||
|
repo *Repo
|
||||||
|
shouldPull bool
|
||||||
|
}{
|
||||||
|
{&Repo{Interval: time.Millisecond * 4900}, false},
|
||||||
|
{&Repo{Interval: time.Millisecond * 1}, false},
|
||||||
|
{&Repo{Interval: time.Second * 5}, true},
|
||||||
|
{&Repo{Interval: time.Second * 10}, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, r := range timeoutTests {
|
||||||
|
r.repo = createRepo(r.repo)
|
||||||
|
|
||||||
|
err := r.repo.Prepare()
|
||||||
|
check(t, err)
|
||||||
|
err = r.repo.Pull()
|
||||||
|
check(t, err)
|
||||||
|
|
||||||
|
before := r.repo.lastPull
|
||||||
|
|
||||||
|
time.Sleep(r.repo.Interval)
|
||||||
|
|
||||||
|
err = r.repo.Pull()
|
||||||
|
after := r.repo.lastPull
|
||||||
|
check(t, err)
|
||||||
|
|
||||||
|
expected := after.After(before)
|
||||||
|
if expected != r.shouldPull {
|
||||||
|
t.Errorf("Pull with Error %v: Expected %v found %v", i, expected, r.shouldPull)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createRepo(r *Repo) *Repo {
|
func createRepo(r *Repo) *Repo {
|
||||||
|
|
110
middleware/git/service.go
Normal file
110
middleware/git/service.go
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RepoService is the repository service that runs in background and
|
||||||
|
// periodic pull from the repository.
|
||||||
|
type RepoService struct {
|
||||||
|
repo *Repo
|
||||||
|
running bool // whether service is running.
|
||||||
|
halt chan struct{} // channel to notify service to halt and stop pulling.
|
||||||
|
exit chan struct{} // channel to notify on exit.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts a new RepoService in background and adds it to monitor.
|
||||||
|
func Start(repo *Repo) {
|
||||||
|
service := &RepoService{
|
||||||
|
repo,
|
||||||
|
true,
|
||||||
|
make(chan struct{}),
|
||||||
|
make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// start service
|
||||||
|
go func(s *RepoService) {
|
||||||
|
for {
|
||||||
|
// if service is halted
|
||||||
|
if !s.running {
|
||||||
|
// notify exit channel
|
||||||
|
service.exit <- struct{}{}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(repo.Interval)
|
||||||
|
|
||||||
|
err := repo.Pull()
|
||||||
|
if err != nil {
|
||||||
|
logger().Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(service)
|
||||||
|
|
||||||
|
// add to monitor to enable halting
|
||||||
|
Monitor.add(service)
|
||||||
|
}
|
||||||
|
|
||||||
|
// monitor monitors running services (RepoService)
|
||||||
|
// and can halt them.
|
||||||
|
type monitor struct {
|
||||||
|
services []*RepoService
|
||||||
|
sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// add adds a new service to the monitor.
|
||||||
|
func (m *monitor) add(service *RepoService) {
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
|
||||||
|
m.services = append(m.services, service)
|
||||||
|
|
||||||
|
// start a goroutine to listen for halt signal
|
||||||
|
service.running = true
|
||||||
|
go func(r *RepoService) {
|
||||||
|
<-r.halt
|
||||||
|
r.running = false
|
||||||
|
}(service)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops at most `limit` currently running services that is pulling from git repo at
|
||||||
|
// repoURL. It returns list of exit channels for the services. A wait for message on the
|
||||||
|
// channels guarantees exit. If limit is less than zero, it is ignored.
|
||||||
|
// TODO find better ways to identify repos
|
||||||
|
func (m *monitor) Stop(repoURL string, limit int) []chan struct{} {
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
|
||||||
|
var chans []chan struct{}
|
||||||
|
|
||||||
|
// locate services
|
||||||
|
for i, j := 0, 0; i < len(m.services) && ((limit >= 0 && j < limit) || limit < 0); i++ {
|
||||||
|
s := m.services[i]
|
||||||
|
if s.repo.URL == repoURL {
|
||||||
|
// send halt signal
|
||||||
|
s.halt <- struct{}{}
|
||||||
|
chans = append(chans, s.exit)
|
||||||
|
j++
|
||||||
|
m.services[i] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove them from services list
|
||||||
|
services := m.services[:0]
|
||||||
|
for _, s := range m.services {
|
||||||
|
if s != nil {
|
||||||
|
services = append(services, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.services = services
|
||||||
|
return chans
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopAndWait is similar to stop but it waits for the services to terminate before
|
||||||
|
// returning.
|
||||||
|
func (m *monitor) StopAndWait(repoUrl string, limit int) {
|
||||||
|
chans := m.Stop(repoUrl, limit)
|
||||||
|
for _, c := range chans {
|
||||||
|
<-c
|
||||||
|
}
|
||||||
|
}
|
60
middleware/git/service_test.go
Normal file
60
middleware/git/service_test.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware/git/gittest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
SetOS(gittest.FakeOS)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test(t *testing.T) {
|
||||||
|
repo := &Repo{URL: "git@github.com", Interval: time.Second}
|
||||||
|
|
||||||
|
Start(repo)
|
||||||
|
if len(Monitor.services) != 1 {
|
||||||
|
t.Errorf("Expected 1 service, found %v", len(Monitor.services))
|
||||||
|
}
|
||||||
|
|
||||||
|
Monitor.StopAndWait(repo.URL, 1)
|
||||||
|
if len(Monitor.services) != 0 {
|
||||||
|
t.Errorf("Expected 1 service, found %v", len(Monitor.services))
|
||||||
|
}
|
||||||
|
|
||||||
|
repos := make([]*Repo, 5)
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
repos[i] = &Repo{URL: fmt.Sprintf("test%v", i), Interval: time.Second * 2}
|
||||||
|
Start(repos[i])
|
||||||
|
if len(Monitor.services) != i+1 {
|
||||||
|
t.Errorf("Expected %v service(s), found %v", i+1, len(Monitor.services))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Second * 5)
|
||||||
|
Monitor.StopAndWait(repos[0].URL, 1)
|
||||||
|
if len(Monitor.services) != 4 {
|
||||||
|
t.Errorf("Expected %v service(s), found %v", 4, len(Monitor.services))
|
||||||
|
}
|
||||||
|
|
||||||
|
repo = &Repo{URL: "git@github.com", Interval: time.Second}
|
||||||
|
Start(repo)
|
||||||
|
if len(Monitor.services) != 5 {
|
||||||
|
t.Errorf("Expected %v service(s), found %v", 5, len(Monitor.services))
|
||||||
|
}
|
||||||
|
|
||||||
|
repo = &Repo{URL: "git@github.com", Interval: time.Second * 2}
|
||||||
|
Start(repo)
|
||||||
|
if len(Monitor.services) != 6 {
|
||||||
|
t.Errorf("Expected %v service(s), found %v", 6, len(Monitor.services))
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Second * 5)
|
||||||
|
Monitor.StopAndWait(repo.URL, -1)
|
||||||
|
if len(Monitor.services) != 4 {
|
||||||
|
t.Errorf("Expected %v service(s), found %v", 4, len(Monitor.services))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue