123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939 |
- // 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.ProtoLegacy {
- 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.ProtoLegacy {
- 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: `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)
- }
- })
- }
- }
|