github.com/hoveychen/protoreflect@v1.4.7-0.20221103114119-0b4b3385ec76/desc/builder/builder_test.go (about)

     1  package builder
     2  
     3  import (
     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/hoveychen/protoreflect/desc"
    18  	"github.com/hoveychen/protoreflect/dynamic"
    19  	_ "github.com/hoveychen/protoreflect/internal/testprotos"
    20  	"github.com/hoveychen/protoreflect/internal/testutil"
    21  )
    22  
    23  func 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  
    71  func 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  
   121  func 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  
   228  func 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  
   277  func 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  
   316  func 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  
   324  func 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  
   431  func 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  
   448  func 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  
   529  func 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  
   558  func 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  
   575  func 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  
   583  func 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  
   600  func 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  
   606  func 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  
   750  func 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  
   763  func 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  
   772  func TestRenamingBuilders(t *testing.T) {
   773  	// TODO
   774  }
   775  
   776  func TestRenumberingFields(t *testing.T) {
   777  	// TODO
   778  }
   779  
   780  var (
   781  	fileOptionsDesc, msgOptionsDesc, fieldOptionsDesc, oneofOptionsDesc, extRangeOptionsDesc,
   782  	enumOptionsDesc, enumValOptionsDesc, svcOptionsDesc, mtdOptionsDesc *desc.MessageDescriptor
   783  )
   784  
   785  func 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  
   825  func 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  
   986  func 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  
   996  func 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  
  1172  func 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  
  1182  func 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  
  1317  func 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  
  1347  func 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  
  1362  func 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  }