// Copyright 2019 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 ( "testing" "google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/proto" preg "google.golang.org/protobuf/reflect/protoregistry" pb2 "google.golang.org/protobuf/internal/testprotos/textpb2" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/wrapperspb" ) func TestRoundTrip(t *testing.T) { tests := []struct { desc string resolver *preg.Types message proto.Message }{{ desc: "well-known type fields set to empty messages", message: &pb2.KnownTypes{ OptBool: &wrapperspb.BoolValue{}, OptInt32: &wrapperspb.Int32Value{}, OptInt64: &wrapperspb.Int64Value{}, OptUint32: &wrapperspb.UInt32Value{}, OptUint64: &wrapperspb.UInt64Value{}, OptFloat: &wrapperspb.FloatValue{}, OptDouble: &wrapperspb.DoubleValue{}, OptString: &wrapperspb.StringValue{}, OptBytes: &wrapperspb.BytesValue{}, OptDuration: &durationpb.Duration{}, OptTimestamp: ×tamppb.Timestamp{}, OptStruct: &structpb.Struct{}, OptList: &structpb.ListValue{}, OptValue: &structpb.Value{}, OptEmpty: &emptypb.Empty{}, OptAny: &anypb.Any{}, }, }, { desc: "well-known type scalar fields", message: &pb2.KnownTypes{ OptBool: &wrapperspb.BoolValue{ Value: true, }, OptInt32: &wrapperspb.Int32Value{ Value: -42, }, OptInt64: &wrapperspb.Int64Value{ Value: -42, }, OptUint32: &wrapperspb.UInt32Value{ Value: 0xff, }, OptUint64: &wrapperspb.UInt64Value{ Value: 0xffff, }, OptFloat: &wrapperspb.FloatValue{ Value: 1.234, }, OptDouble: &wrapperspb.DoubleValue{ Value: 1.23e308, }, OptString: &wrapperspb.StringValue{ Value: "谷歌", }, OptBytes: &wrapperspb.BytesValue{ Value: []byte("\xe8\xb0\xb7\xe6\xad\x8c"), }, }, }, { desc: "well-known type time-related fields", message: &pb2.KnownTypes{ OptDuration: &durationpb.Duration{ Seconds: -3600, Nanos: -123, }, OptTimestamp: ×tamppb.Timestamp{ Seconds: 1257894000, Nanos: 123, }, }, }, { desc: "Struct field and different Value types", message: &pb2.KnownTypes{ OptStruct: &structpb.Struct{ Fields: map[string]*structpb.Value{ "bool": &structpb.Value{ Kind: &structpb.Value_BoolValue{ BoolValue: true, }, }, "double": &structpb.Value{ Kind: &structpb.Value_NumberValue{ NumberValue: 3.1415, }, }, "null": &structpb.Value{ Kind: &structpb.Value_NullValue{ NullValue: structpb.NullValue_NULL_VALUE, }, }, "string": &structpb.Value{ Kind: &structpb.Value_StringValue{ StringValue: "string", }, }, "struct": &structpb.Value{ Kind: &structpb.Value_StructValue{ StructValue: &structpb.Struct{ Fields: map[string]*structpb.Value{ "bool": &structpb.Value{ Kind: &structpb.Value_BoolValue{ BoolValue: false, }, }, }, }, }, }, "list": &structpb.Value{ Kind: &structpb.Value_ListValue{ ListValue: &structpb.ListValue{ Values: []*structpb.Value{ { Kind: &structpb.Value_BoolValue{ BoolValue: false, }, }, { Kind: &structpb.Value_StringValue{ StringValue: "hello", }, }, }, }, }, }, }, }, }, }, { desc: "Any field without registered type", resolver: new(preg.Types), message: func() proto.Message { m := &pb2.Nested{ OptString: proto.String("embedded inside Any"), OptNested: &pb2.Nested{ OptString: proto.String("inception"), }, } b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m) if err != nil { t.Fatalf("error in binary marshaling message for Any.value: %v", err) } return &pb2.KnownTypes{ OptAny: &anypb.Any{ TypeUrl: string(m.ProtoReflect().Descriptor().FullName()), Value: b, }, } }(), }, { desc: "Any field with registered type", message: func() *pb2.KnownTypes { m := &pb2.Nested{ OptString: proto.String("embedded inside Any"), OptNested: &pb2.Nested{ OptString: proto.String("inception"), }, } b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m) if err != nil { t.Fatalf("error in binary marshaling message for Any.value: %v", err) } return &pb2.KnownTypes{ OptAny: &anypb.Any{ TypeUrl: string(m.ProtoReflect().Descriptor().FullName()), Value: b, }, } }(), }, { desc: "Any field containing Any message", message: func() *pb2.KnownTypes { m1 := &pb2.Nested{ OptString: proto.String("message inside Any of another Any field"), } b1, err := proto.MarshalOptions{Deterministic: true}.Marshal(m1) if err != nil { t.Fatalf("error in binary marshaling message for Any.value: %v", err) } m2 := &anypb.Any{ TypeUrl: "pb2.Nested", Value: b1, } b2, err := proto.MarshalOptions{Deterministic: true}.Marshal(m2) if err != nil { t.Fatalf("error in binary marshaling message for Any.value: %v", err) } return &pb2.KnownTypes{ OptAny: &anypb.Any{ TypeUrl: "google.protobuf.Any", Value: b2, }, } }(), }} for _, tt := range tests { tt := tt t.Run(tt.desc, func(t *testing.T) { t.Parallel() b, err := prototext.MarshalOptions{Resolver: tt.resolver}.Marshal(tt.message) if err != nil { t.Errorf("Marshal() returned error: %v\n\n", err) } gotMessage := new(pb2.KnownTypes) err = prototext.UnmarshalOptions{Resolver: tt.resolver}.Unmarshal(b, gotMessage) if err != nil { t.Errorf("Unmarshal() returned error: %v\n\n", err) } if !proto.Equal(gotMessage, tt.message) { t.Errorf("Unmarshal()\n\n%v\n\n%v\n", gotMessage, tt.message) } }) } }