...

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

View as plain text