github.com/Khushbukela/protoreflect@v1.0.1/desc/protoparse/parser_test.go (about)

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