mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-01-09 12:46:46 +00:00
387873dd53
In the upcoming 3.12.x release of protoc, the proto3 language will be amended to support true presence for scalars. This CL adds support to both the generator and runtime to support these semantics. Newly added public API: protogen.Plugin.SupportedFeatures protoreflect.FieldDescriptor.HasPresence protoreflect.FieldDescriptor.HasOptionalKeyword protoreflect.OneofDescriptor.IsSynthetic Change-Id: I7c86bf66d0ae56642109beb5f2132184593747ad Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/230698 Reviewed-by: Damien Neil <dneil@google.com>
291 lines
11 KiB
Go
291 lines
11 KiB
Go
// 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()
|
|
}
|