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 }