protobuf-go/proto/oneof_which_test.go
Michael Stapelberg 8878926ea7 internal/impl: fix WhichOneof() to work with synthetic oneofs
This is the equivalent of CL 638495, but for the Opaque API.

While the behavior is the same for non-synthetic oneofs between
the Open Struct API and Opaque API, the behavior for synthetic oneofs
is not the same: Because the Opaque API uses a bitfield to store
presence, we need to use the Opaque presence check in WhichOneof().

This CL extends the testproto generator to use the protoc
command-line flag for the test3 testproto (which specifies
syntax = "proto3";), as the in-file api_level is only available
in .proto files using editions (but editions does not use synthetic
oneofs for optional fields, only proto3 does).

For golang/protobuf#1659

Change-Id: I0a1fd6e5fc6f96eeab043f966728ce2a14dbd446
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/638515
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Chressie Himpel <chressie@google.com>
2024-12-23 05:33:29 -08:00

250 lines
7.6 KiB
Go

// Copyright 2024 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 (
"testing"
test3openpb "google.golang.org/protobuf/internal/testprotos/test3"
test3hybridpb "google.golang.org/protobuf/internal/testprotos/test3/test3_hybrid"
test3opaquepb "google.golang.org/protobuf/internal/testprotos/test3/test3_opaque"
testhybridpb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_hybrid"
testopaquepb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_opaque"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
)
func TestOpenWhich(t *testing.T) {
var x *testhybridpb.TestAllTypes
if x.WhichOneofField() != 0 {
t.Errorf("WhichOneofField on nil returned %d, expected %d", x.WhichOneofField(), 0)
}
x = &testhybridpb.TestAllTypes{}
if x.WhichOneofField() != 0 {
t.Errorf("WhichOneofField returned %d, expected %d", x.WhichOneofField(), 0)
}
tab := []struct {
m *testhybridpb.TestAllTypes
v protoreflect.FieldNumber
}{
{
m: testhybridpb.TestAllTypes_builder{
OneofUint32: proto.Uint32(46),
}.Build(),
v: protoreflect.FieldNumber(testhybridpb.TestAllTypes_OneofUint32_case),
},
{
m: testhybridpb.TestAllTypes_builder{
OneofNestedMessage: testhybridpb.TestAllTypes_NestedMessage_builder{A: proto.Int32(46)}.Build(),
}.Build(),
v: protoreflect.FieldNumber(testhybridpb.TestAllTypes_OneofNestedMessage_case),
},
{
m: testhybridpb.TestAllTypes_builder{
OneofString: proto.String("foo"),
}.Build(),
v: protoreflect.FieldNumber(testhybridpb.TestAllTypes_OneofString_case),
},
{
m: testhybridpb.TestAllTypes_builder{
OneofBytes: []byte("foo"),
}.Build(),
v: protoreflect.FieldNumber(testhybridpb.TestAllTypes_OneofBytes_case),
},
{
m: testhybridpb.TestAllTypes_builder{
OneofBool: proto.Bool(true),
}.Build(),
v: protoreflect.FieldNumber(testhybridpb.TestAllTypes_OneofBool_case),
},
{
m: testhybridpb.TestAllTypes_builder{
OneofUint64: proto.Uint64(0),
}.Build(),
v: protoreflect.FieldNumber(testhybridpb.TestAllTypes_OneofUint64_case),
},
{
m: testhybridpb.TestAllTypes_builder{
OneofFloat: proto.Float32(0.0),
}.Build(),
v: protoreflect.FieldNumber(testhybridpb.TestAllTypes_OneofFloat_case),
},
{
m: testhybridpb.TestAllTypes_builder{
OneofDouble: proto.Float64(1.1),
}.Build(),
v: protoreflect.FieldNumber(testhybridpb.TestAllTypes_OneofDouble_case),
},
{
m: testhybridpb.TestAllTypes_builder{
OneofEnum: testhybridpb.TestAllTypes_BAZ.Enum(),
}.Build(),
v: protoreflect.FieldNumber(testhybridpb.TestAllTypes_OneofEnum_case),
},
}
for _, mv := range tab {
if protoreflect.FieldNumber(mv.m.WhichOneofField()) != mv.v {
t.Errorf("WhichOneofField returned %d, expected %d", mv.m.WhichOneofField(), mv.v)
}
if !mv.m.HasOneofField() {
t.Errorf("HasOneofField returned %t, expected true", mv.m.HasOneofField())
}
mv.m.ClearOneofField()
if mv.m.WhichOneofField() != 0 {
t.Errorf("WhichOneofField returned %d, expected %d", mv.m.WhichOneofField(), 0)
}
if mv.m.HasOneofField() {
t.Errorf("HasOneofField returned %t, expected false", mv.m.HasOneofField())
}
}
}
func TestOpaqueWhich(t *testing.T) {
var x *testopaquepb.TestAllTypes
if x.WhichOneofField() != 0 {
t.Errorf("WhichOneofField on nil returned %d, expected %d", x.WhichOneofField(), 0)
}
x = &testopaquepb.TestAllTypes{}
if x.WhichOneofField() != 0 {
t.Errorf("WhichOneofField returned %d, expected %d", x.WhichOneofField(), 0)
}
en := testopaquepb.TestAllTypes_BAZ
tab := []struct {
m *testopaquepb.TestAllTypes
v protoreflect.FieldNumber
}{
{
m: testopaquepb.TestAllTypes_builder{
OneofUint32: proto.Uint32(46),
}.Build(),
v: protoreflect.FieldNumber(testopaquepb.TestAllTypes_OneofUint32_case),
},
{
m: testopaquepb.TestAllTypes_builder{
OneofNestedMessage: testopaquepb.TestAllTypes_NestedMessage_builder{A: proto.Int32(46)}.Build(),
}.Build(),
v: protoreflect.FieldNumber(testopaquepb.TestAllTypes_OneofNestedMessage_case),
},
{
m: testopaquepb.TestAllTypes_builder{
OneofString: proto.String("foo"),
}.Build(),
v: protoreflect.FieldNumber(testopaquepb.TestAllTypes_OneofString_case),
},
{
m: testopaquepb.TestAllTypes_builder{
OneofBytes: []byte("foo"),
}.Build(),
v: protoreflect.FieldNumber(testopaquepb.TestAllTypes_OneofBytes_case),
},
{
m: testopaquepb.TestAllTypes_builder{
OneofBool: proto.Bool(true),
}.Build(),
v: protoreflect.FieldNumber(testopaquepb.TestAllTypes_OneofBool_case),
},
{
m: testopaquepb.TestAllTypes_builder{
OneofUint64: proto.Uint64(0),
}.Build(),
v: protoreflect.FieldNumber(testopaquepb.TestAllTypes_OneofUint64_case),
},
{
m: testopaquepb.TestAllTypes_builder{
OneofFloat: proto.Float32(0.0),
}.Build(),
v: protoreflect.FieldNumber(testopaquepb.TestAllTypes_OneofFloat_case),
},
{
m: testopaquepb.TestAllTypes_builder{
OneofDouble: proto.Float64(1.1),
}.Build(),
v: protoreflect.FieldNumber(testopaquepb.TestAllTypes_OneofDouble_case),
},
{
m: testopaquepb.TestAllTypes_builder{
OneofEnum: &en,
}.Build(),
v: protoreflect.FieldNumber(testopaquepb.TestAllTypes_OneofEnum_case),
},
}
for _, mv := range tab {
if protoreflect.FieldNumber(mv.m.WhichOneofField()) != mv.v {
t.Errorf("WhichOneofField returned %d, expected %d", mv.m.WhichOneofField(), mv.v)
}
if !mv.m.HasOneofField() {
t.Errorf("HasOneofField returned %t, expected true", mv.m.HasOneofField())
}
mv.m.ClearOneofField()
if mv.m.WhichOneofField() != 0 {
t.Errorf("WhichOneofField returned %d, expected %d", mv.m.WhichOneofField(), 0)
}
if mv.m.HasOneofField() {
t.Errorf("HasOneofField returned %t, expected false", mv.m.HasOneofField())
}
}
}
func TestSyntheticOneofOpen(t *testing.T) {
msg := test3openpb.TestAllTypes{}
md := msg.ProtoReflect().Descriptor()
ood := md.Oneofs().ByName("_optional_int32")
if ood == nil {
t.Fatal("failed to find oneof _optional_int32")
}
if !ood.IsSynthetic() {
t.Fatal("oneof _optional_int32 should be synthetic")
}
if msg.ProtoReflect().WhichOneof(ood) != nil {
t.Error("oneof _optional_int32 should not have a field set yet")
}
msg.OptionalInt32 = proto.Int32(123)
if msg.ProtoReflect().WhichOneof(ood) == nil {
t.Error("oneof _optional_int32 should have a field set")
}
}
func TestSyntheticOneofHybrid(t *testing.T) {
msg := test3hybridpb.TestAllTypes{}
md := msg.ProtoReflect().Descriptor()
ood := md.Oneofs().ByName("_optional_int32")
if ood == nil {
t.Fatal("failed to find oneof _optional_int32")
}
if !ood.IsSynthetic() {
t.Fatal("oneof _optional_int32 should be synthetic")
}
if msg.ProtoReflect().WhichOneof(ood) != nil {
t.Error("oneof _optional_int32 should not have a field set yet")
}
msg.OptionalInt32 = proto.Int32(123)
if msg.ProtoReflect().WhichOneof(ood) == nil {
t.Error("oneof _optional_int32 should have a field set")
}
}
func TestSyntheticOneofOpaque(t *testing.T) {
msg := test3opaquepb.TestAllTypes{}
md := msg.ProtoReflect().Descriptor()
ood := md.Oneofs().ByName("_optional_int32")
if ood == nil {
t.Fatal("failed to find oneof _optional_int32")
}
if !ood.IsSynthetic() {
t.Fatal("oneof _optional_int32 should be synthetic")
}
if msg.ProtoReflect().WhichOneof(ood) != nil {
t.Error("oneof _optional_int32 should not have a field set yet")
}
msg.SetOptionalInt32(123)
if msg.ProtoReflect().WhichOneof(ood) == nil {
t.Error("oneof _optional_int32 should have a field set")
}
}