Merge pull request #151 from foomo/feature/inline-assertions

feat: add inline utility and assertions
This commit is contained in:
Kevin Franklin Kim 2022-12-27 09:30:29 +01:00 committed by GitHub
commit d523809c10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 324 additions and 0 deletions

1
go.mod
View File

@ -15,6 +15,7 @@ require (
github.com/sony/gobreaker v0.5.0
github.com/spf13/viper v1.12.0
github.com/stretchr/testify v1.8.0
github.com/tidwall/pretty v1.0.0
github.com/tinylib/msgp v1.1.6
go.mongodb.org/mongo-driver v1.10.2
go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.32.0

39
test/assert/assert.go Normal file
View File

@ -0,0 +1,39 @@
package keelassert
import (
"encoding/json"
"fmt"
"testing"
"github.com/foomo/keel/log"
keeltestutil "github.com/foomo/keel/test/util"
"github.com/stretchr/testify/assert"
"github.com/tidwall/pretty"
)
func InlineEqual(t *testing.T, actual interface{}, msgAndArgs ...interface{}) bool {
t.Helper()
expected, ok := keeltestutil.Inline(t, 2, "%v", actual)
if ok {
return assert.Equal(t, expected, fmt.Sprintf("%v", actual), msgAndArgs...)
} else {
return false
}
}
func InlineJSONEq(t *testing.T, actual interface{}, msgAndArgs ...interface{}) bool {
t.Helper()
// marshal value
actualBytes, err := json.Marshal(actual)
if err != nil {
t.Fatal("failed to marshal json", log.FError(err))
}
expected, ok := keeltestutil.Inline(t, 2, string(actualBytes))
if ok {
return assert.Equal(t, string(pretty.Pretty([]byte(expected))), string(pretty.Pretty(actualBytes)), msgAndArgs...)
} else {
return false
}
}

View File

@ -0,0 +1,63 @@
package keelassert_test
import (
"testing"
keelassert "github.com/foomo/keel/test/assert"
"github.com/stretchr/testify/assert"
)
func TestEqualInline(t *testing.T) {
tests := []struct {
name string
when func(t *testing.T) bool
}{
{
name: "equal int",
when: func(t *testing.T) bool { //nolint:thelper
return keelassert.InlineEqual(t, 15) // INLINE: 15
},
},
{
name: "equal bool",
when: func(t *testing.T) bool { //nolint:thelper
return keelassert.InlineEqual(t, true) // INLINE: true
},
},
{
name: "equal string",
when: func(t *testing.T) bool { //nolint:thelper
return keelassert.InlineEqual(t, "foo bar") // INLINE: foo bar
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.True(t, tt.when(t))
})
}
}
func TestEqualInlineJSONEq(t *testing.T) {
tests := []struct {
name string
when func(t *testing.T) bool
}{
{
name: "equal json",
when: func(t *testing.T) bool { //nolint:thelper
x := struct {
Foo string `json:"foo"`
}{
Foo: "bar",
}
return keelassert.InlineJSONEq(t, x) // INLINE: {"foo":"bar"}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.True(t, tt.when(t))
})
}
}

28
test/assert/assertions.go Normal file
View File

@ -0,0 +1,28 @@
package keelassert
import (
"testing"
)
// Assertions provides assertion methods around the
// TestingT interface.
type Assertions struct {
t *testing.T
}
// New makes a new Assertions object for the specified TestingT.
func New(t *testing.T) *Assertions { //nolint:thelper
return &Assertions{
t: t,
}
}
func (a *Assertions) InlineEqual(actual interface{}, msgAndArgs ...interface{}) bool {
a.t.Helper()
return InlineEqual(a.t, actual, msgAndArgs...)
}
func (a *Assertions) InlineJSONEq(actual interface{}, msgAndArgs ...interface{}) bool {
a.t.Helper()
return InlineJSONEq(a.t, actual, msgAndArgs...)
}

View File

@ -0,0 +1,28 @@
package keelrequire
import (
"testing"
)
// Assertions provides assertion methods around the
// TestingT interface.
type Assertions struct {
t *testing.T
}
// New makes a new Assertions object for the specified TestingT.
func New(t *testing.T) *Assertions { //nolint:thelper
return &Assertions{
t: t,
}
}
func (a *Assertions) InlineEqual(actual interface{}, msgAndArgs ...interface{}) {
a.t.Helper()
InlineEqual(a.t, actual, msgAndArgs...)
}
func (a *Assertions) InlineJSONEq(actual interface{}, msgAndArgs ...interface{}) {
a.t.Helper()
InlineJSONEq(a.t, actual, msgAndArgs...)
}

37
test/require/require.go Normal file
View File

@ -0,0 +1,37 @@
package keelrequire
import (
"encoding/json"
"fmt"
"testing"
"github.com/foomo/keel/log"
keeltestutil "github.com/foomo/keel/test/util"
"github.com/stretchr/testify/require"
"github.com/tidwall/pretty"
)
func InlineEqual(t *testing.T, actual interface{}, msgAndArgs ...interface{}) {
t.Helper()
if t.Failed() {
return
}
if expected, ok := keeltestutil.Inline(t, 2, "%v", actual); ok {
require.Equal(t, expected, fmt.Sprintf("%v", actual), msgAndArgs...)
}
}
func InlineJSONEq(t *testing.T, actual interface{}, msgAndArgs ...interface{}) {
t.Helper()
if t.Failed() {
return
}
// marshal value
actualBytes, err := json.Marshal(actual)
if err != nil {
t.Fatal("failed to marshal json", log.FError(err))
}
if expected, ok := keeltestutil.Inline(t, 2, string(actualBytes)); ok {
require.Equal(t, string(pretty.Pretty([]byte(expected))), string(pretty.Pretty(actualBytes)), msgAndArgs...)
}
}

86
test/util/inline.go Normal file
View File

@ -0,0 +1,86 @@
package keeltestutil
import (
"encoding/json"
"fmt"
"os"
"runtime"
"strconv"
"strings"
"testing"
"github.com/foomo/keel/log"
)
func Inline(t *testing.T, skip int, msgAndArgs ...interface{}) (string, bool) {
t.Helper()
// retrieve caller info
_, file, line, ok := runtime.Caller(skip)
if !ok {
t.Fatal("failed to retrieve caller")
}
// read file
fileBytes, err := os.ReadFile(file)
if err != nil {
t.Fatal("failed to read caller file", log.FError(err))
}
fileLines := strings.Split(string(fileBytes), "\n")
fileLine := fileLines[line-1]
fileLineParts := strings.Split(strings.TrimSpace(fileLine), " // INLINE: ")
// compare
if len(fileLineParts) == 2 {
return fileLineParts[1], true
} else if len(msgAndArgs) == 0 {
t.Fatal("missing inline message")
} else if msg, ok := msgAndArgs[0].(string); !ok {
t.Fatal("invalid inline message")
} else if value := fmt.Sprintf(msg, msgAndArgs[1:]...); len(value) == 0 {
t.Fatal("missing inline message")
} else {
fileLines[line-1] = fmt.Sprintf("%s // INLINE: %s", fileLine, value)
if err := os.WriteFile(file, []byte(strings.Join(fileLines, "\n")), 0644); err != nil {
t.Fatal("failed to write inline", log.FError(err))
}
t.Errorf("wrote inline for %s:%d", file, line)
}
return "", false
}
func InlineInt(t *testing.T, skip int) (int, bool) {
t.Helper()
if inline, ok := Inline(t, skip+1); !ok {
return 0, false
} else if value, err := strconv.Atoi(inline); err != nil {
t.Fatal("failed to parse int", log.FError(err))
return 0, false
} else {
return value, true
}
}
func InlineFloat64(t *testing.T, skip int) (float64, bool) {
t.Helper()
if inline, ok := Inline(t, skip+1); !ok {
return 0, false
} else if value, err := strconv.ParseFloat(inline, 64); err != nil {
t.Fatal("failed to parse int", log.FError(err))
return 0, false
} else {
return value, true
}
}
func InlineJSON(t *testing.T, skip int, target interface{}) {
t.Helper()
if inline, ok := Inline(t, skip+1); ok {
if err := json.Unmarshal([]byte(inline), target); err != nil {
t.Fatal("failed to unmarshal json", log.FError(err))
}
}
}

42
test/util/inline_test.go Normal file
View File

@ -0,0 +1,42 @@
package keeltestutil_test
import (
"testing"
keeltestutil "github.com/foomo/keel/test/util"
"github.com/stretchr/testify/assert"
)
func TestInline(t *testing.T) {
t.Run("read inline", func(t *testing.T) {
value, ok := keeltestutil.Inline(t, 1) // INLINE: hello world
assert.True(t, ok)
assert.Equal(t, "hello world", value)
})
t.Run("read inline int", func(t *testing.T) {
value, ok := keeltestutil.InlineInt(t, 1) // INLINE: 1
assert.True(t, ok)
assert.Equal(t, 1, value)
})
t.Run("read inline float", func(t *testing.T) {
value, ok := keeltestutil.InlineFloat64(t, 1) // INLINE: 1.5
assert.True(t, ok)
assert.Equal(t, 1.5, value)
})
t.Run("read inline json", func(t *testing.T) {
var x struct {
Foo string `json:"foo"`
}
keeltestutil.InlineJSON(t, 1, &x) // INLINE: {"foo":"bar"}
assert.Equal(t, "bar", x.Foo)
})
t.Run("write inline", func(t *testing.T) {
value, ok := keeltestutil.Inline(t, 1, "hello %s", "world") // INLINE: hello world
assert.True(t, ok)
assert.Equal(t, "hello world", value)
})
}