| // 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 proto_test |
| |
| import ( |
| "bytes" |
| "fmt" |
| "math" |
| "reflect" |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| |
| "google.golang.org/protobuf/encoding/prototext" |
| "google.golang.org/protobuf/encoding/protowire" |
| "google.golang.org/protobuf/proto" |
| "google.golang.org/protobuf/reflect/protoreflect" |
| "google.golang.org/protobuf/types/known/durationpb" |
| |
| "google.golang.org/protobuf/internal/errors" |
| orderpb "google.golang.org/protobuf/internal/testprotos/order" |
| testpb "google.golang.org/protobuf/internal/testprotos/test" |
| test3pb "google.golang.org/protobuf/internal/testprotos/test3" |
| ) |
| |
| func TestEncode(t *testing.T) { |
| for _, test := range testValidMessages { |
| for _, want := range test.decodeTo { |
| t.Run(fmt.Sprintf("%s (%T)", test.desc, want), func(t *testing.T) { |
| opts := proto.MarshalOptions{ |
| AllowPartial: test.partial, |
| } |
| wire, err := opts.Marshal(want) |
| if err != nil { |
| t.Fatalf("Marshal error: %v\nMessage:\n%v", err, prototext.Format(want)) |
| } |
| |
| size := proto.Size(want) |
| if size != len(wire) { |
| t.Errorf("Size and marshal disagree: Size(m)=%v; len(Marshal(m))=%v\nMessage:\n%v", size, len(wire), prototext.Format(want)) |
| } |
| |
| got := want.ProtoReflect().New().Interface() |
| uopts := proto.UnmarshalOptions{ |
| AllowPartial: test.partial, |
| } |
| if err := uopts.Unmarshal(wire, got); err != nil { |
| t.Errorf("Unmarshal error: %v\nMessage:\n%v", err, prototext.Format(want)) |
| return |
| } |
| if !proto.Equal(got, want) && got.ProtoReflect().IsValid() && want.ProtoReflect().IsValid() { |
| t.Errorf("Unmarshal returned unexpected result; got:\n%v\nwant:\n%v", prototext.Format(got), prototext.Format(want)) |
| } |
| }) |
| } |
| } |
| } |
| |
| func TestEncodeDeterministic(t *testing.T) { |
| for _, test := range testValidMessages { |
| for _, want := range test.decodeTo { |
| t.Run(fmt.Sprintf("%s (%T)", test.desc, want), func(t *testing.T) { |
| opts := proto.MarshalOptions{ |
| Deterministic: true, |
| AllowPartial: test.partial, |
| } |
| wire, err := opts.Marshal(want) |
| if err != nil { |
| t.Fatalf("Marshal error: %v\nMessage:\n%v", err, prototext.Format(want)) |
| } |
| wire2, err := opts.Marshal(want) |
| if err != nil { |
| t.Fatalf("Marshal error: %v\nMessage:\n%v", err, prototext.Format(want)) |
| } |
| if !bytes.Equal(wire, wire2) { |
| t.Fatalf("deterministic marshal returned varying results:\n%v", cmp.Diff(wire, wire2)) |
| } |
| |
| got := want.ProtoReflect().New().Interface() |
| uopts := proto.UnmarshalOptions{ |
| AllowPartial: test.partial, |
| } |
| if err := uopts.Unmarshal(wire, got); err != nil { |
| t.Errorf("Unmarshal error: %v\nMessage:\n%v", err, prototext.Format(want)) |
| return |
| } |
| if !proto.Equal(got, want) && got.ProtoReflect().IsValid() && want.ProtoReflect().IsValid() { |
| t.Errorf("Unmarshal returned unexpected result; got:\n%v\nwant:\n%v", prototext.Format(got), prototext.Format(want)) |
| } |
| }) |
| } |
| } |
| } |
| |
| func TestEncodeRequiredFieldChecks(t *testing.T) { |
| for _, test := range testValidMessages { |
| if !test.partial { |
| continue |
| } |
| for _, m := range test.decodeTo { |
| t.Run(fmt.Sprintf("%s (%T)", test.desc, m), func(t *testing.T) { |
| _, err := proto.Marshal(m) |
| if err == nil { |
| t.Fatalf("Marshal succeeded (want error)\nMessage:\n%v", prototext.Format(m)) |
| } |
| }) |
| } |
| } |
| } |
| |
| func TestEncodeAppend(t *testing.T) { |
| want := []byte("prefix") |
| got := append([]byte(nil), want...) |
| got, err := proto.MarshalOptions{}.MarshalAppend(got, &test3pb.TestAllTypes{ |
| SingularString: "value", |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !bytes.HasPrefix(got, want) { |
| t.Fatalf("MarshalAppend modified prefix: got %v, want prefix %v", got, want) |
| } |
| } |
| |
| func TestEncodeInvalidMessages(t *testing.T) { |
| for _, test := range testInvalidMessages { |
| for _, m := range test.decodeTo { |
| if !m.ProtoReflect().IsValid() { |
| continue |
| } |
| t.Run(fmt.Sprintf("%s (%T)", test.desc, m), func(t *testing.T) { |
| opts := proto.MarshalOptions{ |
| AllowPartial: test.partial, |
| } |
| got, err := opts.Marshal(m) |
| if err == nil { |
| t.Fatalf("Marshal unexpectedly succeeded\noutput bytes: [%x]\nMessage:\n%v", got, prototext.Format(m)) |
| } |
| if !errors.Is(err, proto.Error) { |
| t.Fatalf("Marshal error is not a proto.Error: %v", err) |
| } |
| }) |
| } |
| } |
| } |
| |
| func TestEncodeOneofNilWrapper(t *testing.T) { |
| m := &testpb.TestAllTypes{OneofField: (*testpb.TestAllTypes_OneofUint32)(nil)} |
| b, err := proto.Marshal(m) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if len(b) > 0 { |
| t.Errorf("Marshal return non-empty, want empty") |
| } |
| } |
| |
| func TestMarshalAppendAllocations(t *testing.T) { |
| // This test ensures that MarshalAppend() has the same performance |
| // characteristics as the append() builtin, meaning that repeated calls do |
| // not allocate each time, but allocations are amortized. |
| m := &test3pb.TestAllTypes{SingularInt32: 1} |
| size := proto.Size(m) |
| const count = 1000 |
| b := make([]byte, size) |
| // AllocsPerRun returns an integral value. |
| marshalAllocs := testing.AllocsPerRun(count, func() { |
| _, err := proto.MarshalOptions{}.MarshalAppend(b[:0], m) |
| if err != nil { |
| t.Fatal(err) |
| } |
| }) |
| b = nil |
| marshalAppendAllocs := testing.AllocsPerRun(count, func() { |
| var err error |
| b, err = proto.MarshalOptions{}.MarshalAppend(b, m) |
| if err != nil { |
| t.Fatal(err) |
| } |
| }) |
| if marshalAllocs != marshalAppendAllocs { |
| t.Errorf("%v allocs/op when writing to a preallocated buffer", marshalAllocs) |
| t.Errorf("%v allocs/op when repeatedly appending to a slice", marshalAppendAllocs) |
| t.Errorf("expect amortized allocs/op to be identical") |
| } |
| } |
| |
| func TestEncodeOrder(t *testing.T) { |
| // We make no guarantees about the stability of wire marshal output. |
| // The order in which fields are marshaled may change over time. |
| // If deterministic marshaling is not enabled, it may change over |
| // successive calls to proto.Marshal in the same binary. |
| // |
| // Unfortunately, many users have come to rely on the specific current |
| // wire marshal output. Perhaps someday we will choose to deliberately |
| // change the marshal output; until that day comes, this test verifies |
| // that we don't unintentionally change it. |
| m := &orderpb.Message{ |
| Field_1: proto.String("one"), |
| Field_2: proto.String("two"), |
| Field_20: proto.String("twenty"), |
| Oneof_1: &orderpb.Message_Field_10{"ten"}, |
| } |
| proto.SetExtension(m, orderpb.E_Field_30, "thirty") |
| proto.SetExtension(m, orderpb.E_Field_31, "thirty-one") |
| proto.SetExtension(m, orderpb.E_Field_32, "thirty-two") |
| want := []protoreflect.FieldNumber{ |
| 30, 31, 32, // extensions first, in number order |
| 1, 2, 20, // non-extension, non-oneof in number order |
| 10, // oneofs last, undefined order |
| } |
| |
| // Test with deterministic serialization, since fields are not sorted without |
| // it when -tags=protoreflect. |
| b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m) |
| if err != nil { |
| t.Fatal(err) |
| } |
| var got []protoreflect.FieldNumber |
| for len(b) > 0 { |
| num, _, n := protowire.ConsumeField(b) |
| if n < 0 { |
| t.Fatal(protowire.ParseError(n)) |
| } |
| b = b[n:] |
| got = append(got, num) |
| } |
| if !reflect.DeepEqual(got, want) { |
| t.Errorf("unexpected field marshal order:\ngot: %v\nwant: %v\nmessage:\n%v", got, want, m) |
| } |
| } |
| |
| func TestEncodeLarge(t *testing.T) { |
| // Encode/decode a message large enough to overflow a 32-bit size cache. |
| t.Skip("too slow and memory-hungry to run all the time") |
| size := int64(math.MaxUint32 + 1) |
| m := &testpb.TestAllTypes_NestedMessage{ |
| Corecursive: &testpb.TestAllTypes{ |
| OptionalBytes: make([]byte, size), |
| }, |
| } |
| b, err := proto.Marshal(m) |
| if err != nil { |
| t.Fatalf("Marshal: %v", err) |
| } |
| if got, want := len(b), proto.Size(m); got != want { |
| t.Fatalf("Size(m) = %v, but len(Marshal(m)) = %v", got, want) |
| } |
| if err := proto.Unmarshal(b, m); err != nil { |
| t.Fatalf("Unmarshal: %v", err) |
| } |
| if got, want := int64(len(m.Corecursive.OptionalBytes)), size; got != want { |
| t.Errorf("after round-trip marshal, got len(m.OptionalBytes) = %v, want %v", got, want) |
| } |
| } |
| |
| // TestEncodeEmpty tests for boundary conditions when producing an empty output. |
| // These tests are not necessarily a statement of proper behavior, |
| // but exist to detect accidental changes in behavior. |
| func TestEncodeEmpty(t *testing.T) { |
| for _, m := range []proto.Message{nil, (*testpb.TestAllTypes)(nil), &testpb.TestAllTypes{}} { |
| t.Run(fmt.Sprintf("%T", m), func(t *testing.T) { |
| isValid := m != nil && m.ProtoReflect().IsValid() |
| |
| b, err := proto.Marshal(m) |
| if err != nil { |
| t.Errorf("proto.Marshal() = %v", err) |
| } |
| if isNil := b == nil; isNil == isValid { |
| t.Errorf("proto.Marshal() == nil: %v, want %v", isNil, !isValid) |
| } |
| |
| b, err = proto.MarshalOptions{}.Marshal(m) |
| if err != nil { |
| t.Errorf("proto.MarshalOptions{}.Marshal() = %v", err) |
| } |
| if isNil := b == nil; isNil == isValid { |
| t.Errorf("proto.MarshalOptions{}.Marshal() = %v, want %v", isNil, !isValid) |
| } |
| }) |
| } |
| } |
| |
| // This example illustrates how to marshal (encode) a Protobuf message struct |
| // literal into wire-format encoding. |
| // |
| // This example hard-codes a duration of 125ns for the illustration of struct |
| // fields, but note that you do not need to fill the fields of well-known types |
| // like duration.proto yourself. To convert a time.Duration, use |
| // [google.golang.org/protobuf/types/known/durationpb.New]. |
| func ExampleMarshal() { |
| b, err := proto.Marshal(&durationpb.Duration{ |
| Nanos: 125, |
| }) |
| if err != nil { |
| panic(err) |
| } |
| |
| fmt.Printf("125ns encoded into %d bytes of Protobuf wire format:\n% x\n", len(b), b) |
| |
| // You can use protoscope to explore the wire format: |
| // https://github.com/protocolbuffers/protoscope |
| // |
| // echo -n '10 7d' | xxd -r -ps | protoscope |
| // 2: 125 |
| |
| // Output: 125ns encoded into 2 bytes of Protobuf wire format: |
| // 10 7d |
| } |
| |
| // This example illustrates how to marshal (encode) many Protobuf messages into |
| // wire-format encoding, using the same buffer. |
| // |
| // MarshalAppend will grow the buffer as needed, so over time it will grow large |
| // enough to not need further allocations. |
| // |
| // If unbounded growth of the buffer is undesirable in your application, you can |
| // use [MarshalOptions.Size] to determine a buffer size that is guaranteed to be |
| // large enough for marshaling without allocations. |
| func ExampleMarshalOptions_MarshalAppend_sameBuffer() { |
| var m proto.Message |
| |
| opts := proto.MarshalOptions{ |
| // set e.g. Deterministic: true, if needed |
| } |
| |
| var buf []byte |
| for i := 0; i < 100000; i++ { |
| var err error |
| buf, err = opts.MarshalAppend(buf[:0], m) |
| if err != nil { |
| panic(err) |
| } |
| // cap(buf) will grow to hold the largest m. |
| |
| // write buf to disk, network, etc. |
| } |
| } |