1
2
3
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
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
494
495
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
672
673
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
894
895
896
897
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
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
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
1267 fileDescriptor := ToFileDescriptorProto(filedesc.PlaceholderFile("foo/test.proto"))
1268 _, err := NewFile(fileDescriptor, &protoregistry.Files{} )
1269 if err != nil {
1270 t.Errorf("placeholder file descriptor proto is not valid: %s", err)
1271 }
1272 }
1273
View as plain text