Skip to content

reflect.DeepEqual, not even once #95

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

Merged
merged 1 commit into from
Aug 16, 2019
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
2 changes: 1 addition & 1 deletion schema/elements_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func TestResolve(t *testing.T) {
t.Errorf("expected Atom %v, got %v", tt.expectAtom, atom)
}
if exist != tt.expectExist {
t.Errorf("expeted exist %t, got %t", tt.expectExist, exist)
t.Errorf("expected exist %t, got %t", tt.expectExist, exist)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:-)

}
})
}
Expand Down
166 changes: 166 additions & 0 deletions schema/equals.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package schema

// Equals returns true iff the two Schemas are equal.
func (a Schema) Equals(b Schema) bool {
if len(a.Types) != len(b.Types) {
return false
}
for i := range a.Types {
if !a.Types[i].Equals(b.Types[i]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are they guaranteed to be sorted in the same order?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, but reflect.DeepEqual behaved like this too...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed!

return false
}
}
return true
}

// Equals returns true iff the two TypeRefs are equal.
//
// Note that two typerefs that have an equivalent type but where one is
// inlined and the other is named, are not considered equal.
func (a TypeRef) Equals(b TypeRef) bool {
if (a.NamedType == nil) != (b.NamedType == nil) {
return false
}
if a.NamedType != nil {
if *a.NamedType != *b.NamedType {
return false
}
//return true
}
return a.Inlined.Equals(b.Inlined)
}

// Equals returns true iff the two TypeDefs are equal.
func (a TypeDef) Equals(b TypeDef) bool {
if a.Name != b.Name {
return false
}
return a.Atom.Equals(b.Atom)
}

// Equals returns true iff the two Atoms are equal.
func (a Atom) Equals(b Atom) bool {
if (a.Scalar == nil) != (b.Scalar == nil) {
return false
}
if (a.List == nil) != (b.List == nil) {
return false
}
if (a.Map == nil) != (b.Map == nil) {
return false
}
switch {
case a.Scalar != nil:
return *a.Scalar == *b.Scalar
case a.List != nil:
return a.List.Equals(*b.List)
case a.Map != nil:
return a.Map.Equals(*b.Map)
}
return true
}

// Equals returns true iff the two Maps are equal.
func (a Map) Equals(b Map) bool {
if !a.ElementType.Equals(b.ElementType) {
return false
}
if a.ElementRelationship != b.ElementRelationship {
return false
}
if len(a.Fields) != len(b.Fields) {
return false
}
for i := range a.Fields {
if !a.Fields[i].Equals(b.Fields[i]) {
return false
}
}
if len(a.Unions) != len(b.Unions) {
return false
}
for i := range a.Unions {
if !a.Unions[i].Equals(b.Unions[i]) {
return false
}
}
return true
}

// Equals returns true iff the two Unions are equal.
func (a Union) Equals(b Union) bool {
if (a.Discriminator == nil) != (b.Discriminator == nil) {
return false
}
if a.Discriminator != nil {
if *a.Discriminator != *b.Discriminator {
return false
}
}
if a.DeduceInvalidDiscriminator != b.DeduceInvalidDiscriminator {
return false
}
if len(a.Fields) != len(b.Fields) {
return false
}
for i := range a.Fields {
if !a.Fields[i].Equals(b.Fields[i]) {
return false
}
}
return true
}

// Equals returns true iff the two UnionFields are equal.
func (a UnionField) Equals(b UnionField) bool {
if a.FieldName != b.FieldName {
return false
}
if a.DiscriminatorValue != b.DiscriminatorValue {
return false
}
return true
}

// Equals returns true iff the two StructFields are equal.
func (a StructField) Equals(b StructField) bool {
if a.Name != b.Name {
return false
}
return a.Type.Equals(b.Type)
}

// Equals returns true iff the two Lists are equal.
func (a List) Equals(b List) bool {
if !a.ElementType.Equals(b.ElementType) {
return false
}
if a.ElementRelationship != b.ElementRelationship {
return false
}
if len(a.Keys) != len(b.Keys) {
return false
}
for i := range a.Keys {
if a.Keys[i] != b.Keys[i] {
return false
}
}
return true
}
123 changes: 123 additions & 0 deletions schema/equals_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package schema

import (
"reflect"
"testing"
"testing/quick"
)

