testing/protocmp: add SortRepeated and SortRepeatedFields

SortRepeated is similar to cmpopts.SortSlice where it accepts a
user-provided sort function, but only operates on repeated fields.
It pattern matches based on sort element type.

SortRepeatedFields is similar to SortRepeated, but chooses an
arbitrary sort order for the specified (by name) repeated fields.
It pattern matches based on message field name.

Change-Id: Ib6ef282e5394cf7b22522161d524f22e1b76677a
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/221432
Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
Joe Tsai 2020-02-27 19:07:40 -08:00
parent aadba562d3
commit b4c73aa919
3 changed files with 510 additions and 1 deletions

View File

@ -12,6 +12,7 @@ import (
"strings"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
@ -501,3 +502,178 @@ func IgnoreUnknown() cmp.Option {
return strings.Trim(mi.Key().String(), "0123456789") == ""
}, cmp.Ignore())
}
// SortRepeated sorts repeated fields of the specified element type.
// The less function must be of the form "func(T, T) bool" where T is the
// Go element type for the repeated field kind.
//
// The element type T can be one of the following:
// • Go type for a protobuf scalar kind except for an enum
// (i.e., bool, int32, int64, uint32, uint64, float32, float64, string, and []byte)
// • E where E is a concrete enum type that implements protoreflect.Enum
// • M where M is a concrete message type that implement proto.Message
//
// This option only applies to repeated fields within a protobuf message.
// It does not operate on higher-order Go types that seem like a repeated field.
// For example, a []T outside the context of a protobuf message will not be
// handled by this option. To sort Go slices that are not repeated fields,
// consider using "github.com/google/go-cmp/cmp/cmpopts".SortSlices instead.
//
// This must be used in conjunction with Transform.
func SortRepeated(lessFunc interface{}) cmp.Option {
t, ok := checkTTBFunc(lessFunc)
if !ok {
panic(fmt.Sprintf("invalid less function: %T", lessFunc))
}
var opt cmp.Option
var sliceType reflect.Type
switch vf := reflect.ValueOf(lessFunc); {
case t.Implements(enumV2Type):
et := reflect.Zero(t).Interface().(protoreflect.Enum).Type()
lessFunc = func(x, y Enum) bool {
vx := reflect.ValueOf(et.New(x.Number()))
vy := reflect.ValueOf(et.New(y.Number()))
return vf.Call([]reflect.Value{vx, vy})[0].Bool()
}
opt = FilterDescriptor(et.Descriptor(), cmpopts.SortSlices(lessFunc))
sliceType = reflect.SliceOf(enumReflectType)
case t.Implements(messageV2Type):
mt := reflect.Zero(t).Interface().(protoreflect.ProtoMessage).ProtoReflect().Type()
lessFunc = func(x, y Message) bool {
mx := mt.New().Interface()
my := mt.New().Interface()
proto.Merge(mx, x)
proto.Merge(my, y)
vx := reflect.ValueOf(mx)
vy := reflect.ValueOf(my)
return vf.Call([]reflect.Value{vx, vy})[0].Bool()
}
opt = FilterDescriptor(mt.Descriptor(), cmpopts.SortSlices(lessFunc))
sliceType = reflect.SliceOf(messageReflectType)
default:
switch t {
case reflect.TypeOf(bool(false)):
case reflect.TypeOf(int32(0)):
case reflect.TypeOf(int64(0)):
case reflect.TypeOf(uint32(0)):
case reflect.TypeOf(uint64(0)):
case reflect.TypeOf(float32(0)):
case reflect.TypeOf(float64(0)):
case reflect.TypeOf(string("")):
case reflect.TypeOf([]byte(nil)):
default:
panic(fmt.Sprintf("invalid element type: %v", t))
}
opt = cmpopts.SortSlices(lessFunc)
sliceType = reflect.SliceOf(t)
}
return cmp.FilterPath(func(p cmp.Path) bool {
// Filter to only apply to repeated fields within a message.
if t := p.Index(-1).Type(); t == nil || t != sliceType {
return false
}
if t := p.Index(-2).Type(); t == nil || t.Kind() != reflect.Interface {
return false
}
if t := p.Index(-3).Type(); t == nil || t != messageReflectType {
return false
}
return true
}, opt)
}
func checkTTBFunc(lessFunc interface{}) (reflect.Type, bool) {
switch t := reflect.TypeOf(lessFunc); {
case t == nil:
return nil, false
case t.NumIn() != 2 || t.In(0) != t.In(1) || t.IsVariadic():
return nil, false
case t.NumOut() != 1 || t.Out(0) != reflect.TypeOf(false):
return nil, false
default:
return t.In(0), true
}
}
// SortRepeatedFields sorts the specified repeated fields.
// Sorting a repeated field is useful for treating the list as a multiset
// (i.e., a set where each value can appear multiple times).
// It panics if the field does not exist or is not a repeated field.
//
// The sort ordering is as follows:
// • Booleans are sorted where false is sorted before true.
// • Integers are sorted in ascending order.
// • Floating-point numbers are sorted in ascending order according to
// the total ordering defined by IEEE-754 (section 5.10).
// • Strings and bytes are sorted lexicographically in ascending order.
// • Enums are sorted in ascending order based on its numeric value.
// • Messages are sorted according to some arbitrary ordering
// which is undefined and may change in future implementations.
//
// The ordering chosen for repeated messages is unlikely to be aesthetically
// preferred by humans. Consider using a custom sort function:
//
// FilterField(m, "foo_field", SortRepeated(func(x, y *foopb.MyMessage) bool {
// ... // user-provided definition for less
// }))
//
// This must be used in conjunction with Transform.
func SortRepeatedFields(message proto.Message, names ...protoreflect.Name) cmp.Option {
var opts cmp.Options
md := message.ProtoReflect().Descriptor()
for _, name := range names {
fd := mustFindFieldDescriptor(md, name)
if !fd.IsList() {
panic(fmt.Sprintf("message field %q is not repeated", fd.FullName()))
}
var lessFunc interface{}
switch fd.Kind() {
case protoreflect.BoolKind:
lessFunc = func(x, y bool) bool { return !x && y }
case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
lessFunc = func(x, y int32) bool { return x < y }
case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
lessFunc = func(x, y int64) bool { return x < y }
case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
lessFunc = func(x, y uint32) bool { return x < y }
case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
lessFunc = func(x, y uint64) bool { return x < y }
case protoreflect.FloatKind:
lessFunc = lessF32
case protoreflect.DoubleKind:
lessFunc = lessF64
case protoreflect.StringKind:
lessFunc = func(x, y string) bool { return x < y }
case protoreflect.BytesKind:
lessFunc = func(x, y []byte) bool { return bytes.Compare(x, y) < 0 }
case protoreflect.EnumKind:
lessFunc = func(x, y Enum) bool { return x.Number() < y.Number() }
case protoreflect.MessageKind, protoreflect.GroupKind:
lessFunc = func(x, y Message) bool { return x.String() < y.String() }
default:
panic(fmt.Sprintf("invalid kind: %v", fd.Kind()))
}
opts = append(opts, FilterDescriptor(fd, cmpopts.SortSlices(lessFunc)))
}
return opts
}
func lessF32(x, y float32) bool {
// Bit-wise implementation of IEEE-754, section 5.10.
xi := int32(math.Float32bits(x))
yi := int32(math.Float32bits(y))
xi ^= int32(uint32(xi>>31) >> 1)
yi ^= int32(uint32(yi>>31) >> 1)
return xi < yi
}
func lessF64(x, y float64) bool {
// Bit-wise implementation of IEEE-754, section 5.10.
xi := int64(math.Float64bits(x))
yi := int64(math.Float64bits(y))
xi ^= int64(uint64(xi>>63) >> 1)
yi ^= int64(uint64(yi>>63) >> 1)
return xi < yi
}

