diff --git a/.travis.yml b/.travis.yml index d3013444..17e48090 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,6 @@ -language: go - -go: - - "1.10.x" - -before_install: - - go get golang.org/x/tools/cmd/cover - - go get github.com/mattn/goveralls - install: - docker --version - docker-compose --version script: - - docker build . - -after_success: - - go test $(go list -e ./... | grep -v vendor) -covermode=count -coverprofile=coverage.out - - goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN + - docker build --build-arg COVERALLS_TOKEN=$COVERALLS_TOKEN --build-arg TRAVIS_JOB_ID=$TRAVIS_JOB_ID --build-arg TRAVIS_PULL_REQUEST=$TRAVIS_PULL_REQUEST --build-arg TRAVIS_BRANCH=$TRAVIS_BRANCH . diff --git a/Dockerfile b/Dockerfile index 359e1afa..41b5a4a2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,15 @@ FROM umputun/baseimage:buildgo-latest as build-backend +ARG COVERALLS_TOKEN +ARG TRAVIS_JOB_ID +ARG TRAVIS_PULL_REQUEST +ARG TRAVIS_BRANCH + WORKDIR /go/src/github.com/umputun/remark ADD app /go/src/github.com/umputun/remark/app ADD vendor /go/src/github.com/umputun/remark/vendor +ADD .git /go/src/github.com/umputun/remark/.git RUN cd app && go test $(go list -e ./... | grep -v vendor) @@ -13,7 +19,11 @@ RUN gometalinter --disable-all --deadline=300s --vendor --enable=vet --enable=ve RUN mkdir -p target && /script/coverage.sh -ADD .git /go/src/github.com/umputun/remark/.git +RUN if [ "x$COVERALLS_TOKEN" = "x" ] ; then \ + echo coverall not enabled ; \ + else go get github.com/mattn/goveralls && \ + goveralls -coverprofile=.cover/cover.out -service=travis-ci -repotoken $COVERALLS_TOKEN; fi + RUN go build -o remark -ldflags "-X main.revision=$(git rev-parse --abbrev-ref HEAD)-$(git describe --abbrev=7 --always --tags)-$(date +%Y%m%d-%H:%M:%S) -s -w" ./app diff --git a/app/main.go b/app/main.go index 3b86d3d9..64e4eb79 100644 --- a/app/main.go +++ b/app/main.go @@ -1,11 +1,14 @@ package main import ( + "context" "fmt" "log" "net/http" "os" + "os/signal" "strings" + "syscall" "time" "github.com/coreos/bbolt" @@ -22,7 +25,8 @@ import ( "github.com/umputun/remark/app/rest/proxy" ) -var opts struct { +// Opts with command line flags and env +type Opts struct { BoltPath string `long:"bolt" env:"BOLTDB_PATH" default:"./var" description:"parent dir for bolt files"` Sites []string `long:"site" env:"SITE" default:"remark" description:"site names" env-delim:","` RemarkURL string `long:"url" env:"REMARK_URL" default:"https://remark42.com" description:"url to remark"` @@ -58,47 +62,62 @@ var opts struct { var revision = "unknown" +// Application holds all active objects +type Application struct { + Opts + srv *api.Rest + importer *api.Import + exporter migrator.Exporter + terminated chan struct{} +} + func main() { fmt.Printf("remark %s\n", revision) + + var opts Opts p := flags.NewParser(&opts, flags.Default) if _, e := p.ParseArgs(os.Args[1:]); e != nil { os.Exit(1) } - - setupLog(opts.Dbg) log.Print("[INFO] started remark") - if err := makeDirs(opts.BoltPath, opts.BackupLocation, opts.AvatarStore); err != nil { - log.Fatalf("[ERROR] can't create directories, %+v", err) + ctx, cancel := context.WithCancel(context.Background()) + go func() { // catch signal and invoke graceful termination + stop := make(chan os.Signal, 1) + signal.Notify(stop, os.Interrupt, syscall.SIGTERM) + <-stop + log.Print("[WARN] interrupt signal") + cancel() + }() + + app, err := New(opts) + if err != nil { + log.Fatalf("[ERROR] failed to setup application, %+v", err) } + err = app.Run(ctx) + log.Printf("[INFO] remark terminated %s", err) +} - dataStore := makeBoltStore(opts.Sites) +// New prepares application and return it with all active parts +// doesn't start anything +func New(opts Opts) (*Application, error) { + setupLog(opts.Dbg) - if opts.DevPasswd != "" { - log.Printf("[WARN] running in dev mode") + if err := makeDirs(opts.BoltPath, opts.BackupLocation, opts.AvatarStore); err != nil { + return nil, err } dataService := service.DataStore{ - Interface: dataStore, + Interface: makeBoltStore(opts.Sites, opts.BoltPath), EditDuration: 5 * time.Minute, Secret: opts.SecretKey, MaxCommentSize: opts.MaxCommentSize, } - exporter := migrator.Remark{DataStore: &dataService} - cache := rest.NewLoadingCache(rest.MaxValueSize(opts.MaxCachedValue), rest.MaxKeys(opts.MaxCachedItems), - rest.PostFlushFn(postFlushFn)) + cache := rest.NewLoadingCache(rest.MaxValSize(opts.MaxCachedValue), rest.MaxKeys(opts.MaxCachedItems), + rest.PostFlushFn(postFlushFn(opts.Sites, opts.Port))) - activateBackup(&exporter) - - importSrv := api.Import{ - Version: revision, - Cache: cache, - NativeImporter: &migrator.Remark{DataStore: &dataService}, - DisqusImporter: &migrator.Disqus{DataStore: &dataService}, - SecretKey: opts.SecretKey, - } - go importSrv.Run(opts.Port + 1) + jwtService := auth.NewJWT(opts.SecretKey, strings.HasPrefix(opts.RemarkURL, "https://"), 7*24*time.Hour) avatarProxy := &proxy.Avatar{ StorePath: opts.AvatarStore, @@ -106,46 +125,79 @@ func main() { RemarkURL: strings.TrimSuffix(opts.RemarkURL, "/"), } - jwtService := auth.NewJWT(opts.SecretKey, strings.HasPrefix(opts.RemarkURL, "https://"), 7*24*time.Hour) + exporter := &migrator.Remark{DataStore: &dataService} - srv := api.Rest{ + importer := &api.Import{ + Version: revision, + Cache: cache, + NativeImporter: &migrator.Remark{DataStore: &dataService}, + DisqusImporter: &migrator.Disqus{DataStore: &dataService}, + SecretKey: opts.SecretKey, + } + + srv := &api.Rest{ Version: revision, DataService: dataService, - Exporter: &exporter, + Exporter: exporter, WebRoot: opts.WebRoot, ImageProxy: proxy.Image{Enabled: opts.ImageProxy, RoutePath: "/api/v1/img", RemarkURL: opts.RemarkURL}, Authenticator: auth.Authenticator{ JWTService: jwtService, Admins: opts.Admins, - Providers: makeAuthProviders(jwtService, avatarProxy), + Providers: makeAuthProviders(jwtService, avatarProxy, opts), AvatarProxy: avatarProxy, DevPasswd: opts.DevPasswd, }, Cache: cache, } srv.ScoreThresholds.Low, srv.ScoreThresholds.Critical = opts.LowScore, opts.CriticalScore - srv.Run(opts.Port) + tch := make(chan struct{}) + return &Application{srv: srv, importer: importer, exporter: exporter, Opts: opts, terminated: tch}, nil +} + +// Run all application objects +func (a *Application) Run(ctx context.Context) error { + if a.DevPasswd != "" { + log.Printf("[WARN] running in dev mode") + } + + go func() { + // shutdown on context cancellation + <-ctx.Done() + a.srv.Shutdown() + a.importer.Shutdown() + }() + a.activateBackup(ctx) // runs in goroutine for each site + go a.importer.Run(a.Port + 1) + a.srv.Run(a.Port) + close(a.terminated) + return nil +} + +// Wait for application completion (termination) +func (a *Application) Wait() { + <-a.terminated } // activateBackup runs background backups for each site -func activateBackup(exporter migrator.Exporter) { - for _, siteID := range opts.Sites { +func (a *Application) activateBackup(ctx context.Context) { + for _, siteID := range a.Sites { backup := migrator.AutoBackup{ - Exporter: exporter, - BackupLocation: opts.BackupLocation, + Exporter: a.exporter, + BackupLocation: a.BackupLocation, SiteID: siteID, - KeepMax: opts.MaxBackupFiles, + KeepMax: a.MaxBackupFiles, Duration: 24 * time.Hour, } - go backup.Do() + go backup.Do(ctx) } } // makeBoltStore creates store for all sites -func makeBoltStore(siteNames []string) engine.Interface { +func makeBoltStore(siteNames []string, path string) engine.Interface { sites := []engine.BoltSite{} for _, site := range siteNames { - sites = append(sites, engine.BoltSite{SiteID: site, FileName: fmt.Sprintf("%s/%s.db", opts.BoltPath, site)}) + sites = append(sites, engine.BoltSite{SiteID: site, FileName: fmt.Sprintf("%s/%s.db", path, site)}) } result, err := engine.NewBoltDB(bolt.Options{Timeout: 30 * time.Second}, sites...) if err != nil { @@ -183,7 +235,7 @@ func makeDirs(dirs ...string) error { return nil } -func makeAuthProviders(jwtService *auth.JWT, avatarProxy *proxy.Avatar) (providers []auth.Provider) { +func makeAuthProviders(jwtService *auth.JWT, avatarProxy *proxy.Avatar, opts Opts) (providers []auth.Provider) { makeParams := func(cid, secret string) auth.Params { return auth.Params{ @@ -214,23 +266,25 @@ func makeAuthProviders(jwtService *auth.JWT, avatarProxy *proxy.Avatar) (provide } // post-flush callback invoked by cache after each flush in async way -func postFlushFn() { +func postFlushFn(sites []string, port int) func() { - // list of heavy urls for pre-heating on cache change - urls := []string{ - "http://localhost:%d/api/v1/list?site=%s", - "http://localhost:%d/api/v1/last/50?site=%s", - } + return func() { + // list of heavy urls for pre-heating on cache change + urls := []string{ + "http://localhost:%d/api/v1/list?site=%s", + "http://localhost:%d/api/v1/last/50?site=%s", + } - for _, site := range opts.Sites { - for _, u := range urls { - resp, err := http.Get(fmt.Sprintf(u, opts.Port, site)) - if err != nil { - log.Printf("[WARN] failed to refresh cached list for %s, %s", site, err) - return - } - if err = resp.Body.Close(); err != nil { - log.Printf("[WARN] failed to close response body, %s", err) + for _, site := range sites { + for _, u := range urls { + resp, err := http.Get(fmt.Sprintf(u, port, site)) + if err != nil { + log.Printf("[WARN] failed to refresh cached list for %s, %s", site, err) + return + } + if err = resp.Body.Close(); err != nil { + log.Printf("[WARN] failed to close response body, %s", err) + } } } } diff --git a/app/main_test.go b/app/main_test.go new file mode 100644 index 00000000..287fb10b --- /dev/null +++ b/app/main_test.go @@ -0,0 +1,88 @@ +package main + +import ( + "context" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "strings" + "syscall" + "testing" + "time" + + flags "github.com/jessevdk/go-flags" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestApplication(t *testing.T) { + app, ctx := prepApp(t, 18080, 500*time.Millisecond) + go app.Run(ctx) + time.Sleep(100 * time.Millisecond) // let server start + + // send ping + resp, err := http.Get("http://localhost:18080/api/v1/ping") + require.Nil(t, err) + defer resp.Body.Close() + assert.Equal(t, 200, resp.StatusCode) + body, err := ioutil.ReadAll(resp.Body) + assert.Nil(t, err) + assert.Equal(t, "pong", string(body)) + + // add comment + resp, err = http.Post("http://dev:password@localhost:18080/api/v1/comment", "json", + strings.NewReader(`{"text": "test 123", "locator":{"url": "https://radio-t.com/blah1", "site": "remark"}}`)) + require.Nil(t, err) + assert.Equal(t, http.StatusCreated, resp.StatusCode) + body, _ = ioutil.ReadAll(resp.Body) + t.Log(string(body)) + app.Wait() +} + +func TestApplicationShutdown(t *testing.T) { + app, ctx := prepApp(t, 18090, 500*time.Millisecond) + st := time.Now() + app.Run(ctx) + assert.True(t, time.Since(st).Seconds() < 1, "should take about 500msec") + app.Wait() +} + +func TestApplicationMainSignal(t *testing.T) { + os.Args = []string{"test", "--secret=123456", "--bolt=/tmp/xyz", "--backup=/tmp", "--avatars=/tmp", "--port=18100"} + go func() { + time.Sleep(100 * time.Millisecond) + syscall.Kill(syscall.Getpid(), syscall.SIGTERM) + }() + st := time.Now() + main() + assert.True(t, time.Since(st).Seconds() < 1, "should take about 500msec") +} + +func prepApp(t *testing.T, port int, duration time.Duration) (*Application, context.Context) { + // prepare options + opts := Opts{} + p := flags.NewParser(&opts, flags.Default) + p.ParseArgs([]string{"--secret=123456", "--dev-passwd=password"}) + opts.AvatarStore, opts.BackupLocation = "/tmp", "/tmp" + opts.BoltPath = fmt.Sprintf("/tmp/%d", port) + opts.GithubCSEC, opts.GithubCID = "csec", "cid" + opts.GoogleCSEC, opts.GoogleCID = "csec", "cid" + opts.FacebookCSEC, opts.FacebookCID = "csec", "cid" + opts.Port = port + + os.Remove(opts.BoltPath + "/remark.db") + + // create app + app, err := New(opts) + require.Nil(t, err) + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + time.Sleep(duration) + log.Print("[TEST] terminate app") + cancel() + }() + return app, ctx +} diff --git a/app/migrator/backup.go b/app/migrator/backup.go index 155ef75c..c0fcff59 100644 --- a/app/migrator/backup.go +++ b/app/migrator/backup.go @@ -2,6 +2,7 @@ package migrator import ( "compress/gzip" + "context" "fmt" "io/ioutil" "log" @@ -23,17 +24,24 @@ type AutoBackup struct { } // Do runs daily export to local files, keeps up to keepMax backups for given siteID -func (ab AutoBackup) Do() { +func (ab AutoBackup) Do(ctx context.Context) { log.Printf("[INFO] activate auto-backup for %s", ab.BackupLocation) tick := time.NewTicker(ab.Duration) log.Printf("[DEBUG] first backup at %s", time.Now().Add(ab.Duration)) - for range tick.C { - if _, err := ab.makeBackup(); err != nil { - log.Printf("[WARN] auto-backup for %s failed, %s", ab.SiteID, err) - continue + + for { + select { + case <-tick.C: + if _, err := ab.makeBackup(); err != nil { + log.Printf("[WARN] auto-backup for %s failed, %s", ab.SiteID, err) + continue + } + ab.removeOldBackupFiles() + log.Printf("[DEBUG] next backup at %s", time.Now().Add(ab.Duration)) + case <-ctx.Done(): + log.Printf("[WARN] terminated autobackup for %s", ab.SiteID) + return } - ab.removeOldBackupFiles() - log.Printf("[DEBUG] next backup at %s", time.Now().Add(ab.Duration)) } } diff --git a/app/migrator/backup_test.go b/app/migrator/backup_test.go index a8e11779..55cbc231 100644 --- a/app/migrator/backup_test.go +++ b/app/migrator/backup_test.go @@ -1,6 +1,7 @@ package migrator import ( + "context" "fmt" "io" "io/ioutil" @@ -11,7 +12,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestMigrator_RemoveOldBackupFiles(t *testing.T) { +func TestBackup_RemoveOldBackupFiles(t *testing.T) { loc := "/tmp/remark-backups.test" defer os.RemoveAll(loc) @@ -36,7 +37,7 @@ func TestMigrator_RemoveOldBackupFiles(t *testing.T) { assert.Equal(t, "backup-site2-20171210.gz", ff[3].Name()) } -func TestMigrator_MakeBackup(t *testing.T) { +func TestBackup_MakeBackup(t *testing.T) { loc := "/tmp/remark-backups.test" defer os.RemoveAll(loc) os.MkdirAll(loc, 0700) @@ -52,6 +53,26 @@ func TestMigrator_MakeBackup(t *testing.T) { assert.Equal(t, int64(52), fi.Size()) } +func TestBackup_Do(t *testing.T) { + loc := "/tmp/remark-backups.test" + defer os.RemoveAll(loc) + os.MkdirAll(loc, 0700) + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + time.Sleep(time.Second) + cancel() + }() + + bk := AutoBackup{BackupLocation: loc, SiteID: "site1", KeepMax: 3, Exporter: &mockExporter{}, Duration: 600 * time.Millisecond} + bk.Do(ctx) + + expFile := fmt.Sprintf("/tmp/remark-backups.test/backup-site1-%s.gz", time.Now().Format("20060102")) + fi, err := os.Lstat(expFile) + assert.NoError(t, err) + assert.Equal(t, int64(52), fi.Size()) +} + type mockExporter struct{} func (mock *mockExporter) Export(w io.Writer, siteID string) (int, error) { diff --git a/app/rest/api/import.go b/app/rest/api/import.go index 2f34da6d..551b7ef2 100644 --- a/app/rest/api/import.go +++ b/app/rest/api/import.go @@ -1,10 +1,12 @@ package api import ( + "context" "fmt" "log" "net/http" "strings" + "sync" "time" "github.com/didip/tollbooth" @@ -24,6 +26,9 @@ type Import struct { NativeImporter migrator.Importer DisqusImporter migrator.Importer SecretKey string + + httpServer *http.Server + lock sync.Mutex } // Run the listener and request's router, activate rest server @@ -31,12 +36,31 @@ type Import struct { func (s *Import) Run(port int) { log.Printf("[INFO] activate import server on port %d", port) router := s.routes() - httpServer := &http.Server{Addr: fmt.Sprintf("127.0.0.1:%d", port), Handler: router} - err := httpServer.ListenAndServe() + + s.lock.Lock() + s.httpServer = &http.Server{Addr: fmt.Sprintf("127.0.0.1:%d", port), Handler: router} + s.lock.Unlock() + + err := s.httpServer.ListenAndServe() log.Printf("[WARN] http server terminated, %s", err) } -func (s Import) routes() chi.Router { +// Shutdown import http server +func (s *Import) Shutdown() { + log.Print("[WARN] shutdown import server") + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + s.lock.Lock() + if err := s.httpServer.Shutdown(ctx); err != nil { + log.Printf("[DEBUG] importer shutdown error, %s", err) + } + s.lock.Unlock() + + log.Print("[DEBUG] shutdown import server completed") +} + +func (s *Import) routes() chi.Router { router := chi.NewRouter() router.Use(middleware.RealIP, Recoverer) router.Use(middleware.Throttle(1000), middleware.Timeout(60*time.Second)) diff --git a/app/rest/api/import_test.go b/app/rest/api/import_test.go index 6d6e06e4..8fd59994 100644 --- a/app/rest/api/import_test.go +++ b/app/rest/api/import_test.go @@ -30,6 +30,7 @@ func TestImport(t *testing.T) { req, err := http.NewRequest("POST", ts.URL+"/api/v1/admin/import?site=radio-t&provider=native&secret=123456", r) assert.Nil(t, err) resp, err := client.Do(req) + assert.Nil(t, err) assert.Equal(t, http.StatusCreated, resp.StatusCode) b, err := ioutil.ReadAll(resp.Body) diff --git a/app/rest/api/rest.go b/app/rest/api/rest.go index 717fec68..21242e2f 100644 --- a/app/rest/api/rest.go +++ b/app/rest/api/rest.go @@ -2,6 +2,7 @@ package api import ( "bytes" + "context" "crypto/sha1" "encoding/base64" "encoding/json" @@ -10,6 +11,7 @@ import ( "net/http" "strconv" "strings" + "sync" "time" "github.com/didip/tollbooth" @@ -43,7 +45,9 @@ type Rest struct { Critical int } - httpServer *http.Server + httpServer *http.Server + lock sync.Mutex + adminService admin } @@ -63,6 +67,7 @@ func (s *Rest) Run(port int) { router := s.routes() + s.lock.Lock() s.httpServer = &http.Server{ Addr: fmt.Sprintf(":%d", port), Handler: router, @@ -70,10 +75,25 @@ func (s *Rest) Run(port int) { WriteTimeout: 5 * time.Second, IdleTimeout: 30 * time.Second, } + s.lock.Unlock() + err := s.httpServer.ListenAndServe() log.Printf("[WARN] http server terminated, %s", err) } +// Shutdown rest http server +func (s *Rest) Shutdown() { + log.Print("[WARN] shutdown rest server") + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + s.lock.Lock() + if err := s.httpServer.Shutdown(ctx); err != nil { + log.Printf("[DEBUG] rest shutdown error, %s", err) + } + log.Print("[DEBUG] shutdown rest server completed") + s.lock.Unlock() +} + func (s *Rest) routes() chi.Router { router := chi.NewRouter() router.Use(middleware.RealIP, Recoverer) diff --git a/app/rest/auth/auth_test.go b/app/rest/auth/auth_test.go index b6df5ac2..ddfc2f92 100644 --- a/app/rest/auth/auth_test.go +++ b/app/rest/auth/auth_test.go @@ -87,17 +87,20 @@ func TestAuthRequired(t *testing.T) { client := &http.Client{Timeout: 1 * time.Second} req, err := http.NewRequest("GET", server.URL+"/auth", nil) + require.NoError(t, err) req = withBasicAuth(req, "dev", "123456") resp, err := client.Do(req) require.NoError(t, err) assert.Equal(t, 201, resp.StatusCode, "valid auth user") req, err = http.NewRequest("GET", server.URL+"/auth", nil) + require.NoError(t, err) resp, err = client.Do(req) require.NoError(t, err) assert.Equal(t, 401, resp.StatusCode, "no auth user") req, err = http.NewRequest("GET", server.URL+"/auth", nil) + require.NoError(t, err) req = withBasicAuth(req, "dev", "xyz") resp, err = client.Do(req) require.NoError(t, err) @@ -115,17 +118,20 @@ func TestAuthNotRequired(t *testing.T) { client := &http.Client{Timeout: 1 * time.Second} req, err := http.NewRequest("GET", server.URL+"/auth", nil) + require.NoError(t, err) req = withBasicAuth(req, "dev", "123456") resp, err := client.Do(req) require.NoError(t, err) assert.Equal(t, 201, resp.StatusCode, "valid auth user") req, err = http.NewRequest("GET", server.URL+"/auth", nil) + require.NoError(t, err) resp, err = client.Do(req) require.NoError(t, err) assert.Equal(t, 201, resp.StatusCode, "no auth user") req, err = http.NewRequest("GET", server.URL+"/auth", nil) + require.NoError(t, err) req = withBasicAuth(req, "dev", "ZZZZ123456") resp, err = client.Do(req) require.NoError(t, err) @@ -143,6 +149,7 @@ func TestAdminRequired(t *testing.T) { client := &http.Client{Timeout: 1 * time.Second} req, err := http.NewRequest("GET", server.URL+"/auth", nil) + require.NoError(t, err) req = withBasicAuth(req, "dev", "123456") resp, err := client.Do(req) require.NoError(t, err) @@ -150,6 +157,7 @@ func TestAdminRequired(t *testing.T) { devUser.Admin = false req, err = http.NewRequest("GET", server.URL+"/auth", nil) + require.NoError(t, err) req = withBasicAuth(req, "dev", "123456") resp, err = client.Do(req) require.NoError(t, err) diff --git a/app/rest/auth/provider_test.go b/app/rest/auth/provider_test.go index e6f6dcd6..f238043b 100644 --- a/app/rest/auth/provider_test.go +++ b/app/rest/auth/provider_test.go @@ -27,6 +27,7 @@ func TestLogin(t *testing.T) { }() jar, err := cookiejar.New(nil) + require.Nil(t, err) client := &http.Client{Jar: jar, Timeout: 5 * time.Second} resp, err := client.Get("http://localhost:8981/login") assert.Nil(t, err) diff --git a/app/rest/cache.go b/app/rest/cache.go index e4972a38..294612f4 100644 --- a/app/rest/cache.go +++ b/app/rest/cache.go @@ -82,9 +82,9 @@ func (lc *loadingCache) allowed(data []byte) bool { // CacheOption func type type CacheOption func(lc *loadingCache) error -// MaxValueSize functional option defines the largest value's size allowed to be cached +// MaxValSize functional option defines the largest value's size allowed to be cached // By default it is 0, which means unlimited. -func MaxValueSize(max int) CacheOption { +func MaxValSize(max int) CacheOption { return func(lc *loadingCache) error { lc.maxValueSize = max return nil diff --git a/app/rest/cache_test.go b/app/rest/cache_test.go index 645feb47..2e36ec53 100644 --- a/app/rest/cache_test.go +++ b/app/rest/cache_test.go @@ -43,7 +43,7 @@ func TestLoadingCache_Get(t *testing.T) { func TestLoadingCache_MaxKeys(t *testing.T) { var postFnCall, coldCalls int32 lc := NewLoadingCache(CleanupInterval(200*time.Millisecond), PostFlushFn(func() { atomic.AddInt32(&postFnCall, 1) }), - MaxKeys(5), MaxValueSize(10)) + MaxKeys(5), MaxValSize(10)) // put 5 keys to cache for i := 0; i < 5; i++ { @@ -97,7 +97,7 @@ func TestLoadingCache_MaxKeys(t *testing.T) { } func TestLoadingCache_MaxSize(t *testing.T) { - lc := NewLoadingCache(CleanupInterval(200*time.Millisecond), MaxKeys(5), MaxValueSize(10)) + lc := NewLoadingCache(CleanupInterval(200*time.Millisecond), MaxKeys(5), MaxValSize(10)) // put good size value to cache and make sure it cached res, err := lc.Get("key-Z", time.Minute, func() ([]byte, error) { @@ -157,6 +157,7 @@ func TestLoadingCache_Parallel(t *testing.T) { wg := sync.WaitGroup{} for i := 0; i < 1000; i++ { wg.Add(1) + i := i go func() { defer wg.Done() res, err := lc.Get("key", time.Minute, func() ([]byte, error) { diff --git a/app/rest/proxy/image_test.go b/app/rest/proxy/image_test.go index 31d48498..28f444be 100644 --- a/app/rest/proxy/image_test.go +++ b/app/rest/proxy/image_test.go @@ -62,7 +62,7 @@ func TestImage_Routes(t *testing.T) { img := Image{Enabled: true, RemarkURL: "https://demo.remark42.com", RoutePath: "/api/v1/proxy"} router := img.Routes() - httpSrv := imgHttpServer(t) + httpSrv := imgHTTPServer(t) defer httpSrv.Close() ts := httptest.NewServer(router) defer ts.Close() @@ -88,7 +88,7 @@ func TestPicture_Convert(t *testing.T) { assert.Equal(t, ` xyz `, r) } -func imgHttpServer(t *testing.T) *httptest.Server { +func imgHTTPServer(t *testing.T) *httptest.Server { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/image/img1.png" { t.Log("http img request", r.URL) diff --git a/app/store/engine/bolt_test.go b/app/store/engine/bolt_test.go index 5a8edc9a..1f0ac59e 100644 --- a/app/store/engine/bolt_test.go +++ b/app/store/engine/bolt_test.go @@ -14,8 +14,8 @@ import ( var testDb = "/tmp/test-remark.db" func TestBoltDB_CreateAndFind(t *testing.T) { - var b = prep(t) defer os.Remove(testDb) + var b = prep(t) res, err := b.Find(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, "time") assert.Nil(t, err) @@ -30,8 +30,8 @@ func TestBoltDB_CreateAndFind(t *testing.T) { } func TestBoltDB_Delete(t *testing.T) { - defer os.Remove(testDb) b := prep(t) + defer os.Remove(testDb) loc := store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"} res, err := b.Find(loc, "time") @@ -218,7 +218,7 @@ func TestBoltDB_GetForUser(t *testing.T) { func prep(t *testing.T) *BoltDB { os.Remove(testDb) - boltStore, err := NewBoltDB(bolt.Options{}, BoltSite{FileName: "/tmp/test-remark.db", SiteID: "radio-t"}) + boltStore, err := NewBoltDB(bolt.Options{}, BoltSite{FileName: testDb, SiteID: "radio-t"}) assert.Nil(t, err) b := boltStore