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