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