aboutsummaryrefslogtreecommitdiff
path: root/proto/extension_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'proto/extension_test.go')
-rw-r--r--proto/extension_test.go290
1 files changed, 290 insertions, 0 deletions
diff --git a/proto/extension_test.go b/proto/extension_test.go
new file mode 100644
index 00000000..b3ab7882
--- /dev/null
+++ b/proto/extension_test.go
@@ -0,0 +1,290 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package proto_test
+
+import (
+ "fmt"
+ "reflect"
+ "sync"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+
+ "google.golang.org/protobuf/proto"
+ "google.golang.org/protobuf/reflect/protoreflect"
+ pref "google.golang.org/protobuf/reflect/protoreflect"
+ "google.golang.org/protobuf/runtime/protoimpl"
+ "google.golang.org/protobuf/testing/protocmp"
+
+ legacy1pb "google.golang.org/protobuf/internal/testprotos/legacy/proto2_20160225_2fc053c5"
+ testpb "google.golang.org/protobuf/internal/testprotos/test"
+ test3pb "google.golang.org/protobuf/internal/testprotos/test3"
+ descpb "google.golang.org/protobuf/types/descriptorpb"
+)
+
+func TestExtensionFuncs(t *testing.T) {
+ for _, test := range []struct {
+ message proto.Message
+ ext pref.ExtensionType
+ wantDefault interface{}
+ value interface{}
+ }{
+ {
+ message: &testpb.TestAllExtensions{},
+ ext: testpb.E_OptionalInt32,
+ wantDefault: int32(0),
+ value: int32(1),
+ },
+ {
+ message: &testpb.TestAllExtensions{},
+ ext: testpb.E_RepeatedString,
+ wantDefault: ([]string)(nil),
+ value: []string{"a", "b", "c"},
+ },
+ {
+ message: protoimpl.X.MessageOf(&legacy1pb.Message{}).Interface(),
+ ext: legacy1pb.E_Message_ExtensionOptionalBool,
+ wantDefault: false,
+ value: true,
+ },
+ } {
+ desc := fmt.Sprintf("Extension %v, value %v", test.ext.TypeDescriptor().FullName(), test.value)
+ if proto.HasExtension(test.message, test.ext) {
+ t.Errorf("%v:\nbefore setting extension HasExtension(...) = true, want false", desc)
+ }
+ got := proto.GetExtension(test.message, test.ext)
+ if d := cmp.Diff(test.wantDefault, got); d != "" {
+ t.Errorf("%v:\nbefore setting extension GetExtension(...) returns unexpected value (-want,+got):\n%v", desc, d)
+ }
+ proto.SetExtension(test.message, test.ext, test.value)
+ if !proto.HasExtension(test.message, test.ext) {
+ t.Errorf("%v:\nafter setting extension HasExtension(...) = false, want true", desc)
+ }
+ got = proto.GetExtension(test.message, test.ext)
+ if d := cmp.Diff(test.value, got); d != "" {
+ t.Errorf("%v:\nafter setting extension GetExtension(...) returns unexpected value (-want,+got):\n%v", desc, d)
+ }
+ proto.ClearExtension(test.message, test.ext)
+ if proto.HasExtension(test.message, test.ext) {
+ t.Errorf("%v:\nafter clearing extension HasExtension(...) = true, want false", desc)
+ }
+ }
+}
+
+func TestIsValid(t *testing.T) {
+ tests := []struct {
+ xt protoreflect.ExtensionType
+ vi interface{}
+ want bool
+ }{
+ {testpb.E_OptionalBool, nil, false},
+ {testpb.E_OptionalBool, bool(true), true},
+ {testpb.E_OptionalBool, new(bool), false},
+ {testpb.E_OptionalInt32, nil, false},
+ {testpb.E_OptionalInt32, int32(0), true},
+ {testpb.E_OptionalInt32, new(int32), false},
+ {testpb.E_OptionalInt64, nil, false},
+ {testpb.E_OptionalInt64, int64(0), true},
+ {testpb.E_OptionalInt64, new(int64), false},
+ {testpb.E_OptionalUint32, nil, false},
+ {testpb.E_OptionalUint32, uint32(0), true},
+ {testpb.E_OptionalUint32, new(uint32), false},
+ {testpb.E_OptionalUint64, nil, false},
+ {testpb.E_OptionalUint64, uint64(0), true},
+ {testpb.E_OptionalUint64, new(uint64), false},
+ {testpb.E_OptionalFloat, nil, false},
+ {testpb.E_OptionalFloat, float32(0), true},
+ {testpb.E_OptionalFloat, new(float32), false},
+ {testpb.E_OptionalDouble, nil, false},
+ {testpb.E_OptionalDouble, float64(0), true},
+ {testpb.E_OptionalDouble, new(float32), false},
+ {testpb.E_OptionalString, nil, false},
+ {testpb.E_OptionalString, string(""), true},
+ {testpb.E_OptionalString, new(string), false},
+ {testpb.E_OptionalNestedEnum, nil, false},
+ {testpb.E_OptionalNestedEnum, testpb.TestAllTypes_BAZ, true},
+ {testpb.E_OptionalNestedEnum, testpb.TestAllTypes_BAZ.Enum(), false},
+ {testpb.E_OptionalNestedMessage, nil, false},
+ {testpb.E_OptionalNestedMessage, (*testpb.TestAllExtensions_NestedMessage)(nil), true},
+ {testpb.E_OptionalNestedMessage, new(testpb.TestAllExtensions_NestedMessage), true},
+ {testpb.E_OptionalNestedMessage, new(testpb.TestAllExtensions), false},
+ {testpb.E_RepeatedBool, nil, false},
+ {testpb.E_RepeatedBool, []bool(nil), true},
+ {testpb.E_RepeatedBool, []bool{}, true},
+ {testpb.E_RepeatedBool, []bool{false}, true},
+ {testpb.E_RepeatedBool, []*bool{}, false},
+ {testpb.E_RepeatedInt32, nil, false},
+ {testpb.E_RepeatedInt32, []int32(nil), true},
+ {testpb.E_RepeatedInt32, []int32{}, true},
+ {testpb.E_RepeatedInt32, []int32{0}, true},
+ {testpb.E_RepeatedInt32, []*int32{}, false},
+ {testpb.E_RepeatedInt64, nil, false},
+ {testpb.E_RepeatedInt64, []int64(nil), true},
+ {testpb.E_RepeatedInt64, []int64{}, true},
+ {testpb.E_RepeatedInt64, []int64{0}, true},
+ {testpb.E_RepeatedInt64, []*int64{}, false},
+ {testpb.E_RepeatedUint32, nil, false},
+ {testpb.E_RepeatedUint32, []uint32(nil), true},
+ {testpb.E_RepeatedUint32, []uint32{}, true},
+ {testpb.E_RepeatedUint32, []uint32{0}, true},
+ {testpb.E_RepeatedUint32, []*uint32{}, false},
+ {testpb.E_RepeatedUint64, nil, false},
+ {testpb.E_RepeatedUint64, []uint64(nil), true},
+ {testpb.E_RepeatedUint64, []uint64{}, true},
+ {testpb.E_RepeatedUint64, []uint64{0}, true},
+ {testpb.E_RepeatedUint64, []*uint64{}, false},
+ {testpb.E_RepeatedFloat, nil, false},
+ {testpb.E_RepeatedFloat, []float32(nil), true},
+ {testpb.E_RepeatedFloat, []float32{}, true},
+ {testpb.E_RepeatedFloat, []float32{0}, true},
+ {testpb.E_RepeatedFloat, []*float32{}, false},
+ {testpb.E_RepeatedDouble, nil, false},
+ {testpb.E_RepeatedDouble, []float64(nil), true},
+ {testpb.E_RepeatedDouble, []float64{}, true},
+ {testpb.E_RepeatedDouble, []float64{0}, true},
+ {testpb.E_RepeatedDouble, []*float64{}, false},
+ {testpb.E_RepeatedString, nil, false},
+ {testpb.E_RepeatedString, []string(nil), true},
+ {testpb.E_RepeatedString, []string{}, true},
+ {testpb.E_RepeatedString, []string{""}, true},
+ {testpb.E_RepeatedString, []*string{}, false},
+ {testpb.E_RepeatedNestedEnum, nil, false},
+ {testpb.E_RepeatedNestedEnum, []testpb.TestAllTypes_NestedEnum(nil), true},
+ {testpb.E_RepeatedNestedEnum, []testpb.TestAllTypes_NestedEnum{}, true},
+ {testpb.E_RepeatedNestedEnum, []testpb.TestAllTypes_NestedEnum{0}, true},
+ {testpb.E_RepeatedNestedEnum, []*testpb.TestAllTypes_NestedEnum{}, false},
+ {testpb.E_RepeatedNestedMessage, nil, false},
+ {testpb.E_RepeatedNestedMessage, []*testpb.TestAllExtensions_NestedMessage(nil), true},
+ {testpb.E_RepeatedNestedMessage, []*testpb.TestAllExtensions_NestedMessage{}, true},
+ {testpb.E_RepeatedNestedMessage, []*testpb.TestAllExtensions_NestedMessage{{}}, true},
+ {testpb.E_RepeatedNestedMessage, []*testpb.TestAllExtensions{}, false},
+ }
+
+ for _, tt := range tests {
+ // Check the results of IsValidInterface.
+ got := tt.xt.IsValidInterface(tt.vi)
+ if got != tt.want {
+ t.Errorf("%v.IsValidInterface() = %v, want %v", tt.xt.TypeDescriptor().FullName(), got, tt.want)
+ }
+ if !got {
+ continue
+ }
+
+ // Set the extension value and verify the results of Has.
+ wantHas := true
+ pv := tt.xt.ValueOf(tt.vi)
+ switch v := pv.Interface().(type) {
+ case protoreflect.List:
+ wantHas = v.Len() > 0
+ case protoreflect.Message:
+ wantHas = v.IsValid()
+ }
+ m := &testpb.TestAllExtensions{}
+ proto.SetExtension(m, tt.xt, tt.vi)
+ gotHas := proto.HasExtension(m, tt.xt)
+ if gotHas != wantHas {
+ t.Errorf("HasExtension(%q) = %v, want %v", tt.xt.TypeDescriptor().FullName(), gotHas, wantHas)
+ }
+
+ // Check consistency of IsValidInterface and IsValidValue.
+ got = tt.xt.IsValidValue(pv)
+ if got != tt.want {
+ t.Errorf("%v.IsValidValue() = %v, want %v", tt.xt.TypeDescriptor().FullName(), got, tt.want)
+ }
+ if !got {
+ continue
+ }
+
+ // Use of reflect.DeepEqual is intentional.
+ // We really do want to ensure that the memory layout is identical.
+ vi := tt.xt.InterfaceOf(pv)
+ if !reflect.DeepEqual(vi, tt.vi) {
+ t.Errorf("InterfaceOf(ValueOf(...)) round-trip mismatch: got %v, want %v", vi, tt.vi)
+ }
+ }
+}
+
+func TestExtensionRanger(t *testing.T) {
+ tests := []struct {
+ msg proto.Message
+ want map[pref.ExtensionType]interface{}
+ }{{
+ msg: &testpb.TestAllExtensions{},
+ want: map[pref.ExtensionType]interface{}{
+ testpb.E_OptionalInt32: int32(5),
+ testpb.E_OptionalString: string("hello"),
+ testpb.E_OptionalNestedMessage: &testpb.TestAllExtensions_NestedMessage{},
+ testpb.E_OptionalNestedEnum: testpb.TestAllTypes_BAZ,
+ testpb.E_RepeatedFloat: []float32{+32.32, -32.32},
+ testpb.E_RepeatedNestedMessage: []*testpb.TestAllExtensions_NestedMessage{{}},
+ testpb.E_RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_BAZ},
+ },
+ }, {
+ msg: &descpb.MessageOptions{},
+ want: map[pref.ExtensionType]interface{}{
+ test3pb.E_OptionalInt32: int32(5),
+ test3pb.E_OptionalString: string("hello"),
+ test3pb.E_OptionalForeignMessage: &test3pb.ForeignMessage{},
+ test3pb.E_OptionalForeignEnum: test3pb.ForeignEnum_FOREIGN_BAR,
+
+ test3pb.E_OptionalOptionalInt32: int32(5),
+ test3pb.E_OptionalOptionalString: string("hello"),
+ test3pb.E_OptionalOptionalForeignMessage: &test3pb.ForeignMessage{},
+ test3pb.E_OptionalOptionalForeignEnum: test3pb.ForeignEnum_FOREIGN_BAR,
+ },
+ }}
+
+ for _, tt := range tests {
+ for xt, v := range tt.want {
+ proto.SetExtension(tt.msg, xt, v)
+ }
+
+ got := make(map[pref.ExtensionType]interface{})
+ proto.RangeExtensions(tt.msg, func(xt pref.ExtensionType, v interface{}) bool {
+ got[xt] = v
+ return true
+ })
+
+ if diff := cmp.Diff(tt.want, got, protocmp.Transform()); diff != "" {
+ t.Errorf("proto.RangeExtensions mismatch (-want +got):\n%s", diff)
+ }
+ }
+}
+
+func TestExtensionGetRace(t *testing.T) {
+ // Concurrently fetch an extension value while marshaling the message containing it.
+ // Create the message with proto.Unmarshal to give lazy extension decoding (if present)
+ // a chance to occur.
+ want := int32(42)
+ m1 := &testpb.TestAllExtensions{}
+ proto.SetExtension(m1, testpb.E_OptionalNestedMessage, &testpb.TestAllExtensions_NestedMessage{A: proto.Int32(want)})
+ b, err := proto.Marshal(m1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ m := &testpb.TestAllExtensions{}
+ if err := proto.Unmarshal(b, m); err != nil {
+ t.Fatal(err)
+ }
+ var wg sync.WaitGroup
+ for i := 0; i < 3; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ if _, err := proto.Marshal(m); err != nil {
+ t.Error(err)
+ }
+ }()
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ got := proto.GetExtension(m, testpb.E_OptionalNestedMessage).(*testpb.TestAllExtensions_NestedMessage).GetA()
+ if got != want {
+ t.Errorf("GetExtension(optional_nested_message).a = %v, want %v", got, want)
+ }
+ }()
+ }
+ wg.Wait()
+}