View File

@ -5,6 +5,9 @@
package protocmp
import (
"math"
"math/rand"
"sort"
"testing"
"github.com/google/go-cmp/cmp"
@ -1023,6 +1026,263 @@ func TestEqual(t *testing.T) {
want: true,
}}...)
// Test SortRepeated.
type higherOrderType struct {
M *testpb.TestAllTypes
I32s []int32
Es []testpb.TestAllTypes_NestedEnum
Ms []*testpb.ForeignMessage
}
tests = append(tests, []test{{
x: &testpb.TestAllTypes{RepeatedInt32: []int32{3, 2, 1, 2, 3, 3}},
y: &testpb.TestAllTypes{RepeatedInt32: []int32{2, 3, 3, 2, 1, 3}},
opts: cmp.Options{Transform()},
want: false,
}, {
x: &testpb.TestAllTypes{RepeatedInt32: []int32{3, 2, 1, 2, 3, 3}},
y: &testpb.TestAllTypes{RepeatedInt32: []int32{2, 3, 3, 2, 1, 3}},
opts: cmp.Options{
Transform(),
SortRepeated(func(x, y int32) bool { return x < y }),
},
want: true,
}, {
x: higherOrderType{
M: &testpb.TestAllTypes{RepeatedInt32: []int32{3, 2, 1, 2, 3, 3}},
I32s: []int32{3, 2, 1, 2, 3, 3},
},
y: higherOrderType{
M: &testpb.TestAllTypes{RepeatedInt32: []int32{2, 3, 3, 2, 1, 3}},
I32s: []int32{2, 3, 3, 2, 1, 3},
},
opts: cmp.Options{
Transform(),
SortRepeated(func(x, y int32) bool { return x < y }),
},
want: false, // sort does not apply to []int32 outside of a message
}, {
x: &testpb.TestAllTypes{RepeatedInt32: []int32{3, 2, 1, 2, 3, 3}},
y: &testpb.TestAllTypes{RepeatedInt32: []int32{2, 3, 3, 2, 1, 3}},
opts: cmp.Options{
Transform(),
SortRepeated(func(x, y int64) bool { return x < y }),
},
want: false, // wrong sort type: int32 != int64
}, {
x: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_FOO, testpb.TestAllTypes_BAR, testpb.TestAllTypes_BAZ}},
y: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_BAR, testpb.TestAllTypes_FOO, testpb.TestAllTypes_BAZ}},
opts: cmp.Options{Transform()},
want: false,
}, {
x: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_FOO, testpb.TestAllTypes_BAR, testpb.TestAllTypes_BAZ}},
y: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_BAR, testpb.TestAllTypes_FOO, testpb.TestAllTypes_BAZ}},
opts: cmp.Options{
Transform(),
SortRepeated(func(x, y testpb.TestAllTypes_NestedEnum) bool { return x < y }),
},
want: true,
}, {
x: higherOrderType{
M: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_FOO, testpb.TestAllTypes_BAR, testpb.TestAllTypes_BAZ}},
Es: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_FOO, testpb.TestAllTypes_BAR, testpb.TestAllTypes_BAZ},
},
y: higherOrderType{
M: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_BAR, testpb.TestAllTypes_FOO, testpb.TestAllTypes_BAZ}},
Es: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_BAR, testpb.TestAllTypes_FOO, testpb.TestAllTypes_BAZ},
},
opts: cmp.Options{
Transform(),
SortRepeated(func(x, y testpb.TestAllTypes_NestedEnum) bool { return x < y }),
},
want: false, // sort does not apply to []testpb.TestAllTypes_NestedEnum outside of a message
}, {
x: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_FOO, testpb.TestAllTypes_BAR, testpb.TestAllTypes_BAZ}},
y: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_BAR, testpb.TestAllTypes_FOO, testpb.TestAllTypes_BAZ}},
opts: cmp.Options{
Transform(),
SortRepeated(func(x, y testpb.ForeignEnum) bool { return x < y }),
},
want: false, // wrong sort type: testpb.TestAllTypes_NestedEnum != testpb.ForeignEnum
}, {
x: &testpb.TestAllTypes{RepeatedForeignMessage: []*testpb.ForeignMessage{{}, {C: proto.Int32(3)}, nil, {C: proto.Int32(3)}, {C: proto.Int32(5)}, {C: proto.Int32(4)}}},
y: &testpb.TestAllTypes{RepeatedForeignMessage: []*testpb.ForeignMessage{nil, {C: proto.Int32(3)}, {}, {C: proto.Int32(4)}, {C: proto.Int32(3)}, {C: proto.Int32(5)}}},
opts: cmp.Options{Transform()},
want: false,
}, {
x: &testpb.TestAllTypes{RepeatedForeignMessage: []*testpb.ForeignMessage{{}, {C: proto.Int32(3)}, nil, {C: proto.Int32(3)}, {C: proto.Int32(5)}, {C: proto.Int32(4)}}},
y: &testpb.TestAllTypes{RepeatedForeignMessage: []*testpb.ForeignMessage{nil, {C: proto.Int32(3)}, {}, {C: proto.Int32(4)}, {C: proto.Int32(3)}, {C: proto.Int32(5)}}},
opts: cmp.Options{
Transform(),
SortRepeated(func(x, y *testpb.ForeignMessage) bool { return x.GetC() < y.GetC() }),
},
want: true,
}, {
x: higherOrderType{
M: &testpb.TestAllTypes{RepeatedForeignMessage: []*testpb.ForeignMessage{{}, {C: proto.Int32(3)}, nil, {C: proto.Int32(3)}, {C: proto.Int32(5)}, {C: proto.Int32(4)}}},
Ms: []*testpb.ForeignMessage{{}, {C: proto.Int32(3)}, nil, {C: proto.Int32(3)}, {C: proto.Int32(5)}, {C: proto.Int32(4)}},
},
y: higherOrderType{
M: &testpb.TestAllTypes{RepeatedForeignMessage: []*testpb.ForeignMessage{nil, {C: proto.Int32(3)}, {}, {C: proto.Int32(4)}, {C: proto.Int32(3)}, {C: proto.Int32(5)}}},
Ms: []*testpb.ForeignMessage{nil, {C: proto.Int32(3)}, {}, {C: proto.Int32(4)}, {C: proto.Int32(3)}, {C: proto.Int32(5)}},
},
opts: cmp.Options{
Transform(),
SortRepeated(func(x, y *testpb.ForeignMessage) bool { return x.GetC() < y.GetC() }),
},
want: false, // sort does not apply to []*testpb.ForeignMessage outside of a message
}, {
x: &testpb.TestAllTypes{RepeatedForeignMessage: []*testpb.ForeignMessage{{}, {C: proto.Int32(3)}, nil, {C: proto.Int32(3)}, {C: proto.Int32(5)}, {C: proto.Int32(4)}}},
y: &testpb.TestAllTypes{RepeatedForeignMessage: []*testpb.ForeignMessage{nil, {C: proto.Int32(3)}, {}, {C: proto.Int32(4)}, {C: proto.Int32(3)}, {C: proto.Int32(5)}}},
opts: cmp.Options{
Transform(),
SortRepeated(func(x, y *testpb.TestAllTypes_NestedMessage) bool { return x.GetA() < y.GetA() }),
},
want: false, // wrong sort type: *testpb.ForeignMessage != *testpb.TestAllTypes_NestedMessage
}, {
x: &testpb.TestAllTypes{
RepeatedInt32: []int32{-32, +32},
RepeatedSint32: []int32{-32, +32},
RepeatedSfixed32: []int32{-32, +32},
RepeatedInt64: []int64{-64, +64},
RepeatedSint64: []int64{-64, +64},
RepeatedSfixed64: []int64{-64, +64},
RepeatedUint32: []uint32{0, 32},
RepeatedFixed32: []uint32{0, 32},
RepeatedUint64: []uint64{0, 64},
RepeatedFixed64: []uint64{0, 64},
},
y: &testpb.TestAllTypes{
RepeatedInt32: []int32{+32, -32},
RepeatedSint32: []int32{+32, -32},
RepeatedSfixed32: []int32{+32, -32},
RepeatedInt64: []int64{+64, -64},
RepeatedSint64: []int64{+64, -64},
RepeatedSfixed64: []int64{+64, -64},
RepeatedUint32: []uint32{32, 0},
RepeatedFixed32: []uint32{32, 0},
RepeatedUint64: []uint64{64, 0},
RepeatedFixed64: []uint64{64, 0},
},
opts: cmp.Options{
Transform(),
SortRepeated(func(x, y int32) bool { return x < y }),
SortRepeated(func(x, y int64) bool { return x < y }),
SortRepeated(func(x, y uint32) bool { return x < y }),
SortRepeated(func(x, y uint64) bool { return x < y }),
},
want: true,
}}...)
// Test SortRepeatedFields.
tests = append(tests, []test{{
x: &testpb.TestAllTypes{RepeatedInt32: []int32{3, 2, 1, 2, 3, 3}},
y: &testpb.TestAllTypes{RepeatedInt32: []int32{2, 3, 3, 2, 1, 3}},
opts: cmp.Options{Transform()},
want: false,
}, {
x: &testpb.TestAllTypes{RepeatedInt32: []int32{3, 2, 1, 2, 3, 3}},
y: &testpb.TestAllTypes{RepeatedInt32: []int32{2, 3, 3, 2, 1, 3}},
opts: cmp.Options{
Transform(),
SortRepeatedFields(new(testpb.TestAllTypes), "repeated_int32"),
},
want: true,
}, {
x: &testpb.TestAllTypes{RepeatedInt32: []int32{3, 2, 1, 2, 3, 3}},
y: &testpb.TestAllTypes{RepeatedInt32: []int32{2, 3, 3, 2, 1, 3}},
opts: cmp.Options{
Transform(),
SortRepeatedFields(new(testpb.TestAllTypes), "repeated_int64"),
},
want: false, // wrong field: repeated_int32 != repeated_int64
}, {
x: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_FOO, testpb.TestAllTypes_BAR, testpb.TestAllTypes_BAZ}},
y: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_BAR, testpb.TestAllTypes_FOO, testpb.TestAllTypes_BAZ}},
opts: cmp.Options{Transform()},
want: false,
}, {
x: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_FOO, testpb.TestAllTypes_BAR, testpb.TestAllTypes_BAZ}},
y: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_BAR, testpb.TestAllTypes_FOO, testpb.TestAllTypes_BAZ}},
opts: cmp.Options{
Transform(),
SortRepeatedFields(new(testpb.TestAllTypes), "repeated_nested_enum"),
},
want: true,
}, {
x: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_FOO, testpb.TestAllTypes_BAR, testpb.TestAllTypes_BAZ}},
y: &testpb.TestAllTypes{RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_BAR, testpb.TestAllTypes_FOO, testpb.TestAllTypes_BAZ}},
opts: cmp.Options{
Transform(),
SortRepeatedFields(new(testpb.TestAllTypes), "repeated_foreign_enum"),
},
want: false, // wrong field: repeated_nested_enum != repeated_foreign_enum
}, {
x: &testpb.TestAllTypes{RepeatedForeignMessage: []*testpb.ForeignMessage{{}, {C: proto.Int32(3)}, nil, {C: proto.Int32(3)}, {C: proto.Int32(5)}, {C: proto.Int32(4)}}},
y: &testpb.TestAllTypes{RepeatedForeignMessage: []*testpb.ForeignMessage{nil, {C: proto.Int32(3)}, {}, {C: proto.Int32(4)}, {C: proto.Int32(3)}, {C: proto.Int32(5)}}},
opts: cmp.Options{Transform()},
want: false,
}, {
x: &testpb.TestAllTypes{RepeatedForeignMessage: []*testpb.ForeignMessage{{}, {C: proto.Int32(3)}, nil, {C: proto.Int32(3)}, {C: proto.Int32(5)}, {C: proto.Int32(4)}}},
y: &testpb.TestAllTypes{RepeatedForeignMessage: []*testpb.ForeignMessage{nil, {C: proto.Int32(3)}, {}, {C: proto.Int32(4)}, {C: proto.Int32(3)}, {C: proto.Int32(5)}}},
opts: cmp.Options{
Transform(),
SortRepeatedFields(new(testpb.TestAllTypes), "repeated_foreign_message"),
},
want: true,
}, {
x: &testpb.TestAllTypes{RepeatedForeignMessage: []*testpb.ForeignMessage{{}, {C: proto.Int32(3)}, nil, {C: proto.Int32(3)}, {C: proto.Int32(5)}, {C: proto.Int32(4)}}},
y: &testpb.TestAllTypes{RepeatedForeignMessage: []*testpb.ForeignMessage{nil, {C: proto.Int32(3)}, {}, {C: proto.Int32(4)}, {C: proto.Int32(3)}, {C: proto.Int32(5)}}},
opts: cmp.Options{
Transform(),
SortRepeatedFields(new(testpb.TestAllTypes), "repeated_nested_message"),
},
want: false, // wrong field: repeated_foreign_message != repeated_nested_message
}, {
x: &testpb.TestAllTypes{
RepeatedBool: []bool{false, true},
RepeatedInt32: []int32{-32, +32},
RepeatedInt64: []int64{-64, +64},
RepeatedUint32: []uint32{0, 32},
RepeatedUint64: []uint64{0, 64},
RepeatedFloat: []float32{-32.32, +32.32},
RepeatedDouble: []float64{-64.64, +64.64},
RepeatedString: []string{"hello", "world"},
RepeatedBytes: [][]byte{[]byte("hello"), []byte("world")},
RepeatedForeignEnum: []testpb.ForeignEnum{testpb.ForeignEnum_FOREIGN_FOO, testpb.ForeignEnum_FOREIGN_BAR},
RepeatedForeignMessage: []*testpb.ForeignMessage{{C: proto.Int32(-1)}, {C: proto.Int32(+1)}},
},
y: &testpb.TestAllTypes{
RepeatedBool: []bool{true, false},
RepeatedInt32: []int32{+32, -32},
RepeatedInt64: []int64{+64, -64},
RepeatedUint32: []uint32{32, 0},
RepeatedUint64: []uint64{64, 0},
RepeatedFloat: []float32{+32.32, -32.32},
RepeatedDouble: []float64{+64.64, -64.64},
RepeatedString: []string{"world", "hello"},
RepeatedBytes: [][]byte{[]byte("world"), []byte("hello")},
RepeatedForeignEnum: []testpb.ForeignEnum{testpb.ForeignEnum_FOREIGN_BAR, testpb.ForeignEnum_FOREIGN_FOO},
RepeatedForeignMessage: []*testpb.ForeignMessage{{C: proto.Int32(+1)}, {C: proto.Int32(-1)}},
},
opts: cmp.Options{
Transform(),
SortRepeatedFields(new(testpb.TestAllTypes),
"repeated_bool",
"repeated_int32",
"repeated_int64",
"repeated_uint32",
"repeated_uint64",
"repeated_float",
"repeated_double",
"repeated_string",
"repeated_bytes",
"repeated_foreign_enum",
"repeated_foreign_message",
),
},
want: true,
}}...)
for _, tt := range tests {
t.Run("", func(t *testing.T) {
got := cmp.Equal(tt.x, tt.y, tt.opts)
@ -1066,3 +1326,74 @@ func apply(m proto.Message, ops ...interface{}) proto.Message {
}
return m
}
func TestSort(t *testing.T) {
t.Run("F32", func(t *testing.T) {
want := []float32{
float32(math.Float32frombits(0xffc00000)), // -NaN
float32(math.Inf(-1)),
float32(-math.MaxFloat32),
float32(-123.456),
float32(-math.SmallestNonzeroFloat32),
float32(math.Copysign(0, -1)),
float32(math.Copysign(0, +1)),
float32(+math.SmallestNonzeroFloat32),
float32(+123.456),
float32(+math.MaxFloat32),
float32(math.Inf(+1)),
float32(math.Float32frombits(0x7fc00000)), // +NaN
}
for i := 0; i < 10; i++ {
t.Run("", func(t *testing.T) {
got := append([]float32(nil), want...)
rn := rand.New(rand.NewSource(int64(i)))
for i, j := range rn.Perm(len(got)) {
got[i], got[j] = got[j], got[i]
}
sort.Slice(got, func(i, j int) bool {
return lessF32(got[i], got[j])
})
cmpF32s := cmp.Comparer(func(x, y float32) bool {
return math.Float32bits(x) == math.Float32bits(y)
})
if diff := cmp.Diff(want, got, cmpF32s); diff != "" {
t.Errorf("Sort mismatch (-want +got):\n%s", diff)
}
})
}
})
t.Run("F64", func(t *testing.T) {
want := []float64{
float64(math.Float64frombits(0xfff8000000000001)), // -NaN
float64(math.Inf(-1)),
float64(-math.MaxFloat64),
float64(-123.456),
float64(-math.SmallestNonzeroFloat64),
float64(math.Copysign(0, -1)),
float64(math.Copysign(0, +1)),
float64(+math.SmallestNonzeroFloat64),
float64(+123.456),
float64(+math.MaxFloat64),
float64(math.Inf(+1)),
float64(math.Float64frombits(0x7ff8000000000001)), // +NaN
}
for i := 0; i < 10; i++ {
t.Run("", func(t *testing.T) {
got := append([]float64(nil), want...)
rn := rand.New(rand.NewSource(int64(i)))
for i, j := range rn.Perm(len(got)) {
got[i], got[j] = got[j], got[i]
}
sort.Slice(got, func(i, j int) bool {
return lessF64(got[i], got[j])
})
cmpF64s := cmp.Comparer(func(x, y float64) bool {
return math.Float64bits(x) == math.Float64bits(y)
})
if diff := cmp.Diff(want, got, cmpF64s); diff != "" {
t.Errorf("Sort mismatch (-want +got):\n%s", diff)
}
})
}
})
}

View File

@ -2,7 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package protocmp provides protobuf specific options for the cmp package.
// Package protocmp provides protobuf specific options for the
// "github.com/google/go-cmp/cmp" package.
//
// The primary feature is the Transform option, which transform proto.Message
// types into a Message map that is suitable for cmp to introspect upon.
@ -24,6 +25,7 @@ import (
)
var (
enumV2Type = reflect.TypeOf((*protoreflect.Enum)(nil)).Elem()
messageV1Type = reflect.TypeOf((*protoiface.MessageV1)(nil)).Elem()
messageV2Type = reflect.TypeOf((*proto.Message)(nil)).Elem()
)