diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index e38735a..3b4212f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -27,10 +27,14 @@ jobs: uses: actions/setup-go@v4 with: go-version: 1.21 - - name: Unit Tests - run: go test -v ./... - - name: Integration Tests - run: go test -v -tags=integration ./... + - name: Tests with Coverage + run: | + go test -v -coverprofile=coverage.out.tmp ./... + cat coverage.out.tmp | grep -v "_mock.go" > coverage.out + go tool cover -func=coverage.out + go tool cover -html=coverage.out -o coverage.html + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Create Tag run: | git config --local user.email "github-actions[bot]@users.noreply.github.com" @@ -50,3 +54,8 @@ jobs: with: name: dbm path: dist/* + - name: Upload coverage + uses: actions/upload-artifact@v3 + with: + name: coverage + path: coverage.html diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..ec0f0d5 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,33 @@ +name: Test + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + run-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: 1.21 + - name: Tests with Coverage + run: | + go test -v -coverprofile=coverage.out.tmp ./... + cat coverage.out.tmp | grep -v "_mock.go" > coverage.out + go tool cover -func=coverage.out + go tool cover -html=coverage.out -o coverage.html + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload coverage + uses: actions/upload-artifact@v3 + with: + name: coverage + path: coverage.html diff --git a/.gitignore b/.gitignore index 754d6a2..162ba2b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ test.log dist/ +coverage.* \ No newline at end of file diff --git a/cmd/local/local.go b/cmd/local/local.go index 71e8ac4..ffbbdea 100644 --- a/cmd/local/local.go +++ b/cmd/local/local.go @@ -1,6 +1,8 @@ package local import ( + "context" + "github.com/fbufler/database-monitor/internal/tester" "github.com/fbufler/database-monitor/pkg/database" "github.com/rs/zerolog/log" @@ -18,7 +20,7 @@ func LocalCommand() *cobra.Command { cmd := &cobra.Command{ Use: "local", Short: "Run dbm database tester", - RunE: local, + RunE: localRun, } cmd.Flags().StringSlice("databases", []string{}, "databases to test") cmd.Flags().Int("test_timeout", 5, "test timeout in seconds") @@ -27,8 +29,7 @@ func LocalCommand() *cobra.Command { return cmd } -func local(cmd *cobra.Command, args []string) error { - log.Info().Msg("Starting local") +func localRun(cmd *cobra.Command, args []string) error { ctx := cmd.Context() LocalCfg := LocalCfg{} err := viper.Unmarshal(&LocalCfg) @@ -40,15 +41,21 @@ func local(cmd *cobra.Command, args []string) error { if testTimeout > 0 { LocalCfg.TestTimeout = testTimeout } + return local(&LocalCfg, ctx) +} + +func local(cfg *LocalCfg, ctx context.Context) error { + log.Info().Msg("Starting local") + log.Debug().Msg("Initializing database tester") dbs := []database.Database{} - for _, dbCfg := range LocalCfg.Databases { + for _, dbCfg := range cfg.Databases { dbs = append(dbs, database.NewPostgres(dbCfg)) } tester := tester.NewPostgres(tester.Config{ Databases: dbs, - TestTimeout: LocalCfg.TestTimeout, - TestInterval: LocalCfg.TestInterval, + TestTimeout: cfg.TestTimeout, + TestInterval: cfg.TestInterval, }) log.Info().Msg("Starting database tester") result := tester.Run(ctx) diff --git a/cmd/local/local_test.go b/cmd/local/local_test.go index 469c3dc..ce16c21 100644 --- a/cmd/local/local_test.go +++ b/cmd/local/local_test.go @@ -1 +1,42 @@ package local + +import ( + "bytes" + "context" + "testing" + "time" + + "github.com/fbufler/database-monitor/pkg/database" + "github.com/rs/zerolog/log" + "github.com/stretchr/testify/assert" +) + +// Integration test for the local service. + +func TestLocal(t *testing.T) { + cfg := LocalCfg{ + Databases: []database.Config{ + { + Host: "localhost", + Port: 5432, + Username: "postgres", + Password: "postgres", + Database: "postgres", + }, + }, + TestTimeout: 1, + TestInterval: 1, + } + ctx, cancel := context.WithCancel(context.Background()) + var buf bytes.Buffer + log.Logger = log.Output(&buf) + go local(&cfg, ctx) + time.Sleep(1 * time.Second) + cancel() + time.Sleep(1 * time.Second) + + bufS := buf.String() + assert.Contains(t, bufS, "Starting local") + assert.Contains(t, bufS, "Result: {Database:localhost:5432/postgres") + assert.Contains(t, bufS, "Context terminated") +} diff --git a/cmd/serve/serve.go b/cmd/serve/serve.go index a05fe67..9169e55 100644 --- a/cmd/serve/serve.go +++ b/cmd/serve/serve.go @@ -1,6 +1,8 @@ package serve import ( + "context" + "github.com/fbufler/database-monitor/internal/service" "github.com/fbufler/database-monitor/internal/tester" "github.com/fbufler/database-monitor/pkg/database" @@ -22,7 +24,7 @@ func ServeCommand() *cobra.Command { cmd := &cobra.Command{ Use: "serve", Short: "Run dbm database tester service", - RunE: serve, + RunE: serveRun, } cmd.Flags().StringSlice("databases", []string{}, "databases to test") cmd.Flags().Int("test_timeout", 5, "test timeout in seconds") @@ -37,8 +39,7 @@ func ServeCommand() *cobra.Command { return cmd } -func serve(cmd *cobra.Command, args []string) error { - log.Info().Msg("Starting local") +func serveRun(cmd *cobra.Command, args []string) error { ctx := cmd.Context() serveCfg := ServeCfg{} err := viper.Unmarshal(&serveCfg) @@ -50,23 +51,29 @@ func serve(cmd *cobra.Command, args []string) error { if testTimeout > 0 { serveCfg.TestTimeout = testTimeout } + return serve(&serveCfg, ctx) +} + +func serve(cfg *ServeCfg, ctx context.Context) error { + log.Info().Msg("Starting local") + log.Debug().Msg("Initializing database tester") dbs := []database.Database{} - for _, dbCfg := range serveCfg.Databases { + for _, dbCfg := range cfg.Databases { dbs = append(dbs, database.NewPostgres(dbCfg)) } tester := tester.NewPostgres(tester.Config{ Databases: dbs, - TestTimeout: serveCfg.TestTimeout, - TestInterval: serveCfg.TestInterval, + TestTimeout: cfg.TestTimeout, + TestInterval: cfg.TestInterval, }) log.Info().Msg("Starting database tester") result := tester.Run(ctx) log.Info().Msg("Initializing service") router := mux.NewRouter() service := service.New(service.Config{ - Port: serveCfg.Port, - InvalidationTime: serveCfg.InvalidationTime, + Port: cfg.Port, + InvalidationTime: cfg.InvalidationTime, }, result, router) log.Info().Msg("Starting service") go service.Run(ctx) diff --git a/cmd/serve/serve_test.go b/cmd/serve/serve_test.go new file mode 100644 index 0000000..7158a85 --- /dev/null +++ b/cmd/serve/serve_test.go @@ -0,0 +1,42 @@ +package serve + +import ( + "bytes" + "context" + "net/http" + "testing" + "time" + + "github.com/fbufler/database-monitor/pkg/database" + "github.com/rs/zerolog/log" + "github.com/stretchr/testify/assert" +) + +func TestServe(t *testing.T) { + cfg := ServeCfg{ + Port: 8081, + InvalidationTime: 5, + TestTimeout: 1, + TestInterval: 1, + Databases: []database.Config{ + { + Host: "localhost", + Port: 5432, + Username: "postgres", + Password: "postgres", + Database: "postgres", + }, + }, + } + ctx, cancel := context.WithCancel(context.Background()) + var buf bytes.Buffer + log.Logger = log.Output(&buf) + go serve(&cfg, ctx) + time.Sleep(1 * time.Second) + res, err := http.Get("http://localhost:8081/results") + assert.NoError(t, err) + assert.Equal(t, 200, res.StatusCode) + cancel() + _, err = http.Get("http://localhost:8081/results") + assert.Error(t, err) +} diff --git a/internal/service/service.go b/internal/service/service.go index c45f30e..1fc3007 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -75,7 +75,9 @@ func (s *ServiceImpl) Run(ctx context.Context) { log.Info().Msgf("Starting service on port %d", s.config.Port) go func() { if err := srv.ListenAndServe(); err != nil { - log.Fatal().Msgf("service: %s", err) + if err != http.ErrServerClosed { + log.Fatal().Msgf("service: %s", err) + } } }() <-ctx.Done() diff --git a/internal/tester/postgres.go b/internal/tester/postgres.go index 488d24c..031a857 100644 --- a/internal/tester/postgres.go +++ b/internal/tester/postgres.go @@ -59,6 +59,11 @@ func (p *Postgres) runDatabaseTest(db database.Database, ctx context.Context) { } connectionTime := time.Now() err := db.Connect() + select { + case <-ctx.Done(): + return + default: + } if err != nil { log.Error().Msgf("connecting to %s: %s", result.Database, err) p.results <- result @@ -69,6 +74,11 @@ func (p *Postgres) runDatabaseTest(db database.Database, ctx context.Context) { result.Connectable = true readTime := time.Now() err = db.TestRead(ctx) + select { + case <-ctx.Done(): + return + default: + } if err != nil { log.Error().Msgf("reading from %s: %s", result.Database, err) p.results <- result @@ -78,6 +88,11 @@ func (p *Postgres) runDatabaseTest(db database.Database, ctx context.Context) { result.Readable = true writeTime := time.Now() err = db.TestWrite(ctx) + select { + case <-ctx.Done(): + return + default: + } if err != nil { log.Error().Msgf("writing to %s: %s", result.Database, err) p.results <- result @@ -85,7 +100,12 @@ func (p *Postgres) runDatabaseTest(db database.Database, ctx context.Context) { } result.WriteTime = time.Since(writeTime) result.Writable = true - p.results <- result + select { + case <-ctx.Done(): + return + default: + p.results <- result + } } func (p *Postgres) Setup(ctx context.Context) error {