| // Copyright 2018 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 protodesc |
| |
| import ( |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| "google.golang.org/protobuf/internal/filedesc" |
| "google.golang.org/protobuf/proto" |
| "google.golang.org/protobuf/reflect/protoreflect" |
| "google.golang.org/protobuf/reflect/protoregistry" |
| "google.golang.org/protobuf/testing/protocmp" |
| "google.golang.org/protobuf/types/descriptorpb" |
| "google.golang.org/protobuf/types/dynamicpb" |
| ) |
| |
| func TestEditionsRequired(t *testing.T) { |
| fd := new(filedesc.Field) |
| fd.L0.ParentFile = filedesc.SurrogateEdition2023 |
| fd.L0.FullName = "foo_field" |
| fd.L1.Number = 1337 |
| fd.L1.Cardinality = protoreflect.Required |
| fd.L1.Kind = protoreflect.BytesKind |
| |
| want := &descriptorpb.FieldDescriptorProto{ |
| Name: proto.String("foo_field"), |
| Number: proto.Int32(1337), |
| Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), |
| Type: descriptorpb.FieldDescriptorProto_TYPE_BYTES.Enum(), |
| } |
| |
| got := ToFieldDescriptorProto(fd) |
| if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" { |
| t.Errorf("ToFieldDescriptor: unexpected diff (-want +got):\n%s", diff) |
| } |
| } |
| |
| func TestProto2Required(t *testing.T) { |
| fd := new(filedesc.Field) |
| fd.L0.ParentFile = filedesc.SurrogateProto2 |
| fd.L0.FullName = "foo_field" |
| fd.L1.Number = 1337 |
| fd.L1.Cardinality = protoreflect.Required |
| fd.L1.Kind = protoreflect.BytesKind |
| |
| want := &descriptorpb.FieldDescriptorProto{ |
| Name: proto.String("foo_field"), |
| Number: proto.Int32(1337), |
| Label: descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(), |
| Type: descriptorpb.FieldDescriptorProto_TYPE_BYTES.Enum(), |
| } |
| |
| got := ToFieldDescriptorProto(fd) |
| if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" { |
| t.Errorf("ToFieldDescriptor: unexpected diff (-want +got):\n%s", diff) |
| } |
| } |
| |
| func TestEditionsDelimited(t *testing.T) { |
| md := new(filedesc.Message) |
| md.L0.ParentFile = filedesc.SurrogateEdition2023 |
| md.L0.FullName = "foo_message" |
| fd := new(filedesc.Field) |
| fd.L0.ParentFile = filedesc.SurrogateEdition2023 |
| fd.L0.FullName = "foo_field" |
| fd.L1.Number = 1337 |
| fd.L1.Cardinality = protoreflect.Optional |
| fd.L1.Kind = protoreflect.GroupKind |
| fd.L1.Message = md |
| |
| want := &descriptorpb.FieldDescriptorProto{ |
| Name: proto.String("foo_field"), |
| Number: proto.Int32(1337), |
| Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), |
| Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), |
| TypeName: proto.String(".foo_message"), |
| } |
| |
| got := ToFieldDescriptorProto(fd) |
| if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" { |
| t.Errorf("ToFieldDescriptor: unexpected diff (-want +got):\n%s", diff) |
| } |
| } |
| |
| func TestProto2Group(t *testing.T) { |
| md := new(filedesc.Message) |
| md.L0.ParentFile = filedesc.SurrogateProto2 |
| md.L0.FullName = "foo_message" |
| fd := new(filedesc.Field) |
| fd.L0.ParentFile = filedesc.SurrogateProto2 |
| fd.L0.FullName = "foo_field" |
| fd.L1.Number = 1337 |
| fd.L1.Cardinality = protoreflect.Optional |
| fd.L1.Kind = protoreflect.GroupKind |
| fd.L1.Message = md |
| |
| want := &descriptorpb.FieldDescriptorProto{ |
| Name: proto.String("foo_field"), |
| Number: proto.Int32(1337), |
| Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), |
| Type: descriptorpb.FieldDescriptorProto_TYPE_GROUP.Enum(), |
| TypeName: proto.String(".foo_message"), |
| } |
| |
| got := ToFieldDescriptorProto(fd) |
| if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" { |
| t.Errorf("ToFieldDescriptor: unexpected diff (-want +got):\n%s", diff) |
| } |
| } |
| |
| func TestEdition2024Attributes(t *testing.T) { |
| // Edition 2024 introduces new language capabilities which are used to populate new |
| // attributes in a FileDescriptorProto. But none of them are used by the runtime so |
| // are not accessible via protoreflect.FileDescriptor. This verifies that, despite |
| // there not being a way to access them through a protoreflect.FileDescriptor, they |
| // still successfully make it through a round-trip of descriptor proto -> descriptor |
| // and back. |
| optionsProto := &descriptorpb.FileDescriptorProto{ |
| Name: proto.String("foo_options.proto"), |
| Package: proto.String("foo"), |
| Syntax: proto.String("proto3"), |
| Dependency: []string{"google/protobuf/descriptor.proto"}, |
| Extension: []*descriptorpb.FieldDescriptorProto{ |
| { |
| Extendee: proto.String("google.protobuf.FileOptions"), |
| Name: proto.String("foo_option"), |
| Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), |
| Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), |
| Number: proto.Int32(10101), |
| }, |
| }, |
| } |
| |
| fd, err := NewFile(optionsProto, protoregistry.GlobalFiles) |
| if err != nil { |
| t.Fatalf("failed to create protoreflect.FileDescriptor: %v", err) |
| } |
| fooFileOption := dynamicpb.NewExtensionType(fd.Extensions().Get(0)) |
| fileOpts := &descriptorpb.FileOptions{} |
| proto.SetExtension(fileOpts, fooFileOption, "abc") |
| |
| msgs := []*descriptorpb.DescriptorProto{ |
| { |
| Name: proto.String("DefaultMessage"), |
| }, |
| { |
| Name: proto.String("ExportMessage"), |
| Visibility: descriptorpb.SymbolVisibility_VISIBILITY_EXPORT.Enum(), |
| }, |
| { |
| Name: proto.String("LocalMessage"), |
| Visibility: descriptorpb.SymbolVisibility_VISIBILITY_LOCAL.Enum(), |
| }, |
| } |
| enums := []*descriptorpb.EnumDescriptorProto{ |
| { |
| Name: proto.String("DefaultEnum"), |
| Value: []*descriptorpb.EnumValueDescriptorProto{ |
| { |
| Name: proto.String("DEFAULT_ZERO"), |
| Number: proto.Int32(0), |
| }, |
| }, |
| }, |
| { |
| Name: proto.String("ExportEnum"), |
| Visibility: descriptorpb.SymbolVisibility_VISIBILITY_EXPORT.Enum(), |
| Value: []*descriptorpb.EnumValueDescriptorProto{ |
| { |
| Name: proto.String("EXPORT_ZERO"), |
| Number: proto.Int32(0), |
| }, |
| }, |
| }, |
| { |
| Name: proto.String("LocalEnum"), |
| Visibility: descriptorpb.SymbolVisibility_VISIBILITY_LOCAL.Enum(), |
| Value: []*descriptorpb.EnumValueDescriptorProto{ |
| { |
| Name: proto.String("LOCAL_ZERO"), |
| Number: proto.Int32(0), |
| }, |
| }, |
| }, |
| } |
| |
| fdProto := &descriptorpb.FileDescriptorProto{ |
| Name: proto.String("foo.proto"), |
| Package: proto.String("foo"), |
| Syntax: proto.String("editions"), |
| Edition: descriptorpb.Edition_EDITION_2024.Enum(), |
| OptionDependency: []string{"foo_options.proto"}, |
| Options: fileOpts, |
| MessageType: []*descriptorpb.DescriptorProto{ |
| { |
| Name: proto.String("DefaultMessage"), |
| NestedType: msgs, |
| EnumType: enums, |
| }, |
| { |
| Name: proto.String("ExportMessage"), |
| Visibility: descriptorpb.SymbolVisibility_VISIBILITY_EXPORT.Enum(), |
| NestedType: msgs, |
| EnumType: enums, |
| }, |
| { |
| Name: proto.String("LocalMessage"), |
| Visibility: descriptorpb.SymbolVisibility_VISIBILITY_LOCAL.Enum(), |
| NestedType: msgs, |
| EnumType: enums, |
| }, |
| }, |
| EnumType: enums, |
| } |
| |
| var reg protoregistry.Files |
| if err := reg.RegisterFile(descriptorpb.File_google_protobuf_descriptor_proto); err != nil { |
| t.Fatalf("failed to register google/protobuf/descriptor.proto: %v", err) |
| } |
| if err := reg.RegisterFile(fd); err != nil { |
| t.Fatalf("failed to register foo_options.proto: %v", err) |
| } |
| fd, err = NewFile(fdProto, ®) |
| if err != nil { |
| t.Fatalf("failed to create protoreflect.FileDescriptor: %v", err) |
| } |
| |
| roundTripped := ToFileDescriptorProto(fd) |
| if diff := cmp.Diff(fdProto, roundTripped, protocmp.Transform()); diff != "" { |
| t.Fatalf("file did not survive round trip unchanged; diff:\n%s", diff) |
| } |
| } |