protobuf-go/reflect/protodesc/file_test.go
Joe Tsai 3d8e369c4e all: implement proto1 weak fields
This implements generation of and reflection support for weak fields.
Weak fields are a proto1 feature where the "weak" option can be specified
on a singular message field. A weak reference results in generated code
that does not directly link in the dependency containing the weak message.

Weak field support is not added to any of the serialization logic.

Change-Id: I08ccfa72bc80b2ffb6af527a1677a0a81dcf33fb
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/185399
Reviewed-by: Damien Neil <dneil@google.com>
2019-07-15 18:44:12 +00:00

940 lines
27 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 protodesc
import (
"fmt"
"strings"
"testing"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/internal/flags"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/descriptorpb"
)
func mustParseFile(s string) *descriptorpb.FileDescriptorProto {
pb := new(descriptorpb.FileDescriptorProto)
if err := prototext.Unmarshal([]byte(s), pb); err != nil {
panic(err)
}
return pb
}
func cloneFile(in *descriptorpb.FileDescriptorProto) *descriptorpb.FileDescriptorProto {
out := new(descriptorpb.FileDescriptorProto)
proto.Merge(out, in)
return out
}
var (
proto2Enum = mustParseFile(`
syntax: "proto2"
name: "proto2_enum.proto"
package: "test.proto2"
enum_type: [{name:"Enum" value:[{name:"ONE" number:1}]}]
`)
proto3Message = mustParseFile(`
syntax: "proto3"
name: "proto3_message.proto"
package: "test.proto3"
message_type: [{
name: "Message"
field: [
{name:"foo" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
{name:"bar" number:2 label:LABEL_OPTIONAL type:TYPE_STRING}
]
}]
`)
extendableMessage = mustParseFile(`
syntax: "proto2"
name: "extendable_message.proto"
package: "test.proto2"
message_type: [{name:"Message" extension_range:[{start:1 end:1000}]}]
`)
importPublicFile1 = mustParseFile(`
syntax: "proto3"
name: "import_public1.proto"
dependency: ["proto2_enum.proto", "proto3_message.proto", "extendable_message.proto"]
message_type: [{name:"Public1"}]
`)
importPublicFile2 = mustParseFile(`
syntax: "proto3"
name: "import_public2.proto"
dependency: ["import_public1.proto"]
public_dependency: [0]
message_type: [{name:"Public2"}]
`)
importPublicFile3 = mustParseFile(`
syntax: "proto3"
name: "import_public3.proto"
dependency: ["import_public2.proto", "extendable_message.proto"]
public_dependency: [0]
message_type: [{name:"Public3"}]
`)
importPublicFile4 = mustParseFile(`
syntax: "proto3"
name: "import_public4.proto"
dependency: ["import_public2.proto", "import_public3.proto", "proto2_enum.proto"]
public_dependency: [0, 1]
message_type: [{name:"Public4"}]
`)
)
func TestNewFile(t *testing.T) {
tests := []struct {
label string
inDeps []*descriptorpb.FileDescriptorProto
inDesc *descriptorpb.FileDescriptorProto
inOpts []option
wantDesc *descriptorpb.FileDescriptorProto
wantErr string
}{{
label: "empty path",
inDesc: mustParseFile(``),
wantErr: `path must be populated`,
}, {
label: "empty package and syntax",
inDesc: mustParseFile(`name:"weird" package:""`),
}, {
label: "invalid syntax",
inDesc: mustParseFile(`name:"weird" syntax:"proto9"`),
wantErr: `invalid syntax: "proto9"`,
}, {
label: "bad package",
inDesc: mustParseFile(`name:"weird" package:"$"`),
wantErr: `invalid package: "$"`,
}, {
label: "unresolvable import",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
dependency: "dep.proto"
`),
wantErr: `could not resolve import "dep.proto": not found`,
}, {
label: "unresolvable import but allowed",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
dependency: "dep.proto"
`),
inOpts: []option{allowUnresolvable()},
}, {
label: "duplicate import",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
dependency: ["dep.proto", "dep.proto"]
`),
inOpts: []option{allowUnresolvable()},
wantErr: `already imported "dep.proto"`,
}, {
label: "invalid weak import",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
dependency: "dep.proto"
weak_dependency: [-23]
`),
inOpts: []option{allowUnresolvable()},
wantErr: `invalid or duplicate weak import index: -23`,
}, {
label: "normal weak and public import",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
dependency: "dep.proto"
weak_dependency: [0]
public_dependency: [0]
`),
inOpts: []option{allowUnresolvable()},
}, {
label: "import public indirect dependency duplicate",
inDeps: []*descriptorpb.FileDescriptorProto{
mustParseFile(`name:"leaf.proto"`),
mustParseFile(`name:"public.proto" dependency:"leaf.proto" public_dependency:0`),
},
inDesc: mustParseFile(`
name: "test.proto"
package: ""
dependency: ["public.proto", "leaf.proto"]
`),
}, {
label: "import public graph",
inDeps: []*descriptorpb.FileDescriptorProto{
cloneFile(proto2Enum),
cloneFile(proto3Message),
cloneFile(extendableMessage),
cloneFile(importPublicFile1),
cloneFile(importPublicFile2),
cloneFile(importPublicFile3),
cloneFile(importPublicFile4),
},
inDesc: mustParseFile(`
name: "test.proto"
package: "test.graph"
dependency: ["import_public4.proto"],
`),
// TODO: Test import public
}, {
label: "preserve source code locations",
inDesc: mustParseFile(`
name: "test.proto"
package: "fizz.buzz"
source_code_info: {location: [{
span: [39,0,882,1]
}, {
path: [12]
span: [39,0,18]
leading_detached_comments: [" foo\n"," bar\n"]
}, {
path: [8,9]
span: [51,0,28]
leading_comments: " Comment\n"
}]}
`),
}, {
label: "invalid source code span",
inDesc: mustParseFile(`
name: "test.proto"
package: "fizz.buzz"
source_code_info: {location: [{
span: [39]
}]}
`),
wantErr: `invalid span: [39]`,
}, {
label: "resolve relative reference",
inDesc: mustParseFile(`
name: "test.proto"
package: "fizz.buzz"
message_type: [{
name: "A"
field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"B.C"}]
nested_type: [{name: "B"}]
}, {
name: "B"
nested_type: [{name: "C"}]
}]
`),
wantDesc: mustParseFile(`
name: "test.proto"
package: "fizz.buzz"
message_type: [{
name: "A"
field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:".fizz.buzz.B.C"}]
nested_type: [{name: "B"}]
}, {
name: "B"
nested_type: [{name: "C"}]
}]
`),
}, {
label: "resolve the wrong type",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{
name: "M"
field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"E"}]
enum_type: [{name: "E" value: [{name:"V0" number:0}, {name:"V1" number:1}]}]
}]
`),
wantErr: `message field "M.F" cannot resolve type: resolved "M.E", but it is not an message`,
}, {
label: "auto-resolve unknown kind",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{
name: "M"
field: [{name:"F" number:1 label:LABEL_OPTIONAL type_name:"E"}]
enum_type: [{name: "E" value: [{name:"V0" number:0}, {name:"V1" number:1}]}]
}]
`),
wantDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{
name: "M"
field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".M.E"}]
enum_type: [{name: "E" value: [{name:"V0" number:0}, {name:"V1" number:1}]}]
}]
`),
}, {
label: "unresolved import",
inDesc: mustParseFile(`
name: "test.proto"
package: "fizz.buzz"
dependency: "remote.proto"
`),
wantErr: `could not resolve import "remote.proto": not found`,
}, {
label: "unresolved message field",
inDesc: mustParseFile(`
name: "test.proto"
package: "fizz.buzz"
message_type: [{
name: "M"
field: [{name:"F1" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:"some.other.enum" default_value:"UNKNOWN"}]
}]
`),
wantErr: `message field "fizz.buzz.M.F1" cannot resolve type: "*.some.other.enum" not found`,
}, {
label: "unresolved default enum value",
inDesc: mustParseFile(`
name: "test.proto"
package: "fizz.buzz"
message_type: [{
name: "M"
field: [{name:"F1" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:"E" default_value:"UNKNOWN"}]
enum_type: [{name:"E" value:[{name:"V0" number:0}]}]
}]
`),
wantErr: `message field "fizz.buzz.M.F1" has invalid default: could not parse value for enum: "UNKNOWN"`,
}, {
label: "allowed unresolved default enum value",
inDesc: mustParseFile(`
name: "test.proto"
package: "fizz.buzz"
message_type: [{
name: "M"
field: [{name:"F1" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".fizz.buzz.M.E" default_value:"UNKNOWN"}]
enum_type: [{name:"E" value:[{name:"V0" number:0}]}]
}]
`),
inOpts: []option{allowUnresolvable()},
}, {
label: "unresolved extendee",
inDesc: mustParseFile(`
name: "test.proto"
package: "fizz.buzz"
extension: [{name:"X" number:1 label:LABEL_OPTIONAL extendee:"some.extended.message" type:TYPE_MESSAGE type_name:"some.other.message"}]
`),
wantErr: `extension field "fizz.buzz.X" cannot resolve extendee: "*.some.extended.message" not found`,
}, {
label: "unresolved method input",
inDesc: mustParseFile(`
name: "test.proto"
package: "fizz.buzz"
service: [{
name: "S"
method: [{name:"M" input_type:"foo.bar.input" output_type:".absolute.foo.bar.output"}]
}]
`),
wantErr: `service method "fizz.buzz.S.M" cannot resolve input: "*.foo.bar.input" not found`,
}, {
label: "allowed unresolved references",
inDesc: mustParseFile(`
name: "test.proto"
package: "fizz.buzz"
dependency: "remote.proto"
message_type: [{
name: "M"
field: [{name:"F1" number:1 label:LABEL_OPTIONAL type_name:"some.other.enum" default_value:"UNKNOWN"}]
}]
extension: [{name:"X" number:1 label:LABEL_OPTIONAL extendee:"some.extended.message" type:TYPE_MESSAGE type_name:"some.other.message"}]
service: [{
name: "S"
method: [{name:"M" input_type:"foo.bar.input" output_type:".absolute.foo.bar.output"}]
}]
`),
inOpts: []option{allowUnresolvable()},
}, {
label: "resolved but not imported",
inDeps: []*descriptorpb.FileDescriptorProto{mustParseFile(`
name: "dep.proto"
package: "fizz"
message_type: [{name:"M" nested_type:[{name:"M"}]}]
`)},
inDesc: mustParseFile(`
name: "test.proto"
package: "fizz.buzz"
message_type: [{
name: "M"
field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"M.M"}]
}]
`),
wantErr: `message field "fizz.buzz.M.F" cannot resolve type: resolved "fizz.M.M", but "dep.proto" is not imported`,
}, {
label: "resolved from remote import",
inDeps: []*descriptorpb.FileDescriptorProto{mustParseFile(`
name: "dep.proto"
package: "fizz"
message_type: [{name:"M" nested_type:[{name:"M"}]}]
`)},
inDesc: mustParseFile(`
name: "test.proto"
package: "fizz.buzz"
dependency: "dep.proto"
message_type: [{
name: "M"
field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"M.M"}]
}]
`),
wantDesc: mustParseFile(`
name: "test.proto"
package: "fizz.buzz"
dependency: "dep.proto"
message_type: [{
name: "M"
field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:".fizz.M.M"}]
}]
`),
}, {
label: "namespace conflict on enum value",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
enum_type: [{
name: "foo"
value: [{name:"foo" number:0}]
}]
`),
wantErr: `descriptor "foo" already declared`,
}, {
label: "no namespace conflict on message field",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{
name: "foo"
field: [{name:"foo" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
}]
`),
}, {
label: "invalid name",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name: "$"}]
`),
wantErr: `descriptor "" has an invalid nested name: "$"`,
}, {
label: "invalid empty enum",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" enum_type:[{name:"E"}]}]
`),
wantErr: `enum "M.E" must contain at least one value declaration`,
}, {
label: "invalid enum value without number",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" enum_type:[{name:"E" value:[{name:"one"}]}]}]
`),
wantErr: `enum value "M.one" must have a specified number`,
}, {
label: "valid enum",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" enum_type:[{name:"E" value:[{name:"one" number:1}]}]}]
`),
}, {
label: "invalid enum reserved names",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" enum_type:[{
name: "E"
reserved_name: [""]
value: [{name:"V" number:0}]
}]}]
`),
// NOTE: In theory this should be an error.
// See https://github.com/protocolbuffers/protobuf/issues/6335.
/*wantErr: `enum "M.E" reserved names has invalid name: ""`,*/
}, {
label: "duplicate enum reserved names",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" enum_type:[{
name: "E"
reserved_name: ["foo", "foo"]
}]}]
`),
wantErr: `enum "M.E" reserved names has duplicate name: "foo"`,
}, {
label: "valid enum reserved names",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" enum_type:[{
name: "E"
reserved_name: ["foo", "bar"]
value: [{name:"baz" number:1}]
}]}]
`),
}, {
label: "use of enum reserved names",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" enum_type:[{
name: "E"
reserved_name: ["foo", "bar"]
value: [{name:"foo" number:1}]
}]}]
`),
wantErr: `enum value "M.foo" must not use reserved name`,
}, {
label: "invalid enum reserved ranges",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" enum_type:[{
name: "E"
reserved_range: [{start:5 end:4}]
}]}]
`),
wantErr: `enum "M.E" reserved ranges has invalid range: 5 to 4`,
}, {
label: "overlapping enum reserved ranges",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" enum_type:[{
name: "E"
reserved_range: [{start:1 end:1000}, {start:10 end:100}]
}]}]
`),
wantErr: `enum "M.E" reserved ranges has overlapping ranges: 1 to 1000 with 10 to 100`,
}, {
label: "valid enum reserved names",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" enum_type:[{
name: "E"
reserved_range: [{start:1 end:10}, {start:100 end:1000}]
value: [{name:"baz" number:50}]
}]}]
`),
}, {
label: "use of enum reserved range",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" enum_type:[{
name: "E"
reserved_range: [{start:1 end:10}, {start:100 end:1000}]
value: [{name:"baz" number:500}]
}]}]
`),
wantErr: `enum value "M.baz" must not use reserved number 500`,
}, {
label: "unused enum alias feature",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" enum_type:[{
name: "E"
value: [{name:"baz" number:500}]
options: {allow_alias:true}
}]}]
`),
wantErr: `enum "M.E" allows aliases, but none were found`,
}, {
label: "enum number conflicts",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" enum_type:[{
name: "E"
value: [{name:"foo" number:0}, {name:"bar" number:1}, {name:"baz" number:1}]
}]}]
`),
wantErr: `enum "M.E" has conflicting non-aliased values on number 1: "baz" with "bar"`,
}, {
label: "aliased enum numbers",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" enum_type:[{
name: "E"
value: [{name:"foo" number:0}, {name:"bar" number:1}, {name:"baz" number:1}]
options: {allow_alias:true}
}]}]
`),
}, {
label: "invalid proto3 enum",
inDesc: mustParseFile(`
syntax: "proto3"
name: "test.proto"
package: ""
message_type: [{name:"M" enum_type:[{
name: "E"
value: [{name:"baz" number:500}]
}]}]
`),
wantErr: `enum "M.baz" using proto3 semantics must have zero number for the first value`,
}, {
label: "valid proto3 enum",
inDesc: mustParseFile(`
syntax: "proto3"
name: "test.proto"
package: ""
message_type: [{name:"M" enum_type:[{
name: "E"
value: [{name:"baz" number:0}]
}]}]
`),
}, {
label: "proto3 enum name prefix conflict",
inDesc: mustParseFile(`
syntax: "proto3"
name: "test.proto"
package: ""
message_type: [{name:"M" enum_type:[{
name: "E"
value: [{name:"e_Foo" number:0}, {name:"fOo" number:1}]
}]}]
`),
wantErr: `enum "M.E" using proto3 semantics has conflict: "fOo" with "e_Foo"`,
}, {
label: "proto2 enum has name prefix check",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" enum_type:[{
name: "E"
value: [{name:"e_Foo" number:0}, {name:"fOo" number:1}]
}]}]
`),
}, {
label: "proto3 enum same name prefix with number conflict",
inDesc: mustParseFile(`
syntax: "proto3"
name: "test.proto"
package: ""
message_type: [{name:"M" enum_type:[{
name: "E"
value: [{name:"e_Foo" number:0}, {name:"fOo" number:0}]
}]}]
`),
wantErr: `enum "M.E" has conflicting non-aliased values on number 0: "fOo" with "e_Foo"`,
}, {
label: "proto3 enum same name prefix with alias numbers",
inDesc: mustParseFile(`
syntax: "proto3"
name: "test.proto"
package: ""
message_type: [{name:"M" enum_type:[{
name: "E"
value: [{name:"e_Foo" number:0}, {name:"fOo" number:0}]
options: {allow_alias: true}
}]}]
`),
}, {
label: "invalid message reserved names",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" nested_type:[{
name: "M"
reserved_name: ["$"]
}]}]
`),
// NOTE: In theory this should be an error.
// See https://github.com/protocolbuffers/protobuf/issues/6335.
/*wantErr: `message "M.M" reserved names has invalid name: "$"`,*/
}, {
label: "valid message reserved names",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" nested_type:[{
name: "M"
reserved_name: ["foo", "bar"]
field: [{name:"foo" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
}]}]
`),
wantErr: `message field "M.M.foo" must not use reserved name`,
}, {
label: "valid message reserved names",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" nested_type:[{
name: "M"
reserved_name: ["foo", "bar"]
field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:0}]
oneof_decl: [{name:"foo"}] # not affected by reserved_name
}]}]
`),
}, {
label: "invalid reserved number",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" nested_type:[{
name: "M"
reserved_range: [{start:1 end:1}]
field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
}]}]
`),
wantErr: `message "M.M" reserved ranges has invalid field number: 0`,
}, {
label: "invalid reserved ranges",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" nested_type:[{
name: "M"
reserved_range: [{start:2 end:2}]
field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
}]}]
`),
wantErr: `message "M.M" reserved ranges has invalid range: 2 to 1`,
}, {
label: "overlapping reserved ranges",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" nested_type:[{
name: "M"
reserved_range: [{start:1 end:10}, {start:2 end:9}]
field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
}]}]
`),
wantErr: `message "M.M" reserved ranges has overlapping ranges: 1 to 9 with 2 to 8`,
}, {
label: "use of reserved message field number",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" nested_type:[{
name: "M"
reserved_range: [{start:10 end:20}, {start:20 end:30}, {start:30 end:31}]
field: [{name:"baz" number:30 label:LABEL_OPTIONAL type:TYPE_STRING}]
}]}]
`),
wantErr: `message field "M.M.baz" must not use reserved number 30`,
}, {
label: "invalid extension ranges",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" nested_type:[{
name: "M"
extension_range: [{start:-500 end:2}]
field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
}]}]
`),
wantErr: `message "M.M" extension ranges has invalid field number: -500`,
}, {
label: "overlapping reserved and extension ranges",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" nested_type:[{
name: "M"
reserved_range: [{start:15 end:20}, {start:1 end:3}, {start:7 end:10}]
extension_range: [{start:8 end:9}, {start:3 end:5}]
}]}]
`),
wantErr: `message "M.M" reserved and extension ranges has overlapping ranges: 7 to 9 with 8`,
}, {
label: "message field conflicting number",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" nested_type:[{
name: "M"
field: [
{name:"one" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
{name:"One" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}
]
}]}]
`),
wantErr: `message "M.M" has conflicting fields: "One" with "one"`,
}, {
label: "invalid MessageSet",
inDesc: mustParseFile(`
syntax: "proto3"
name: "test.proto"
package: ""
message_type: [{name:"M" nested_type:[{
name: "M"
options: {message_set_wire_format:true}
}]}]
`),
wantErr: func() string {
if flags.Proto1Legacy {
return `message "M.M" is an invalid proto1 MessageSet`
} else {
return `message "M.M" is a MessageSet, which is a legacy proto1 feature that is no longer supported`
}
}(),
}, {
label: "valid MessageSet",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" nested_type:[{
name: "M"
extension_range: [{start:1 end:100000}]
options: {message_set_wire_format:true}
}]}]
`),
wantErr: func() string {
if flags.Proto1Legacy {
return ""
} else {
return `message "M.M" is a MessageSet, which is a legacy proto1 feature that is no longer supported`
}
}(),
}, {
label: "invalid extension ranges in proto3",
inDesc: mustParseFile(`
syntax: "proto3"
name: "test.proto"
package: ""
message_type: [{name:"M" nested_type:[{
name: "M"
extension_range: [{start:1 end:100000}]
}]}]
`),
wantErr: `message "M.M" using proto3 semantics cannot have extension ranges`,
}, {
label: "proto3 message fields conflict",
inDesc: mustParseFile(`
syntax: "proto3"
name: "test.proto"
package: ""
message_type: [{name:"M" nested_type:[{
name: "M"
field: [
{name:"_b_a_z_" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
{name:"baz" number:2 label:LABEL_OPTIONAL type:TYPE_STRING}
]
}]}]
`),
wantErr: `proto: message "M.M" using proto3 semantics has conflict: "baz" with "_b_a_z_"`,
}, {
label: "proto3 message fields",
inDesc: mustParseFile(`
syntax: "proto3"
name: "test.proto"
package: ""
message_type: [{name:"M" nested_type:[{
name: "M"
field: [{name:"_b_a_z_" number:1 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:0}]
oneof_decl: [{name:"baz"}] # proto3 name conflict logic does not include oneof
}]}]
`),
}, {
label: "proto2 message fields with no conflict",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
message_type: [{name:"M" nested_type:[{
name: "M"
field: [
{name:"_b_a_z_" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
{name:"baz" number:2 label:LABEL_OPTIONAL type:TYPE_STRING}
]
}]}]
`),
}, {
label: "proto3 message with unresolved enum",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
syntax: "proto3"
message_type: [{
name: "M"
field: [
{name:"enum" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".fizz.buzz.Enum"}
]
}]
`),
inOpts: []option{allowUnresolvable()},
// TODO: Test field and oneof handling in validateMessageDeclarations
// TODO: Test unmarshalDefault
// TODO: Test validateExtensionDeclarations
// TODO: Test checkValidGroup
// TODO: Test checkValidMap
}, {
label: "empty service",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
service: [{name:"service"}]
`),
}, {
label: "service with method with unresolved",
inDesc: mustParseFile(`
name: "test.proto"
package: ""
service: [{
name: "service"
method: [{
name:"method"
input_type:"foo"
output_type:".foo.bar.baz"
}]
}]
`),
inOpts: []option{allowUnresolvable()},
}, {
label: "service with wrong reference type",
inDeps: []*descriptorpb.FileDescriptorProto{
cloneFile(proto3Message),
cloneFile(proto2Enum),
},
inDesc: mustParseFile(`
name: "test.proto"
package: ""
dependency: ["proto2_enum.proto", "proto3_message.proto"]
service: [{
name: "service"
method: [{
name: "method"
input_type: ".test.proto2.Enum",
output_type: ".test.proto3.Message"
}]
}]
`),
wantErr: `service method "service.method" cannot resolve input: resolved "test.proto2.Enum", but it is not an message`,
}}
for _, tt := range tests {
t.Run(tt.label, func(t *testing.T) {
r := new(protoregistry.Files)
for i, dep := range tt.inDeps {
f, err := newFile(dep, r)
if err != nil {
t.Fatalf("dependency %d: unexpected NewFile() error: %v", i, err)
}
if err := r.Register(f); err != nil {
t.Fatalf("dependency %d: unexpected Register() error: %v", i, err)
}
}
var gotDesc *descriptorpb.FileDescriptorProto
if tt.wantErr == "" && tt.wantDesc == nil {
tt.wantDesc = cloneFile(tt.inDesc)
}
gotFile, err := newFile(tt.inDesc, r, tt.inOpts...)
if gotFile != nil {
gotDesc = ToFileDescriptorProto(gotFile)
}
if !proto.Equal(gotDesc, tt.wantDesc) {
t.Errorf("NewFile() mismatch:\ngot %v\nwant %v", gotDesc, tt.wantDesc)
}
if ((err == nil) != (tt.wantErr == "")) || !strings.Contains(fmt.Sprint(err), tt.wantErr) {
t.Errorf("NewFile() error:\ngot: %v\nwant: %v", err, tt.wantErr)
}
})
}
}