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