1package builder
2
3import (
4	"fmt"
5	"io/ioutil"
6	"os"
7	"sort"
8	"strings"
9	"testing"
10
11	"github.com/golang/protobuf/proto"
12	dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
13	"github.com/golang/protobuf/ptypes/any"
14	"github.com/golang/protobuf/ptypes/empty"
15	"github.com/golang/protobuf/ptypes/timestamp"
16
17	"github.com/jhump/protoreflect/desc"
18	"github.com/jhump/protoreflect/dynamic"
19	_ "github.com/jhump/protoreflect/internal/testprotos"
20	"github.com/jhump/protoreflect/internal/testutil"
21)
22
23func TestSimpleDescriptorsFromScratch(t *testing.T) {
24	md, err := desc.LoadMessageDescriptorForMessage((*empty.Empty)(nil))
25	testutil.Ok(t, err)
26
27	file := NewFile("foo/bar.proto").SetPackageName("foo.bar")
28	en := NewEnum("Options").
29		AddValue(NewEnumValue("OPTION_1")).
30		AddValue(NewEnumValue("OPTION_2")).
31		AddValue(NewEnumValue("OPTION_3"))
32	file.AddEnum(en)
33
34	msg := NewMessage("FooRequest").
35		AddField(NewField("id", FieldTypeInt64())).
36		AddField(NewField("name", FieldTypeString())).
37		AddField(NewField("options", FieldTypeEnum(en)).
38			SetRepeated())
39	file.AddMessage(msg)
40
41	sb := NewService("FooService").
42		AddMethod(NewMethod("DoSomething", RpcTypeMessage(msg, false), RpcTypeMessage(msg, false))).
43		AddMethod(NewMethod("ReturnThings", RpcTypeImportedMessage(md, false), RpcTypeMessage(msg, true)))
44	file.AddService(sb)
45
46	fd, err := file.Build()
47	testutil.Ok(t, err)
48
49	testutil.Eq(t, []*desc.FileDescriptor{md.GetFile()}, fd.GetDependencies())
50	testutil.Require(t, fd.FindEnum("foo.bar.Options") != nil)
51	testutil.Eq(t, 3, len(fd.FindEnum("foo.bar.Options").GetValues()))
52	testutil.Require(t, fd.FindMessage("foo.bar.FooRequest") != nil)
53	testutil.Eq(t, 3, len(fd.FindMessage("foo.bar.FooRequest").GetFields()))
54	testutil.Require(t, fd.FindService("foo.bar.FooService") != nil)
55	testutil.Eq(t, 2, len(fd.FindService("foo.bar.FooService").GetMethods()))
56
57	// building the others produces same results
58	ed, err := en.Build()
59	testutil.Ok(t, err)
60	testutil.Require(t, proto.Equal(ed.AsProto(), fd.FindEnum("foo.bar.Options").AsProto()))
61
62	md, err = msg.Build()
63	testutil.Ok(t, err)
64	testutil.Require(t, proto.Equal(md.AsProto(), fd.FindMessage("foo.bar.FooRequest").AsProto()))
65
66	sd, err := sb.Build()
67	testutil.Ok(t, err)
68	testutil.Require(t, proto.Equal(sd.AsProto(), fd.FindService("foo.bar.FooService").AsProto()))
69}
70
71func TestSimpleDescriptorsFromScratch_SyntheticFiles(t *testing.T) {
72	md, err := desc.LoadMessageDescriptorForMessage((*empty.Empty)(nil))
73	testutil.Ok(t, err)
74
75	en := NewEnum("Options")
76	en.AddValue(NewEnumValue("OPTION_1"))
77	en.AddValue(NewEnumValue("OPTION_2"))
78	en.AddValue(NewEnumValue("OPTION_3"))
79
80	msg := NewMessage("FooRequest")
81	msg.AddField(NewField("id", FieldTypeInt64()))
82	msg.AddField(NewField("name", FieldTypeString()))
83	msg.AddField(NewField("options", FieldTypeEnum(en)).
84		SetRepeated())
85
86	sb := NewService("FooService")
87	sb.AddMethod(NewMethod("DoSomething", RpcTypeMessage(msg, false), RpcTypeMessage(msg, false)))
88	sb.AddMethod(NewMethod("ReturnThings", RpcTypeImportedMessage(md, false), RpcTypeMessage(msg, true)))
89
90	sd, err := sb.Build()
91	testutil.Ok(t, err)
92	testutil.Eq(t, "FooService", sd.GetFullyQualifiedName())
93	testutil.Eq(t, 2, len(sd.GetMethods()))
94
95	// it imports google/protobuf/empty.proto and a synthetic file that has message
96	testutil.Eq(t, 2, len(sd.GetFile().GetDependencies()))
97	fd := sd.GetFile().GetDependencies()[0]
98	testutil.Eq(t, "google/protobuf/empty.proto", fd.GetName())
99	testutil.Eq(t, md.GetFile(), fd)
100	fd = sd.GetFile().GetDependencies()[1]
101	testutil.Require(t, strings.Contains(fd.GetName(), "generated"))
102	testutil.Require(t, fd.FindMessage("FooRequest") != nil)
103	testutil.Eq(t, 3, len(fd.FindMessage("FooRequest").GetFields()))
104
105	// this one imports only a synthetic file that has enum
106	testutil.Eq(t, 1, len(fd.GetDependencies()))
107	fd2 := fd.GetDependencies()[0]
108	testutil.Require(t, fd2.FindEnum("Options") != nil)
109	testutil.Eq(t, 3, len(fd2.FindEnum("Options").GetValues()))
110
111	// building the others produces same results
112	ed, err := en.Build()
113	testutil.Ok(t, err)
114	testutil.Require(t, proto.Equal(ed.AsProto(), fd2.FindEnum("Options").AsProto()))
115
116	md, err = msg.Build()
117	testutil.Ok(t, err)
118	testutil.Require(t, proto.Equal(md.AsProto(), fd.FindMessage("FooRequest").AsProto()))
119}
120
121func TestComplexDescriptorsFromScratch(t *testing.T) {
122	mdEmpty, err := desc.LoadMessageDescriptorForMessage((*empty.Empty)(nil))
123	testutil.Ok(t, err)
124	mdAny, err := desc.LoadMessageDescriptorForMessage((*any.Any)(nil))
125	testutil.Ok(t, err)
126	mdTimestamp, err := desc.LoadMessageDescriptorForMessage((*timestamp.Timestamp)(nil))
127	testutil.Ok(t, err)
128
129	msgA := NewMessage("FooA").
130		AddField(NewField("id", FieldTypeUInt64())).
131		AddField(NewField("when", FieldTypeImportedMessage(mdTimestamp))).
132		AddField(NewField("extras", FieldTypeImportedMessage(mdAny)).
133			SetRepeated()).
134		SetExtensionRanges([]*dpb.DescriptorProto_ExtensionRange{{Start: proto.Int32(100), End: proto.Int32(201)}})
135	msgA2 := NewMessage("Nnn").
136		AddField(NewField("uid1", FieldTypeFixed64())).
137		AddField(NewField("uid2", FieldTypeFixed64()))
138	NewFile("").
139		SetPackageName("foo.bar").
140		AddMessage(msgA).
141		AddMessage(msgA2)
142
143	msgB := NewMessage("FooB").
144		AddField(NewField("foo_a", FieldTypeMessage(msgA)).
145			SetRepeated()).
146		AddField(NewField("name", FieldTypeString()))
147	NewFile("").
148		SetPackageName("foo.bar").
149		AddMessage(msgB)
150
151	enC := NewEnum("Vals").
152		AddValue(NewEnumValue("DEFAULT")).
153		AddValue(NewEnumValue("VALUE_A")).
154		AddValue(NewEnumValue("VALUE_B")).
155		AddValue(NewEnumValue("VALUE_C"))
156	msgC := NewMessage("BarBaz").
157		AddOneOf(NewOneOf("bbb").
158			AddChoice(NewField("b1", FieldTypeMessage(msgA))).
159			AddChoice(NewField("b2", FieldTypeMessage(msgB)))).
160		AddField(NewField("v", FieldTypeEnum(enC)))
161	NewFile("some/path/file.proto").
162		SetPackageName("foo.baz").
163		AddEnum(enC).
164		AddMessage(msgC)
165
166	enD := NewEnum("Ppp").
167		AddValue(NewEnumValue("P0")).
168		AddValue(NewEnumValue("P1")).
169		AddValue(NewEnumValue("P2")).
170		AddValue(NewEnumValue("P3"))
171	exD := NewExtension("ppp", 123, FieldTypeEnum(enD), msgA)
172	NewFile("some/other/path/file.proto").
173		SetPackageName("foo.biz").
174		AddEnum(enD).
175		AddExtension(exD)
176
177	msgE := NewMessage("Ppp").
178		AddField(NewField("p", FieldTypeEnum(enD))).
179		AddField(NewField("n", FieldTypeMessage(msgA2)))
180	fd, err := NewFile("").
181		SetPackageName("foo.bar").
182		AddMessage(msgE).
183		AddService(NewService("PppSvc").
184			AddMethod(NewMethod("Method1", RpcTypeMessage(msgE, false), RpcTypeImportedMessage(mdEmpty, false))).
185			AddMethod(NewMethod("Method2", RpcTypeMessage(msgB, false), RpcTypeMessage(msgC, true)))).
186		Build()
187
188	testutil.Ok(t, err)
189
190	testutil.Eq(t, 5, len(fd.GetDependencies()))
191	// dependencies sorted; those with generated names come last
192	depEmpty := fd.GetDependencies()[0]
193	testutil.Eq(t, "google/protobuf/empty.proto", depEmpty.GetName())
194	testutil.Eq(t, mdEmpty.GetFile(), depEmpty)
195	depD := fd.GetDependencies()[1]
196	testutil.Eq(t, "some/other/path/file.proto", depD.GetName())
197	depC := fd.GetDependencies()[2]
198	testutil.Eq(t, "some/path/file.proto", depC.GetName())
199	depA := fd.GetDependencies()[3]
200	testutil.Require(t, strings.Contains(depA.GetName(), "generated"))
201	depB := fd.GetDependencies()[4]
202	testutil.Require(t, strings.Contains(depB.GetName(), "generated"))
203
204	// check contents of files
205	testutil.Require(t, depA.FindMessage("foo.bar.FooA") != nil)
206	testutil.Eq(t, 3, len(depA.FindMessage("foo.bar.FooA").GetFields()))
207	testutil.Require(t, depA.FindMessage("foo.bar.Nnn") != nil)
208	testutil.Eq(t, 2, len(depA.FindMessage("foo.bar.Nnn").GetFields()))
209
210	testutil.Require(t, depB.FindMessage("foo.bar.FooB") != nil)
211	testutil.Eq(t, 2, len(depB.FindMessage("foo.bar.FooB").GetFields()))
212
213	testutil.Require(t, depC.FindMessage("foo.baz.BarBaz") != nil)
214	testutil.Eq(t, 3, len(depC.FindMessage("foo.baz.BarBaz").GetFields()))
215	testutil.Require(t, depC.FindEnum("foo.baz.Vals") != nil)
216	testutil.Eq(t, 4, len(depC.FindEnum("foo.baz.Vals").GetValues()))
217
218	testutil.Require(t, depD.FindEnum("foo.biz.Ppp") != nil)
219	testutil.Eq(t, 4, len(depD.FindEnum("foo.biz.Ppp").GetValues()))
220	testutil.Require(t, depD.FindExtensionByName("foo.biz.ppp") != nil)
221
222	testutil.Require(t, fd.FindMessage("foo.bar.Ppp") != nil)
223	testutil.Eq(t, 2, len(fd.FindMessage("foo.bar.Ppp").GetFields()))
224	testutil.Require(t, fd.FindService("foo.bar.PppSvc") != nil)
225	testutil.Eq(t, 2, len(fd.FindService("foo.bar.PppSvc").GetMethods()))
226}
227
228func TestCreatingGroupField(t *testing.T) {
229	grpMb := NewMessage("GroupA").
230		AddField(NewField("name", FieldTypeString())).
231		AddField(NewField("id", FieldTypeInt64()))
232	grpFlb := NewGroupField(grpMb)
233
234	mb := NewMessage("TestMessage").
235		AddField(NewField("foo", FieldTypeBool())).
236		AddField(grpFlb)
237	md, err := mb.Build()
238	testutil.Ok(t, err)
239
240	testutil.Require(t, md.FindFieldByName("groupa") != nil)
241	testutil.Eq(t, dpb.FieldDescriptorProto_TYPE_GROUP, md.FindFieldByName("groupa").GetType())
242	nmd := md.GetNestedMessageTypes()[0]
243	testutil.Eq(t, "GroupA", nmd.GetName())
244	testutil.Eq(t, nmd, md.FindFieldByName("groupa").GetMessageType())
245
246	// try a rename that will fail
247	err = grpMb.TrySetName("fooBarBaz")
248	testutil.Require(t, err != nil)
249	testutil.Eq(t, "group name fooBarBaz must start with capital letter", err.Error())
250	// failed rename should not have modified any state
251	md2, err := mb.Build()
252	testutil.Ok(t, err)
253	testutil.Require(t, proto.Equal(md.AsProto(), md2.AsProto()))
254	// another attempt that will fail
255	err = grpFlb.TrySetName("foobarbaz")
256	testutil.Require(t, err != nil)
257	testutil.Eq(t, "cannot change name of group field TestMessage.groupa; change name of group instead", err.Error())
258	// again, no state should have been modified
259	md2, err = mb.Build()
260	testutil.Ok(t, err)
261	testutil.Require(t, proto.Equal(md.AsProto(), md2.AsProto()))
262
263	// and a rename that succeeds
264	err = grpMb.TrySetName("FooBarBaz")
265	testutil.Ok(t, err)
266	md, err = mb.Build()
267	testutil.Ok(t, err)
268
269	// field also renamed
270	testutil.Require(t, md.FindFieldByName("foobarbaz") != nil)
271	testutil.Eq(t, dpb.FieldDescriptorProto_TYPE_GROUP, md.FindFieldByName("foobarbaz").GetType())
272	nmd = md.GetNestedMessageTypes()[0]
273	testutil.Eq(t, "FooBarBaz", nmd.GetName())
274	testutil.Eq(t, nmd, md.FindFieldByName("foobarbaz").GetMessageType())
275}
276
277func TestCreatingMapField(t *testing.T) {
278	mapFlb := NewMapField("countsByName", FieldTypeString(), FieldTypeUInt64())
279	testutil.Require(t, mapFlb.IsMap())
280
281	mb := NewMessage("TestMessage").
282		AddField(NewField("foo", FieldTypeBool())).
283		AddField(mapFlb)
284	md, err := mb.Build()
285	testutil.Ok(t, err)
286
287	testutil.Require(t, md.FindFieldByName("countsByName") != nil)
288	testutil.Require(t, md.FindFieldByName("countsByName").IsMap())
289	nmd := md.GetNestedMessageTypes()[0]
290	testutil.Eq(t, "CountsByNameEntry", nmd.GetName())
291	testutil.Eq(t, nmd, md.FindFieldByName("countsByName").GetMessageType())
292
293	// try a rename that will fail
294	err = mapFlb.GetType().localMsgType.TrySetName("fooBarBaz")
295	testutil.Require(t, err != nil)
296	testutil.Eq(t, "cannot change name of map entry TestMessage.CountsByNameEntry; change name of field instead", err.Error())
297	// failed rename should not have modified any state
298	md2, err := mb.Build()
299	testutil.Ok(t, err)
300	testutil.Require(t, proto.Equal(md.AsProto(), md2.AsProto()))
301
302	// and a rename that succeeds
303	err = mapFlb.TrySetName("fooBarBaz")
304	testutil.Ok(t, err)
305	md, err = mb.Build()
306	testutil.Ok(t, err)
307
308	// map entry also renamed
309	testutil.Require(t, md.FindFieldByName("fooBarBaz") != nil)
310	testutil.Require(t, md.FindFieldByName("fooBarBaz").IsMap())
311	nmd = md.GetNestedMessageTypes()[0]
312	testutil.Eq(t, "FooBarBazEntry", nmd.GetName())
313	testutil.Eq(t, nmd, md.FindFieldByName("fooBarBaz").GetMessageType())
314}
315
316func TestBuildersFromDescriptors(t *testing.T) {
317	for _, s := range []string{"desc_test1.proto", "desc_test2.proto", "desc_test_defaults.proto", "desc_test_options.proto", "desc_test_proto3.proto", "desc_test_wellknowntypes.proto", "nopkg/desc_test_nopkg.proto", "nopkg/desc_test_nopkg_new.proto", "pkg/desc_test_pkg.proto"} {
318		fd, err := desc.LoadFileDescriptor(s)
319		testutil.Ok(t, err)
320		roundTripFile(t, fd)
321	}
322}
323
324func TestBuildersFromDescriptors_PreserveComments(t *testing.T) {
325	fd, err := loadProtoset("../../internal/testprotos/desc_test1.protoset")
326	testutil.Ok(t, err)
327
328	fb, err := FromFile(fd)
329	testutil.Ok(t, err)
330
331	count := 0
332	var checkBuilderComments func(b Builder)
333	checkBuilderComments = func(b Builder) {
334		hasComment := true
335		switch b := b.(type) {
336		case *FileBuilder:
337			hasComment = false
338		case *FieldBuilder:
339			// comments for groups are on the message, not the field
340			hasComment = b.GetType().GetType() != dpb.FieldDescriptorProto_TYPE_GROUP
341		case *MessageBuilder:
342			// comments for maps are on the field, not the entry message
343			if b.Options.GetMapEntry() {
344				// we just return to also skip checking child elements
345				// (map entry child elements are synthetic and have no comments)
346				return
347			}
348		}
349
350		if hasComment {
351			count++
352			testutil.Eq(t, fmt.Sprintf(" Comment for %s\n", b.GetName()), b.GetComments().LeadingComment,
353				"wrong comment for builder %s", GetFullyQualifiedName(b))
354		}
355		for _, ch := range b.GetChildren() {
356			checkBuilderComments(ch)
357		}
358	}
359
360	checkBuilderComments(fb)
361	// sanity check that we didn't accidentally short-circuit above and fail to check comments
362	testutil.Require(t, count > 30, "too few elements checked")
363
364	// now check that they also come out in the resulting descriptor
365	fd, err = fb.Build()
366	testutil.Ok(t, err)
367
368	descCount := 0
369	var checkDescriptorComments func(d desc.Descriptor)
370	checkDescriptorComments = func(d desc.Descriptor) {
371		switch d := d.(type) {
372		case *desc.FileDescriptor:
373			for _, ch := range d.GetMessageTypes() {
374				checkDescriptorComments(ch)
375			}
376			for _, ch := range d.GetEnumTypes() {
377				checkDescriptorComments(ch)
378			}
379			for _, ch := range d.GetExtensions() {
380				checkDescriptorComments(ch)
381			}
382			for _, ch := range d.GetServices() {
383				checkDescriptorComments(ch)
384			}
385			// files don't have comments, so bail out before check below
386			return
387		case *desc.MessageDescriptor:
388			if d.IsMapEntry() {
389				// map entry messages have no comments (and neither do their child fields)
390				return
391			}
392			for _, ch := range d.GetFields() {
393				checkDescriptorComments(ch)
394			}
395			for _, ch := range d.GetNestedMessageTypes() {
396				checkDescriptorComments(ch)
397			}
398			for _, ch := range d.GetNestedEnumTypes() {
399				checkDescriptorComments(ch)
400			}
401			for _, ch := range d.GetNestedExtensions() {
402				checkDescriptorComments(ch)
403			}
404			for _, ch := range d.GetOneOfs() {
405				checkDescriptorComments(ch)
406			}
407		case *desc.FieldDescriptor:
408			if d.GetType() == dpb.FieldDescriptorProto_TYPE_GROUP {
409				// groups comments are on the message, not hte field; so bail out before check below
410				return
411			}
412		case *desc.EnumDescriptor:
413			for _, ch := range d.GetValues() {
414				checkDescriptorComments(ch)
415			}
416		case *desc.ServiceDescriptor:
417			for _, ch := range d.GetMethods() {
418				checkDescriptorComments(ch)
419			}
420		}
421
422		descCount++
423		testutil.Eq(t, fmt.Sprintf(" Comment for %s\n", d.GetName()), d.GetSourceInfo().GetLeadingComments(),
424			"wrong comment for descriptor %s", d.GetFullyQualifiedName())
425	}
426
427	checkDescriptorComments(fd)
428	testutil.Eq(t, count, descCount)
429}
430
431func loadProtoset(path string) (*desc.FileDescriptor, error) {
432	var fds dpb.FileDescriptorSet
433	f, err := os.Open(path)
434	if err != nil {
435		return nil, err
436	}
437	defer f.Close()
438	bb, err := ioutil.ReadAll(f)
439	if err != nil {
440		return nil, err
441	}
442	if err = proto.Unmarshal(bb, &fds); err != nil {
443		return nil, err
444	}
445	return desc.CreateFileDescriptorFromSet(&fds)
446}
447
448func roundTripFile(t *testing.T, fd *desc.FileDescriptor) {
449	// First, recursively verify that every child element can be converted to a
450	// Builder and back without loss of fidelity.
451	for _, md := range fd.GetMessageTypes() {
452		roundTripMessage(t, md)
453	}
454	for _, ed := range fd.GetEnumTypes() {
455		roundTripEnum(t, ed)
456	}
457	for _, exd := range fd.GetExtensions() {
458		roundTripField(t, exd)
459	}
460	for _, sd := range fd.GetServices() {
461		roundTripService(t, sd)
462	}
463
464	// Finally, we check the whole file itself.
465	fb, err := FromFile(fd)
466	testutil.Ok(t, err)
467
468	roundTripped, err := fb.Build()
469	testutil.Ok(t, err)
470
471	// Round tripping from a file descriptor to a builder and back will
472	// experience some minor changes (that do not impact the semantics of
473	// any of the file's contents):
474	//  1. The builder sorts dependencies. However the original file
475	//     descriptor has dependencies in the order they appear in import
476	//     statements in the source file.
477	//  2. The builder imports the actual source of all elements and never
478	//     uses public imports. The original file, on the other hand, could
479	//     used public imports and "indirectly" import other files that way.
480	//  3. The builder never emits weak imports.
481	//  4. The builder tries to preserve SourceCodeInfo, but will not preserve
482	//     position information. So that info does not survive round-tripping
483	//     (though comments do: there is a separate test for that). Also, the
484	//     round-tripped version will have source code info (even though it
485	//     may have no comments and zero position info), even if the original
486	//     descriptor had none.
487	// So we're going to modify the original descriptor in the same ways.
488	// That way, a simple proto.Equal() check will suffice to confirm that
489	// the file descriptor survived the round trip.
490
491	// The files we are testing have one occurrence of a public import. The
492	// file nopkg/desc_test_nopkg.proto declares nothing and public imports
493	// nopkg/desc_test_nopkg_new.proto. So any file that depends on the
494	// former will be updated to instead depend on the latter (since it is
495	// the actual file that declares used elements).
496	fdp := fd.AsFileDescriptorProto()
497	needsNopkgNew := false
498	hasNoPkgNew := false
499	for _, dep := range fdp.Dependency {
500		if dep == "nopkg/desc_test_nopkg.proto" {
501			needsNopkgNew = true
502		}
503		if dep == "nopkg/desc_test_nopkg_new.proto" {
504			hasNoPkgNew = false
505		}
506	}
507	if needsNopkgNew && !hasNoPkgNew {
508		fdp.Dependency = append(fdp.Dependency, "nopkg/desc_test_nopkg_new.proto")
509	}
510
511	// Strip any public and weak imports. (The step above should have "fixed"
512	// files to handle any actual public import encountered.)
513	fdp.PublicDependency = nil
514	fdp.WeakDependency = nil
515
516	// Remove source code info that the builder generated since the original
517	// has none.
518	roundTripped.AsFileDescriptorProto().SourceCodeInfo = nil
519
520	// Finally, sort the imports. That way they match the built result (which
521	// is always sorted).
522	sort.Strings(fdp.Dependency)
523
524	// Now (after tweaking) the original should match the round-tripped descriptor:
525	testutil.Require(t, proto.Equal(fdp, roundTripped.AsProto()), "File %q failed round trip.\nExpecting: %s\nGot: %s\n",
526		fd.GetName(), proto.MarshalTextString(fdp), proto.MarshalTextString(roundTripped.AsProto()))
527}
528
529func roundTripMessage(t *testing.T, md *desc.MessageDescriptor) {
530	// first recursively validate all nested elements
531	for _, fld := range md.GetFields() {
532		roundTripField(t, fld)
533	}
534	for _, ood := range md.GetOneOfs() {
535		oob, err := FromOneOf(ood)
536		testutil.Ok(t, err)
537		roundTripped, err := oob.Build()
538		testutil.Ok(t, err)
539		checkDescriptors(t, ood, roundTripped)
540	}
541	for _, nmd := range md.GetNestedMessageTypes() {
542		roundTripMessage(t, nmd)
543	}
544	for _, ed := range md.GetNestedEnumTypes() {
545		roundTripEnum(t, ed)
546	}
547	for _, exd := range md.GetNestedExtensions() {
548		roundTripField(t, exd)
549	}
550
551	mb, err := FromMessage(md)
552	testutil.Ok(t, err)
553	roundTripped, err := mb.Build()
554	testutil.Ok(t, err)
555	checkDescriptors(t, md, roundTripped)
556}
557
558func roundTripEnum(t *testing.T, ed *desc.EnumDescriptor) {
559	// first recursively validate all nested elements
560	for _, evd := range ed.GetValues() {
561		evb, err := FromEnumValue(evd)
562		testutil.Ok(t, err)
563		roundTripped, err := evb.Build()
564		testutil.Ok(t, err)
565		checkDescriptors(t, evd, roundTripped)
566	}
567
568	eb, err := FromEnum(ed)
569	testutil.Ok(t, err)
570	roundTripped, err := eb.Build()
571	testutil.Ok(t, err)
572	checkDescriptors(t, ed, roundTripped)
573}
574
575func roundTripField(t *testing.T, fld *desc.FieldDescriptor) {
576	flb, err := FromField(fld)
577	testutil.Ok(t, err)
578	roundTripped, err := flb.Build()
579	testutil.Ok(t, err)
580	checkDescriptors(t, fld, roundTripped)
581}
582
583func roundTripService(t *testing.T, sd *desc.ServiceDescriptor) {
584	// first recursively validate all nested elements
585	for _, mtd := range sd.GetMethods() {
586		mtb, err := FromMethod(mtd)
587		testutil.Ok(t, err)
588		roundTripped, err := mtb.Build()
589		testutil.Ok(t, err)
590		checkDescriptors(t, mtd, roundTripped)
591	}
592
593	sb, err := FromService(sd)
594	testutil.Ok(t, err)
595	roundTripped, err := sb.Build()
596	testutil.Ok(t, err)
597	checkDescriptors(t, sd, roundTripped)
598}
599
600func checkDescriptors(t *testing.T, d1, d2 desc.Descriptor) {
601	testutil.Eq(t, d1.GetFullyQualifiedName(), d2.GetFullyQualifiedName())
602	testutil.Require(t, proto.Equal(d1.AsProto(), d2.AsProto()), "%s failed round trip.\nExpecting: %s\nGot: %s\n",
603		d1.GetFullyQualifiedName(), proto.MarshalTextString(d1.AsProto()), proto.MarshalTextString(d2.AsProto()))
604}
605
606func TestAddRemoveMoveBuilders(t *testing.T) {
607	// add field to one-of
608	fld1 := NewField("foo", FieldTypeInt32())
609	oo1 := NewOneOf("oofoo")
610	oo1.AddChoice(fld1)
611	checkChildren(t, oo1, fld1)
612	testutil.Eq(t, oo1.GetChoice("foo"), fld1)
613
614	// add one-of w/ field to a message
615	msg1 := NewMessage("foo")
616	msg1.AddOneOf(oo1)
617	checkChildren(t, msg1, oo1)
618	testutil.Eq(t, msg1.GetOneOf("oofoo"), oo1)
619	// field remains unchanged
620	testutil.Eq(t, fld1.GetParent(), oo1)
621	testutil.Eq(t, oo1.GetChoice("foo"), fld1)
622	// field also now registered with msg1
623	testutil.Eq(t, msg1.GetField("foo"), fld1)
624
625	// add empty one-of to message
626	oo2 := NewOneOf("oobar")
627	msg1.AddOneOf(oo2)
628	checkChildren(t, msg1, oo1, oo2)
629	testutil.Eq(t, msg1.GetOneOf("oobar"), oo2)
630	// now add field to that one-of
631	fld2 := NewField("bar", FieldTypeInt32())
632	oo2.AddChoice(fld2)
633	checkChildren(t, oo2, fld2)
634	testutil.Eq(t, oo2.GetChoice("bar"), fld2)
635	// field also now registered with msg1
636	testutil.Eq(t, msg1.GetField("bar"), fld2)
637
638	// add fails due to name collisions
639	fld1 = NewField("foo", FieldTypeInt32())
640	err := oo1.TryAddChoice(fld1)
641	checkFailedAdd(t, err, oo1, fld1, "already contains field")
642	fld2 = NewField("bar", FieldTypeInt32())
643	err = msg1.TryAddField(fld2)
644	checkFailedAdd(t, err, msg1, fld2, "already contains element")
645	msg2 := NewMessage("oofoo")
646	// name collision can be different type
647	// (here, nested message conflicts with a one-of)
648	err = msg1.TryAddNestedMessage(msg2)
649	checkFailedAdd(t, err, msg1, msg2, "already contains element")
650
651	msg2 = NewMessage("baz")
652	msg1.AddNestedMessage(msg2)
653	checkChildren(t, msg1, oo1, oo2, msg2)
654	testutil.Eq(t, msg1.GetNestedMessage("baz"), msg2)
655
656	// can't add extension, group, or map fields to one-of
657	ext1 := NewExtension("abc", 123, FieldTypeInt32(), msg1)
658	err = oo1.TryAddChoice(ext1)
659	checkFailedAdd(t, err, oo1, ext1, "is an extension, not a regular field")
660	err = msg1.TryAddField(ext1)
661	checkFailedAdd(t, err, msg1, ext1, "is an extension, not a regular field")
662	mapField := NewMapField("abc", FieldTypeInt32(), FieldTypeString())
663	err = oo1.TryAddChoice(mapField)
664	checkFailedAdd(t, err, oo1, mapField, "cannot add a group or map field")
665	groupMsg := NewMessage("Group")
666	groupField := NewGroupField(groupMsg)
667	err = oo1.TryAddChoice(groupField)
668	checkFailedAdd(t, err, oo1, groupField, "cannot add a group or map field")
669	// adding map and group to msg succeeds
670	msg1.AddField(groupField)
671	msg1.AddField(mapField)
672	checkChildren(t, msg1, oo1, oo2, msg2, groupField, mapField)
673	// messages associated with map and group fields are not children of the
674	// message, but are in its scope and accessible via GetNestedMessage
675	testutil.Eq(t, msg1.GetNestedMessage("Group"), groupMsg)
676	testutil.Eq(t, msg1.GetNestedMessage("AbcEntry"), mapField.GetType().localMsgType)
677
678	// adding extension to message
679	ext2 := NewExtension("xyz", 234, FieldTypeInt32(), msg1)
680	msg1.AddNestedExtension(ext2)
681	checkChildren(t, msg1, oo1, oo2, msg2, groupField, mapField, ext2)
682	err = msg1.TryAddNestedExtension(ext1) // name collision
683	checkFailedAdd(t, err, msg1, ext1, "already contains element")
684	fld3 := NewField("ijk", FieldTypeString())
685	err = msg1.TryAddNestedExtension(fld3)
686	checkFailedAdd(t, err, msg1, fld3, "is not an extension")
687
688	// add enum values to enum
689	enumVal1 := NewEnumValue("A")
690	enum1 := NewEnum("bazel")
691	enum1.AddValue(enumVal1)
692	checkChildren(t, enum1, enumVal1)
693	testutil.Eq(t, enum1.GetValue("A"), enumVal1)
694	enumVal2 := NewEnumValue("B")
695	enum1.AddValue(enumVal2)
696	checkChildren(t, enum1, enumVal1, enumVal2)
697	testutil.Eq(t, enum1.GetValue("B"), enumVal2)
698	// fail w/ name collision
699	enumVal3 := NewEnumValue("B")
700	err = enum1.TryAddValue(enumVal3)
701	checkFailedAdd(t, err, enum1, enumVal3, "already contains value")
702
703	msg2.AddNestedEnum(enum1)
704	checkChildren(t, msg2, enum1)
705	testutil.Eq(t, msg2.GetNestedEnum("bazel"), enum1)
706	ext3 := NewExtension("bazel", 987, FieldTypeString(), msg2)
707	err = msg2.TryAddNestedExtension(ext3)
708	checkFailedAdd(t, err, msg2, ext3, "already contains element")
709
710	// services and methods
711	mtd1 := NewMethod("foo", RpcTypeMessage(msg1, false), RpcTypeMessage(msg1, false))
712	svc1 := NewService("FooService")
713	svc1.AddMethod(mtd1)
714	checkChildren(t, svc1, mtd1)
715	testutil.Eq(t, svc1.GetMethod("foo"), mtd1)
716	mtd2 := NewMethod("foo", RpcTypeMessage(msg1, false), RpcTypeMessage(msg1, false))
717	err = svc1.TryAddMethod(mtd2)
718	checkFailedAdd(t, err, svc1, mtd2, "already contains method")
719
720	// finally, test adding things to  a file
721	fb := NewFile("")
722	fb.AddMessage(msg1)
723	checkChildren(t, fb, msg1)
724	testutil.Eq(t, fb.GetMessage("foo"), msg1)
725	fb.AddService(svc1)
726	checkChildren(t, fb, msg1, svc1)
727	testutil.Eq(t, fb.GetService("FooService"), svc1)
728	enum2 := NewEnum("fizzle")
729	fb.AddEnum(enum2)
730	checkChildren(t, fb, msg1, svc1, enum2)
731	testutil.Eq(t, fb.GetEnum("fizzle"), enum2)
732	ext3 = NewExtension("foosball", 123, FieldTypeInt32(), msg1)
733	fb.AddExtension(ext3)
734	checkChildren(t, fb, msg1, svc1, enum2, ext3)
735	testutil.Eq(t, fb.GetExtension("foosball"), ext3)
736
737	// errors and name collisions
738	err = fb.TryAddExtension(fld3)
739	checkFailedAdd(t, err, fb, fld3, "is not an extension")
740	msg3 := NewMessage("fizzle")
741	err = fb.TryAddMessage(msg3)
742	checkFailedAdd(t, err, fb, msg3, "already contains element")
743	enum3 := NewEnum("foosball")
744	err = fb.TryAddEnum(enum3)
745	checkFailedAdd(t, err, fb, enum3, "already contains element")
746
747	// TODO: test moving and removing, too
748}
749
750func checkChildren(t *testing.T, parent Builder, children ...Builder) {
751	testutil.Eq(t, len(children), len(parent.GetChildren()), "Wrong number of children for %s (%T)", GetFullyQualifiedName(parent), parent)
752	ch := map[Builder]struct{}{}
753	for _, child := range children {
754		testutil.Eq(t, child.GetParent(), parent, "Child %s (%T) does not report %s (%T) as its parent", child.GetName(), child, GetFullyQualifiedName(parent), parent)
755		ch[child] = struct{}{}
756	}
757	for _, child := range parent.GetChildren() {
758		_, ok := ch[child]
759		testutil.Require(t, ok, "Child %s (%T) does appear in list of children for %s (%T)", child.GetName(), child, GetFullyQualifiedName(parent), parent)
760	}
761}
762
763func checkFailedAdd(t *testing.T, err error, parent Builder, child Builder, errorMsg string) {
764	testutil.Require(t, err != nil, "Expecting error assigning %s (%T) to %s (%T)", child.GetName(), child, GetFullyQualifiedName(parent), parent)
765	testutil.Require(t, strings.Contains(err.Error(), errorMsg), "Expecting error assigning %s (%T) to %s (%T) to contain text %q: %q", child.GetName(), child, GetFullyQualifiedName(parent), parent, errorMsg, err.Error())
766	testutil.Eq(t, nil, child.GetParent(), "Child %s (%T) should not have a parent after failed add", child.GetName(), child)
767	for _, ch := range parent.GetChildren() {
768		testutil.Require(t, ch != child, "Child %s (%T) should not appear in list of children for %s (%T) but does", child.GetName(), child, GetFullyQualifiedName(parent), parent)
769	}
770}
771
772func TestRenamingBuilders(t *testing.T) {
773	// TODO
774}
775
776func TestRenumberingFields(t *testing.T) {
777	// TODO
778}
779
780var (
781	fileOptionsDesc, msgOptionsDesc, fieldOptionsDesc, oneofOptionsDesc, extRangeOptionsDesc,
782	enumOptionsDesc, enumValOptionsDesc, svcOptionsDesc, mtdOptionsDesc *desc.MessageDescriptor
783)
784
785func init() {
786	var err error
787	fileOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*dpb.FileOptions)(nil))
788	if err != nil {
789		panic(err)
790	}
791	msgOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*dpb.MessageOptions)(nil))
792	if err != nil {
793		panic(err)
794	}
795	fieldOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*dpb.FieldOptions)(nil))
796	if err != nil {
797		panic(err)
798	}
799	oneofOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*dpb.OneofOptions)(nil))
800	if err != nil {
801		panic(err)
802	}
803	extRangeOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*dpb.ExtensionRangeOptions)(nil))
804	if err != nil {
805		panic(err)
806	}
807	enumOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*dpb.EnumOptions)(nil))
808	if err != nil {
809		panic(err)
810	}
811	enumValOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*dpb.EnumValueOptions)(nil))
812	if err != nil {
813		panic(err)
814	}
815	svcOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*dpb.ServiceOptions)(nil))
816	if err != nil {
817		panic(err)
818	}
819	mtdOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*dpb.MethodOptions)(nil))
820	if err != nil {
821		panic(err)
822	}
823}
824
825func TestCustomOptionsDiscoveredInSameFile(t *testing.T) {
826	// Add option for every type to file
827	file := NewFile("foo.proto")
828
829	fileOpt := NewExtensionImported("file_foo", 54321, FieldTypeString(), fileOptionsDesc)
830	file.AddExtension(fileOpt)
831
832	msgOpt := NewExtensionImported("msg_foo", 54321, FieldTypeString(), msgOptionsDesc)
833	file.AddExtension(msgOpt)
834
835	fieldOpt := NewExtensionImported("field_foo", 54321, FieldTypeString(), fieldOptionsDesc)
836	file.AddExtension(fieldOpt)
837
838	oneofOpt := NewExtensionImported("oneof_foo", 54321, FieldTypeString(), oneofOptionsDesc)
839	file.AddExtension(oneofOpt)
840
841	extRangeOpt := NewExtensionImported("ext_range_foo", 54321, FieldTypeString(), extRangeOptionsDesc)
842	file.AddExtension(extRangeOpt)
843
844	enumOpt := NewExtensionImported("enum_foo", 54321, FieldTypeString(), enumOptionsDesc)
845	file.AddExtension(enumOpt)
846
847	enumValOpt := NewExtensionImported("enum_val_foo", 54321, FieldTypeString(), enumValOptionsDesc)
848	file.AddExtension(enumValOpt)
849
850	svcOpt := NewExtensionImported("svc_foo", 54321, FieldTypeString(), svcOptionsDesc)
851	file.AddExtension(svcOpt)
852
853	mtdOpt := NewExtensionImported("mtd_foo", 54321, FieldTypeString(), mtdOptionsDesc)
854	file.AddExtension(mtdOpt)
855
856	// Now we can test referring to these and making sure they show up correctly
857	// in built descriptors
858
859	t.Run("file options", func(t *testing.T) {
860		fb := clone(t, file)
861		fb.Options = &dpb.FileOptions{}
862		ext, err := fileOpt.Build()
863		testutil.Ok(t, err)
864		err = dynamic.SetExtension(fb.Options, ext, "fubar")
865		testutil.Ok(t, err)
866		checkBuildWithLocalExtensions(t, fb)
867	})
868
869	t.Run("message options", func(t *testing.T) {
870		mb := NewMessage("Foo")
871		mb.Options = &dpb.MessageOptions{}
872		ext, err := msgOpt.Build()
873		testutil.Ok(t, err)
874		err = dynamic.SetExtension(mb.Options, ext, "fubar")
875		testutil.Ok(t, err)
876
877		fb := clone(t, file)
878		fb.AddMessage(mb)
879		checkBuildWithLocalExtensions(t, mb)
880	})
881
882	t.Run("field options", func(t *testing.T) {
883		flb := NewField("foo", FieldTypeString())
884		flb.Options = &dpb.FieldOptions{}
885		// fields must be connected to a message
886		mb := NewMessage("Foo").AddField(flb)
887		ext, err := fieldOpt.Build()
888		testutil.Ok(t, err)
889		err = dynamic.SetExtension(flb.Options, ext, "fubar")
890		testutil.Ok(t, err)
891
892		fb := clone(t, file)
893		fb.AddMessage(mb)
894		checkBuildWithLocalExtensions(t, flb)
895	})
896
897	t.Run("oneof options", func(t *testing.T) {
898		oob := NewOneOf("oo")
899		oob.Options = &dpb.OneofOptions{}
900		// oneofs must be connected to a message
901		mb := NewMessage("Foo").AddOneOf(oob)
902		ext, err := oneofOpt.Build()
903		testutil.Ok(t, err)
904		err = dynamic.SetExtension(oob.Options, ext, "fubar")
905		testutil.Ok(t, err)
906
907		fb := clone(t, file)
908		fb.AddMessage(mb)
909		checkBuildWithLocalExtensions(t, oob)
910	})
911
912	t.Run("extension range options", func(t *testing.T) {
913		var erOpts dpb.ExtensionRangeOptions
914		ext, err := extRangeOpt.Build()
915		testutil.Ok(t, err)
916		err = dynamic.SetExtension(&erOpts, ext, "fubar")
917		testutil.Ok(t, err)
918		mb := NewMessage("foo").AddExtensionRangeWithOptions(100, 200, &erOpts)
919
920		fb := clone(t, file)
921		fb.AddMessage(mb)
922		checkBuildWithLocalExtensions(t, mb)
923	})
924
925	t.Run("enum options", func(t *testing.T) {
926		eb := NewEnum("Foo")
927		eb.Options = &dpb.EnumOptions{}
928		ext, err := enumOpt.Build()
929		testutil.Ok(t, err)
930		err = dynamic.SetExtension(eb.Options, ext, "fubar")
931		testutil.Ok(t, err)
932
933		fb := clone(t, file)
934		fb.AddEnum(eb)
935		checkBuildWithLocalExtensions(t, eb)
936	})
937
938	t.Run("enum val options", func(t *testing.T) {
939		evb := NewEnumValue("FOO")
940		// enum values must be connected to an enum
941		eb := NewEnum("Foo").AddValue(evb)
942		evb.Options = &dpb.EnumValueOptions{}
943		ext, err := enumValOpt.Build()
944		testutil.Ok(t, err)
945		err = dynamic.SetExtension(evb.Options, ext, "fubar")
946		testutil.Ok(t, err)
947
948		fb := clone(t, file)
949		fb.AddEnum(eb)
950		checkBuildWithLocalExtensions(t, evb)
951	})
952
953	t.Run("service options", func(t *testing.T) {
954		sb := NewService("Foo")
955		sb.Options = &dpb.ServiceOptions{}
956		ext, err := svcOpt.Build()
957		testutil.Ok(t, err)
958		err = dynamic.SetExtension(sb.Options, ext, "fubar")
959		testutil.Ok(t, err)
960
961		fb := clone(t, file)
962		fb.AddService(sb)
963		checkBuildWithLocalExtensions(t, sb)
964	})
965
966	t.Run("method options", func(t *testing.T) {
967		req := NewMessage("Request")
968		resp := NewMessage("Response")
969		mtb := NewMethod("Foo",
970			RpcTypeMessage(req, false),
971			RpcTypeMessage(resp, false))
972		// methods must be connected to a service
973		sb := NewService("Bar").AddMethod(mtb)
974		mtb.Options = &dpb.MethodOptions{}
975		ext, err := mtdOpt.Build()
976		testutil.Ok(t, err)
977		err = dynamic.SetExtension(mtb.Options, ext, "fubar")
978		testutil.Ok(t, err)
979
980		fb := clone(t, file)
981		fb.AddService(sb).AddMessage(req).AddMessage(resp)
982		checkBuildWithLocalExtensions(t, mtb)
983	})
984}
985
986func checkBuildWithLocalExtensions(t *testing.T, builder Builder) {
987	// requiring options and succeeding (since they are defined locally)
988	var opts BuilderOptions
989	opts.RequireInterpretedOptions = true
990	d, err := opts.Build(builder)
991	testutil.Ok(t, err)
992	// since they are defined locally, no extra imports
993	testutil.Eq(t, []string{"google/protobuf/descriptor.proto"}, d.GetFile().AsFileDescriptorProto().GetDependency())
994}
995
996func TestCustomOptionsDiscoveredInDependencies(t *testing.T) {
997	// Add option for every type to file
998	file := NewFile("options.proto")
999
1000	fileOpt := NewExtensionImported("file_foo", 54321, FieldTypeString(), fileOptionsDesc)
1001	file.AddExtension(fileOpt)
1002
1003	msgOpt := NewExtensionImported("msg_foo", 54321, FieldTypeString(), msgOptionsDesc)
1004	file.AddExtension(msgOpt)
1005
1006	fieldOpt := NewExtensionImported("field_foo", 54321, FieldTypeString(), fieldOptionsDesc)
1007	file.AddExtension(fieldOpt)
1008
1009	oneofOpt := NewExtensionImported("oneof_foo", 54321, FieldTypeString(), oneofOptionsDesc)
1010	file.AddExtension(oneofOpt)
1011
1012	extRangeOpt := NewExtensionImported("ext_range_foo", 54321, FieldTypeString(), extRangeOptionsDesc)
1013	file.AddExtension(extRangeOpt)
1014
1015	enumOpt := NewExtensionImported("enum_foo", 54321, FieldTypeString(), enumOptionsDesc)
1016	file.AddExtension(enumOpt)
1017
1018	enumValOpt := NewExtensionImported("enum_val_foo", 54321, FieldTypeString(), enumValOptionsDesc)
1019	file.AddExtension(enumValOpt)
1020
1021	svcOpt := NewExtensionImported("svc_foo", 54321, FieldTypeString(), svcOptionsDesc)
1022	file.AddExtension(svcOpt)
1023
1024	mtdOpt := NewExtensionImported("mtd_foo", 54321, FieldTypeString(), mtdOptionsDesc)
1025	file.AddExtension(mtdOpt)
1026
1027	fileDesc, err := file.Build()
1028	testutil.Ok(t, err)
1029
1030	// Now we can test referring to these and making sure they show up correctly
1031	// in built descriptors
1032	for name, useBuilder := range map[string]bool{"descriptor": false, "builder": true} {
1033		newFile := func() *FileBuilder {
1034			fb := NewFile("foo.proto")
1035			if useBuilder {
1036				fb.AddDependency(file)
1037			} else {
1038				fb.AddImportedDependency(fileDesc)
1039			}
1040			return fb
1041		}
1042		t.Run(name, func(t *testing.T) {
1043			t.Run("file options", func(t *testing.T) {
1044				fb := newFile()
1045				fb.Options = &dpb.FileOptions{}
1046				ext, err := fileOpt.Build()
1047				testutil.Ok(t, err)
1048				err = dynamic.SetExtension(fb.Options, ext, "fubar")
1049				testutil.Ok(t, err)
1050				checkBuildWithImportedExtensions(t, fb)
1051			})
1052
1053			t.Run("message options", func(t *testing.T) {
1054				mb := NewMessage("Foo")
1055				mb.Options = &dpb.MessageOptions{}
1056				ext, err := msgOpt.Build()
1057				testutil.Ok(t, err)
1058				err = dynamic.SetExtension(mb.Options, ext, "fubar")
1059				testutil.Ok(t, err)
1060
1061				fb := newFile()
1062				fb.AddMessage(mb)
1063				checkBuildWithImportedExtensions(t, mb)
1064			})
1065
1066			t.Run("field options", func(t *testing.T) {
1067				flb := NewField("foo", FieldTypeString())
1068				flb.Options = &dpb.FieldOptions{}
1069				// fields must be connected to a message
1070				mb := NewMessage("Foo").AddField(flb)
1071				ext, err := fieldOpt.Build()
1072				testutil.Ok(t, err)
1073				err = dynamic.SetExtension(flb.Options, ext, "fubar")
1074				testutil.Ok(t, err)
1075
1076				fb := newFile()
1077				fb.AddMessage(mb)
1078				checkBuildWithImportedExtensions(t, flb)
1079			})
1080
1081			t.Run("oneof options", func(t *testing.T) {
1082				oob := NewOneOf("oo")
1083				oob.Options = &dpb.OneofOptions{}
1084				// oneofs must be connected to a message
1085				mb := NewMessage("Foo").AddOneOf(oob)
1086				ext, err := oneofOpt.Build()
1087				testutil.Ok(t, err)
1088				err = dynamic.SetExtension(oob.Options, ext, "fubar")
1089				testutil.Ok(t, err)
1090
1091				fb := newFile()
1092				fb.AddMessage(mb)
1093				checkBuildWithImportedExtensions(t, oob)
1094			})
1095
1096			t.Run("extension range options", func(t *testing.T) {
1097				var erOpts dpb.ExtensionRangeOptions
1098				ext, err := extRangeOpt.Build()
1099				testutil.Ok(t, err)
1100				err = dynamic.SetExtension(&erOpts, ext, "fubar")
1101				testutil.Ok(t, err)
1102				mb := NewMessage("foo").AddExtensionRangeWithOptions(100, 200, &erOpts)
1103
1104				fb := newFile()
1105				fb.AddMessage(mb)
1106				checkBuildWithImportedExtensions(t, mb)
1107			})
1108
1109			t.Run("enum options", func(t *testing.T) {
1110				eb := NewEnum("Foo")
1111				eb.Options = &dpb.EnumOptions{}
1112				ext, err := enumOpt.Build()
1113				testutil.Ok(t, err)
1114				err = dynamic.SetExtension(eb.Options, ext, "fubar")
1115				testutil.Ok(t, err)
1116
1117				fb := newFile()
1118				fb.AddEnum(eb)
1119				checkBuildWithImportedExtensions(t, eb)
1120			})
1121
1122			t.Run("enum val options", func(t *testing.T) {
1123				evb := NewEnumValue("FOO")
1124				// enum values must be connected to an enum
1125				eb := NewEnum("Foo").AddValue(evb)
1126				evb.Options = &dpb.EnumValueOptions{}
1127				ext, err := enumValOpt.Build()
1128				testutil.Ok(t, err)
1129				err = dynamic.SetExtension(evb.Options, ext, "fubar")
1130				testutil.Ok(t, err)
1131
1132				fb := newFile()
1133				fb.AddEnum(eb)
1134				checkBuildWithImportedExtensions(t, evb)
1135			})
1136
1137			t.Run("service options", func(t *testing.T) {
1138				sb := NewService("Foo")
1139				sb.Options = &dpb.ServiceOptions{}
1140				ext, err := svcOpt.Build()
1141				testutil.Ok(t, err)
1142				err = dynamic.SetExtension(sb.Options, ext, "fubar")
1143				testutil.Ok(t, err)
1144
1145				fb := newFile()
1146				fb.AddService(sb)
1147				checkBuildWithImportedExtensions(t, sb)
1148			})
1149
1150			t.Run("method options", func(t *testing.T) {
1151				req := NewMessage("Request")
1152				resp := NewMessage("Response")
1153				mtb := NewMethod("Foo",
1154					RpcTypeMessage(req, false),
1155					RpcTypeMessage(resp, false))
1156				// methods must be connected to a service
1157				sb := NewService("Bar").AddMethod(mtb)
1158				mtb.Options = &dpb.MethodOptions{}
1159				ext, err := mtdOpt.Build()
1160				testutil.Ok(t, err)
1161				err = dynamic.SetExtension(mtb.Options, ext, "fubar")
1162				testutil.Ok(t, err)
1163
1164				fb := newFile()
1165				fb.AddService(sb).AddMessage(req).AddMessage(resp)
1166				checkBuildWithImportedExtensions(t, mtb)
1167			})
1168		})
1169	}
1170}
1171
1172func checkBuildWithImportedExtensions(t *testing.T, builder Builder) {
1173	// requiring options and succeeding (since they are defined in explicit import)
1174	var opts BuilderOptions
1175	opts.RequireInterpretedOptions = true
1176	d, err := opts.Build(builder)
1177	testutil.Ok(t, err)
1178	// the only import is for the custom options
1179	testutil.Eq(t, []string{"options.proto"}, d.GetFile().AsFileDescriptorProto().GetDependency())
1180}
1181
1182func TestUseOfExtensionRegistry(t *testing.T) {
1183	// Add option for every type to extension registry
1184	var exts dynamic.ExtensionRegistry
1185
1186	fileOpt, err := NewExtensionImported("file_foo", 54321, FieldTypeString(), fileOptionsDesc).Build()
1187	testutil.Ok(t, err)
1188	err = exts.AddExtension(fileOpt)
1189	testutil.Ok(t, err)
1190
1191	msgOpt, err := NewExtensionImported("msg_foo", 54321, FieldTypeString(), msgOptionsDesc).Build()
1192	testutil.Ok(t, err)
1193	err = exts.AddExtension(msgOpt)
1194	testutil.Ok(t, err)
1195
1196	fieldOpt, err := NewExtensionImported("field_foo", 54321, FieldTypeString(), fieldOptionsDesc).Build()
1197	testutil.Ok(t, err)
1198	err = exts.AddExtension(fieldOpt)
1199	testutil.Ok(t, err)
1200
1201	oneofOpt, err := NewExtensionImported("oneof_foo", 54321, FieldTypeString(), oneofOptionsDesc).Build()
1202	testutil.Ok(t, err)
1203	err = exts.AddExtension(oneofOpt)
1204	testutil.Ok(t, err)
1205
1206	extRangeOpt, err := NewExtensionImported("ext_range_foo", 54321, FieldTypeString(), extRangeOptionsDesc).Build()
1207	testutil.Ok(t, err)
1208	err = exts.AddExtension(extRangeOpt)
1209	testutil.Ok(t, err)
1210
1211	enumOpt, err := NewExtensionImported("enum_foo", 54321, FieldTypeString(), enumOptionsDesc).Build()
1212	testutil.Ok(t, err)
1213	err = exts.AddExtension(enumOpt)
1214	testutil.Ok(t, err)
1215
1216	enumValOpt, err := NewExtensionImported("enum_val_foo", 54321, FieldTypeString(), enumValOptionsDesc).Build()
1217	testutil.Ok(t, err)
1218	err = exts.AddExtension(enumValOpt)
1219	testutil.Ok(t, err)
1220
1221	svcOpt, err := NewExtensionImported("svc_foo", 54321, FieldTypeString(), svcOptionsDesc).Build()
1222	testutil.Ok(t, err)
1223	err = exts.AddExtension(svcOpt)
1224	testutil.Ok(t, err)
1225
1226	mtdOpt, err := NewExtensionImported("mtd_foo", 54321, FieldTypeString(), mtdOptionsDesc).Build()
1227	testutil.Ok(t, err)
1228	err = exts.AddExtension(mtdOpt)
1229	testutil.Ok(t, err)
1230
1231	// Now we can test referring to these and making sure they show up correctly
1232	// in built descriptors
1233
1234	t.Run("file options", func(t *testing.T) {
1235		fb := NewFile("foo.proto")
1236		fb.Options = &dpb.FileOptions{}
1237		err = dynamic.SetExtension(fb.Options, fileOpt, "fubar")
1238		testutil.Ok(t, err)
1239		checkBuildWithExtensions(t, &exts, fileOpt.GetFile(), fb)
1240	})
1241
1242	t.Run("message options", func(t *testing.T) {
1243		mb := NewMessage("Foo")
1244		mb.Options = &dpb.MessageOptions{}
1245		err = dynamic.SetExtension(mb.Options, msgOpt, "fubar")
1246		testutil.Ok(t, err)
1247		checkBuildWithExtensions(t, &exts, msgOpt.GetFile(), mb)
1248	})
1249
1250	t.Run("field options", func(t *testing.T) {
1251		flb := NewField("foo", FieldTypeString())
1252		flb.Options = &dpb.FieldOptions{}
1253		// fields must be connected to a message
1254		NewMessage("Foo").AddField(flb)
1255		err = dynamic.SetExtension(flb.Options, fieldOpt, "fubar")
1256		testutil.Ok(t, err)
1257		checkBuildWithExtensions(t, &exts, fieldOpt.GetFile(), flb)
1258	})
1259
1260	t.Run("oneof options", func(t *testing.T) {
1261		oob := NewOneOf("oo")
1262		oob.Options = &dpb.OneofOptions{}
1263		// oneofs must be connected to a message
1264		NewMessage("Foo").AddOneOf(oob)
1265		err = dynamic.SetExtension(oob.Options, oneofOpt, "fubar")
1266		testutil.Ok(t, err)
1267		checkBuildWithExtensions(t, &exts, oneofOpt.GetFile(), oob)
1268	})
1269
1270	t.Run("extension range options", func(t *testing.T) {
1271		var erOpts dpb.ExtensionRangeOptions
1272		err = dynamic.SetExtension(&erOpts, extRangeOpt, "fubar")
1273		testutil.Ok(t, err)
1274		mb := NewMessage("foo").AddExtensionRangeWithOptions(100, 200, &erOpts)
1275		checkBuildWithExtensions(t, &exts, extRangeOpt.GetFile(), mb)
1276	})
1277
1278	t.Run("enum options", func(t *testing.T) {
1279		eb := NewEnum("Foo")
1280		eb.Options = &dpb.EnumOptions{}
1281		err = dynamic.SetExtension(eb.Options, enumOpt, "fubar")
1282		testutil.Ok(t, err)
1283		checkBuildWithExtensions(t, &exts, enumOpt.GetFile(), eb)
1284	})
1285
1286	t.Run("enum val options", func(t *testing.T) {
1287		evb := NewEnumValue("FOO")
1288		// enum values must be connected to an enum
1289		NewEnum("Foo").AddValue(evb)
1290		evb.Options = &dpb.EnumValueOptions{}
1291		err = dynamic.SetExtension(evb.Options, enumValOpt, "fubar")
1292		testutil.Ok(t, err)
1293		checkBuildWithExtensions(t, &exts, enumValOpt.GetFile(), evb)
1294	})
1295
1296	t.Run("service options", func(t *testing.T) {
1297		sb := NewService("Foo")
1298		sb.Options = &dpb.ServiceOptions{}
1299		err = dynamic.SetExtension(sb.Options, svcOpt, "fubar")
1300		testutil.Ok(t, err)
1301		checkBuildWithExtensions(t, &exts, svcOpt.GetFile(), sb)
1302	})
1303
1304	t.Run("method options", func(t *testing.T) {
1305		mtb := NewMethod("Foo",
1306			RpcTypeMessage(NewMessage("Request"), false),
1307			RpcTypeMessage(NewMessage("Response"), false))
1308		// methods must be connected to a service
1309		NewService("Bar").AddMethod(mtb)
1310		mtb.Options = &dpb.MethodOptions{}
1311		err = dynamic.SetExtension(mtb.Options, mtdOpt, "fubar")
1312		testutil.Ok(t, err)
1313		checkBuildWithExtensions(t, &exts, mtdOpt.GetFile(), mtb)
1314	})
1315}
1316
1317func checkBuildWithExtensions(t *testing.T, exts *dynamic.ExtensionRegistry, expected *desc.FileDescriptor, builder Builder) {
1318	// without interpreting custom option
1319	d, err := builder.BuildDescriptor()
1320	testutil.Ok(t, err)
1321	for _, dep := range d.GetFile().GetDependencies() {
1322		testutil.Neq(t, expected, dep)
1323	}
1324	numDeps := len(d.GetFile().GetDependencies())
1325
1326	// requiring options (and failing)
1327	var opts BuilderOptions
1328	opts.RequireInterpretedOptions = true
1329	_, err = opts.Build(builder)
1330	testutil.Require(t, err != nil)
1331
1332	// able to interpret options via extension registry
1333	opts.Extensions = exts
1334	d, err = opts.Build(builder)
1335	testutil.Ok(t, err)
1336	testutil.Eq(t, numDeps+1, len(d.GetFile().GetDependencies()))
1337	found := false
1338	for _, dep := range d.GetFile().GetDependencies() {
1339		if expected == dep {
1340			found = true
1341			break
1342		}
1343	}
1344	testutil.Require(t, found)
1345}
1346
1347func TestRemoveField(t *testing.T) {
1348	msg := NewMessage("FancyMessage").
1349		AddField(NewField("one", FieldTypeInt64())).
1350		AddField(NewField("two", FieldTypeString())).
1351		AddField(NewField("three", FieldTypeString()))
1352
1353	ok := msg.TryRemoveField("two")
1354	children := msg.GetChildren()
1355
1356	testutil.Require(t, ok)
1357	testutil.Eq(t, 2, len(children))
1358	testutil.Eq(t, "one", children[0].GetName())
1359	testutil.Eq(t, "three", children[1].GetName())
1360}
1361
1362func clone(t *testing.T, fb *FileBuilder) *FileBuilder {
1363	fd, err := fb.Build()
1364	testutil.Ok(t, err)
1365	fb, err = FromFile(fd)
1366	testutil.Ok(t, err)
1367	return fb
1368}
1369