github.com/syumai/protoreflect@v1.7.1-0.20200810020253-2ac7e3b3a321/desc/protoparse/parser_test.go (about)

     1  package protoparse
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"io"
     7  	"io/ioutil"
     8  	"os"
     9  	"sort"
    10  	"testing"
    11  
    12  	"github.com/golang/protobuf/proto"
    13  	dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
    14  
    15  	"github.com/syumai/protoreflect/codec"
    16  	"github.com/syumai/protoreflect/desc"
    17  	"github.com/syumai/protoreflect/internal"
    18  	"github.com/syumai/protoreflect/internal/testutil"
    19  )
    20  
    21  func TestEmptyParse(t *testing.T) {
    22  	p := Parser{
    23  		Accessor: func(filename string) (io.ReadCloser, error) {
    24  			return ioutil.NopCloser(bytes.NewReader(nil)), nil
    25  		},
    26  	}
    27  	fd, err := p.ParseFiles("foo.proto")
    28  	testutil.Ok(t, err)
    29  	testutil.Eq(t, 1, len(fd))
    30  	testutil.Eq(t, "foo.proto", fd[0].GetName())
    31  	testutil.Eq(t, 0, len(fd[0].GetDependencies()))
    32  	testutil.Eq(t, 0, len(fd[0].GetMessageTypes()))
    33  	testutil.Eq(t, 0, len(fd[0].GetEnumTypes()))
    34  	testutil.Eq(t, 0, len(fd[0].GetExtensions()))
    35  	testutil.Eq(t, 0, len(fd[0].GetServices()))
    36  }
    37  
    38  func TestSimpleParse(t *testing.T) {
    39  	protos := map[string]*parseResult{}
    40  
    41  	// Just verify that we can successfully parse the same files we use for
    42  	// testing. We do a *very* shallow check of what was parsed because we know
    43  	// it won't be fully correct until after linking. (So that will be tested
    44  	// below, where we parse *and* link.)
    45  	res, err := parseFileForTest("../../internal/testprotos/desc_test1.proto")
    46  	testutil.Ok(t, err)
    47  	fd := res.fd
    48  	testutil.Eq(t, "../../internal/testprotos/desc_test1.proto", fd.GetName())
    49  	testutil.Eq(t, "testprotos", fd.GetPackage())
    50  	testutil.Require(t, hasExtension(fd, "xtm"))
    51  	testutil.Require(t, hasMessage(fd, "TestMessage"))
    52  	protos[fd.GetName()] = res
    53  
    54  	res, err = parseFileForTest("../../internal/testprotos/desc_test2.proto")
    55  	testutil.Ok(t, err)
    56  	fd = res.fd
    57  	testutil.Eq(t, "../../internal/testprotos/desc_test2.proto", fd.GetName())
    58  	testutil.Eq(t, "testprotos", fd.GetPackage())
    59  	testutil.Require(t, hasExtension(fd, "groupx"))
    60  	testutil.Require(t, hasMessage(fd, "GroupX"))
    61  	testutil.Require(t, hasMessage(fd, "Frobnitz"))
    62  	protos[fd.GetName()] = res
    63  
    64  	res, err = parseFileForTest("../../internal/testprotos/desc_test_defaults.proto")
    65  	testutil.Ok(t, err)
    66  	fd = res.fd
    67  	testutil.Eq(t, "../../internal/testprotos/desc_test_defaults.proto", fd.GetName())
    68  	testutil.Eq(t, "testprotos", fd.GetPackage())
    69  	testutil.Require(t, hasMessage(fd, "PrimitiveDefaults"))
    70  	protos[fd.GetName()] = res
    71  
    72  	res, err = parseFileForTest("../../internal/testprotos/desc_test_field_types.proto")
    73  	testutil.Ok(t, err)
    74  	fd = res.fd
    75  	testutil.Eq(t, "../../internal/testprotos/desc_test_field_types.proto", fd.GetName())
    76  	testutil.Eq(t, "testprotos", fd.GetPackage())
    77  	testutil.Require(t, hasEnum(fd, "TestEnum"))
    78  	testutil.Require(t, hasMessage(fd, "UnaryFields"))
    79  	protos[fd.GetName()] = res
    80  
    81  	res, err = parseFileForTest("../../internal/testprotos/desc_test_options.proto")
    82  	testutil.Ok(t, err)
    83  	fd = res.fd
    84  	testutil.Eq(t, "../../internal/testprotos/desc_test_options.proto", fd.GetName())
    85  	testutil.Eq(t, "testprotos", fd.GetPackage())
    86  	testutil.Require(t, hasExtension(fd, "mfubar"))
    87  	testutil.Require(t, hasEnum(fd, "ReallySimpleEnum"))
    88  	testutil.Require(t, hasMessage(fd, "ReallySimpleMessage"))
    89  	protos[fd.GetName()] = res
    90  
    91  	res, err = parseFileForTest("../../internal/testprotos/desc_test_proto3.proto")
    92  	testutil.Ok(t, err)
    93  	fd = res.fd
    94  	testutil.Eq(t, "../../internal/testprotos/desc_test_proto3.proto", fd.GetName())
    95  	testutil.Eq(t, "testprotos", fd.GetPackage())
    96  	testutil.Require(t, hasEnum(fd, "Proto3Enum"))
    97  	testutil.Require(t, hasService(fd, "TestService"))
    98  	protos[fd.GetName()] = res
    99  
   100  	res, err = parseFileForTest("../../internal/testprotos/desc_test_wellknowntypes.proto")
   101  	testutil.Ok(t, err)
   102  	fd = res.fd
   103  	testutil.Eq(t, "../../internal/testprotos/desc_test_wellknowntypes.proto", fd.GetName())
   104  	testutil.Eq(t, "testprotos", fd.GetPackage())
   105  	testutil.Require(t, hasMessage(fd, "TestWellKnownTypes"))
   106  	protos[fd.GetName()] = res
   107  
   108  	res, err = parseFileForTest("../../internal/testprotos/nopkg/desc_test_nopkg.proto")
   109  	testutil.Ok(t, err)
   110  	fd = res.fd
   111  	testutil.Eq(t, "../../internal/testprotos/nopkg/desc_test_nopkg.proto", fd.GetName())
   112  	testutil.Eq(t, "", fd.GetPackage())
   113  	protos[fd.GetName()] = res
   114  
   115  	res, err = parseFileForTest("../../internal/testprotos/nopkg/desc_test_nopkg_new.proto")
   116  	testutil.Ok(t, err)
   117  	fd = res.fd
   118  	testutil.Eq(t, "../../internal/testprotos/nopkg/desc_test_nopkg_new.proto", fd.GetName())
   119  	testutil.Eq(t, "", fd.GetPackage())
   120  	testutil.Require(t, hasMessage(fd, "TopLevel"))
   121  	protos[fd.GetName()] = res
   122  
   123  	res, err = parseFileForTest("../../internal/testprotos/pkg/desc_test_pkg.proto")
   124  	testutil.Ok(t, err)
   125  	fd = res.fd
   126  	testutil.Eq(t, "../../internal/testprotos/pkg/desc_test_pkg.proto", fd.GetName())
   127  	testutil.Eq(t, "jhump.protoreflect.desc", fd.GetPackage())
   128  	testutil.Require(t, hasEnum(fd, "Foo"))
   129  	testutil.Require(t, hasMessage(fd, "Bar"))
   130  	protos[fd.GetName()] = res
   131  
   132  	// We'll also check our fixup logic to make sure it correctly rewrites the
   133  	// names of the files to match corresponding import statementes. This should
   134  	// strip the "../../internal/testprotos/" prefix from each file.
   135  	protos = fixupFilenames(protos)
   136  	var actual []string
   137  	for n := range protos {
   138  		actual = append(actual, n)
   139  	}
   140  	sort.Strings(actual)
   141  	expected := []string{
   142  		"desc_test1.proto",
   143  		"desc_test2.proto",
   144  		"desc_test_defaults.proto",
   145  		"desc_test_field_types.proto",
   146  		"desc_test_options.proto",
   147  		"desc_test_proto3.proto",
   148  		"desc_test_wellknowntypes.proto",
   149  		"nopkg/desc_test_nopkg.proto",
   150  		"nopkg/desc_test_nopkg_new.proto",
   151  		"pkg/desc_test_pkg.proto",
   152  	}
   153  	testutil.Eq(t, expected, actual)
   154  }
   155  
   156  func parseFileForTest(filename string) (*parseResult, error) {
   157  	f, err := os.Open(filename)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  	defer func() {
   162  		_ = f.Close()
   163  	}()
   164  	errs := newErrorHandler(nil, nil)
   165  	res := parseProto(filename, f, errs, true)
   166  	return res, errs.getError()
   167  }
   168  
   169  func hasExtension(fd *dpb.FileDescriptorProto, name string) bool {
   170  	for _, ext := range fd.Extension {
   171  		if ext.GetName() == name {
   172  			return true
   173  		}
   174  	}
   175  	return false
   176  }
   177  
   178  func hasMessage(fd *dpb.FileDescriptorProto, name string) bool {
   179  	for _, md := range fd.MessageType {
   180  		if md.GetName() == name {
   181  			return true
   182  		}
   183  	}
   184  	return false
   185  }
   186  
   187  func hasEnum(fd *dpb.FileDescriptorProto, name string) bool {
   188  	for _, ed := range fd.EnumType {
   189  		if ed.GetName() == name {
   190  			return true
   191  		}
   192  	}
   193  	return false
   194  }
   195  
   196  func hasService(fd *dpb.FileDescriptorProto, name string) bool {
   197  	for _, sd := range fd.Service {
   198  		if sd.GetName() == name {
   199  			return true
   200  		}
   201  	}
   202  	return false
   203  }
   204  
   205  func TestAggregateValueInUninterpretedOptions(t *testing.T) {
   206  	res, err := parseFileForTest("../../internal/testprotos/desc_test_complex.proto")
   207  	testutil.Ok(t, err)
   208  	fd := res.fd
   209  
   210  	aggregateValue1 := *fd.Service[0].Method[0].Options.UninterpretedOption[0].AggregateValue
   211  	testutil.Eq(t, "{ authenticated: true permission{ action: LOGIN entity: \"client\" } }", aggregateValue1)
   212  
   213  	aggregateValue2 := *fd.Service[0].Method[1].Options.UninterpretedOption[0].AggregateValue
   214  	testutil.Eq(t, "{ authenticated: true permission{ action: READ entity: \"user\" } }", aggregateValue2)
   215  }
   216  
   217  func TestParseFilesMessageComments(t *testing.T) {
   218  	p := Parser{
   219  		IncludeSourceCodeInfo: true,
   220  	}
   221  	protos, err := p.ParseFiles("../../internal/testprotos/desc_test1.proto")
   222  	testutil.Ok(t, err)
   223  	comments := ""
   224  	expected := " Comment for TestMessage\n"
   225  	for _, p := range protos {
   226  		msg := p.FindMessage("testprotos.TestMessage")
   227  		if msg != nil {
   228  			si := msg.GetSourceInfo()
   229  			if si != nil {
   230  				comments = si.GetLeadingComments()
   231  			}
   232  			break
   233  		}
   234  	}
   235  	testutil.Eq(t, expected, comments)
   236  }
   237  
   238  func TestParseFilesWithImportsNoImportPath(t *testing.T) {
   239  	relFilePaths := []string{
   240  		"a/b/b1.proto",
   241  		"a/b/b2.proto",
   242  		"c/c.proto",
   243  	}
   244  
   245  	pwd, err := os.Getwd()
   246  	testutil.Require(t, err == nil, "%v", err)
   247  
   248  	err = os.Chdir("../../internal/testprotos/protoparse")
   249  	testutil.Require(t, err == nil, "%v", err)
   250  	p := Parser{}
   251  	protos, parseErr := p.ParseFiles(relFilePaths...)
   252  	err = os.Chdir(pwd)
   253  	testutil.Require(t, err == nil, "%v", err)
   254  	testutil.Require(t, parseErr == nil, "%v", parseErr)
   255  
   256  	testutil.Ok(t, err)
   257  	testutil.Eq(t, len(relFilePaths), len(protos))
   258  }
   259  
   260  func TestParseFilesWithDependencies(t *testing.T) {
   261  	// Create some file contents that import a non-well-known proto.
   262  	// (One of the protos in internal/testprotos is fine.)
   263  	contents := map[string]string{
   264  		"test.proto": `
   265  			syntax = "proto3";
   266  			import "desc_test_wellknowntypes.proto";
   267  
   268  			message TestImportedType {
   269  				testprotos.TestWellKnownTypes imported_field = 1;
   270  			}
   271  		`,
   272  	}
   273  
   274  	// Establish that we *can* parse the source file with a parser that
   275  	// registers the dependency.
   276  	t.Run("DependencyIncluded", func(t *testing.T) {
   277  		// Create a dependency-aware parser.
   278  		parser := Parser{
   279  			Accessor: FileContentsFromMap(contents),
   280  			LookupImport: func(imp string) (*desc.FileDescriptor, error) {
   281  				if imp == "desc_test_wellknowntypes.proto" {
   282  					return desc.LoadFileDescriptor(imp)
   283  				}
   284  				return nil, errors.New("unexpected filename")
   285  			},
   286  		}
   287  		if _, err := parser.ParseFiles("test.proto"); err != nil {
   288  			t.Errorf("Could not parse with a non-well-known import: %v", err)
   289  		}
   290  	})
   291  	t.Run("DependencyIncludedProto", func(t *testing.T) {
   292  		// Create a dependency-aware parser.
   293  		parser := Parser{
   294  			Accessor: FileContentsFromMap(contents),
   295  			LookupImportProto: func(imp string) (*dpb.FileDescriptorProto, error) {
   296  				if imp == "desc_test_wellknowntypes.proto" {
   297  					fileDescriptor, err := desc.LoadFileDescriptor(imp)
   298  					if err != nil {
   299  						return nil, err
   300  					}
   301  					return fileDescriptor.AsFileDescriptorProto(), nil
   302  				}
   303  				return nil, errors.New("unexpected filename")
   304  			},
   305  		}
   306  		if _, err := parser.ParseFiles("test.proto"); err != nil {
   307  			t.Errorf("Could not parse with a non-well-known import: %v", err)
   308  		}
   309  	})
   310  
   311  	// Establish that we *can not* parse the source file with a parser that
   312  	// did not register the dependency.
   313  	t.Run("DependencyExcluded", func(t *testing.T) {
   314  		// Create a dependency-aware parser.
   315  		parser := Parser{
   316  			Accessor: FileContentsFromMap(contents),
   317  		}
   318  		if _, err := parser.ParseFiles("test.proto"); err == nil {
   319  			t.Errorf("Expected parse to fail due to lack of an import.")
   320  		}
   321  	})
   322  
   323  	// Establish that the accessor has precedence over LookupImport.
   324  	t.Run("AccessorWins", func(t *testing.T) {
   325  		// Create a dependency-aware parser that should never be called.
   326  		parser := Parser{
   327  			Accessor: FileContentsFromMap(map[string]string{
   328  				"test.proto": `syntax = "proto3";`,
   329  			}),
   330  			LookupImport: func(imp string) (*desc.FileDescriptor, error) {
   331  				t.Errorf("LookupImport was called on a filename available to the Accessor.")
   332  				return nil, errors.New("unimportant")
   333  			},
   334  		}
   335  		if _, err := parser.ParseFiles("test.proto"); err != nil {
   336  			t.Error(err)
   337  		}
   338  	})
   339  }
   340  
   341  func TestParseCommentsBeforeDot(t *testing.T) {
   342  	accessor := FileContentsFromMap(map[string]string{
   343  		"test.proto": `
   344  syntax = "proto3";
   345  message Foo {
   346    // leading comments
   347    .Foo foo = 1;
   348  }
   349  `,
   350  	})
   351  
   352  	p := Parser{
   353  		Accessor:              accessor,
   354  		IncludeSourceCodeInfo: true,
   355  	}
   356  	fds, err := p.ParseFiles("test.proto")
   357  	testutil.Ok(t, err)
   358  
   359  	comment := fds[0].GetMessageTypes()[0].GetFields()[0].GetSourceInfo().GetLeadingComments()
   360  	testutil.Eq(t, " leading comments\n", comment)
   361  }
   362  
   363  func TestParseCustomOptions(t *testing.T) {
   364  	accessor := FileContentsFromMap(map[string]string{
   365  		"test.proto": `
   366  syntax = "proto3";
   367  import "google/protobuf/descriptor.proto";
   368  extend google.protobuf.MessageOptions {
   369      string foo = 30303;
   370      int64 bar = 30304;
   371  }
   372  message Foo {
   373    option (.foo) = "foo";
   374    option (bar) = 123;
   375  }
   376  `,
   377  	})
   378  
   379  	p := Parser{
   380  		Accessor:              accessor,
   381  		IncludeSourceCodeInfo: true,
   382  	}
   383  	fds, err := p.ParseFiles("test.proto")
   384  	testutil.Ok(t, err)
   385  
   386  	md := fds[0].GetMessageTypes()[0]
   387  	opts := md.GetMessageOptions()
   388  	data := internal.GetUnrecognized(opts)
   389  	buf := codec.NewBuffer(data)
   390  
   391  	tag, wt, err := buf.DecodeTagAndWireType()
   392  	testutil.Ok(t, err)
   393  	testutil.Eq(t, int32(30303), tag)
   394  	testutil.Eq(t, int8(proto.WireBytes), wt)
   395  	fieldData, err := buf.DecodeRawBytes(false)
   396  	testutil.Ok(t, err)
   397  	testutil.Eq(t, "foo", string(fieldData))
   398  
   399  	tag, wt, err = buf.DecodeTagAndWireType()
   400  	testutil.Ok(t, err)
   401  	testutil.Eq(t, int32(30304), tag)
   402  	testutil.Eq(t, int8(proto.WireVarint), wt)
   403  	fieldVal, err := buf.DecodeVarint()
   404  	testutil.Ok(t, err)
   405  	testutil.Eq(t, uint64(123), fieldVal)
   406  }