Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ported Cortex e2e modules, so we can reuse it smaller projects too. #5

Merged
merged 2 commits into from
Mar 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ lint: $(FAILLINT) $(GOLANGCI_LINT) $(MISSPELL) build format docs check-git deps
$(call require_clean_work_tree,"detected not clean master before running lint - run make lint and commit changes.")
@echo ">> verifying imported "
for dir in $(MODULES) ; do \
cd $${dir} && $(FAILLINT) -paths "fmt.{Print,PrintfPrintln,Sprint}" -ignore-tests ./...; \
cd $${dir} && $(FAILLINT) -paths "fmt.{Print,PrintfPrintln,Sprint}" -ignore-tests ./... && \
$(FAILLINT) -paths "github.com/stretchr/testify=github.com/efficientgo/tools/core/pkg/testutil" ./...; \
done
@echo ">> examining all of the Go files"
for dir in $(MODULES) ; do \
Expand Down
24 changes: 21 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

[![golang docs](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/efficientgo/tools)

Set of tools, packages and libraries that every open-source Go project always needs with almost no dependencies.
Set of lightweight tools, packages and modules that every open-source Go project always needs with almost no dependencies.

## Release model

Since this is meant to be critical, tiny import, multi module toolset, there are currently no semver releases planned. It's designed to pin modules via git commits, all commits to master should be stable and properly tests, vetted and linted.

## Modules

### `github.com/efficientgo/tools/core`
### Module `github.com/efficientgo/tools/core`

The main module containing set of useful, core packages for testing, closing, running and repeating.

Expand Down Expand Up @@ -148,7 +148,25 @@ This module contains:
// Simplistic assertion helpers for testing code. TestOrBench utils for union of testing and benchmarks.
```

### `github.com/efficientgo/tools/copyright`
### Module `github.com/efficientgo/tools/e2e`

This module is a fully featured e2e suite allowing utilizing `go test` for setting hermetic up complex microservice testing scenarios using docker.

```go mdox-gen-exec="sh -c 'tail -n +6 e2e/doc.go'"
// This module is a fully featured e2e suite allowing utilizing `go test` for setting hermetic up complex microservice integration testing scenarios using docker.
// Example usages:
// * https://github.com/cortexproject/cortex/tree/master/integration
// * https://github.com/thanos-io/thanos/tree/master/test/e2e
//
// Check github.com/efficientgo/tools/e2e/db for common DBs services you can run out of the box.
```

Credits:

* [Cortex Team](https://github.com/cortexproject/cortex/tree/f639b1855c9f0c9564113709a6bce2996d151ec7/integration)
* Initial Author: [@pracucci](https://github.com/pracucci)

### Module `github.com/efficientgo/tools/copyright`

This module is a very simple CLI for ensuring copyright header on code files.

Expand Down
1 change: 0 additions & 1 deletion core/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,5 @@ require (
github.com/davecgh/go-spew v1.1.1
github.com/pkg/errors v0.9.1
github.com/pmezard/go-difflib v1.0.0
github.com/stretchr/testify v1.6.1
go.uber.org/goleak v1.1.10
)
4 changes: 0 additions & 4 deletions core/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
Expand All @@ -34,5 +32,3 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
112 changes: 112 additions & 0 deletions core/pkg/backoff/backoff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright (c) The EfficientGo Authors.
// Licensed under the Apache License 2.0.

package backoff

// Copied from https://github.com/cortexproject/cortex/blob/0ec7b9664a01d538f1f49580b4c359a5c3cc755a/pkg/util/backoff.go

import (
"context"
"fmt"
"math/rand"
"time"
)

// Config configures a Backoff.
type Config struct {
Min time.Duration `yaml:"min_period"` // Start backoff at this level
Max time.Duration `yaml:"max_period"` // Increase exponentially to this level
MaxRetries int `yaml:"max_retries"` // Give up after this many; zero means infinite retries
}

// Backoff implements exponential backoff with randomized wait times.
type Backoff struct {
cfg Config
ctx context.Context
numRetries int
nextDelayMin time.Duration
nextDelayMax time.Duration
}

// New creates a Backoff object. Pass a Context that can also terminate the operation.
func New(ctx context.Context, cfg Config) *Backoff {
return &Backoff{
cfg: cfg,
ctx: ctx,
nextDelayMin: cfg.Min,
nextDelayMax: doubleDuration(cfg.Min, cfg.Max),
}
}

// Reset the Backoff back to its initial condition.
func (b *Backoff) Reset() {
b.numRetries = 0
b.nextDelayMin = b.cfg.Min
b.nextDelayMax = doubleDuration(b.cfg.Min, b.cfg.Max)
}

// Ongoing returns true if caller should keep going.
func (b *Backoff) Ongoing() bool {
// Stop if Context has errored or max retry count is exceeded.
return b.ctx.Err() == nil && (b.cfg.MaxRetries == 0 || b.numRetries < b.cfg.MaxRetries)
}

// Err returns the reason for terminating the backoff, or nil if it didn't terminate.
func (b *Backoff) Err() error {
if b.ctx.Err() != nil {
return b.ctx.Err()
}
if b.cfg.MaxRetries != 0 && b.numRetries >= b.cfg.MaxRetries {
return fmt.Errorf("terminated after %d retries", b.numRetries)
}
return nil
}

// NumRetries returns the number of retries so far.
func (b *Backoff) NumRetries() int {
return b.numRetries
}

// Wait sleeps for the backoff time then increases the retry count and backoff time.
// Returns immediately if Context is terminated.
func (b *Backoff) Wait() {
// Increase the number of retries and get the next delay.
sleepTime := b.NextDelay()

if b.Ongoing() {
select {
case <-b.ctx.Done():
case <-time.After(sleepTime):
}
}
}

func (b *Backoff) NextDelay() time.Duration {
b.numRetries++

// Handle the edge case the min and max have the same value
// (or due to some misconfig max is < min).
if b.nextDelayMin >= b.nextDelayMax {
return b.nextDelayMin
}

// Add a jitter within the next exponential backoff range.
sleepTime := b.nextDelayMin + time.Duration(rand.Int63n(int64(b.nextDelayMax-b.nextDelayMin)))

// Apply the exponential backoff to calculate the next jitter
// range, unless we've already reached the max.
if b.nextDelayMax < b.cfg.Max {
b.nextDelayMin = doubleDuration(b.nextDelayMin, b.cfg.Max)
b.nextDelayMax = doubleDuration(b.nextDelayMax, b.cfg.Max)
}

return sleepTime
}

func doubleDuration(value time.Duration, max time.Duration) time.Duration {
value = value * 2
if value <= max {
return value
}
return max
}
108 changes: 108 additions & 0 deletions core/pkg/backoff/backoff_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright (c) The EfficientGo Authors.
// Licensed under the Apache License 2.0.

package backoff

// Copied from https://github.com/cortexproject/cortex/blob/0ec7b9664a01d538f1f49580b4c359a5c3cc755a/pkg/util/backoff.go

import (
"context"
"testing"
"time"
)

func TestBackoff_NextDelay(t *testing.T) {
t.Parallel()

tests := map[string]struct {
minBackoff time.Duration
maxBackoff time.Duration
expectedRanges [][]time.Duration
}{
"exponential backoff with jitter honoring min and max": {
minBackoff: 100 * time.Millisecond,
maxBackoff: 10 * time.Second,
expectedRanges: [][]time.Duration{
{100 * time.Millisecond, 200 * time.Millisecond},
{200 * time.Millisecond, 400 * time.Millisecond},
{400 * time.Millisecond, 800 * time.Millisecond},
{800 * time.Millisecond, 1600 * time.Millisecond},
{1600 * time.Millisecond, 3200 * time.Millisecond},
{3200 * time.Millisecond, 6400 * time.Millisecond},
{6400 * time.Millisecond, 10000 * time.Millisecond},
{6400 * time.Millisecond, 10000 * time.Millisecond},
},
},
"exponential backoff with max equal to the end of a range": {
minBackoff: 100 * time.Millisecond,
maxBackoff: 800 * time.Millisecond,
expectedRanges: [][]time.Duration{
{100 * time.Millisecond, 200 * time.Millisecond},
{200 * time.Millisecond, 400 * time.Millisecond},
{400 * time.Millisecond, 800 * time.Millisecond},
{400 * time.Millisecond, 800 * time.Millisecond},
},
},
"exponential backoff with max equal to the end of a range + 1": {
minBackoff: 100 * time.Millisecond,
maxBackoff: 801 * time.Millisecond,
expectedRanges: [][]time.Duration{
{100 * time.Millisecond, 200 * time.Millisecond},
{200 * time.Millisecond, 400 * time.Millisecond},
{400 * time.Millisecond, 800 * time.Millisecond},
{800 * time.Millisecond, 801 * time.Millisecond},
{800 * time.Millisecond, 801 * time.Millisecond},
},
},
"exponential backoff with max equal to the end of a range - 1": {
minBackoff: 100 * time.Millisecond,
maxBackoff: 799 * time.Millisecond,
expectedRanges: [][]time.Duration{
{100 * time.Millisecond, 200 * time.Millisecond},
{200 * time.Millisecond, 400 * time.Millisecond},
{400 * time.Millisecond, 799 * time.Millisecond},
{400 * time.Millisecond, 799 * time.Millisecond},
},
},
"min backoff is equal to max": {
minBackoff: 100 * time.Millisecond,
maxBackoff: 100 * time.Millisecond,
expectedRanges: [][]time.Duration{
{100 * time.Millisecond, 100 * time.Millisecond},
{100 * time.Millisecond, 100 * time.Millisecond},
{100 * time.Millisecond, 100 * time.Millisecond},
},
},
"min backoff is greater then max": {
minBackoff: 200 * time.Millisecond,
maxBackoff: 100 * time.Millisecond,
expectedRanges: [][]time.Duration{
{200 * time.Millisecond, 200 * time.Millisecond},
{200 * time.Millisecond, 200 * time.Millisecond},
{200 * time.Millisecond, 200 * time.Millisecond},
},
},
}

for testName, testData := range tests {
testData := testData

t.Run(testName, func(t *testing.T) {
t.Parallel()

b := New(context.Background(), Config{
Min: testData.minBackoff,
Max: testData.maxBackoff,
MaxRetries: len(testData.expectedRanges),
})

for _, expectedRange := range testData.expectedRanges {
delay := b.NextDelay()

if delay < expectedRange[0] || delay > expectedRange[1] {
t.Errorf("%d expected to be within %d and %d", delay, expectedRange[0], expectedRange[1])
}
}
})
}
}
Loading