// 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" "sync" "testing" "github.com/google/go-cmp/cmp" "google.golang.org/protobuf/proto" pref "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/runtime/protoimpl" legacy1pb "google.golang.org/protobuf/internal/testprotos/legacy/proto2_20160225_2fc053c5" testpb "google.golang.org/protobuf/internal/testprotos/test" ) 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 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() }