blob: 45910fcf22a9e2b96ab8162027f77876eef306d5 [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
33func TestNewFile(t *testing.T) {
34 tests := []struct {
35 label string
36 inDeps []*descriptorpb.FileDescriptorProto
37 inDesc *descriptorpb.FileDescriptorProto
38 inOpts []option
39 wantDesc *descriptorpb.FileDescriptorProto
40 wantErr string
41 }{{
42 label: "resolve relative reference",
43 inDesc: mustParseFile(`
44 name: "test.proto"
45 package: "fizz.buzz"
46 message_type: [{
47 name: "A"
48 field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"B.C"}]
49 nested_type: [{name: "B"}]
50 }, {
51 name: "B"
52 nested_type: [{name: "C"}]
53 }]
54 `),
55 wantDesc: mustParseFile(`
56 name: "test.proto"
57 package: "fizz.buzz"
58 message_type: [{
59 name: "A"
60 field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:".fizz.buzz.B.C"}]
61 nested_type: [{name: "B"}]
62 }, {
63 name: "B"
64 nested_type: [{name: "C"}]
65 }]
66 `),
67 }, {
68 label: "resolve the wrong type",
69 inDesc: mustParseFile(`
70 name: "test.proto"
71 package: ""
72 message_type: [{
73 name: "M"
74 field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"E"}]
75 enum_type: [{name: "E" value: [{name:"V0" number:0}, {name:"V1" number:1}]}]
76 }]
77 `),
78 wantErr: `message field "M.F" cannot resolve type: resolved "M.E", but it is not an message`,
79 }, {
80 label: "auto-resolve unknown kind",
81 inDesc: mustParseFile(`
82 name: "test.proto"
83 package: ""
84 message_type: [{
85 name: "M"
86 field: [{name:"F" number:1 label:LABEL_OPTIONAL type_name:"E"}]
87 enum_type: [{name: "E" value: [{name:"V0" number:0}, {name:"V1" number:1}]}]
88 }]
89 `),
90 wantDesc: mustParseFile(`
91 name: "test.proto"
92 package: ""
93 message_type: [{
94 name: "M"
95 field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".M.E"}]
96 enum_type: [{name: "E" value: [{name:"V0" number:0}, {name:"V1" number:1}]}]
97 }]
98 `),
99 }, {
100 label: "unresolved import",
101 inDesc: mustParseFile(`
102 name: "test.proto"
103 package: "fizz.buzz"
104 dependency: "remote.proto"
105 `),
106 wantErr: `could not resolve import "remote.proto": not found`,
107 }, {
108 label: "unresolved message field",
109 inDesc: mustParseFile(`
110 name: "test.proto"
111 package: "fizz.buzz"
112 message_type: [{
113 name: "M"
114 field: [{name:"F1" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:"some.other.enum" default_value:"UNKNOWN"}]
115 }]
116 `),
117 wantErr: `message field "fizz.buzz.M.F1" cannot resolve type: "*.some.other.enum" not found`,
118 }, {
119 label: "unresolved default enum value",
120 inDesc: mustParseFile(`
121 name: "test.proto"
122 package: "fizz.buzz"
123 message_type: [{
124 name: "M"
125 field: [{name:"F1" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:"E" default_value:"UNKNOWN"}]
126 enum_type: [{name:"E" value:[{name:"V0" number:0}]}]
127 }]
128 `),
129 wantErr: `message field "fizz.buzz.M.F1" has invalid default: could not parse value for enum: "UNKNOWN"`,
130 }, {
131 label: "allowed unresolved default enum value",
132 inDesc: mustParseFile(`
133 name: "test.proto"
134 package: "fizz.buzz"
135 message_type: [{
136 name: "M"
137 field: [{name:"F1" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".fizz.buzz.M.E" default_value:"UNKNOWN"}]
138 enum_type: [{name:"E" value:[{name:"V0" number:0}]}]
139 }]
140 `),
141 inOpts: []option{allowUnresolvable()},
142 }, {
143 label: "unresolved extendee",
144 inDesc: mustParseFile(`
145 name: "test.proto"
146 package: "fizz.buzz"
147 extension: [{name:"X" number:1 label:LABEL_OPTIONAL extendee:"some.extended.message" type:TYPE_MESSAGE type_name:"some.other.message"}]
148 `),
149 wantErr: `extension field "fizz.buzz.X" cannot resolve extendee: "*.some.extended.message" not found`,
150 }, {
151 label: "unresolved method input",
152 inDesc: mustParseFile(`
153 name: "test.proto"
154 package: "fizz.buzz"
155 service: [{
156 name: "S"
157 method: [{name:"M" input_type:"foo.bar.input" output_type:".absolute.foo.bar.output"}]
158 }]
159 `),
160 wantErr: `service method "fizz.buzz.S.M" cannot resolve input: "*.foo.bar.input" not found`,
161 }, {
162 label: "allowed unresolved references",
163 inDesc: mustParseFile(`
164 name: "test.proto"
165 package: "fizz.buzz"
166 dependency: "remote.proto"
167 message_type: [{
168 name: "M"
169 field: [{name:"F1" number:1 label:LABEL_OPTIONAL type_name:"some.other.enum" default_value:"UNKNOWN"}]
170 }]
171 extension: [{name:"X" number:1 label:LABEL_OPTIONAL extendee:"some.extended.message" type:TYPE_MESSAGE type_name:"some.other.message"}]
172 service: [{
173 name: "S"
174 method: [{name:"M" input_type:"foo.bar.input" output_type:".absolute.foo.bar.output"}]
175 }]
176 `),
177 inOpts: []option{allowUnresolvable()},
178 }, {
179 label: "resolved but not imported",
180 inDeps: []*descriptorpb.FileDescriptorProto{mustParseFile(`
181 name: "dep.proto"
182 package: "fizz"
183 message_type: [{name:"M" nested_type:[{name:"M"}]}]
184 `)},
185 inDesc: mustParseFile(`
186 name: "test.proto"
187 package: "fizz.buzz"
188 message_type: [{
189 name: "M"
190 field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"M.M"}]
191 }]
192 `),
193 wantErr: `message field "fizz.buzz.M.F" cannot resolve type: resolved "fizz.M.M", but "dep.proto" is not imported`,
194 }, {
195 label: "resolved from remote import",
196 inDeps: []*descriptorpb.FileDescriptorProto{mustParseFile(`
197 name: "dep.proto"
198 package: "fizz"
199 message_type: [{name:"M" nested_type:[{name:"M"}]}]
200 `)},
201 inDesc: mustParseFile(`
202 name: "test.proto"
203 package: "fizz.buzz"
204 dependency: "dep.proto"
205 message_type: [{
206 name: "M"
207 field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"M.M"}]
208 }]
209 `),
210 wantDesc: mustParseFile(`
211 name: "test.proto"
212 package: "fizz.buzz"
213 dependency: "dep.proto"
214 message_type: [{
215 name: "M"
216 field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:".fizz.M.M"}]
217 }]
218 `),
219 }}
220
221 for _, tt := range tests {
222 t.Run(tt.label, func(t *testing.T) {
223 r := new(protoregistry.Files)
224 for i, dep := range tt.inDeps {
225 f, err := newFile(dep, r)
226 if err != nil {
227 t.Fatalf("dependency %d: unexpected NewFile() error: %v", i, err)
228 }
229 if err := r.Register(f); err != nil {
230 t.Fatalf("dependency %d: unexpected Register() error: %v", i, err)
231 }
232 }
233 var gotDesc *descriptorpb.FileDescriptorProto
234 if tt.wantErr == "" && tt.wantDesc == nil {
235 tt.wantDesc = cloneFile(tt.inDesc)
236 }
237 gotFile, err := newFile(tt.inDesc, r, tt.inOpts...)
238 if gotFile != nil {
239 gotDesc = ToFileDescriptorProto(gotFile)
240 }
241 if !proto.Equal(gotDesc, tt.wantDesc) {
242 t.Errorf("NewFile() mismatch:\ngot %v\nwant %v", gotDesc, tt.wantDesc)
243 }
244 if ((err == nil) != (tt.wantErr == "")) || !strings.Contains(fmt.Sprint(err), tt.wantErr) {
245 t.Errorf("NewFile() error:\ngot: %v\nwant: %v", err, tt.wantErr)
246 }
247 })
248 }
249}