Skip to content

Commit

Permalink
add unit tests, custom errors and makefile
Browse files Browse the repository at this point in the history
  • Loading branch information
fcgravalos committed May 16, 2020
1 parent 96a2844 commit 05fe5eb
Show file tree
Hide file tree
Showing 23 changed files with 716 additions and 71 deletions.
19 changes: 19 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
GO111MODULE=on
GONFIGD_BINARY_NAME=gonfigd
GONFIGD_VERSION=1.0.0
BUILD_FLAGS=-ldflags "-X main.version=v${GONFIGD_VERSION}"

fmt:
go fmt ./...

vet:
go vet ./...

test: fmt vet
go test -v ./gonfig... ./fswatcher... ./kv/... ./pubsub/... -coverprofile cover.out

tidy:
go mod tidy

build: fmt vet test tidy
go build ${BUILD_FLAGS} -o bin/${GONFIGD_BINARY_NAME} main.go
6 changes: 4 additions & 2 deletions api/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ func (s *server) WatchConfig(req *WatchConfigRequest, stream Gonfig_WatchConfigS
return err
}
}
sID, sCh := s.Subscribe(req.ConfigPath)
sub, _ := s.Subscribe(req.ConfigPath)
sID := sub.ID()
sCh := sub.Channel()
defer s.UnSubscribe(req.ConfigPath, sID)

