blob: dd6dde68d5082087f7dc29a6eb699877621c594a [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 Tsaib2107fb2019-06-29 01:31:37 -0700184 label: "resolve relative reference",
185 inDesc: mustParseFile(`
186 name: "test.proto"
187 package: "fizz.buzz"
188 message_type: [{
189 name: "A"
190 field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"B.C"}]
191 nested_type: [{name: "B"}]
192 }, {
193 name: "B"
194 nested_type: [{name: "C"}]
195 }]
196 `),
197 wantDesc: mustParseFile(`
198 name: "test.proto"
199 package: "fizz.buzz"
200 message_type: [{
201 name: "A"
202 field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:".fizz.buzz.B.C"}]
203 nested_type: [{name: "B"}]
204 }, {
205 name: "B"
206 nested_type: [{name: "C"}]
207 }]
208 `),
209 }, {
210 label: "resolve the wrong type",
211 inDesc: mustParseFile(`
212 name: "test.proto"
213 package: ""
214 message_type: [{
215 name: "M"
216 field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"E"}]
217 enum_type: [{name: "E" value: [{name:"V0" number:0}, {name:"V1" number:1}]}]
218 }]
219 `),
220 wantErr: `message field "M.F" cannot resolve type: resolved "M.E", but it is not an message`,
221 }, {
222 label: "auto-resolve unknown kind",
223 inDesc: mustParseFile(`
224 name: "test.proto"
225 package: ""
226 message_type: [{
227 name: "M"
228 field: [{name:"F" number:1 label:LABEL_OPTIONAL type_name:"E"}]
229 enum_type: [{name: "E" value: [{name:"V0" number:0}, {name:"V1" number:1}]}]
230 }]
231 `),
232 wantDesc: mustParseFile(`
233 name: "test.proto"
234 package: ""
235 message_type: [{
236 name: "M"
237 field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".M.E"}]
238 enum_type: [{name: "E" value: [{name:"V0" number:0}, {name:"V1" number:1}]}]
239 }]
240 `),
241 }, {
242 label: "unresolved import",
243 inDesc: mustParseFile(`
244 name: "test.proto"
245 package: "fizz.buzz"
246 dependency: "remote.proto"
247 `),
248 wantErr: `could not resolve import "remote.proto": not found`,
249 }, {
250 label: "unresolved message field",
251 inDesc: mustParseFile(`
252 name: "test.proto"
253 package: "fizz.buzz"
254 message_type: [{
255 name: "M"
256 field: [{name:"F1" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:"some.other.enum" default_value:"UNKNOWN"}]
257 }]
258 `),
259 wantErr: `message field "fizz.buzz.M.F1" cannot resolve type: "*.some.other.enum" not found`,
260 }, {
261 label: "unresolved default enum value",
262 inDesc: mustParseFile(`
263 name: "test.proto"
264 package: "fizz.buzz"
265 message_type: [{
266 name: "M"
267 field: [{name:"F1" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:"E" default_value:"UNKNOWN"}]
268 enum_type: [{name:"E" value:[{name:"V0" number:0}]}]
269 }]
270 `),
271 wantErr: `message field "fizz.buzz.M.F1" has invalid default: could not parse value for enum: "UNKNOWN"`,
272 }, {
273 label: "allowed unresolved default enum value",
274 inDesc: mustParseFile(`
275 name: "test.proto"
276 package: "fizz.buzz"
277 message_type: [{
278 name: "M"
279 field: [{name:"F1" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".fizz.buzz.M.E" default_value:"UNKNOWN"}]
280 enum_type: [{name:"E" value:[{name:"V0" number:0}]}]
281 }]
282 `),
283 inOpts: []option{allowUnresolvable()},
284 }, {
285 label: "unresolved extendee",
286 inDesc: mustParseFile(`
287 name: "test.proto"
288 package: "fizz.buzz"
289 extension: [{name:"X" number:1 label:LABEL_OPTIONAL extendee:"some.extended.message" type:TYPE_MESSAGE type_name:"some.other.message"}]
290 `),
291 wantErr: `extension field "fizz.buzz.X" cannot resolve extendee: "*.some.extended.message" not found`,
292 }, {
293 label: "unresolved method input",
294 inDesc: mustParseFile(`
295 name: "test.proto"
296 package: "fizz.buzz"
297 service: [{
298 name: "S"
299 method: [{name:"M" input_type:"foo.bar.input" output_type:".absolute.foo.bar.output"}]
300 }]
301 `),
302 wantErr: `service method "fizz.buzz.S.M" cannot resolve input: "*.foo.bar.input" not found`,
303 }, {
304 label: "allowed unresolved references",
305 inDesc: mustParseFile(`
306 name: "test.proto"
307 package: "fizz.buzz"
308 dependency: "remote.proto"
309 message_type: [{
310 name: "M"
311 field: [{name:"F1" number:1 label:LABEL_OPTIONAL type_name:"some.other.enum" default_value:"UNKNOWN"}]
312 }]
313 extension: [{name:"X" number:1 label:LABEL_OPTIONAL extendee:"some.extended.message" type:TYPE_MESSAGE type_name:"some.other.message"}]
314 service: [{
315 name: "S"
316 method: [{name:"M" input_type:"foo.bar.input" output_type:".absolute.foo.bar.output"}]
317 }]
318 `),
319 inOpts: []option{allowUnresolvable()},
320 }, {
321 label: "resolved but not imported",
322 inDeps: []*descriptorpb.FileDescriptorProto{mustParseFile(`
323 name: "dep.proto"
324 package: "fizz"
325 message_type: [{name:"M" nested_type:[{name:"M"}]}]
326 `)},
327 inDesc: mustParseFile(`
328 name: "test.proto"
329 package: "fizz.buzz"
330 message_type: [{
331 name: "M"
332 field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"M.M"}]
333 }]
334 `),
335 wantErr: `message field "fizz.buzz.M.F" cannot resolve type: resolved "fizz.M.M", but "dep.proto" is not imported`,
336 }, {
337 label: "resolved from remote import",
338 inDeps: []*descriptorpb.FileDescriptorProto{mustParseFile(`
339 name: "dep.proto"
340 package: "fizz"
341 message_type: [{name:"M" nested_type:[{name:"M"}]}]
342 `)},
343 inDesc: mustParseFile(`
344 name: "test.proto"
345 package: "fizz.buzz"
346 dependency: "dep.proto"
347 message_type: [{
348 name: "M"
349 field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"M.M"}]
350 }]
351 `),
352 wantDesc: mustParseFile(`
353 name: "test.proto"
354 package: "fizz.buzz"
355 dependency: "dep.proto"
356 message_type: [{
357 name: "M"
358 field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:".fizz.M.M"}]
359 }]
360 `),
Joe Tsai15076352019-07-02 15:19:08 -0700361 }, {
362 label: "namespace conflict on enum value",
363 inDesc: mustParseFile(`
364 name: "test.proto"
365 package: ""
366 enum_type: [{
367 name: "foo"
368 value: [{name:"foo" number:0}]
369 }]
370 `),
371 wantErr: `descriptor "foo" already declared`,
372 }, {
373 label: "no namespace conflict on message field",
374 inDesc: mustParseFile(`
375 name: "test.proto"
376 package: ""
377 message_type: [{
378 name: "foo"
379 field: [{name:"foo" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
380 }]
381 `),
382 }, {
383 label: "invalid name",
384 inDesc: mustParseFile(`
385 name: "test.proto"
386 package: ""
387 message_type: [{name: "$"}]
388 `),
389 wantErr: `descriptor "" has an invalid nested name: "$"`,
390 }, {
391 label: "invalid empty enum",
392 inDesc: mustParseFile(`
393 name: "test.proto"
394 package: ""
395 message_type: [{name:"M" enum_type:[{name:"E"}]}]
396 `),
397 wantErr: `enum "M.E" must contain at least one value declaration`,
398 }, {
399 label: "invalid enum value without number",
400 inDesc: mustParseFile(`
401 name: "test.proto"
402 package: ""
403 message_type: [{name:"M" enum_type:[{name:"E" value:[{name:"one"}]}]}]
404 `),
405 wantErr: `enum value "M.one" must have a specified number`,
406 }, {
407 label: "valid enum",
408 inDesc: mustParseFile(`
409 name: "test.proto"
410 package: ""
411 message_type: [{name:"M" enum_type:[{name:"E" value:[{name:"one" number:1}]}]}]
412 `),
413 }, {
414 label: "invalid enum reserved names",
415 inDesc: mustParseFile(`
416 name: "test.proto"
417 package: ""
418 message_type: [{name:"M" enum_type:[{
419 name: "E"
420 reserved_name: [""]
421 value: [{name:"V" number:0}]
422 }]}]
423 `),
424 // NOTE: In theory this should be an error.
425 // See https://github.com/protocolbuffers/protobuf/issues/6335.
426 /*wantErr: `enum "M.E" reserved names has invalid name: ""`,*/
427 }, {
428 label: "duplicate enum reserved names",
429 inDesc: mustParseFile(`
430 name: "test.proto"
431 package: ""
432 message_type: [{name:"M" enum_type:[{
433 name: "E"
434 reserved_name: ["foo", "foo"]
435 }]}]
436 `),
437 wantErr: `enum "M.E" reserved names has duplicate name: "foo"`,
438 }, {
439 label: "valid enum reserved names",
440 inDesc: mustParseFile(`
441 name: "test.proto"
442 package: ""
443 message_type: [{name:"M" enum_type:[{
444 name: "E"
445 reserved_name: ["foo", "bar"]
446 value: [{name:"baz" number:1}]
447 }]}]
448 `),
449 }, {
450 label: "use of enum reserved names",
451 inDesc: mustParseFile(`
452 name: "test.proto"
453 package: ""
454 message_type: [{name:"M" enum_type:[{
455 name: "E"
456 reserved_name: ["foo", "bar"]
457 value: [{name:"foo" number:1}]
458 }]}]
459 `),
460 wantErr: `enum value "M.foo" must not use reserved name`,
461 }, {
462 label: "invalid enum reserved ranges",
463 inDesc: mustParseFile(`
464 name: "test.proto"
465 package: ""
466 message_type: [{name:"M" enum_type:[{
467 name: "E"
468 reserved_range: [{start:5 end:4}]
469 }]}]
470 `),
471 wantErr: `enum "M.E" reserved ranges has invalid range: 5 to 4`,
472 }, {
473 label: "overlapping enum reserved ranges",
474 inDesc: mustParseFile(`
475 name: "test.proto"
476 package: ""
477 message_type: [{name:"M" enum_type:[{
478 name: "E"
479 reserved_range: [{start:1 end:1000}, {start:10 end:100}]
480 }]}]
481 `),
482 wantErr: `enum "M.E" reserved ranges has overlapping ranges: 1 to 1000 with 10 to 100`,
483 }, {
484 label: "valid enum reserved names",
485 inDesc: mustParseFile(`
486 name: "test.proto"
487 package: ""
488 message_type: [{name:"M" enum_type:[{
489 name: "E"
490 reserved_range: [{start:1 end:10}, {start:100 end:1000}]
491 value: [{name:"baz" number:50}]
492 }]}]
493 `),
494 }, {
495 label: "use of enum reserved range",
496 inDesc: mustParseFile(`
497 name: "test.proto"
498 package: ""
499 message_type: [{name:"M" enum_type:[{
500 name: "E"
501 reserved_range: [{start:1 end:10}, {start:100 end:1000}]
502 value: [{name:"baz" number:500}]
503 }]}]
504 `),
505 wantErr: `enum value "M.baz" must not use reserved number 500`,
506 }, {
507 label: "unused enum alias feature",
508 inDesc: mustParseFile(`
509 name: "test.proto"
510 package: ""
511 message_type: [{name:"M" enum_type:[{
512 name: "E"
513 value: [{name:"baz" number:500}]
514 options: {allow_alias:true}
515 }]}]
516 `),
517 wantErr: `enum "M.E" allows aliases, but none were found`,
518 }, {
519 label: "enum number conflicts",
520 inDesc: mustParseFile(`
521 name: "test.proto"
522 package: ""
523 message_type: [{name:"M" enum_type:[{
524 name: "E"
525 value: [{name:"foo" number:0}, {name:"bar" number:1}, {name:"baz" number:1}]
526 }]}]
527 `),
528 wantErr: `enum "M.E" has conflicting non-aliased values on number 1: "baz" with "bar"`,
529 }, {
530 label: "aliased enum numbers",
531 inDesc: mustParseFile(`
532 name: "test.proto"
533 package: ""
534 message_type: [{name:"M" enum_type:[{
535 name: "E"
536 value: [{name:"foo" number:0}, {name:"bar" number:1}, {name:"baz" number:1}]
537 options: {allow_alias:true}
538 }]}]
539 `),
540 }, {
541 label: "invalid proto3 enum",
542 inDesc: mustParseFile(`
543 syntax: "proto3"
544 name: "test.proto"
545 package: ""
546 message_type: [{name:"M" enum_type:[{
547 name: "E"
548 value: [{name:"baz" number:500}]
549 }]}]
550 `),
551 wantErr: `enum "M.baz" using proto3 semantics must have zero number for the first value`,
552 }, {
553 label: "valid proto3 enum",
554 inDesc: mustParseFile(`
555 syntax: "proto3"
556 name: "test.proto"
557 package: ""
558 message_type: [{name:"M" enum_type:[{
559 name: "E"
560 value: [{name:"baz" number:0}]
561 }]}]
562 `),
563 }, {
564 label: "proto3 enum name prefix conflict",
565 inDesc: mustParseFile(`
566 syntax: "proto3"
567 name: "test.proto"
568 package: ""
569 message_type: [{name:"M" enum_type:[{
570 name: "E"
571 value: [{name:"e_Foo" number:0}, {name:"fOo" number:1}]
572 }]}]
573 `),
574 wantErr: `enum "M.E" using proto3 semantics has conflict: "fOo" with "e_Foo"`,
575 }, {
576 label: "proto2 enum has name prefix check",
577 inDesc: mustParseFile(`
578 name: "test.proto"
579 package: ""
580 message_type: [{name:"M" enum_type:[{
581 name: "E"
582 value: [{name:"e_Foo" number:0}, {name:"fOo" number:1}]
583 }]}]
584 `),
585 }, {
586 label: "proto3 enum same name prefix with number conflict",
587 inDesc: mustParseFile(`
588 syntax: "proto3"
589 name: "test.proto"
590 package: ""
591 message_type: [{name:"M" enum_type:[{
592 name: "E"
593 value: [{name:"e_Foo" number:0}, {name:"fOo" number:0}]
594 }]}]
595 `),
596 wantErr: `enum "M.E" has conflicting non-aliased values on number 0: "fOo" with "e_Foo"`,
597 }, {
598 label: "proto3 enum same name prefix with alias numbers",
599 inDesc: mustParseFile(`
600 syntax: "proto3"
601 name: "test.proto"
602 package: ""
603 message_type: [{name:"M" enum_type:[{
604 name: "E"
605 value: [{name:"e_Foo" number:0}, {name:"fOo" number:0}]
606 options: {allow_alias: true}
607 }]}]
608 `),
609 }, {
610 label: "invalid message reserved names",
611 inDesc: mustParseFile(`
612 name: "test.proto"
613 package: ""
614 message_type: [{name:"M" nested_type:[{
615 name: "M"
616 reserved_name: ["$"]
617 }]}]
618 `),
619 // NOTE: In theory this should be an error.
620 // See https://github.com/protocolbuffers/protobuf/issues/6335.
621 /*wantErr: `message "M.M" reserved names has invalid name: "$"`,*/
622 }, {
623 label: "valid message reserved names",
624 inDesc: mustParseFile(`
625 name: "test.proto"
626 package: ""
627 message_type: [{name:"M" nested_type:[{
628 name: "M"
629 reserved_name: ["foo", "bar"]
630 field: [{name:"foo" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
631 }]}]
632 `),
633 wantErr: `message field "M.M.foo" must not use reserved name`,
634 }, {
635 label: "valid message reserved names",
636 inDesc: mustParseFile(`
637 name: "test.proto"
638 package: ""
639 message_type: [{name:"M" nested_type:[{
640 name: "M"
641 reserved_name: ["foo", "bar"]
642 field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:0}]
643 oneof_decl: [{name:"foo"}] # not affected by reserved_name
644 }]}]
645 `),
646 }, {
647 label: "invalid reserved number",
648 inDesc: mustParseFile(`
649 name: "test.proto"
650 package: ""
651 message_type: [{name:"M" nested_type:[{
652 name: "M"
653 reserved_range: [{start:1 end:1}]
654 field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
655 }]}]
656 `),
657 wantErr: `message "M.M" reserved ranges has invalid field number: 0`,
658 }, {
659 label: "invalid reserved ranges",
660 inDesc: mustParseFile(`
661 name: "test.proto"
662 package: ""
663 message_type: [{name:"M" nested_type:[{
664 name: "M"
665 reserved_range: [{start:2 end:2}]
666 field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
667 }]}]
668 `),
669 wantErr: `message "M.M" reserved ranges has invalid range: 2 to 1`,
670 }, {
671 label: "overlapping reserved ranges",
672 inDesc: mustParseFile(`
673 name: "test.proto"
674 package: ""
675 message_type: [{name:"M" nested_type:[{
676 name: "M"
677 reserved_range: [{start:1 end:10}, {start:2 end:9}]
678 field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
679 }]}]
680 `),
681 wantErr: `message "M.M" reserved ranges has overlapping ranges: 1 to 9 with 2 to 8`,
682 }, {
683 label: "use of reserved message field number",
684 inDesc: mustParseFile(`
685 name: "test.proto"
686 package: ""
687 message_type: [{name:"M" nested_type:[{
688 name: "M"
689 reserved_range: [{start:10 end:20}, {start:20 end:30}, {start:30 end:31}]
690 field: [{name:"baz" number:30 label:LABEL_OPTIONAL type:TYPE_STRING}]
691 }]}]
692 `),
693 wantErr: `message field "M.M.baz" must not use reserved number 30`,
694 }, {
695 label: "invalid extension ranges",
696 inDesc: mustParseFile(`
697 name: "test.proto"
698 package: ""
699 message_type: [{name:"M" nested_type:[{
700 name: "M"
701 extension_range: [{start:-500 end:2}]
702 field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
703 }]}]
704 `),
705 wantErr: `message "M.M" extension ranges has invalid field number: -500`,
706 }, {
707 label: "overlapping reserved and extension ranges",
708 inDesc: mustParseFile(`
709 name: "test.proto"
710 package: ""
711 message_type: [{name:"M" nested_type:[{
712 name: "M"
713 reserved_range: [{start:15 end:20}, {start:1 end:3}, {start:7 end:10}]
714 extension_range: [{start:8 end:9}, {start:3 end:5}]
715 }]}]
716 `),
717 wantErr: `message "M.M" reserved and extension ranges has overlapping ranges: 7 to 9 with 8`,
718 }, {
719 label: "message field conflicting number",
720 inDesc: mustParseFile(`
721 name: "test.proto"
722 package: ""
723 message_type: [{name:"M" nested_type:[{
724 name: "M"
725 field: [
726 {name:"one" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
727 {name:"One" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}
728 ]
729 }]}]
730 `),
731 wantErr: `message "M.M" has conflicting fields: "One" with "one"`,
732 }, {
733 label: "invalid MessageSet",
734 inDesc: mustParseFile(`
735 syntax: "proto3"
736 name: "test.proto"
737 package: ""
738 message_type: [{name:"M" nested_type:[{
739 name: "M"
740 options: {message_set_wire_format:true}
741 }]}]
742 `),
743 wantErr: `message "M.M" is an invalid proto1 MessageSet`,
744 }, {
745 label: "valid MessageSet",
746 inDesc: mustParseFile(`
747 name: "test.proto"
748 package: ""
749 message_type: [{name:"M" nested_type:[{
750 name: "M"
751 extension_range: [{start:1 end:100000}]
752 options: {message_set_wire_format:true}
753 }]}]
754 `),
755 }, {
756 label: "invalid extension ranges in proto3",
757 inDesc: mustParseFile(`
758 syntax: "proto3"
759 name: "test.proto"
760 package: ""
761 message_type: [{name:"M" nested_type:[{
762 name: "M"
763 extension_range: [{start:1 end:100000}]
764 }]}]
765 `),
766 wantErr: `message "M.M" using proto3 semantics cannot have extension ranges`,
767 }, {
768 label: "proto3 message fields conflict",
769 inDesc: mustParseFile(`
770 syntax: "proto3"
771 name: "test.proto"
772 package: ""
773 message_type: [{name:"M" nested_type:[{
774 name: "M"
775 field: [
776 {name:"_b_a_z_" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
777 {name:"baz" number:2 label:LABEL_OPTIONAL type:TYPE_STRING}
778 ]
779 }]}]
780 `),
781 wantErr: `proto: message "M.M" using proto3 semantics has conflict: "baz" with "_b_a_z_"`,
782 }, {
783 label: "proto3 message fields",
784 inDesc: mustParseFile(`
785 syntax: "proto3"
786 name: "test.proto"
787 package: ""
788 message_type: [{name:"M" nested_type:[{
789 name: "M"
790 field: [{name:"_b_a_z_" number:1 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:0}]
791 oneof_decl: [{name:"baz"}] # proto3 name conflict logic does not include oneof
792 }]}]
793 `),
794 }, {
795 label: "proto2 message fields with no conflict",
796 inDesc: mustParseFile(`
797 name: "test.proto"
798 package: ""
799 message_type: [{name:"M" nested_type:[{
800 name: "M"
801 field: [
802 {name:"_b_a_z_" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
803 {name:"baz" number:2 label:LABEL_OPTIONAL type:TYPE_STRING}
804 ]
805 }]}]
806 `),
807 // TODO: Test field and oneof handling in validateMessageDeclarations
808 // TODO: Test unmarshalDefault
809 // TODO: Test validateExtensionDeclarations
810 // TODO: Test checkValidGroup
811 // TODO: Test checkValidMap
812 }, {
813 label: "empty service",
814 inDesc: mustParseFile(`
815 name: "test.proto"
816 package: ""
817 service: [{name:"service"}]
818 `),
819 }, {
820 label: "service with method with unresolved",
821 inDesc: mustParseFile(`
822 name: "test.proto"
823 package: ""
824 service: [{
825 name: "service"
826 method: [{
827 name:"method"
828 input_type:"foo"
829 output_type:".foo.bar.baz"
830 }]
831 }]
832 `),
833 inOpts: []option{allowUnresolvable()},
834 }, {
835 label: "service with wrong reference type",
836 inDeps: []*descriptorpb.FileDescriptorProto{
837 cloneFile(proto3Message),
838 cloneFile(proto2Enum),
839 },
840 inDesc: mustParseFile(`
841 name: "test.proto"
842 package: ""
843 dependency: ["proto2_enum.proto", "proto3_message.proto"]
844 service: [{
845 name: "service"
846 method: [{
847 name: "method"
848 input_type: ".test.proto2.Enum",
849 output_type: ".test.proto3.Message"
850 }]
851 }]
852 `),
853 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 -0700854 }}
855
856 for _, tt := range tests {
857 t.Run(tt.label, func(t *testing.T) {
858 r := new(protoregistry.Files)
859 for i, dep := range tt.inDeps {
860 f, err := newFile(dep, r)
861 if err != nil {
862 t.Fatalf("dependency %d: unexpected NewFile() error: %v", i, err)
863 }
864 if err := r.Register(f); err != nil {
865 t.Fatalf("dependency %d: unexpected Register() error: %v", i, err)
866 }
867 }
868 var gotDesc *descriptorpb.FileDescriptorProto
869 if tt.wantErr == "" && tt.wantDesc == nil {
870 tt.wantDesc = cloneFile(tt.inDesc)
871 }
872 gotFile, err := newFile(tt.inDesc, r, tt.inOpts...)
873 if gotFile != nil {
874 gotDesc = ToFileDescriptorProto(gotFile)
875 }
876 if !proto.Equal(gotDesc, tt.wantDesc) {
877 t.Errorf("NewFile() mismatch:\ngot %v\nwant %v", gotDesc, tt.wantDesc)
878 }
879 if ((err == nil) != (tt.wantErr == "")) || !strings.Contains(fmt.Sprint(err), tt.wantErr) {
880 t.Errorf("NewFile() error:\ngot: %v\nwant: %v", err, tt.wantErr)
881 }
882 })
883 }
884}