Quantcast
Channel: Recent Gists from brandur
Viewing all articles
Browse latest Browse all 30

PartialEqual implementation

$
0
0
go.mod
modulegithub.com/brandur/prequire
go1.19
requiregithub.com/stretchr/testifyv1.8.1
require (
github.com/davecgh/go-spewv1.1.1// indirect
github.com/pmezard/go-difflibv1.0.0// indirect
gopkg.in/yaml.v3v3.0.1// indirect
)
go.sum
github.com/davecgh/go-spewv1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spewv1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spewv1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflibv1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflibv1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objxv0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objxv0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objxv0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testifyv1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testifyv1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testifyv1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testifyv1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
gopkg.in/check.v1v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
prequire.go
package prequire
import (
"reflect"
"github.com/stretchr/testify/require"
)
// PartialEqual takes a partially-initialized struct its expected (first
// argument) side, and compares all non-zero values on it or any substructs to
// the symmetrical values of actual (second argument). In other words, it can
// take a struct that only has values that the caller cares about, and compares
// just those, even if actual has other non-zero values.
//
// Example use:
//
// prequire.PartialEqual(t, dbsqlc.Cluster{
// Environment: sql.NullString{String: string(dbsqlc.ClusterEnvironmentProduction), Valid: true},
// Name: cluster.Name,
// }, cluster)
//
// WARNING: In Go, there's no difference between an explicit zero value versus an
// implicit one from when a field is just left out of a struct initialization,
// so there's no way for this helper to compare zero values set on the expected
// side -- they'll just be silently ignored. So watch out for use of anything
// like `false`, `nil`, `sql.NullString{}`, etc. as none of them will work.
// Recommended work around is to compare non-zero values with this assertion,
// and then assert on values expected to be zero with `require.Zero`. For API
// resources, also consider making fields pointer primitives like `*bool` or
// `*int` so that an explicit `ptrutil.Ptr(false)` or `ptrutil.Ptr(0)` can be
// checked.
funcPartialEqual(tTestingT, expected, actualany) {
t.Helper()
expectedVal:=reflect.ValueOf(expected)
actualVal:=reflect.ValueOf(actual)
varwasPtrbool
ifexpectedVal.Kind() ==reflect.Ptr {
ifactualVal.Kind() !=reflect.Ptr {
panic("expected value is a pointer; actual value should also be a pointer")
}
expectedVal=expectedVal.Elem()
actualVal=actualVal.Elem()
wasPtr=true
}
switch {
caseexpectedVal.Kind() !=reflect.Struct:
panic("expected value must be a struct")
case!wasPtr&&actualVal.Kind() ==reflect.Ptr:
panic("expected value was not a pointer; actual value should also not be a pointer")
caseexpectedVal.Type() !=actualVal.Type():
panic("expected and actual values must be the same type")
}
partialActual:=buildPartialStruct(expectedVal, actualVal)
if!wasPtr {
partialActual=reflect.ValueOf(partialActual).Elem().Interface()
}
require.Equal(t, expected, partialActual, "Expected all non-zero fields on structs to be the same")
}
// Builds a partial struct, taking values from actualVal according to which
// values are non-zero in expectedVal.
funcbuildPartialStruct(expectedVal, actualVal reflect.Value) any {
// Creates a pointer to a new value of the given type.
partialActual:=reflect.New(actualVal.Type())
fori:=0; i<expectedVal.NumField(); i++ {
expectedField:=expectedVal.Field(i)
actualField:=actualVal.Field(i)
ifexpectedField.IsZero() {
continue
}
field:=partialActual.Elem().Field(i)
switch {
caseexpectedField.Kind() ==reflect.Struct:
if!actualField.IsZero() {
s:=buildPartialStruct(expectedField, actualField)
field.Set(reflect.ValueOf(s).Elem())
}
caseexpectedField.Kind() ==reflect.Ptr&&expectedField.Elem().Kind() ==reflect.Struct:
if!actualField.IsNil() {
s:=buildPartialStruct(expectedField.Elem(), actualField.Elem())
field.Set(reflect.ValueOf(s))
}
default:
field.Set(actualField)
}
}
returnpartialActual.Interface()
}
// MockT mocks TestingT (or testing.T).
//
// Copied from testify/require.
typeMockTstruct {
Failedbool
}
func (t*MockT) Errorf(formatstring, args...interface{}) {
_, _=format, args
}
func (t*MockT) FailNow() {
t.Failed=true
}
func (t*MockT) Helper() {}
// TestingT is an interface wrapper around *testing.T
//
// Copied from testify/require. Included so we can test failure conditions but
// if this becomes too much of a maintenance hassle, it's probably fine to just
// remove it.
typeTestingTinterface {
Errorf(formatstring, args...interface{})
FailNow()
Helper()
}
prequire_test.go
package prequire_test
import (
"testing"
"github.com/brandur/prequire"
"github.com/stretchr/testify/require"
)
funcTestPartialEqual(t*testing.T) {
typetestStructstruct {
Boolbool
Intint
Pointer*string
Stringstring
}
target:=&testStruct{
Bool: false,
Int: 123,
Pointer: ptr("hello"),
String: "hello",
}
typetestContainerstruct {
Intint
Pointer*testStruct
ValuetestStruct
}
targetContainer:=&testContainer{
Int: 123,
Pointer: target,
Value: *target,
}
// Here to show a hidden danger of using this assertion -- if a zero value
// is accidentally used in the expected struct (left side) then it's not
// considered for comparison, even if it was intended to be. Moreover, we
// can't detect the problem because even with reflection there's no way to
// differentiate a zero value that was set explicitly versus one that
// occurred implicitly by leaving it out.
//
// This will be dangerous for things like boolean `false`, but also `nil`
// for pointers and `0` for int, and something to watch out for.
t.Run("AccidentalNoOp", func(t*testing.T) {
prequire.PartialEqual(t,
&testStruct{Bool: false},
&testStruct{Bool: true},
)
})
t.Run("SingleField", func(t*testing.T) {
// Bool
prequire.PartialEqual(t, &testStruct{Bool: false}, target)
expectFailure(t, func(t prequire.TestingT) {
prequire.PartialEqual(t, &testStruct{Bool: true}, target)
})
// Int
prequire.PartialEqual(t, &testStruct{Int: 123}, target)
expectFailure(t, func(t prequire.TestingT) {
prequire.PartialEqual(t, &testStruct{Int: 122}, target)
})
// Pointer
prequire.PartialEqual(t, &testStruct{Pointer: ptr("hello")}, target)
expectFailure(t, func(t prequire.TestingT) {
prequire.PartialEqual(t, &testStruct{Pointer: ptr("goodbye")}, target)
})
// String
prequire.PartialEqual(t, &testStruct{String: "hello"}, target)
expectFailure(t, func(t prequire.TestingT) {
prequire.PartialEqual(t, &testStruct{String: "goodbye"}, target)
})
})
t.Run("NonPointerStruct", func(t*testing.T) {
prequire.PartialEqual(t, testStruct{String: "hello"}, *target)
expectFailure(t, func(t prequire.TestingT) {
prequire.PartialEqual(t, testStruct{String: "goodbye"}, *target)
})
})
t.Run("MultipleFields", func(t*testing.T) {
prequire.PartialEqual(t, &testStruct{Int: 123, String: "hello"}, target)
expectFailure(t, func(t prequire.TestingT) {
prequire.PartialEqual(t, &testStruct{Int: 123, String: "goodbye"}, target)
})
})
t.Run("Substruct", func(t*testing.T) {
// Differing inner field
prequire.PartialEqual(t, &testContainer{Int: 123, Pointer: &testStruct{String: "hello"}}, targetContainer)
expectFailure(t, func(t prequire.TestingT) {
prequire.PartialEqual(t, &testContainer{Int: 123, Pointer: &testStruct{String: "goodbye"}}, targetContainer)
})
// Differing outer field
prequire.PartialEqual(t, &testContainer{Int: 123, Pointer: &testStruct{String: "hello"}}, targetContainer)
expectFailure(t, func(t prequire.TestingT) {
prequire.PartialEqual(t, &testContainer{Int: 124, Pointer: &testStruct{String: "hello"}}, targetContainer)
})
// Struct value instead of pointer
prequire.PartialEqual(t, &testContainer{Int: 123, Value: testStruct{String: "hello"}}, targetContainer)
expectFailure(t, func(t prequire.TestingT) {
prequire.PartialEqual(t, &testContainer{Int: 123, Value: testStruct{String: "goodbye"}}, targetContainer)
})
// Nil and zero substructs
prequire.PartialEqual(t, &testContainer{Int: 123}, targetContainer)
expectFailure(t, func(t prequire.TestingT) {
prequire.PartialEqual(t, &testContainer{Int: 123, Pointer: &testStruct{String: "goodbye"}}, &testContainer{Int: 123})
})
})
t.Run("MisusePanics", func(t*testing.T) {
require.PanicsWithValue(t, "expected value is a pointer; actual value should also be a pointer", func() {
prequire.PartialEqual(t,
&testStruct{String: "hello"},
testStruct{String: "hello"},
)
})
require.PanicsWithValue(t, "expected value must be a struct", func() {
prequire.PartialEqual(t, "hello", "hello")
})
require.PanicsWithValue(t, "expected value was not a pointer; actual value should also not be a pointer", func() {
prequire.PartialEqual(t,
testStruct{String: "hello"},
&testStruct{String: "hello"},
)
})
require.PanicsWithValue(t, "expected and actual values must be the same type", func() {
prequire.PartialEqual(t,
&testStruct{Int: 123},
&testContainer{Int: 123},
)
})
})
}
funcexpectFailure(t*testing.T, ffunc(t prequire.TestingT)) {
t.Helper()
mockT:=&prequire.MockT{}
f(mockT)
require.True(t, mockT.Failed, "Expected MockT to have failed, but it didn't")
}
// ptr returns a pointer to the given value.
funcptr[Tany](vT) *T {
return&v
}

Viewing all articles
Browse latest Browse all 30

Latest Images

Trending Articles





Latest Images