...

Source file src/google.golang.org/protobuf/reflect/protodesc/file_test.go

Documentation: google.golang.org/protobuf/reflect/protodesc

     1  // Copyright 2019 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package protodesc
     6  
     7  import (
     8  	"fmt"
     9  	"strings"
    10  	"testing"
    11  
    12  	"google.golang.org/protobuf/encoding/prototext"
    13  	"google.golang.org/protobuf/internal/flags"
    14  	"google.golang.org/protobuf/proto"
    15  	"google.golang.org/protobuf/reflect/protoreflect"
    16  	"google.golang.org/protobuf/reflect/protoregistry"
    17  
    18  	"google.golang.org/protobuf/internal/filedesc"
    19  	"google.golang.org/protobuf/types/descriptorpb"
    20  )
    21  
    22  func mustParseFile(s string) *descriptorpb.FileDescriptorProto {
    23  	pb := new(descriptorpb.FileDescriptorProto)
    24  	if err := prototext.Unmarshal([]byte(s), pb); err != nil {
    25  		panic(err)
    26  	}
    27  	return pb
    28  }
    29  
    30  func cloneFile(in *descriptorpb.FileDescriptorProto) *descriptorpb.FileDescriptorProto {
    31  	return proto.Clone(in).(*descriptorpb.FileDescriptorProto)
    32  }
    33  
    34  var (
    35  	proto2Enum = mustParseFile(`
    36  		syntax:    "proto2"
    37  		name:      "proto2_enum.proto"
    38  		package:   "test.proto2"
    39  		enum_type: [{name:"Enum" value:[{name:"ONE" number:1}]}]
    40  	`)
    41  	proto3Message = mustParseFile(`
    42  		syntax:    "proto3"
    43  		name:      "proto3_message.proto"
    44  		package:   "test.proto3"
    45  		message_type: [{
    46  			name:  "Message"
    47  			field: [
    48  				{name:"foo" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
    49  				{name:"bar" number:2 label:LABEL_OPTIONAL type:TYPE_STRING}
    50  			]
    51  		}]
    52  	`)
    53  	extendableMessage = mustParseFile(`
    54  		syntax:       "proto2"
    55  		name:         "extendable_message.proto"
    56  		package:      "test.proto2"
    57  		message_type: [{name:"Message" extension_range:[{start:1 end:1000}]}]
    58  	`)
    59  	importPublicFile1 = mustParseFile(`
    60  		syntax:            "proto3"
    61  		name:              "import_public1.proto"
    62  		dependency:        ["proto2_enum.proto", "proto3_message.proto", "extendable_message.proto"]
    63  		message_type:      [{name:"Public1"}]
    64  	`)
    65  	importPublicFile2 = mustParseFile(`
    66  		syntax:            "proto3"
    67  		name:              "import_public2.proto"
    68  		dependency:        ["import_public1.proto"]
    69  		public_dependency: [0]
    70  		message_type:      [{name:"Public2"}]
    71  	`)
    72  	importPublicFile3 = mustParseFile(`
    73  		syntax:            "proto3"
    74  		name:              "import_public3.proto"
    75  		dependency:        ["import_public2.proto", "extendable_message.proto"]
    76  		public_dependency: [0]
    77  		message_type:      [{name:"Public3"}]
    78  	`)
    79  	importPublicFile4 = mustParseFile(`
    80  		syntax:            "proto3"
    81  		name:              "import_public4.proto"
    82  		dependency:        ["import_public2.proto", "import_public3.proto", "proto2_enum.proto"]
    83  		public_dependency: [0, 1]
    84  		message_type:      [{name:"Public4"}]
    85  	`)
    86  )
    87  
    88  func TestNewFile(t *testing.T) {
    89  	tests := []struct {
    90  		label    string
    91  		inDeps   []*descriptorpb.FileDescriptorProto
    92  		inDesc   *descriptorpb.FileDescriptorProto
    93  		inOpts   FileOptions
    94  		wantDesc *descriptorpb.FileDescriptorProto
    95  		wantErr  string
    96  	}{{
    97  		label:   "empty path",
    98  		inDesc:  mustParseFile(``),
    99  		wantErr: `path must be populated`,
   100  	}, {
   101  		label:  "empty package and syntax",
   102  		inDesc: mustParseFile(`name:"weird"`),
   103  	}, {
   104  		label:   "invalid syntax",
   105  		inDesc:  mustParseFile(`name:"weird" syntax:"proto9"`),
   106  		wantErr: `invalid syntax: "proto9"`,
   107  	}, {
   108  		label:   "bad package",
   109  		inDesc:  mustParseFile(`name:"weird" package:"$"`),
   110  		wantErr: `invalid package: "$"`,
   111  	}, {
   112  		label: "unresolvable import",
   113  		inDesc: mustParseFile(`
   114  			name:       "test.proto"
   115  			dependency: "dep.proto"
   116  		`),
   117  		wantErr: `could not resolve import "dep.proto": not found`,
   118  	}, {
   119  		label: "unresolvable import but allowed",
   120  		inDesc: mustParseFile(`
   121  			name:       "test.proto"
   122  			dependency: "dep.proto"
   123  		`),
   124  		inOpts: FileOptions{AllowUnresolvable: true},
   125  	}, {
   126  		label: "duplicate import",
   127  		inDesc: mustParseFile(`
   128  			name:       "test.proto"
   129  			dependency: ["dep.proto", "dep.proto"]
   130  		`),
   131  		inOpts:  FileOptions{AllowUnresolvable: true},
   132  		wantErr: `already imported "dep.proto"`,
   133  	}, {
   134  		label: "invalid weak import",
   135  		inDesc: mustParseFile(`
   136  			name:            "test.proto"
   137  			dependency:      "dep.proto"
   138  			weak_dependency: [-23]
   139  		`),
   140  		inOpts:  FileOptions{AllowUnresolvable: true},
   141  		wantErr: `invalid or duplicate weak import index: -23`,
   142  	}, {
   143  		label: "normal weak and public import",
   144  		inDesc: mustParseFile(`
   145  			name:              "test.proto"
   146  			dependency:        "dep.proto"
   147  			weak_dependency:   [0]
   148  			public_dependency: [0]
   149  		`),
   150  		inOpts: FileOptions{AllowUnresolvable: true},
   151  	}, {
   152  		label: "import public indirect dependency duplicate",
   153  		inDeps: []*descriptorpb.FileDescriptorProto{
   154  			mustParseFile(`name:"leaf.proto"`),
   155  			mustParseFile(`name:"public.proto" dependency:"leaf.proto" public_dependency:0`),
   156  		},
   157  		inDesc: mustParseFile(`
   158  			name: "test.proto"
   159  			dependency: ["public.proto", "leaf.proto"]
   160  		`),
   161  	}, {
   162  		label: "import public graph",
   163  		inDeps: []*descriptorpb.FileDescriptorProto{
   164  			cloneFile(proto2Enum),
   165  			cloneFile(proto3Message),
   166  			cloneFile(extendableMessage),
   167  			cloneFile(importPublicFile1),
   168  			cloneFile(importPublicFile2),
   169  			cloneFile(importPublicFile3),
   170  			cloneFile(importPublicFile4),
   171  		},
   172  		inDesc: mustParseFile(`
   173  			name:       "test.proto"
   174  			package:    "test.graph"
   175  			dependency: ["import_public4.proto"],
   176  		`),
   177  		// TODO: Test import public
   178  	}, {
   179  		label: "preserve source code locations",
   180  		inDesc: mustParseFile(`
   181  			name: "test.proto"
   182  			package: "fizz.buzz"
   183  			source_code_info: {location: [{
   184  				span: [39,0,882,1]
   185  			}, {
   186  				path: [12]
   187  				span: [39,0,18]
   188  				leading_detached_comments: [" foo\n"," bar\n"]
   189  			}, {
   190  				path: [8,9]
   191  				span: [51,0,28]
   192  				leading_comments: " Comment\n"
   193  			}]}
   194  		`),
   195  	}, {
   196  		label: "invalid source code span",
   197  		inDesc: mustParseFile(`
   198  			name: "test.proto"
   199  			package: "fizz.buzz"
   200  			source_code_info: {location: [{
   201  				span: [39]
   202  			}]}
   203  		`),
   204  		wantErr: `invalid span: [39]`,
   205  	}, {
   206  		label: "resolve relative reference",
   207  		inDesc: mustParseFile(`
   208  			name: "test.proto"
   209  			package: "fizz.buzz"
   210  			message_type: [{
   211  				name: "A"
   212  				field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"B.C"}]
   213  				nested_type: [{name: "B"}]
   214  			}, {
   215  				name: "B"
   216  				nested_type: [{name: "C"}]
   217  			}]
   218  		`),
   219  		wantDesc: mustParseFile(`
   220  			name: "test.proto"
   221  			package: "fizz.buzz"
   222  			message_type: [{
   223  				name: "A"
   224  				field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:".fizz.buzz.B.C"}]
   225  				nested_type: [{name: "B"}]
   226  			}, {
   227  				name: "B"
   228  				nested_type: [{name: "C"}]
   229  			}]
   230  		`),
   231  	}, {
   232  		label: "resolve the wrong type",
   233  		inDesc: mustParseFile(`
   234  			name: "test.proto"
   235  			message_type: [{
   236  				name: "M"
   237  				field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"E"}]
   238  				enum_type: [{name: "E" value: [{name:"V0" number:0}, {name:"V1" number:1}]}]
   239  			}]
   240  		`),
   241  		wantErr: `message field "M.F" cannot resolve type: resolved "M.E", but it is not an message`,
   242  	}, {
   243  		label: "auto-resolve unknown kind",
   244  		inDesc: mustParseFile(`
   245  			name: "test.proto"
   246  			message_type: [{
   247  				name: "M"
   248  				field: [{name:"F" number:1 label:LABEL_OPTIONAL type_name:"E"}]
   249  				enum_type: [{name: "E" value: [{name:"V0" number:0}, {name:"V1" number:1}]}]
   250  			}]
   251  		`),
   252  		wantDesc: mustParseFile(`
   253  			name: "test.proto"
   254  			message_type: [{
   255  				name: "M"
   256  				field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".M.E"}]
   257  				enum_type: [{name: "E" value: [{name:"V0" number:0}, {name:"V1" number:1}]}]
   258  			}]
   259  		`),
   260  	}, {
   261  		label: "unresolved import",
   262  		inDesc: mustParseFile(`
   263  			name: "test.proto"
   264  			package: "fizz.buzz"
   265  			dependency: "remote.proto"
   266  		`),
   267  		wantErr: `could not resolve import "remote.proto": not found`,
   268  	}, {
   269  		label: "unresolved message field",
   270  		inDesc: mustParseFile(`
   271  			name: "test.proto"
   272  			package: "fizz.buzz"
   273  			message_type: [{
   274  				name: "M"
   275  				field: [{name:"F1" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:"some.other.enum" default_value:"UNKNOWN"}]
   276  			}]
   277  		`),
   278  		wantErr: `message field "fizz.buzz.M.F1" cannot resolve type: "*.some.other.enum" not found`,
   279  	}, {
   280  		label: "unresolved default enum value",
   281  		inDesc: mustParseFile(`
   282  			name: "test.proto"
   283  			package: "fizz.buzz"
   284  			message_type: [{
   285  				name: "M"
   286  				field: [{name:"F1" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:"E" default_value:"UNKNOWN"}]
   287  				enum_type: [{name:"E" value:[{name:"V0" number:0}]}]
   288  			}]
   289  		`),
   290  		wantErr: `message field "fizz.buzz.M.F1" has invalid default: could not parse value for enum: "UNKNOWN"`,
   291  	}, {
   292  		label: "allowed unresolved default enum value",
   293  		inDesc: mustParseFile(`
   294  			name: "test.proto"
   295  			package: "fizz.buzz"
   296  			message_type: [{
   297  				name: "M"
   298  				field: [{name:"F1" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".fizz.buzz.M.E" default_value:"UNKNOWN"}]
   299  				enum_type: [{name:"E" value:[{name:"V0" number:0}]}]
   300  			}]
   301  		`),
   302  		inOpts: FileOptions{AllowUnresolvable: true},
   303  	}, {
   304  		label: "unresolved extendee",
   305  		inDesc: mustParseFile(`
   306  			name: "test.proto"
   307  			package: "fizz.buzz"
   308  			extension: [{name:"X" number:1 label:LABEL_OPTIONAL extendee:"some.extended.message" type:TYPE_MESSAGE type_name:"some.other.message"}]
   309  		`),
   310  		wantErr: `extension field "fizz.buzz.X" cannot resolve extendee: "*.some.extended.message" not found`,
   311  	}, {
   312  		label: "unresolved method input",
   313  		inDesc: mustParseFile(`
   314  			name: "test.proto"
   315  			package: "fizz.buzz"
   316  			service: [{
   317  				name: "S"
   318  				method: [{name:"M" input_type:"foo.bar.input" output_type:".absolute.foo.bar.output"}]
   319  			}]
   320  		`),
   321  		wantErr: `service method "fizz.buzz.S.M" cannot resolve input: "*.foo.bar.input" not found`,
   322  	}, {
   323  		label: "allowed unresolved references",
   324  		inDesc: mustParseFile(`
   325  			name: "test.proto"
   326  			package: "fizz.buzz"
   327  			dependency: "remote.proto"
   328  			message_type: [{
   329  				name: "M"
   330  				field: [{name:"F1" number:1 label:LABEL_OPTIONAL type_name:"some.other.enum" default_value:"UNKNOWN"}]
   331  			}]
   332  			extension: [{name:"X" number:1 label:LABEL_OPTIONAL extendee:"some.extended.message" type:TYPE_MESSAGE type_name:"some.other.message"}]
   333  			service: [{
   334  				name: "S"
   335  				method: [{name:"M" input_type:"foo.bar.input" output_type:".absolute.foo.bar.output"}]
   336  			}]
   337  		`),
   338  		inOpts: FileOptions{AllowUnresolvable: true},
   339  	}, {
   340  		label: "resolved but not imported",
   341  		inDeps: []*descriptorpb.FileDescriptorProto{mustParseFile(`
   342  			name: "dep.proto"
   343  			package: "fizz"
   344  			message_type: [{name:"M" nested_type:[{name:"M"}]}]
   345  		`)},
   346  		inDesc: mustParseFile(`
   347  			name: "test.proto"
   348  			package: "fizz.buzz"
   349  			message_type: [{
   350  				name: "M"
   351  				field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"M.M"}]
   352  			}]
   353  		`),
   354  		wantErr: `message field "fizz.buzz.M.F" cannot resolve type: resolved "fizz.M.M", but "dep.proto" is not imported`,
   355  	}, {
   356  		label: "resolved from remote import",
   357  		inDeps: []*descriptorpb.FileDescriptorProto{mustParseFile(`
   358  			name: "dep.proto"
   359  			package: "fizz"
   360  			message_type: [{name:"M" nested_type:[{name:"M"}]}]
   361  		`)},
   362  		inDesc: mustParseFile(`
   363  			name: "test.proto"
   364  			package: "fizz.buzz"
   365  			dependency: "dep.proto"
   366  			message_type: [{
   367  				name: "M"
   368  				field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"M.M"}]
   369  			}]
   370  		`),
   371  		wantDesc: mustParseFile(`
   372  			name: "test.proto"
   373  			package: "fizz.buzz"
   374  			dependency: "dep.proto"
   375  			message_type: [{
   376  				name: "M"
   377  				field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:".fizz.M.M"}]
   378  			}]
   379  		`),
   380  	}, {
   381  		label: "namespace conflict on enum value",
   382  		inDesc: mustParseFile(`
   383  			name:    "test.proto"
   384  			enum_type: [{
   385  				name: "foo"
   386  				value: [{name:"foo" number:0}]
   387  			}]
   388  		`),
   389  		wantErr: `descriptor "foo" already declared`,
   390  	}, {
   391  		label: "no namespace conflict on message field",
   392  		inDesc: mustParseFile(`
   393  			name:    "test.proto"
   394  			message_type: [{
   395  				name: "foo"
   396  				field: [{name:"foo" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
   397  			}]
   398  		`),
   399  	}, {
   400  		label: "invalid name",
   401  		inDesc: mustParseFile(`
   402  			name:    "test.proto"
   403  			message_type: [{name: "$"}]
   404  		`),
   405  		wantErr: `descriptor "" has an invalid nested name: "$"`,
   406  	}, {
   407  		label: "invalid empty enum",
   408  		inDesc: mustParseFile(`
   409  			name:    "test.proto"
   410  			message_type: [{name:"M" enum_type:[{name:"E"}]}]
   411  		`),
   412  		wantErr: `enum "M.E" must contain at least one value declaration`,
   413  	}, {
   414  		label: "invalid enum value without number",
   415  		inDesc: mustParseFile(`
   416  			name:    "test.proto"
   417  			message_type: [{name:"M" enum_type:[{name:"E" value:[{name:"one"}]}]}]
   418  		`),
   419  		wantErr: `enum value "M.one" must have a specified number`,
   420  	}, {
   421  		label: "valid enum",
   422  		inDesc: mustParseFile(`
   423  			name:    "test.proto"
   424  			message_type: [{name:"M" enum_type:[{name:"E" value:[{name:"one" number:1}]}]}]
   425  		`),
   426  	}, {
   427  		label: "invalid enum reserved names",
   428  		inDesc: mustParseFile(`
   429  			name:    "test.proto"
   430  			message_type: [{name:"M" enum_type:[{
   431  				name:          "E"
   432  				reserved_name: [""]
   433  				value: [{name:"V" number:0}]
   434  			}]}]
   435  		`),
   436  		// NOTE: In theory this should be an error.
   437  		// See https://github.com/protocolbuffers/protobuf/issues/6335.
   438  		/*wantErr: `enum "M.E" reserved names has invalid name: ""`,*/
   439  	}, {
   440  		label: "duplicate enum reserved names",
   441  		inDesc: mustParseFile(`
   442  			name:    "test.proto"
   443  			message_type: [{name:"M" enum_type:[{
   444  				name:          "E"
   445  				reserved_name: ["foo", "foo"]
   446  			}]}]
   447  		`),
   448  		wantErr: `enum "M.E" reserved names has duplicate name: "foo"`,
   449  	}, {
   450  		label: "valid enum reserved names",
   451  		inDesc: mustParseFile(`
   452  			name:    "test.proto"
   453  			message_type: [{name:"M" enum_type:[{
   454  				name:          "E"
   455  				reserved_name: ["foo", "bar"]
   456  				value:         [{name:"baz" number:1}]
   457  			}]}]
   458  		`),
   459  	}, {
   460  		label: "use of enum reserved names",
   461  		inDesc: mustParseFile(`
   462  			name:    "test.proto"
   463  			message_type: [{name:"M" enum_type:[{
   464  				name:          "E"
   465  				reserved_name: ["foo", "bar"]
   466  				value:         [{name:"foo" number:1}]
   467  			}]}]
   468  		`),
   469  		wantErr: `enum value "M.foo" must not use reserved name`,
   470  	}, {
   471  		label: "invalid enum reserved ranges",
   472  		inDesc: mustParseFile(`
   473  			name:    "test.proto"
   474  			message_type: [{name:"M" enum_type:[{
   475  				name:           "E"
   476  				reserved_range: [{start:5 end:4}]
   477  			}]}]
   478  		`),
   479  		wantErr: `enum "M.E" reserved ranges has invalid range: 5 to 4`,
   480  	}, {
   481  		label: "overlapping enum reserved ranges",
   482  		inDesc: mustParseFile(`
   483  			name:    "test.proto"
   484  			message_type: [{name:"M" enum_type:[{
   485  				name:           "E"
   486  				reserved_range: [{start:1 end:1000}, {start:10 end:100}]
   487  			}]}]
   488  		`),
   489  		wantErr: `enum "M.E" reserved ranges has overlapping ranges: 1 to 1000 with 10 to 100`,
   490  	}, {
   491  		label: "valid enum reserved names",
   492  		inDesc: mustParseFile(`
   493  			name:    "test.proto"
   494  			message_type: [{name:"M" enum_type:[{
   495  				name:           "E"
   496  				reserved_range: [{start:1 end:10}, {start:100 end:1000}]
   497  				value:          [{name:"baz" number:50}]
   498  			}]}]
   499  		`),
   500  	}, {
   501  		label: "use of enum reserved range",
   502  		inDesc: mustParseFile(`
   503  			name:    "test.proto"
   504  			message_type: [{name:"M" enum_type:[{
   505  				name:           "E"
   506  				reserved_range: [{start:1 end:10}, {start:100 end:1000}]
   507  				value:          [{name:"baz" number:500}]
   508  			}]}]
   509  		`),
   510  		wantErr: `enum value "M.baz" must not use reserved number 500`,
   511  	}, {
   512  		label: "unused enum alias feature",
   513  		inDesc: mustParseFile(`
   514  			name:    "test.proto"
   515  			message_type: [{name:"M" enum_type:[{
   516  				name:    "E"
   517  				value:   [{name:"baz" number:500}]
   518  				options: {allow_alias:true}
   519  			}]}]
   520  		`),
   521  		wantErr: `enum "M.E" allows aliases, but none were found`,
   522  	}, {
   523  		label: "enum number conflicts",
   524  		inDesc: mustParseFile(`
   525  			name:    "test.proto"
   526  			message_type: [{name:"M" enum_type:[{
   527  				name:  "E"
   528  				value: [{name:"foo" number:0}, {name:"bar" number:1}, {name:"baz" number:1}]
   529  			}]}]
   530  		`),
   531  		wantErr: `enum "M.E" has conflicting non-aliased values on number 1: "baz" with "bar"`,
   532  	}, {
   533  		label: "aliased enum numbers",
   534  		inDesc: mustParseFile(`
   535  			name:    "test.proto"
   536  			message_type: [{name:"M" enum_type:[{
   537  				name:    "E"
   538  				value:   [{name:"foo" number:0}, {name:"bar" number:1}, {name:"baz" number:1}]
   539  				options: {allow_alias:true}
   540  			}]}]
   541  		`),
   542  	}, {
   543  		label: "invalid proto3 enum",
   544  		inDesc: mustParseFile(`
   545  			syntax:  "proto3"
   546  			name:    "test.proto"
   547  			message_type: [{name:"M" enum_type:[{
   548  				name:  "E"
   549  				value: [{name:"baz" number:500}]
   550  			}]}]
   551  		`),
   552  		wantErr: `enum "M.baz" using proto3 semantics must have zero number for the first value`,
   553  	}, {
   554  		label: "valid proto3 enum",
   555  		inDesc: mustParseFile(`
   556  			syntax:  "proto3"
   557  			name:    "test.proto"
   558  			message_type: [{name:"M" enum_type:[{
   559  				name:  "E"
   560  				value: [{name:"baz" number:0}]
   561  			}]}]
   562  		`),
   563  	}, {
   564  		label: "proto3 enum name prefix conflict",
   565  		inDesc: mustParseFile(`
   566  			syntax:  "proto3"
   567  			name:    "test.proto"
   568  			message_type: [{name:"M" enum_type:[{
   569  				name:  "E"
   570  				value: [{name:"e_Foo" number:0}, {name:"fOo" number:1}]
   571  			}]}]
   572  		`),
   573  		wantErr: `enum "M.E" using proto3 semantics has conflict: "fOo" with "e_Foo"`,
   574  	}, {
   575  		label: "proto2 enum has name prefix check",
   576  		inDesc: mustParseFile(`
   577  			name:    "test.proto"
   578  			message_type: [{name:"M" enum_type:[{
   579  				name:  "E"
   580  				value: [{name:"e_Foo" number:0}, {name:"fOo" number:1}]
   581  			}]}]
   582  		`),
   583  	}, {
   584  		label: "proto3 enum same name prefix with number conflict",
   585  		inDesc: mustParseFile(`
   586  			syntax:  "proto3"
   587  			name:    "test.proto"
   588  			message_type: [{name:"M" enum_type:[{
   589  				name:  "E"
   590  				value: [{name:"e_Foo" number:0}, {name:"fOo" number:0}]
   591  			}]}]
   592  		`),
   593  		wantErr: `enum "M.E" has conflicting non-aliased values on number 0: "fOo" with "e_Foo"`,
   594  	}, {
   595  		label: "proto3 enum same name prefix with alias numbers",
   596  		inDesc: mustParseFile(`
   597  			syntax:  "proto3"
   598  			name:    "test.proto"
   599  			message_type: [{name:"M" enum_type:[{
   600  				name:    "E"
   601  				value:   [{name:"e_Foo" number:0}, {name:"fOo" number:0}]
   602  				options: {allow_alias: true}
   603  			}]}]
   604  		`),
   605  	}, {
   606  		label: "invalid message reserved names",
   607  		inDesc: mustParseFile(`
   608  			name:    "test.proto"
   609  			message_type: [{name:"M" nested_type:[{
   610  				name:          "M"
   611  				reserved_name: ["$"]
   612  			}]}]
   613  		`),
   614  		// NOTE: In theory this should be an error.
   615  		// See https://github.com/protocolbuffers/protobuf/issues/6335.
   616  		/*wantErr: `message "M.M" reserved names has invalid name: "$"`,*/
   617  	}, {
   618  		label: "valid message reserved names",
   619  		inDesc: mustParseFile(`
   620  			name:    "test.proto"
   621  			message_type: [{name:"M" nested_type:[{
   622  				name:          "M"
   623  				reserved_name: ["foo", "bar"]
   624  				field:         [{name:"foo" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
   625  			}]}]
   626  		`),
   627  		wantErr: `message field "M.M.foo" must not use reserved name`,
   628  	}, {
   629  		label: "valid message reserved names",
   630  		inDesc: mustParseFile(`
   631  			name:    "test.proto"
   632  			message_type: [{name:"M" nested_type:[{
   633  				name:          "M"
   634  				reserved_name: ["foo", "bar"]
   635  				field:         [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:0}]
   636  				oneof_decl:    [{name:"foo"}] # not affected by reserved_name
   637  			}]}]
   638  		`),
   639  	}, {
   640  		label: "invalid reserved number",
   641  		inDesc: mustParseFile(`
   642  			name:    "test.proto"
   643  			message_type: [{name:"M" nested_type:[{
   644  				name:           "M"
   645  				reserved_range: [{start:1 end:1}]
   646  				field:          [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
   647  			}]}]
   648  		`),
   649  		wantErr: `message "M.M" reserved ranges has invalid field number: 0`,
   650  	}, {
   651  		label: "invalid reserved ranges",
   652  		inDesc: mustParseFile(`
   653  			name:    "test.proto"
   654  			message_type: [{name:"M" nested_type:[{
   655  				name:           "M"
   656  				reserved_range: [{start:2 end:2}]
   657  				field:          [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
   658  			}]}]
   659  		`),
   660  		wantErr: `message "M.M" reserved ranges has invalid range: 2 to 1`,
   661  	}, {
   662  		label: "overlapping reserved ranges",
   663  		inDesc: mustParseFile(`
   664  			name:    "test.proto"
   665  			message_type: [{name:"M" nested_type:[{
   666  				name:           "M"
   667  				reserved_range: [{start:1 end:10}, {start:2 end:9}]
   668  				field:          [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
   669  			}]}]
   670  		`),
   671  		wantErr: `message "M.M" reserved ranges has overlapping ranges: 1 to 9 with 2 to 8`,
   672  	}, {
   673  		label: "use of reserved message field number",
   674  		inDesc: mustParseFile(`
   675  			name:    "test.proto"
   676  			message_type: [{name:"M" nested_type:[{
   677  				name:           "M"
   678  				reserved_range: [{start:10 end:20}, {start:20 end:30}, {start:30 end:31}]
   679  				field:          [{name:"baz" number:30 label:LABEL_OPTIONAL type:TYPE_STRING}]
   680  			}]}]
   681  		`),
   682  		wantErr: `message field "M.M.baz" must not use reserved number 30`,
   683  	}, {
   684  		label: "invalid extension ranges",
   685  		inDesc: mustParseFile(`
   686  			name:    "test.proto"
   687  			message_type: [{name:"M" nested_type:[{
   688  				name:            "M"
   689  				extension_range: [{start:-500 end:2}]
   690  				field:           [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
   691  			}]}]
   692  		`),
   693  		wantErr: `message "M.M" extension ranges has invalid field number: -500`,
   694  	}, {
   695  		label: "overlapping reserved and extension ranges",
   696  		inDesc: mustParseFile(`
   697  			name:    "test.proto"
   698  			message_type: [{name:"M" nested_type:[{
   699  				name:            "M"
   700  				reserved_range:  [{start:15 end:20}, {start:1 end:3}, {start:7 end:10}]
   701  				extension_range: [{start:8 end:9}, {start:3 end:5}]
   702  			}]}]
   703  		`),
   704  		wantErr: `message "M.M" reserved and extension ranges has overlapping ranges: 7 to 9 with 8`,
   705  	}, {
   706  		label: "message field conflicting number",
   707  		inDesc: mustParseFile(`
   708  			name:    "test.proto"
   709  			message_type: [{name:"M" nested_type:[{
   710  				name:            "M"
   711  				field: [
   712  					{name:"one" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
   713  					{name:"One" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}
   714  				]
   715  			}]}]
   716  		`),
   717  		wantErr: `message "M.M" has conflicting fields: "One" with "one"`,
   718  	}, {
   719  		label: "invalid MessageSet",
   720  		inDesc: mustParseFile(`
   721  			syntax:  "proto3"
   722  			name:    "test.proto"
   723  			message_type: [{name:"M" nested_type:[{
   724  				name:    "M"
   725  				options: {message_set_wire_format:true}
   726  			}]}]
   727  		`),
   728  		wantErr: func() string {
   729  			if flags.ProtoLegacy {
   730  				return `message "M.M" is an invalid proto1 MessageSet`
   731  			} else {
   732  				return `message "M.M" is a MessageSet, which is a legacy proto1 feature that is no longer supported`
   733  			}
   734  		}(),
   735  	}, {
   736  		label: "valid MessageSet",
   737  		inDesc: mustParseFile(`
   738  			name:    "test.proto"
   739  			message_type: [{name:"M" nested_type:[{
   740  				name:            "M"
   741  				extension_range: [{start:1 end:100000}]
   742  				options:         {message_set_wire_format:true}
   743  			}]}]
   744  		`),
   745  		wantErr: func() string {
   746  			if flags.ProtoLegacy {
   747  				return ""
   748  			} else {
   749  				return `message "M.M" is a MessageSet, which is a legacy proto1 feature that is no longer supported`
   750  			}
   751  		}(),
   752  	}, {
   753  		label: "invalid extension ranges in proto3",
   754  		inDesc: mustParseFile(`
   755  			syntax:  "proto3"
   756  			name:    "test.proto"
   757  			message_type: [{name:"M" nested_type:[{
   758  				name:            "M"
   759  				extension_range: [{start:1 end:100000}]
   760  			}]}]
   761  		`),
   762  		wantErr: `message "M.M" using proto3 semantics cannot have extension ranges`,
   763  	}, {
   764  		label: "proto3 message fields conflict",
   765  		inDesc: mustParseFile(`
   766  			syntax:  "proto3"
   767  			name:    "test.proto"
   768  			message_type: [{name:"M" nested_type:[{
   769  				name: "M"
   770  				field: [
   771  					{name:"_b_a_z_" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
   772  					{name:"baz" number:2 label:LABEL_OPTIONAL type:TYPE_STRING}
   773  				]
   774  			}]}]
   775  		`),
   776  		wantErr: `message "M.M" using proto3 semantics has conflict: "baz" with "_b_a_z_"`,
   777  	}, {
   778  		label: "proto3 message fields",
   779  		inDesc: mustParseFile(`
   780  			syntax:  "proto3"
   781  			name:    "test.proto"
   782  			message_type: [{name:"M" nested_type:[{
   783  				name:       "M"
   784  				field:      [{name:"_b_a_z_" number:1 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:0}]
   785  				oneof_decl: [{name:"baz"}] # proto3 name conflict logic does not include oneof
   786  			}]}]
   787  		`),
   788  	}, {
   789  		label: "proto2 message fields with no conflict",
   790  		inDesc: mustParseFile(`
   791  			name:    "test.proto"
   792  			message_type: [{name:"M" nested_type:[{
   793  				name: "M"
   794  				field: [
   795  					{name:"_b_a_z_" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
   796  					{name:"baz" number:2 label:LABEL_OPTIONAL type:TYPE_STRING}
   797  				]
   798  			}]}]
   799  		`),
   800  	}, {
   801  		label: "proto3 message with unresolved enum",
   802  		inDesc: mustParseFile(`
   803  			name:    "test.proto"
   804  			syntax:  "proto3"
   805  			message_type: [{
   806  				name: "M"
   807  				field: [
   808  					{name:"enum" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".fizz.buzz.Enum"}
   809  				]
   810  			}]
   811  		`),
   812  		inOpts: FileOptions{AllowUnresolvable: true},
   813  		// TODO: Test field and oneof handling in validateMessageDeclarations
   814  		// TODO: Test unmarshalDefault
   815  		// TODO: Test validateExtensionDeclarations
   816  		// TODO: Test checkValidGroup
   817  		// TODO: Test checkValidMap
   818  	}, {
   819  		label: "empty service",
   820  		inDesc: mustParseFile(`
   821  			name:    "test.proto"
   822  			service: [{name:"service"}]
   823  		`),
   824  	}, {
   825  		label: "service with method with unresolved",
   826  		inDesc: mustParseFile(`
   827  			name:    "test.proto"
   828  			service: [{
   829  				name: "service"
   830  				method: [{
   831  					name:"method"
   832  					input_type:"foo"
   833  					output_type:".foo.bar.baz"
   834  				}]
   835  			}]
   836  		`),
   837  		inOpts: FileOptions{AllowUnresolvable: true},
   838  	}, {
   839  		label: "service with wrong reference type",
   840  		inDeps: []*descriptorpb.FileDescriptorProto{
   841  			cloneFile(proto3Message),
   842  			cloneFile(proto2Enum),
   843  		},
   844  		inDesc: mustParseFile(`
   845  			name:    "test.proto"
   846  			dependency: ["proto2_enum.proto", "proto3_message.proto"]
   847  			service: [{
   848  				name: "service"
   849  				method: [{
   850  					name:        "method"
   851  					input_type:  ".test.proto2.Enum",
   852  					output_type: ".test.proto3.Message"
   853  				}]
   854  			}]
   855  		`),
   856  		wantErr: `service method "service.method" cannot resolve input: resolved "test.proto2.Enum", but it is not an message`,
   857  	}}
   858  
   859  	for _, tt := range tests {
   860  		t.Run(tt.label, func(t *testing.T) {
   861  			r := new(protoregistry.Files)
   862  			for i, dep := range tt.inDeps {
   863  				f, err := tt.inOpts.New(dep, r)
   864  				if err != nil {
   865  					t.Fatalf("dependency %d: unexpected NewFile() error: %v", i, err)
   866  				}
   867  				if err := r.RegisterFile(f); err != nil {
   868  					t.Fatalf("dependency %d: unexpected Register() error: %v", i, err)
   869  				}
   870  			}
   871  			var gotDesc *descriptorpb.FileDescriptorProto
   872  			if tt.wantErr == "" && tt.wantDesc == nil {
   873  				tt.wantDesc = cloneFile(tt.inDesc)
   874  			}
   875  			gotFile, err := tt.inOpts.New(tt.inDesc, r)
   876  			if gotFile != nil {
   877  				gotDesc = ToFileDescriptorProto(gotFile)
   878  			}
   879  			if !proto.Equal(gotDesc, tt.wantDesc) {
   880  				t.Errorf("NewFile() mismatch:\ngot  %v\nwant %v", gotDesc, tt.wantDesc)
   881  			}
   882  			if ((err == nil) != (tt.wantErr == "")) || !strings.Contains(fmt.Sprint(err), tt.wantErr) {
   883  				t.Errorf("NewFile() error:\ngot:  %v\nwant: %v", err, tt.wantErr)
   884  			}
   885  		})
   886  	}
   887  }
   888  
   889  func TestNewFiles(t *testing.T) {
   890  	fdset := &descriptorpb.FileDescriptorSet{
   891  		File: []*descriptorpb.FileDescriptorProto{
   892  			mustParseFile(`
   893  				name: "test.proto"
   894  				package: "fizz"
   895  				dependency: "dep.proto"
   896  				message_type: [{
   897  					name: "M2"
   898  					field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"M1"}]
   899  				}]
   900  			`),
   901  			// Inputs deliberately out of order.
   902  			mustParseFile(`
   903  				name: "dep.proto"
   904  				package: "fizz"
   905  				message_type: [{name:"M1"}]
   906  			`),
   907  		},
   908  	}
   909  	f, err := NewFiles(fdset)
   910  	if err != nil {
   911  		t.Fatal(err)
   912  	}
   913  	m1, err := f.FindDescriptorByName("fizz.M1")
   914  	if err != nil {
   915  		t.Fatalf(`f.FindDescriptorByName("fizz.M1") = %v`, err)
   916  	}
   917  	m2, err := f.FindDescriptorByName("fizz.M2")
   918  	if err != nil {
   919  		t.Fatalf(`f.FindDescriptorByName("fizz.M2") = %v`, err)
   920  	}
   921  	if m2.(protoreflect.MessageDescriptor).Fields().ByName("F").Message() != m1 {
   922  		t.Fatalf(`m1.Fields().ByName("F").Message() != m2`)
   923  	}
   924  }
   925  
   926  func TestNewFilesImportCycle(t *testing.T) {
   927  	fdset := &descriptorpb.FileDescriptorSet{
   928  		File: []*descriptorpb.FileDescriptorProto{
   929  			mustParseFile(`
   930  				name: "test.proto"
   931  				package: "fizz"
   932  				dependency: "dep.proto"
   933  			`),
   934  			mustParseFile(`
   935  				name: "dep.proto"
   936  				package: "fizz"
   937  				dependency: "test.proto"
   938  			`),
   939  		},
   940  	}
   941  	_, err := NewFiles(fdset)
   942  	if err == nil {
   943  		t.Fatal("NewFiles with import cycle: success, want error")
   944  	}
   945  }
   946  
   947  func TestSourceLocations(t *testing.T) {
   948  	fd := mustParseFile(`
   949  		name: "comments.proto"
   950  		message_type: [{
   951  			name: "Message1"
   952  			field: [
   953  				{name:"field1" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
   954  				{name:"field2" number:2 label:LABEL_OPTIONAL type:TYPE_STRING},
   955  				{name:"field3" number:3 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:0},
   956  				{name:"field4" number:4 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:0},
   957  				{name:"field5" number:5 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:1},
   958  				{name:"field6" number:6 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:1}
   959  			]
   960  			extension: [
   961  				{name:"extension1" number:100 label:LABEL_OPTIONAL type:TYPE_STRING extendee:".Message1"},
   962  				{name:"extension2" number:101 label:LABEL_OPTIONAL type:TYPE_STRING extendee:".Message1"}
   963  			]
   964  			nested_type: [{name:"Message1"}, {name:"Message2"}]
   965  			extension_range: {start:100 end:536870912}
   966  			oneof_decl: [{name:"oneof1"}, {name:"oneof2"}]
   967  		}, {
   968  			name: "Message2"
   969  			enum_type: {
   970  				name: "Enum1"
   971  				value: [
   972  					{name: "FOO", number: 0},
   973  					{name: "BAR", number: 1}
   974  				]
   975  			}
   976  		}]
   977  		enum_type: {
   978  			name: "Enum1"
   979  			value: [
   980  				{name: "FOO", number: 0},
   981  				{name: "BAR", number: 1}
   982  			]
   983  		}
   984  		service: {
   985  			name: "Service1"
   986  			method: [
   987  				{name:"Method1" input_type:".Message1" output_type:".Message1"},
   988  				{name:"Method2" input_type:".Message2" output_type:".Message2"}
   989  			]
   990  		}
   991  		extension: [
   992  			{name:"extension1" number:102 label:LABEL_OPTIONAL type:TYPE_STRING extendee:".Message1"},
   993  			{name:"extension2" number:103 label:LABEL_OPTIONAL type:TYPE_STRING extendee:".Message1"}
   994  		]
   995  		source_code_info: {
   996  			location: [
   997  				{span:[0,0,69,1]},
   998  				{path:[12] span:[0,0,18]},
   999  				{path:[5,0] span:[3,0,8,1] leading_comments:" Enum1\r\n"},
  1000  				{path:[5,0,1] span:[3,5,10]},
  1001  				{path:[5,0,2,0] span:[5,2,10] leading_comments:" FOO\r\n"},
  1002  				{path:[5,0,2,0,1] span:[5,2,5]},
  1003  				{path:[5,0,2,0,2] span:[5,8,9]},
  1004  				{path:[5,0,2,1] span:[7,2,10] leading_comments:" BAR\r\n"},
  1005  				{path:[5,0,2,1,1] span:[7,2,5]},
  1006  				{path:[5,0,2,1,2] span:[7,8,9]},
  1007  				{path:[4,0] span:[11,0,43,1] leading_comments:" Message1\r\n"},
  1008  				{path:[4,0,1] span:[11,8,16]},
  1009  				{path:[4,0,3,0] span:[13,2,21] leading_comments:" Message1.Message1\r\n"},
  1010  				{path:[4,0,3,0,1] span:[13,10,18]},
  1011  				{path:[4,0,3,1] span:[15,2,21] leading_comments:" Message1.Message2\r\n"},
  1012  				{path:[4,0,3,1,1] span:[15,10,18]},
  1013  				{path:[4,0,2,0] span:[18,2,29] leading_comments:" Message1.field1\r\n"},
  1014  				{path:[4,0,2,0,4] span:[18,2,10]},
  1015  				{path:[4,0,2,0,5] span:[18,11,17]},
  1016  				{path:[4,0,2,0,1] span:[18,18,24]},
  1017  				{path:[4,0,2,0,3] span:[18,27,28]},
  1018  				{path:[4,0,2,1] span:[20,2,29] leading_comments:" Message1.field2\r\n"},
  1019  				{path:[4,0,2,1,4] span:[20,2,10]},
  1020  				{path:[4,0,2,1,5] span:[20,11,17]},
  1021  				{path:[4,0,2,1,1] span:[20,18,24]},
  1022  				{path:[4,0,2,1,3] span:[20,27,28]},
  1023  				{path:[4,0,8,0] span:[22,2,27,3] leading_comments:" Message1.oneof1\r\n"},
  1024  				{path:[4,0,8,0,1] span:[22,8,14]},
  1025  				{path:[4,0,2,2] span:[24,4,22] leading_comments:" Message1.field3\r\n"},
  1026  				{path:[4,0,2,2,5] span:[24,4,10]},
  1027  				{path:[4,0,2,2,1] span:[24,11,17]},
  1028  				{path:[4,0,2,2,3] span:[24,20,21]},
  1029  				{path:[4,0,2,3] span:[26,4,22] leading_comments:" Message1.field4\r\n"},
  1030  				{path:[4,0,2,3,5] span:[26,4,10]},
  1031  				{path:[4,0,2,3,1] span:[26,11,17]},
  1032  				{path:[4,0,2,3,3] span:[26,20,21]},
  1033  				{path:[4,0,8,1] span:[29,2,34,3] leading_comments:" Message1.oneof2\r\n"},
  1034  				{path:[4,0,8,1,1] span:[29,8,14]},
  1035  				{path:[4,0,2,4] span:[31,4,22] leading_comments:" Message1.field5\r\n"},
  1036  				{path:[4,0,2,4,5] span:[31,4,10]},
  1037  				{path:[4,0,2,4,1] span:[31,11,17]},
  1038  				{path:[4,0,2,4,3] span:[31,20,21]},
  1039  				{path:[4,0,2,5] span:[33,4,22] leading_comments:" Message1.field6\r\n"},
  1040  				{path:[4,0,2,5,5] span:[33,4,10]},
  1041  				{path:[4,0,2,5,1] span:[33,11,17]},
  1042  				{path:[4,0,2,5,3] span:[33,20,21]},
  1043  				{path:[4,0,5] span:[36,2,24]},
  1044  				{path:[4,0,5,0] span:[36,13,23]},
  1045  				{path:[4,0,5,0,1] span:[36,13,16]},
  1046  				{path:[4,0,5,0,2] span:[36,20,23]},
  1047  				{path:[4,0,6] span:[37,2,42,3]},
  1048  				{path:[4,0,6,0] span:[39,4,37] leading_comments:" Message1.extension1\r\n"},
  1049  				{path:[4,0,6,0,2] span:[37,9,18]},
  1050  				{path:[4,0,6,0,4] span:[39,4,12]},
  1051  				{path:[4,0,6,0,5] span:[39,13,19]},
  1052  				{path:[4,0,6,0,1] span:[39,20,30]},
  1053  				{path:[4,0,6,0,3] span:[39,33,36]},
  1054  				{path:[4,0,6,1] span:[41,4,37] leading_comments:" Message1.extension2\r\n"},
  1055  				{path:[4,0,6,1,2] span:[37,9,18]},
  1056  				{path:[4,0,6,1,4] span:[41,4,12]},
  1057  				{path:[4,0,6,1,5] span:[41,13,19]},
  1058  				{path:[4,0,6,1,1] span:[41,20,30]},
  1059  				{path:[4,0,6,1,3] span:[41,33,36]},
  1060  				{path:[7] span:[45,0,50,1]},
  1061  				{path:[7,0] span:[47,2,35] leading_comments:" extension1\r\n"},
  1062  				{path:[7,0,2] span:[45,7,15]},
  1063  				{path:[7,0,4] span:[47,2,10]},
  1064  				{path:[7,0,5] span:[47,11,17]},
  1065  				{path:[7,0,1] span:[47,18,28]},
  1066  				{path:[7,0,3] span:[47,31,34]},
  1067  				{path:[7,1] span:[49,2,35] leading_comments:" extension2\r\n"},
  1068  				{path:[7,1,2] span:[45,7,15]},
  1069  				{path:[7,1,4] span:[49,2,10]},
  1070  				{path:[7,1,5] span:[49,11,17]},
  1071  				{path:[7,1,1] span:[49,18,28]},
  1072  				{path:[7,1,3] span:[49,31,34]},
  1073  				{path:[4,1] span:[53,0,61,1] leading_comments:" Message2\r\n"},
  1074  				{path:[4,1,1] span:[53,8,16]},
  1075  				{path:[4,1,4,0] span:[55,2,60,3] leading_comments:" Message2.Enum1\r\n"},
  1076  				{path:[4,1,4,0,1] span:[55,7,12]},
  1077  				{path:[4,1,4,0,2,0] span:[57,4,12] leading_comments:" Message2.FOO\r\n"},
  1078  				{path:[4,1,4,0,2,0,1] span:[57,4,7]},
  1079  				{path:[4,1,4,0,2,0,2] span:[57,10,11]},
  1080  				{path:[4,1,4,0,2,1] span:[59,4,12] leading_comments:" Message2.BAR\r\n"},
  1081  				{path:[4,1,4,0,2,1,1] span:[59,4,7]},
  1082  				{path:[4,1,4,0,2,1,2] span:[59,10,11]},
  1083  				{path:[6,0] span:[64,0,69,1] leading_comments:" Service1\r\n"},
  1084  				{path:[6,0,1] span:[64,8,16]},
  1085  				{path:[6,0,2,0] span:[66,2,43] leading_comments:" Service1.Method1\r\n"},
  1086  				{path:[6,0,2,0,1] span:[66,6,13]},
  1087  				{path:[6,0,2,0,2] span:[66,14,22]},
  1088  				{path:[6,0,2,0,3] span:[66,33,41]},
  1089  				{path:[6,0,2,1] span:[68,2,43] leading_comments:" Service1.Method2\r\n"},
  1090  				{path:[6,0,2,1,1] span:[68,6,13]},
  1091  				{path:[6,0,2,1,2] span:[68,14,22]},
  1092  				{path:[6,0,2,1,3] span:[68,33,41]}
  1093  			]
  1094  		}
  1095  	`)
  1096  	fileDesc, err := NewFile(fd, nil)
  1097  	if err != nil {
  1098  		t.Fatalf("NewFile error: %v", err)
  1099  	}
  1100  
  1101  	var walkDescs func(protoreflect.Descriptor, func(protoreflect.Descriptor))
  1102  	walkDescs = func(d protoreflect.Descriptor, f func(protoreflect.Descriptor)) {
  1103  		f(d)
  1104  		if d, ok := d.(interface {
  1105  			Enums() protoreflect.EnumDescriptors
  1106  		}); ok {
  1107  			eds := d.Enums()
  1108  			for i := 0; i < eds.Len(); i++ {
  1109  				walkDescs(eds.Get(i), f)
  1110  			}
  1111  		}
  1112  		if d, ok := d.(interface {
  1113  			Values() protoreflect.EnumValueDescriptors
  1114  		}); ok {
  1115  			vds := d.Values()
  1116  			for i := 0; i < vds.Len(); i++ {
  1117  				walkDescs(vds.Get(i), f)
  1118  			}
  1119  		}
  1120  		if d, ok := d.(interface {
  1121  			Messages() protoreflect.MessageDescriptors
  1122  		}); ok {
  1123  			mds := d.Messages()
  1124  			for i := 0; i < mds.Len(); i++ {
  1125  				walkDescs(mds.Get(i), f)
  1126  			}
  1127  		}
  1128  		if d, ok := d.(interface {
  1129  			Fields() protoreflect.FieldDescriptors
  1130  		}); ok {
  1131  			fds := d.Fields()
  1132  			for i := 0; i < fds.Len(); i++ {
  1133  				walkDescs(fds.Get(i), f)
  1134  			}
  1135  		}
  1136  		if d, ok := d.(interface {
  1137  			Oneofs() protoreflect.OneofDescriptors
  1138  		}); ok {
  1139  			ods := d.Oneofs()
  1140  			for i := 0; i < ods.Len(); i++ {
  1141  				walkDescs(ods.Get(i), f)
  1142  			}
  1143  		}
  1144  		if d, ok := d.(interface {
  1145  			Extensions() protoreflect.ExtensionDescriptors
  1146  		}); ok {
  1147  			xds := d.Extensions()
  1148  			for i := 0; i < xds.Len(); i++ {
  1149  				walkDescs(xds.Get(i), f)
  1150  			}
  1151  		}
  1152  		if d, ok := d.(interface {
  1153  			Services() protoreflect.ServiceDescriptors
  1154  		}); ok {
  1155  			sds := d.Services()
  1156  			for i := 0; i < sds.Len(); i++ {
  1157  				walkDescs(sds.Get(i), f)
  1158  			}
  1159  		}
  1160  		if d, ok := d.(interface {
  1161  			Methods() protoreflect.MethodDescriptors
  1162  		}); ok {
  1163  			mds := d.Methods()
  1164  			for i := 0; i < mds.Len(); i++ {
  1165  				walkDescs(mds.Get(i), f)
  1166  			}
  1167  		}
  1168  	}
  1169  
  1170  	var numDescs int
  1171  	walkDescs(fileDesc, func(d protoreflect.Descriptor) {
  1172  		// The comment for every descriptor should be the full name itself.
  1173  		got := strings.TrimSpace(fileDesc.SourceLocations().ByDescriptor(d).LeadingComments)
  1174  		want := string(d.FullName())
  1175  		if got != want {
  1176  			t.Errorf("comment mismatch: got %v, want %v", got, want)
  1177  		}
  1178  		numDescs++
  1179  	})
  1180  	if numDescs != 30 {
  1181  		t.Errorf("visited %d descriptor, expected 30", numDescs)
  1182  	}
  1183  }
  1184  
  1185  func TestToFileDescriptorProtoPlaceHolder(t *testing.T) {
  1186  	// Make sure placeholders produce valid protos.
  1187  	fileDescriptor := ToFileDescriptorProto(filedesc.PlaceholderFile("foo/test.proto"))
  1188  	_, err := NewFile(fileDescriptor, &protoregistry.Files{} /* empty files since placeholder has no deps */)
  1189  	if err != nil {
  1190  		t.Errorf("placeholder file descriptor proto is not valid: %s", err)
  1191  	}
  1192  }
  1193  

View as plain text