func TestEquals(t *testing.T) {
// In general this test will make sure people update things when they
// add a field.
//
// The "copy known fields" section of these function is to break if folks
// add new fields without fixing the Equals function and this test.
funcs := []interface{}{
func(x Schema) bool {
if !x.Equals(x) {
return false
}
var y Schema
y.Types = x.Types
return x.Equals(y) == reflect.DeepEqual(x, y)
},
func(x TypeDef) bool {
if !x.Equals(x) {
return false
}
var y TypeDef
y.Name = x.Name
y.Atom = x.Atom
return x.Equals(y) == reflect.DeepEqual(x, y)
},
func(x TypeRef) bool {
if !x.Equals(x) {
return false
}
var y TypeRef
y.NamedType = x.NamedType
y.Inlined = x.Inlined
return x.Equals(y) == reflect.DeepEqual(x, y)
},
func(x Atom) bool {
if !x.Equals(x) {
return false
}
var y Atom
y.Scalar = x.Scalar
y.List = x.List
y.Map = x.Map
return x.Equals(y) == reflect.DeepEqual(x, y)
},
func(x Map) bool {
if !x.Equals(x) {
return false
}
var y Map
y.ElementType = x.ElementType
y.ElementRelationship = x.ElementRelationship
y.Fields = x.Fields
y.Unions = x.Unions
return x.Equals(y) == reflect.DeepEqual(x, y)
},
func(x Union) bool {
if !x.Equals(x) {
return false
}
var y Union
y.Discriminator = x.Discriminator
y.DeduceInvalidDiscriminator = x.DeduceInvalidDiscriminator
y.Fields = x.Fields
return x.Equals(y) == reflect.DeepEqual(x, y)
},
func(x UnionField) bool {
if !x.Equals(x) {
return false
}
var y UnionField
y.DiscriminatorValue = x.DiscriminatorValue
y.FieldName = x.FieldName
return x.Equals(y) == reflect.DeepEqual(x, y)
},
func(x StructField) bool {
if !x.Equals(x) {
return false
}
var y StructField
y.Name = x.Name
y.Type = x.Type
return x.Equals(y) == reflect.DeepEqual(x, y)
},
func(x List) bool {
if !x.Equals(x) {
return false
}
var y List
y.ElementType = x.ElementType
y.ElementRelationship = x.ElementRelationship
y.Keys = x.Keys
return x.Equals(y) == reflect.DeepEqual(x, y)
},
}
for i, f := range funcs {
if err := quick.Check(f, nil); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

t.Errorf("%v: %v", i, err)
}
}
}
4 changes: 1 addition & 3 deletions typed/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ limitations under the License.
package typed

import (
"reflect"

"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/schema"
"sigs.k8s.io/structured-merge-diff/value"
Expand Down Expand Up @@ -78,7 +76,7 @@ func (w *mergingWalker) merge() (errs ValidationErrors) {

alhs := deduceAtom(a, w.lhs)
arhs := deduceAtom(a, w.rhs)
if reflect.DeepEqual(alhs, arhs) {
if alhs.Equals(arhs) {
errs = append(errs, handleAtom(arhs, w.typeRef, w)...)
} else {
w2 := *w
Expand Down
7 changes: 3 additions & 4 deletions typed/typed.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package typed

import (
"fmt"
"reflect"

"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/schema"
Expand Down Expand Up @@ -118,8 +117,8 @@ func (tv TypedValue) Compare(rhs *TypedValue) (c *Comparison, err error) {
c.Added.Insert(w.path)
} else if w.rhs == nil {
c.Removed.Insert(w.path)
} else if !reflect.DeepEqual(w.rhs, w.lhs) {
// TODO: reflect.DeepEqual is not sufficient for this.
} else if !w.rhs.Equals(*w.lhs) {
// TODO: Equality is not sufficient for this.
// Need to implement equality check on the value type.
c.Modified.Insert(w.path)
}
Expand Down Expand Up @@ -209,7 +208,7 @@ func merge(lhs, rhs *TypedValue, rule, postRule mergeRule) (*TypedValue, error)
return nil, errorFormatter{}.
errorf("expected objects with types from the same schema")
}
if !reflect.DeepEqual(lhs.typeRef, rhs.typeRef) {
if !lhs.typeRef.Equals(rhs.typeRef) {
return nil, errorFormatter{}.
errorf("expected objects of the same type, but got %v and %v", lhs.typeRef, rhs.typeRef)
}
Expand Down
5 changes: 5 additions & 0 deletions value/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ type Value struct {
Null bool // represents an explicit `"foo" = null`
}

// Equals returns true iff the two values are equal.
func (v Value) Equals(rhs Value) bool {
return !v.Less(rhs) && !rhs.Less(v)
}

// Less provides a total ordering for Value (so that they can be sorted, even
// if they are of different types).
func (v Value) Less(rhs Value) bool {
Expand Down