github.com/whiteCcinn/protobuf-go@v1.0.9/reflect/protodesc/file_test.go (about)

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