ctx := stream.Context()
Expand All @@ -42,7 +44,7 @@ func (s *server) WatchConfig(req *WatchConfigRequest, stream Gonfig_WatchConfigS
Event: ev.String(),
}
if err := stream.Send(resp); err != nil {
s.Error().Msgf("failed to send response %v trhough stream: %v", resp, err)
s.Error().Msgf("failed to send response %v through stream: %v", resp, err)
return err
}
s.Info().Msgf("event %s sent to subscription ID %s", resp.Event, resp.SubscriptionID)
Expand Down
25 changes: 12 additions & 13 deletions fswatcher/fswatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package fswatcher
import (
"context"
"crypto/md5"
"errors"
"fmt"
"io/ioutil"
"os"
Expand Down Expand Up @@ -56,7 +55,7 @@ func (r *registry) unregister(path string) {
r.Unlock()
}

func (fsw *fsWatcher) isValidFileName(name string) bool {
func isValidFileName(name string) bool {
// Discard hidden files
if match, _ := filepath.Match("\\.*", name); match {
return false
Expand All @@ -67,12 +66,12 @@ func (fsw *fsWatcher) isValidFileName(name string) bool {
return true
}

func (fsw *fsWatcher) isValidFile(name string) bool {
func isValidFile(name string) bool {
fi, err := os.Stat(name)
if err != nil {
return false
}
return fi.Mode().IsRegular() && fsw.isValidFileName(filepath.Base(name))
return fi.Mode().IsRegular() && isValidFileName(filepath.Base(name))
}

func (fsw *fsWatcher) upsertFileOnDb(path string) (bool, error) {
Expand Down Expand Up @@ -101,8 +100,8 @@ func (fsw *fsWatcher) walk(path string, fi os.FileInfo, err error) error {
fsw.log.Info().Msgf("adding watcher for %s", abs)
return fsw.watcher.Add(path)

// If it's a regular file, we don't have a have set a fsnotify watch but we check if it's stored in db
} else if fsw.isValidFile(path) {
// If it's a regular file, we don't have to set a fsnotify watch but we check if it's stored in db
} else if isValidFile(path) {
if _, err := fsw.kv.Get(path); err != nil {
fsw.log.Warn().Msgf("missing file %s in kv, inserting and creating event", path)
return fsw.createEventHandler(path)
Expand All @@ -112,7 +111,7 @@ func (fsw *fsWatcher) walk(path string, fi os.FileInfo, err error) error {
}

func (fsw *fsWatcher) publishEvent(config string, evType pubsub.EventType) error {
ev := pubsub.CreateEvent(evType, config)
ev := pubsub.NewEvent(evType, config)
if !fsw.ps.TopicExists(config) {
err := fsw.ps.CreateTopic(config)
if err != nil {
Expand Down Expand Up @@ -157,15 +156,15 @@ func (fsw *fsWatcher) removeEventHandler(name string) error {

func (fsw *fsWatcher) routeEvent(ev fsnotify.Event) {
evOp := ev.Op.String()
err := errors.New("")
var err error
switch evOp {
case "CREATE":
if fsw.isValidFile(ev.Name) {
if isValidFile(ev.Name) {
err = fsw.createEventHandler(ev.Name)
}
break
case "WRITE":
if fsw.isValidFile(ev.Name) {
if isValidFile(ev.Name) {
err = fsw.writeEventHandler(ev.Name)
}
break
Expand All @@ -178,13 +177,13 @@ func (fsw *fsWatcher) routeEvent(ev fsnotify.Event) {
}

if err != nil {
fsw.log.Error().Msgf("error while handling %s event for %s: %v\n", evOp, ev.Name, err)
fsw.log.Error().Msgf("error while handling %s event for %s: %v", evOp, ev.Name, err)
}
}

// Start creates a new fsWatcher
// It will return an error if it's not able to create a *fsnotify.Watcer
func Start(ctx context.Context, root string, kv kv.KV, ps pubsub.PubSub, logger zerolog.Logger) error {
func Start(ctx context.Context, root string, fwalkInterval time.Duration, kv kv.KV, ps pubsub.PubSub, logger zerolog.Logger) error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
logger.Error().Msgf("failed to create new fsnotify watcher: %v", err)
Expand All @@ -200,7 +199,7 @@ func Start(ctx context.Context, root string, kv kv.KV, ps pubsub.PubSub, logger
go func(ctx context.Context, root string, fsw *fsWatcher, stopCh chan struct{}) {
for {
select {
case <-time.After(5 * time.Second):
case <-time.After(fwalkInterval):
fsw.log.Debug().Msgf("walking %s directory", root)
filepath.Walk(root, fsw.walk)
break
Expand Down
187 changes: 187 additions & 0 deletions fswatcher/fswatcher_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package fswatcher

import (
"context"
"fmt"
"io/ioutil"
"log"
"os"
"testing"
"time"

"github.com/fcgravalos/gonfigd/kv"
"github.com/fcgravalos/gonfigd/pubsub"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
)

type testConfig struct {
kv kv.KV
ps pubsub.PubSub
log zerolog.Logger
root string
}

var testCfg *testConfig

func TestUpsertFileOnDb(t *testing.T) {
fullPath := fmt.Sprintf("%s/test.yaml", testCfg.root)
err := ioutil.WriteFile(fullPath, []byte("foo: bar"), 0644)
if err != nil {
panic(err)
}
fsw := &fsWatcher{kv: testCfg.kv, ps: testCfg.ps}
changed, e1 := fsw.upsertFileOnDb(fullPath)
assert.Nil(t, e1)
assert.True(t, changed)

v1, e2 := testCfg.kv.Get(fmt.Sprintf("%s/test.yaml", testCfg.root))
assert.Nil(t, e2)
assert.Equal(t, "foo: bar", v1.Text())

changed2, e3 := fsw.upsertFileOnDb(fullPath)
assert.Nil(t, e3)
assert.False(t, changed2)

f, err := os.OpenFile(fullPath, os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
defer f.Close()
if _, err := f.WriteString("\nbar: baz"); err != nil {
panic(err)
}
changed3, e4 := fsw.upsertFileOnDb(fullPath)
assert.Nil(t, e4)
assert.True(t, changed3)

v2, e5 := testCfg.kv.Get(fmt.Sprintf("%s/test.yaml", testCfg.root))
assert.Nil(t, e5)
assert.Equal(t, "foo: bar\nbar: baz", v2.Text())
}

func TestIsValidFileName(t *testing.T) {
assert.True(t, isValidFileName("foo.yaml"))
assert.False(t, isValidFileName("foo.swp"))
assert.False(t, isValidFileName("foo.swx"))
assert.False(t, isValidFileName("foo.~"))
assert.False(t, isValidFileName("foo.tmp"))
}

func TestIsValidFile(t *testing.T) {
assert.False(t, isValidFile("foo.yaml"))

fp1 := fmt.Sprintf("%s/foo.yaml", testCfg.root)
e1 := ioutil.WriteFile(fp1, []byte("foo: bar"), 0644)
if e1 != nil {
panic(e1)
}
assert.True(t, isValidFile(fmt.Sprintf("%s/foo.yaml", testCfg.root)))

fp2 := fmt.Sprintf("%s/foo.swp", testCfg.root)
e2 := ioutil.WriteFile(fp2, []byte("foo: bar"), 0644)
if e2 != nil {
panic(e2)
}
assert.False(t, isValidFile(fmt.Sprintf("%s/foo.swp", testCfg.root)))
}

func TestPublishEvent(t *testing.T) {
fsw := &fsWatcher{ps: testCfg.ps}
e1 := fsw.ps.CreateTopic("foo/config.yaml")
assert.Nil(t, e1)

sub, e2 := fsw.ps.Subscribe("foo/config.yaml")
assert.Nil(t, e2)

var ev *pubsub.Event

done := make(chan struct{})

go func(done chan struct{}) {
scH := sub.Channel()
ev = <-scH
done <- struct{}{}
}(done)

e3 := fsw.publishEvent("foo/config.yaml", pubsub.ConfigCreated)
assert.Nil(t, e3)
<-done
assert.Equal(t, "foo/config.yaml", ev.ConfigPath())
assert.Equal(t, pubsub.ConfigCreated, ev.Kind())
}

func TestStart(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

fp := fmt.Sprintf("%s/test-start.yaml", testCfg.root)
testCfg.ps.CreateTopic(fp)
sub, _ := testCfg.ps.Subscribe(fp)
sCh := sub.Channel()

go Start(ctx, testCfg.root, 5*time.Second, testCfg.kv, testCfg.ps, testCfg.log)

err := ioutil.WriteFile(fp, []byte("foo: bar"), 0644)
if err != nil {
panic(err)
}

ev1 := <-sCh
assert.Equal(t, pubsub.ConfigCreated, ev1.Kind())

v1, e1 := testCfg.kv.Get(fp)
assert.Nil(t, e1)
assert.Equal(t, "foo: bar", v1.Text())

f, err := os.OpenFile(fp, os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
defer f.Close()
if _, err := f.WriteString("\nbar: baz"); err != nil {
panic(err)
}

ev2 := <-sCh
assert.Equal(t, pubsub.ConfigUpdated, ev2.Kind())

v2, e2 := testCfg.kv.Get(fp)
assert.Nil(t, e2)
assert.Equal(t, "foo: bar\nbar: baz", v2.Text())

os.Remove(fp)

ev3 := <-sCh
assert.Equal(t, pubsub.ConfigDeleted, ev3.Kind())

v3, e3 := testCfg.kv.Get(fp)
assert.Nil(t, v3)
assert.True(t, kv.IsKeyNotFoundError(e3))
assert.EqualError(t, e3, fmt.Sprintf("[%s] Key %s not found in KV", kv.KeyNotFound, fp))
}

func TestMain(m *testing.M) {
dir, err := ioutil.TempDir("", fmt.Sprintf("fswatcher-tests-%s", time.Now()))
if err != nil {
log.Fatal(err)
}
defer os.Remove(dir)

kv, _ := kv.NewKV(kv.INMEMORY)
ps, _ := pubsub.NewPubSub(pubsub.INMEMORY)

logger := zerolog.New(os.Stderr).
With().
Timestamp().
Caller().
Logger()

testCfg = &testConfig{
kv: kv,
ps: ps,
log: logger,
root: dir,
}
os.Exit(m.Run())
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/golang/protobuf v1.4.1
github.com/google/uuid v1.1.1
github.com/rs/zerolog v1.18.0
github.com/stretchr/testify v1.5.1
google.golang.org/grpc v1.29.1
google.golang.org/protobuf v1.22.0
)
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
Expand All @@ -29,10 +31,16 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.18.0 h1:CbAm3kP2Tptby1i9sYy2MGRg0uxIN9cyDb59Ys7W8z8=
github.com/rs/zerolog v1.18.0/go.mod h1:9nvC1axdVrAHcu/s9taAVfBuIdTZLVQmKQyvrUjF5+I=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
Expand Down Expand Up @@ -80,5 +88,9 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0 h1:cJv5/xdbk1NnMPR1VP9+HU6gupuG9MLBoH1r6RHZ2MY=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
Loading

0 comments on commit 05fe5eb

Please sign in to comment.