blob: 934b32f60b9a83c80edac4cc2c77948b9f214b6a [file] [log] [blame]
Joe Tsaib2107fb2019-06-29 01:31:37 -07001// 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
5package protodesc
6
7import (
8 "fmt"
9 "strings"
10 "testing"
11
12 "google.golang.org/protobuf/encoding/prototext"
Joe Tsai3d8e3692019-04-08 13:52:14 -070013 "google.golang.org/protobuf/internal/flags"
Joe Tsaib2107fb2019-06-29 01:31:37 -070014 "google.golang.org/protobuf/proto"
15 "google.golang.org/protobuf/reflect/protoregistry"
16
17 "google.golang.org/protobuf/types/descriptorpb"
18)
19
20func mustParseFile(s string) *descriptorpb.FileDescriptorProto {
21 pb := new(descriptorpb.FileDescriptorProto)
22 if err := prototext.Unmarshal([]byte(s), pb); err != nil {
23 panic(err)
24 }
25 return pb
26}
27
28func cloneFile(in *descriptorpb.FileDescriptorProto) *descriptorpb.FileDescriptorProto {
Joe Tsaib7695fa2020-01-04 09:09:04 -080029 return proto.Clone(in).(*descriptorpb.FileDescriptorProto)
Joe Tsaib2107fb2019-06-29 01:31:37 -070030}
31
Joe Tsai15076352019-07-02 15:19:08 -070032var (
33 proto2Enum = mustParseFile(`
34 syntax: "proto2"
35 name: "proto2_enum.proto"
36 package: "test.proto2"
37 enum_type: [{name:"Enum" value:[{name:"ONE" number:1}]}]
38 `)
39 proto3Message = mustParseFile(`
40 syntax: "proto3"
41 name: "proto3_message.proto"
42 package: "test.proto3"
43 message_type: [{
44 name: "Message"
45 field: [
46 {name:"foo" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
47 {name:"bar" number:2 label:LABEL_OPTIONAL type:TYPE_STRING}
48 ]
49 }]
50 `)
51 extendableMessage = mustParseFile(`
52 syntax: "proto2"
53 name: "extendable_message.proto"
54 package: "test.proto2"
55 message_type: [{name:"Message" extension_range:[{start:1 end:1000}]}]
56 `)
57 importPublicFile1 = mustParseFile(`
58 syntax: "proto3"
59 name: "import_public1.proto"
60 dependency: ["proto2_enum.proto", "proto3_message.proto", "extendable_message.proto"]
61 message_type: [{name:"Public1"}]
62 `)
63 importPublicFile2 = mustParseFile(`
64 syntax: "proto3"
65 name: "import_public2.proto"
66 dependency: ["import_public1.proto"]
67 public_dependency: [0]
68 message_type: [{name:"Public2"}]
69 `)
70 importPublicFile3 = mustParseFile(`
71 syntax: "proto3"
72 name: "import_public3.proto"
73 dependency: ["import_public2.proto", "extendable_message.proto"]
74 public_dependency: [0]
75 message_type: [{name:"Public3"}]
76 `)
77 importPublicFile4 = mustParseFile(`
78 syntax: "proto3"
79 name: "import_public4.proto"
80 dependency: ["import_public2.proto", "import_public3.proto", "proto2_enum.proto"]
81 public_dependency: [0, 1]
82 message_type: [{name:"Public4"}]
83 `)
84)
85
Joe Tsaib2107fb2019-06-29 01:31:37 -070086func TestNewFile(t *testing.T) {
87 tests := []struct {
88 label string
89 inDeps []*descriptorpb.FileDescriptorProto
90 inDesc *descriptorpb.FileDescriptorProto
91 inOpts []option
92 wantDesc *descriptorpb.FileDescriptorProto
93 wantErr string
94 }{{
Joe Tsai15076352019-07-02 15:19:08 -070095 label: "empty path",
96 inDesc: mustParseFile(``),
97 wantErr: `path must be populated`,
98 }, {
99 label: "empty package and syntax",
100 inDesc: mustParseFile(`name:"weird" package:""`),
101 }, {
102 label: "invalid syntax",
103 inDesc: mustParseFile(`name:"weird" syntax:"proto9"`),
104 wantErr: `invalid syntax: "proto9"`,
105 }, {
106 label: "bad package",
107 inDesc: mustParseFile(`name:"weird" package:"$"`),
108 wantErr: `invalid package: "$"`,
109 }, {
110 label: "unresolvable import",
111 inDesc: mustParseFile(`
112 name: "test.proto"
113 package: ""
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 package: ""
122 dependency: "dep.proto"
123 `),
124 inOpts: []option{allowUnresolvable()},
125 }, {
126 label: "duplicate import",
127 inDesc: mustParseFile(`
128 name: "test.proto"
129 package: ""
130 dependency: ["dep.proto", "dep.proto"]
131 `),
132 inOpts: []option{allowUnresolvable()},
133 wantErr: `already imported "dep.proto"`,
134 }, {
135 label: "invalid weak import",
136 inDesc: mustParseFile(`
137 name: "test.proto"
138 package: ""
139 dependency: "dep.proto"
140 weak_dependency: [-23]
141 `),
142 inOpts: []option{allowUnresolvable()},
143 wantErr: `invalid or duplicate weak import index: -23`,
144 }, {
145 label: "normal weak and public import",
146 inDesc: mustParseFile(`
147 name: "test.proto"
148 package: ""
149 dependency: "dep.proto"
150 weak_dependency: [0]
151 public_dependency: [0]
152 `),
153 inOpts: []option{allowUnresolvable()},
154 }, {
155 label: "import public indirect dependency duplicate",
156 inDeps: []*descriptorpb.FileDescriptorProto{
157 mustParseFile(`name:"leaf.proto"`),
158 mustParseFile(`name:"public.proto" dependency:"leaf.proto" public_dependency:0`),
159 },
160 inDesc: mustParseFile(`
161 name: "test.proto"
162 package: ""
163 dependency: ["public.proto", "leaf.proto"]
164 `),
165 }, {
166 label: "import public graph",
167 inDeps: []*descriptorpb.FileDescriptorProto{
168 cloneFile(proto2Enum),
169 cloneFile(proto3Message),
170 cloneFile(extendableMessage),
171 cloneFile(importPublicFile1),
172 cloneFile(importPublicFile2),
173 cloneFile(importPublicFile3),
174 cloneFile(importPublicFile4),
175 },
176 inDesc: mustParseFile(`
177 name: "test.proto"
178 package: "test.graph"
179 dependency: ["import_public4.proto"],
180 `),
181 // TODO: Test import public
182 }, {
Joe Tsaie182c912019-06-18 01:02:13 -0700183 label: "preserve source code locations",
184 inDesc: mustParseFile(`
185 name: "test.proto"
186 package: "fizz.buzz"
187 source_code_info: {location: [{
188 span: [39,0,882,1]
189 }, {
190 path: [12]
191 span: [39,0,18]
192 leading_detached_comments: [" foo\n"," bar\n"]
193 }, {
194 path: [8,9]
195 span: [51,0,28]
196 leading_comments: " Comment\n"
197 }]}
198 `),
199 }, {
200 label: "invalid source code span",
201 inDesc: mustParseFile(`
202 name: "test.proto"
203 package: "fizz.buzz"
204 source_code_info: {location: [{
205 span: [39]
206 }]}
207 `),
208 wantErr: `invalid span: [39]`,
209 }, {
Joe Tsaib2107fb2019-06-29 01:31:37 -0700210 label: "resolve relative reference",
211 inDesc: mustParseFile(`
212 name: "test.proto"
213 package: "fizz.buzz"
214 message_type: [{
215 name: "A"
216 field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"B.C"}]
217 nested_type: [{name: "B"}]
218 }, {
219 name: "B"
220 nested_type: [{name: "C"}]
221 }]
222 `),
223 wantDesc: mustParseFile(`
224 name: "test.proto"
225 package: "fizz.buzz"
226 message_type: [{
227 name: "A"
228 field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:".fizz.buzz.B.C"}]
229 nested_type: [{name: "B"}]
230 }, {
231 name: "B"
232 nested_type: [{name: "C"}]
233 }]
234 `),
235 }, {
236 label: "resolve the wrong type",
237 inDesc: mustParseFile(`
238 name: "test.proto"
239 package: ""
240 message_type: [{
241 name: "M"
242 field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"E"}]
243 enum_type: [{name: "E" value: [{name:"V0" number:0}, {name:"V1" number:1}]}]
244 }]
245 `),
246 wantErr: `message field "M.F" cannot resolve type: resolved "M.E", but it is not an message`,
247 }, {
248 label: "auto-resolve unknown kind",
249 inDesc: mustParseFile(`
250 name: "test.proto"
251 package: ""
252 message_type: [{
253 name: "M"
254 field: [{name:"F" number:1 label:LABEL_OPTIONAL type_name:"E"}]
255 enum_type: [{name: "E" value: [{name:"V0" number:0}, {name:"V1" number:1}]}]
256 }]
257 `),
258 wantDesc: mustParseFile(`
259 name: "test.proto"
260 package: ""
261 message_type: [{
262 name: "M"
263 field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".M.E"}]
264 enum_type: [{name: "E" value: [{name:"V0" number:0}, {name:"V1" number:1}]}]
265 }]
266 `),
267 }, {
268 label: "unresolved import",
269 inDesc: mustParseFile(`
270 name: "test.proto"
271 package: "fizz.buzz"
272 dependency: "remote.proto"
273 `),
274 wantErr: `could not resolve import "remote.proto": not found`,
275 }, {
276 label: "unresolved message field",
277 inDesc: mustParseFile(`
278 name: "test.proto"
279 package: "fizz.buzz"
280 message_type: [{
281 name: "M"
282 field: [{name:"F1" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:"some.other.enum" default_value:"UNKNOWN"}]
283 }]
284 `),
285 wantErr: `message field "fizz.buzz.M.F1" cannot resolve type: "*.some.other.enum" not found`,
286 }, {
287 label: "unresolved default enum value",
288 inDesc: mustParseFile(`
289 name: "test.proto"
290 package: "fizz.buzz"
291 message_type: [{
292 name: "M"
293 field: [{name:"F1" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:"E" default_value:"UNKNOWN"}]
294 enum_type: [{name:"E" value:[{name:"V0" number:0}]}]
295 }]
296 `),
297 wantErr: `message field "fizz.buzz.M.F1" has invalid default: could not parse value for enum: "UNKNOWN"`,
298 }, {
299 label: "allowed unresolved default enum value",
300 inDesc: mustParseFile(`
301 name: "test.proto"
302 package: "fizz.buzz"
303 message_type: [{
304 name: "M"
305 field: [{name:"F1" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".fizz.buzz.M.E" default_value:"UNKNOWN"}]
306 enum_type: [{name:"E" value:[{name:"V0" number:0}]}]
307 }]
308 `),
309 inOpts: []option{allowUnresolvable()},
310 }, {
311 label: "unresolved extendee",
312 inDesc: mustParseFile(`
313 name: "test.proto"
314 package: "fizz.buzz"
315 extension: [{name:"X" number:1 label:LABEL_OPTIONAL extendee:"some.extended.message" type:TYPE_MESSAGE type_name:"some.other.message"}]
316 `),
317 wantErr: `extension field "fizz.buzz.X" cannot resolve extendee: "*.some.extended.message" not found`,
318 }, {
319 label: "unresolved method input",
320 inDesc: mustParseFile(`
321 name: "test.proto"
322 package: "fizz.buzz"
323 service: [{
324 name: "S"
325 method: [{name:"M" input_type:"foo.bar.input" output_type:".absolute.foo.bar.output"}]
326 }]
327 `),
328 wantErr: `service method "fizz.buzz.S.M" cannot resolve input: "*.foo.bar.input" not found`,
329 }, {
330 label: "allowed unresolved references",
331 inDesc: mustParseFile(`
332 name: "test.proto"
333 package: "fizz.buzz"
334 dependency: "remote.proto"
335 message_type: [{
336 name: "M"
337 field: [{name:"F1" number:1 label:LABEL_OPTIONAL type_name:"some.other.enum" default_value:"UNKNOWN"}]
338 }]
339 extension: [{name:"X" number:1 label:LABEL_OPTIONAL extendee:"some.extended.message" type:TYPE_MESSAGE type_name:"some.other.message"}]
340 service: [{
341 name: "S"
342 method: [{name:"M" input_type:"foo.bar.input" output_type:".absolute.foo.bar.output"}]
343 }]
344 `),
345 inOpts: []option{allowUnresolvable()},
346 }, {
347 label: "resolved but not imported",
348 inDeps: []*descriptorpb.FileDescriptorProto{mustParseFile(`
349 name: "dep.proto"
350 package: "fizz"
351 message_type: [{name:"M" nested_type:[{name:"M"}]}]
352 `)},
353 inDesc: mustParseFile(`
354 name: "test.proto"
355 package: "fizz.buzz"
356 message_type: [{
357 name: "M"
358 field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"M.M"}]
359 }]
360 `),
361 wantErr: `message field "fizz.buzz.M.F" cannot resolve type: resolved "fizz.M.M", but "dep.proto" is not imported`,
362 }, {
363 label: "resolved from remote import",
364 inDeps: []*descriptorpb.FileDescriptorProto{mustParseFile(`
365 name: "dep.proto"
366 package: "fizz"
367 message_type: [{name:"M" nested_type:[{name:"M"}]}]
368 `)},
369 inDesc: mustParseFile(`
370 name: "test.proto"
371 package: "fizz.buzz"
372 dependency: "dep.proto"
373 message_type: [{
374 name: "M"
375 field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"M.M"}]
376 }]
377 `),
378 wantDesc: mustParseFile(`
379 name: "test.proto"
380 package: "fizz.buzz"
381 dependency: "dep.proto"
382 message_type: [{
383 name: "M"
384 field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:".fizz.M.M"}]
385 }]
386 `),
Joe Tsai15076352019-07-02 15:19:08 -0700387 }, {
388 label: "namespace conflict on enum value",
389 inDesc: mustParseFile(`
390 name: "test.proto"
391 package: ""
392 enum_type: [{
393 name: "foo"
394 value: [{name:"foo" number:0}]
395 }]
396 `),
397 wantErr: `descriptor "foo" already declared`,
398 }, {
399 label: "no namespace conflict on message field",
400 inDesc: mustParseFile(`
401 name: "test.proto"
402 package: ""
403 message_type: [{
404 name: "foo"
405 field: [{name:"foo" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
406 }]
407 `),
408 }, {
409 label: "invalid name",
410 inDesc: mustParseFile(`
411 name: "test.proto"
412 package: ""
413 message_type: [{name: "$"}]
414 `),
415 wantErr: `descriptor "" has an invalid nested name: "$"`,
416 }, {
417 label: "invalid empty enum",
418 inDesc: mustParseFile(`
419 name: "test.proto"
420 package: ""
421 message_type: [{name:"M" enum_type:[{name:"E"}]}]
422 `),
423 wantErr: `enum "M.E" must contain at least one value declaration`,
424 }, {
425 label: "invalid enum value without number",
426 inDesc: mustParseFile(`
427 name: "test.proto"
428 package: ""
429 message_type: [{name:"M" enum_type:[{name:"E" value:[{name:"one"}]}]}]
430 `),
431 wantErr: `enum value "M.one" must have a specified number`,
432 }, {
433 label: "valid enum",
434 inDesc: mustParseFile(`
435 name: "test.proto"
436 package: ""
437 message_type: [{name:"M" enum_type:[{name:"E" value:[{name:"one" number:1}]}]}]
438 `),
439 }, {
440 label: "invalid enum reserved names",
441 inDesc: mustParseFile(`
442 name: "test.proto"
443 package: ""
444 message_type: [{name:"M" enum_type:[{
445 name: "E"
446 reserved_name: [""]
447 value: [{name:"V" number:0}]
448 }]}]
449 `),
450 // NOTE: In theory this should be an error.
451 // See https://github.com/protocolbuffers/protobuf/issues/6335.
452 /*wantErr: `enum "M.E" reserved names has invalid name: ""`,*/
453 }, {
454 label: "duplicate enum reserved names",
455 inDesc: mustParseFile(`
456 name: "test.proto"
457 package: ""
458 message_type: [{name:"M" enum_type:[{
459 name: "E"
460 reserved_name: ["foo", "foo"]
461 }]}]
462 `),
463 wantErr: `enum "M.E" reserved names has duplicate name: "foo"`,
464 }, {
465 label: "valid enum reserved names",
466 inDesc: mustParseFile(`
467 name: "test.proto"
468 package: ""
469 message_type: [{name:"M" enum_type:[{
470 name: "E"
471 reserved_name: ["foo", "bar"]
472 value: [{name:"baz" number:1}]
473 }]}]
474 `),
475 }, {
476 label: "use of enum reserved names",
477 inDesc: mustParseFile(`
478 name: "test.proto"
479 package: ""
480 message_type: [{name:"M" enum_type:[{
481 name: "E"
482 reserved_name: ["foo", "bar"]
483 value: [{name:"foo" number:1}]
484 }]}]
485 `),
486 wantErr: `enum value "M.foo" must not use reserved name`,
487 }, {
488 label: "invalid enum reserved ranges",
489 inDesc: mustParseFile(`
490 name: "test.proto"
491 package: ""
492 message_type: [{name:"M" enum_type:[{
493 name: "E"
494 reserved_range: [{start:5 end:4}]
495 }]}]
496 `),
497 wantErr: `enum "M.E" reserved ranges has invalid range: 5 to 4`,
498 }, {
499 label: "overlapping enum reserved ranges",
500 inDesc: mustParseFile(`
501 name: "test.proto"
502 package: ""
503 message_type: [{name:"M" enum_type:[{
504 name: "E"
505 reserved_range: [{start:1 end:1000}, {start:10 end:100}]
506 }]}]
507 `),
508 wantErr: `enum "M.E" reserved ranges has overlapping ranges: 1 to 1000 with 10 to 100`,
509 }, {
510 label: "valid enum reserved names",
511 inDesc: mustParseFile(`
512 name: "test.proto"
513 package: ""
514 message_type: [{name:"M" enum_type:[{
515 name: "E"
516 reserved_range: [{start:1 end:10}, {start:100 end:1000}]
517 value: [{name:"baz" number:50}]
518 }]}]
519 `),
520 }, {
521 label: "use of enum reserved range",
522 inDesc: mustParseFile(`
523 name: "test.proto"
524 package: ""
525 message_type: [{name:"M" enum_type:[{
526 name: "E"
527 reserved_range: [{start:1 end:10}, {start:100 end:1000}]
528 value: [{name:"baz" number:500}]
529 }]}]
530 `),
531 wantErr: `enum value "M.baz" must not use reserved number 500`,
532 }, {
533 label: "unused enum alias feature",
534 inDesc: mustParseFile(`
535 name: "test.proto"
536 package: ""
537 message_type: [{name:"M" enum_type:[{
538 name: "E"
539 value: [{name:"baz" number:500}]
540 options: {allow_alias:true}
541 }]}]
542 `),
543 wantErr: `enum "M.E" allows aliases, but none were found`,
544 }, {
545 label: "enum number conflicts",
546 inDesc: mustParseFile(`
547 name: "test.proto"
548 package: ""
549 message_type: [{name:"M" enum_type:[{
550 name: "E"
551 value: [{name:"foo" number:0}, {name:"bar" number:1}, {name:"baz" number:1}]
552 }]}]
553 `),
554 wantErr: `enum "M.E" has conflicting non-aliased values on number 1: "baz" with "bar"`,
555 }, {
556 label: "aliased enum numbers",
557 inDesc: mustParseFile(`
558 name: "test.proto"
559 package: ""
560 message_type: [{name:"M" enum_type:[{
561 name: "E"
562 value: [{name:"foo" number:0}, {name:"bar" number:1}, {name:"baz" number:1}]
563 options: {allow_alias:true}
564 }]}]
565 `),
566 }, {
567 label: "invalid proto3 enum",
568 inDesc: mustParseFile(`
569 syntax: "proto3"
570 name: "test.proto"
571 package: ""
572 message_type: [{name:"M" enum_type:[{
573 name: "E"
574 value: [{name:"baz" number:500}]
575 }]}]
576 `),
577 wantErr: `enum "M.baz" using proto3 semantics must have zero number for the first value`,
578 }, {
579 label: "valid proto3 enum",
580 inDesc: mustParseFile(`
581 syntax: "proto3"
582 name: "test.proto"
583 package: ""
584 message_type: [{name:"M" enum_type:[{
585 name: "E"
586 value: [{name:"baz" number:0}]
587 }]}]
588 `),
589 }, {
590 label: "proto3 enum name prefix conflict",
591 inDesc: mustParseFile(`
592 syntax: "proto3"
593 name: "test.proto"
594 package: ""
595 message_type: [{name:"M" enum_type:[{
596 name: "E"
597 value: [{name:"e_Foo" number:0}, {name:"fOo" number:1}]
598 }]}]
599 `),
600 wantErr: `enum "M.E" using proto3 semantics has conflict: "fOo" with "e_Foo"`,
601 }, {
602 label: "proto2 enum has name prefix check",
603 inDesc: mustParseFile(`
604 name: "test.proto"
605 package: ""
606 message_type: [{name:"M" enum_type:[{
607 name: "E"
608 value: [{name:"e_Foo" number:0}, {name:"fOo" number:1}]
609 }]}]
610 `),
611 }, {
612 label: "proto3 enum same name prefix with number conflict",
613 inDesc: mustParseFile(`
614 syntax: "proto3"
615 name: "test.proto"
616 package: ""
617 message_type: [{name:"M" enum_type:[{
618 name: "E"
619 value: [{name:"e_Foo" number:0}, {name:"fOo" number:0}]
620 }]}]
621 `),
622 wantErr: `enum "M.E" has conflicting non-aliased values on number 0: "fOo" with "e_Foo"`,
623 }, {
624 label: "proto3 enum same name prefix with alias numbers",
625 inDesc: mustParseFile(`
626 syntax: "proto3"
627 name: "test.proto"
628 package: ""
629 message_type: [{name:"M" enum_type:[{
630 name: "E"
631 value: [{name:"e_Foo" number:0}, {name:"fOo" number:0}]
632 options: {allow_alias: true}
633 }]}]
634 `),
635 }, {
636 label: "invalid message reserved names",
637 inDesc: mustParseFile(`
638 name: "test.proto"
639 package: ""
640 message_type: [{name:"M" nested_type:[{
641 name: "M"
642 reserved_name: ["$"]
643 }]}]
644 `),
645 // NOTE: In theory this should be an error.
646 // See https://github.com/protocolbuffers/protobuf/issues/6335.
647 /*wantErr: `message "M.M" reserved names has invalid name: "$"`,*/
648 }, {
649 label: "valid message reserved names",
650 inDesc: mustParseFile(`
651 name: "test.proto"
652 package: ""
653 message_type: [{name:"M" nested_type:[{
654 name: "M"
655 reserved_name: ["foo", "bar"]
656 field: [{name:"foo" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
657 }]}]
658 `),
659 wantErr: `message field "M.M.foo" must not use reserved name`,
660 }, {
661 label: "valid message reserved names",
662 inDesc: mustParseFile(`
663 name: "test.proto"
664 package: ""
665 message_type: [{name:"M" nested_type:[{
666 name: "M"
667 reserved_name: ["foo", "bar"]
668 field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:0}]
669 oneof_decl: [{name:"foo"}] # not affected by reserved_name
670 }]}]
671 `),
672 }, {
673 label: "invalid reserved number",
674 inDesc: mustParseFile(`
675 name: "test.proto"
676 package: ""
677 message_type: [{name:"M" nested_type:[{
678 name: "M"
679 reserved_range: [{start:1 end:1}]
680 field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
681 }]}]
682 `),
683 wantErr: `message "M.M" reserved ranges has invalid field number: 0`,
684 }, {
685 label: "invalid reserved ranges",
686 inDesc: mustParseFile(`
687 name: "test.proto"
688 package: ""
689 message_type: [{name:"M" nested_type:[{
690 name: "M"
691 reserved_range: [{start:2 end:2}]
692 field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
693 }]}]
694 `),
695 wantErr: `message "M.M" reserved ranges has invalid range: 2 to 1`,
696 }, {
697 label: "overlapping reserved ranges",
698 inDesc: mustParseFile(`
699 name: "test.proto"
700 package: ""
701 message_type: [{name:"M" nested_type:[{
702 name: "M"
703 reserved_range: [{start:1 end:10}, {start:2 end:9}]
704 field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
705 }]}]
706 `),
707 wantErr: `message "M.M" reserved ranges has overlapping ranges: 1 to 9 with 2 to 8`,
708 }, {
709 label: "use of reserved message field number",
710 inDesc: mustParseFile(`
711 name: "test.proto"
712 package: ""
713 message_type: [{name:"M" nested_type:[{
714 name: "M"
715 reserved_range: [{start:10 end:20}, {start:20 end:30}, {start:30 end:31}]
716 field: [{name:"baz" number:30 label:LABEL_OPTIONAL type:TYPE_STRING}]
717 }]}]
718 `),
719 wantErr: `message field "M.M.baz" must not use reserved number 30`,
720 }, {
721 label: "invalid extension ranges",
722 inDesc: mustParseFile(`
723 name: "test.proto"
724 package: ""
725 message_type: [{name:"M" nested_type:[{
726 name: "M"
727 extension_range: [{start:-500 end:2}]
728 field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
729 }]}]
730 `),
731 wantErr: `message "M.M" extension ranges has invalid field number: -500`,
732 }, {
733 label: "overlapping reserved and extension ranges",
734 inDesc: mustParseFile(`
735 name: "test.proto"
736 package: ""
737 message_type: [{name:"M" nested_type:[{
738 name: "M"
739 reserved_range: [{start:15 end:20}, {start:1 end:3}, {start:7 end:10}]
740 extension_range: [{start:8 end:9}, {start:3 end:5}]
741 }]}]
742 `),
743 wantErr: `message "M.M" reserved and extension ranges has overlapping ranges: 7 to 9 with 8`,
744 }, {
745 label: "message field conflicting number",
746 inDesc: mustParseFile(`
747 name: "test.proto"
748 package: ""
749 message_type: [{name:"M" nested_type:[{
750 name: "M"
751 field: [
752 {name:"one" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
753 {name:"One" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}
754 ]
755 }]}]
756 `),
757 wantErr: `message "M.M" has conflicting fields: "One" with "one"`,
758 }, {
759 label: "invalid MessageSet",
760 inDesc: mustParseFile(`
761 syntax: "proto3"
762 name: "test.proto"
763 package: ""
764 message_type: [{name:"M" nested_type:[{
765 name: "M"
766 options: {message_set_wire_format:true}
767 }]}]
768 `),
Joe Tsai3d8e3692019-04-08 13:52:14 -0700769 wantErr: func() string {
Joe Tsai1799d112019-08-08 13:31:59 -0700770 if flags.ProtoLegacy {
Joe Tsai3d8e3692019-04-08 13:52:14 -0700771 return `message "M.M" is an invalid proto1 MessageSet`
772 } else {
773 return `message "M.M" is a MessageSet, which is a legacy proto1 feature that is no longer supported`
774 }
775 }(),
Joe Tsai15076352019-07-02 15:19:08 -0700776 }, {
777 label: "valid MessageSet",
778 inDesc: mustParseFile(`
779 name: "test.proto"
780 package: ""
781 message_type: [{name:"M" nested_type:[{
782 name: "M"
783 extension_range: [{start:1 end:100000}]
784 options: {message_set_wire_format:true}
785 }]}]
786 `),
Joe Tsai3d8e3692019-04-08 13:52:14 -0700787 wantErr: func() string {
Joe Tsai1799d112019-08-08 13:31:59 -0700788 if flags.ProtoLegacy {
Joe Tsai3d8e3692019-04-08 13:52:14 -0700789 return ""
790 } else {
791 return `message "M.M" is a MessageSet, which is a legacy proto1 feature that is no longer supported`
792 }
793 }(),
Joe Tsai15076352019-07-02 15:19:08 -0700794 }, {
795 label: "invalid extension ranges in proto3",
796 inDesc: mustParseFile(`
797 syntax: "proto3"
798 name: "test.proto"
799 package: ""
800 message_type: [{name:"M" nested_type:[{
801 name: "M"
802 extension_range: [{start:1 end:100000}]
803 }]}]
804 `),
805 wantErr: `message "M.M" using proto3 semantics cannot have extension ranges`,
806 }, {
807 label: "proto3 message fields conflict",
808 inDesc: mustParseFile(`
809 syntax: "proto3"
810 name: "test.proto"
811 package: ""
812 message_type: [{name:"M" nested_type:[{
813 name: "M"
814 field: [
815 {name:"_b_a_z_" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
816 {name:"baz" number:2 label:LABEL_OPTIONAL type:TYPE_STRING}
817 ]
818 }]}]
819 `),
Joe Tsai09217f02019-09-06 16:57:46 -0700820 wantErr: `message "M.M" using proto3 semantics has conflict: "baz" with "_b_a_z_"`,
Joe Tsai15076352019-07-02 15:19:08 -0700821 }, {
822 label: "proto3 message fields",
823 inDesc: mustParseFile(`
824 syntax: "proto3"
825 name: "test.proto"
826 package: ""
827 message_type: [{name:"M" nested_type:[{
828 name: "M"
829 field: [{name:"_b_a_z_" number:1 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:0}]
830 oneof_decl: [{name:"baz"}] # proto3 name conflict logic does not include oneof
831 }]}]
832 `),
833 }, {
834 label: "proto2 message fields with no conflict",
835 inDesc: mustParseFile(`
836 name: "test.proto"
837 package: ""
838 message_type: [{name:"M" nested_type:[{
839 name: "M"
840 field: [
841 {name:"_b_a_z_" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
842 {name:"baz" number:2 label:LABEL_OPTIONAL type:TYPE_STRING}
843 ]
844 }]}]
845 `),
Joe Tsai67da21c2019-07-12 17:27:43 -0700846 }, {
847 label: "proto3 message with unresolved enum",
848 inDesc: mustParseFile(`
849 name: "test.proto"
850 package: ""
851 syntax: "proto3"
852 message_type: [{
853 name: "M"
854 field: [
855 {name:"enum" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".fizz.buzz.Enum"}
856 ]
857 }]
858 `),
859 inOpts: []option{allowUnresolvable()},
Joe Tsai15076352019-07-02 15:19:08 -0700860 // TODO: Test field and oneof handling in validateMessageDeclarations
861 // TODO: Test unmarshalDefault
862 // TODO: Test validateExtensionDeclarations
863 // TODO: Test checkValidGroup
864 // TODO: Test checkValidMap
865 }, {
866 label: "empty service",
867 inDesc: mustParseFile(`
868 name: "test.proto"
869 package: ""
870 service: [{name:"service"}]
871 `),
872 }, {
873 label: "service with method with unresolved",
874 inDesc: mustParseFile(`
875 name: "test.proto"
876 package: ""
877 service: [{
878 name: "service"
879 method: [{
880 name:"method"
881 input_type:"foo"
882 output_type:".foo.bar.baz"
883 }]
884 }]
885 `),
886 inOpts: []option{allowUnresolvable()},
887 }, {
888 label: "service with wrong reference type",
889 inDeps: []*descriptorpb.FileDescriptorProto{
890 cloneFile(proto3Message),
891 cloneFile(proto2Enum),
892 },
893 inDesc: mustParseFile(`
894 name: "test.proto"
895 package: ""
896 dependency: ["proto2_enum.proto", "proto3_message.proto"]
897 service: [{
898 name: "service"
899 method: [{
900 name: "method"
901 input_type: ".test.proto2.Enum",
902 output_type: ".test.proto3.Message"
903 }]
904 }]
905 `),
906 wantErr: `service method "service.method" cannot resolve input: resolved "test.proto2.Enum", but it is not an message`,
Joe Tsaib2107fb2019-06-29 01:31:37 -0700907 }}
908
909 for _, tt := range tests {
910 t.Run(tt.label, func(t *testing.T) {
911 r := new(protoregistry.Files)
912 for i, dep := range tt.inDeps {
913 f, err := newFile(dep, r)
914 if err != nil {
915 t.Fatalf("dependency %d: unexpected NewFile() error: %v", i, err)
916 }
Damien Neilc8268852019-10-08 13:28:53 -0700917 if err := r.RegisterFile(f); err != nil {
Joe Tsaib2107fb2019-06-29 01:31:37 -0700918 t.Fatalf("dependency %d: unexpected Register() error: %v", i, err)
919 }
920 }
921 var gotDesc *descriptorpb.FileDescriptorProto
922 if tt.wantErr == "" && tt.wantDesc == nil {
923 tt.wantDesc = cloneFile(tt.inDesc)
924 }
925 gotFile, err := newFile(tt.inDesc, r, tt.inOpts...)
926 if gotFile != nil {
927 gotDesc = ToFileDescriptorProto(gotFile)
928 }
929 if !proto.Equal(gotDesc, tt.wantDesc) {
930 t.Errorf("NewFile() mismatch:\ngot %v\nwant %v", gotDesc, tt.wantDesc)
931 }
932 if ((err == nil) != (tt.wantErr == "")) || !strings.Contains(fmt.Sprint(err), tt.wantErr) {
933 t.Errorf("NewFile() error:\ngot: %v\nwant: %v", err, tt.wantErr)
934 }
935 })
936 }
937}