| // Copyright 2024 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package prototext_test | |
| import ( | |
| "math" | |
| "testing" | |
| "github.com/google/go-cmp/cmp" | |
| "google.golang.org/protobuf/encoding/prototext" | |
| "google.golang.org/protobuf/proto" | |
| "google.golang.org/protobuf/reflect/protoreflect" | |
| "google.golang.org/protobuf/testing/protocmp" | |
| testfuzzpb "google.golang.org/protobuf/internal/testprotos/editionsfuzztest" | |
| ) | |
| // roundTripAndCompareProto tests if a prototext.Marshal/Unmarshal roundtrip | |
| // preserves the contents of the message. Note: wireBytes are a protocol | |
| // buffer wire format message, not the proto in text format. We do this because | |
| // a random stream of bytes (e.g. generated by the fuzz engine) is more likely | |
| // to be valid proto wire format than that it is valid text format. | |
| func roundTripAndCompareProto(t *testing.T, wireBytes []byte, messages ...proto.Message) { | |
| for _, msg := range messages { | |
| src := msg.ProtoReflect().Type().New().Interface() | |
| if err := proto.Unmarshal(wireBytes, src); err != nil { | |
| // Ignoring invalid wire format since we want to test the prototext | |
| // implementation, not the wireformat implementation. | |
| return | |
| } | |
| // Unknown fields are not marshaled by prototext so we strip them. | |
| src.ProtoReflect().SetUnknown(nil) | |
| var ranger func(protoreflect.FieldDescriptor, protoreflect.Value) bool | |
| stripUnknown := func(m protoreflect.Message) { | |
| m.SetUnknown(nil) | |
| m.Range(ranger) | |
| } | |
| ranger = func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { | |
| switch { | |
| case fd.IsMap(): | |
| if fd.MapValue().Message() != nil { | |
| v.Map().Range(func(_ protoreflect.MapKey, v protoreflect.Value) bool { | |
| stripUnknown(v.Message()) | |
| return true | |
| }) | |
| } | |
| case fd.Message() != nil: | |
| if fd.Cardinality() == protoreflect.Repeated { | |
| l := v.List() | |
| for i := 0; i < l.Len(); i++ { | |
| stripUnknown(l.Get(i).Message()) | |
| } | |
| } else { | |
| stripUnknown(v.Message()) | |
| } | |
| } | |
| return true | |
| } | |
| stripUnknown(src.ProtoReflect()) | |
| textFormat, err := prototext.Marshal(src) | |
| if err != nil { | |
| t.Errorf("failed to marshal messsage to text format: %v\nmessage: %v", err, src) | |
| } | |
| dst := msg.ProtoReflect().Type().New().Interface() | |
| if err := (prototext.Unmarshal(textFormat, dst)); err != nil { | |
| t.Errorf("failed to unmarshal messsage from text format: %v\ntext format: %s", err, textFormat) | |
| } | |
| // The cmp package does not deal with NaN on its own and will report | |
| // NaN != NaN. | |
| optNaN64 := cmp.Comparer(func(x, y float32) bool { | |
| return (math.IsNaN(float64(x)) && math.IsNaN(float64(y))) || x == y | |
| }) | |
| optNaN32 := cmp.Comparer(func(x, y float64) bool { | |
| return (math.IsNaN(x) && math.IsNaN(y)) || x == y | |
| }) | |
| if diff := cmp.Diff(src, dst, protocmp.Transform(), optNaN64, optNaN32); diff != "" { | |
| t.Error(diff) | |
| } | |
| } | |
| } | |
| func FuzzEncodeDecodeRoundTrip(f *testing.F) { | |
| f.Add([]byte("Hello World!")) | |
| f.Fuzz(func(t *testing.T, in []byte) { | |
| roundTripAndCompareProto(t, in, (*testfuzzpb.TestAllTypesProto2)(nil), (*testfuzzpb.TestAllTypesProto2Editions)(nil), (*testfuzzpb.TestAllTypesProto3)(nil), (*testfuzzpb.TestAllTypesProto3Editions)(nil)) | |
| }) | |
| } |