protobuf-go/proto/encode_test.go
Damien Neil 1e5516a4c2 proto: improve slice growth in MarshalAppend
When allocating more space for the destination message in MarshalAppend,
use the same slice growth algorithm as the Go runtime's append rather
than allocating precisely the desired space.

Change-Id: If6033f6f7abdca473bc5188c4d3938ce57d3bdd2
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/197758
Reviewed-by: Joe Tsai <joetsai@google.com>
2019-09-28 01:00:09 +00:00

185 lines
5.3 KiB
Go

// 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"
"testing"
"github.com/google/go-cmp/cmp"
"google.golang.org/protobuf/internal/flags"
"google.golang.org/protobuf/proto"
testpb "google.golang.org/protobuf/internal/testprotos/test"
test3pb "google.golang.org/protobuf/internal/testprotos/test3"
)
func TestEncode(t *testing.T) {
for _, test := range testProtos {
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, marshalText(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), marshalText(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, marshalText(want))
return
}
if !proto.Equal(got, want) {
t.Errorf("Unmarshal returned unexpected result; got:\n%v\nwant:\n%v", marshalText(got), marshalText(want))
}
})
}
}
}
func TestEncodeDeterministic(t *testing.T) {
for _, test := range testProtos {
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, marshalText(want))
}
wire2, err := opts.Marshal(want)
if err != nil {
t.Fatalf("Marshal error: %v\nMessage:\n%v", err, marshalText(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, marshalText(want))
return
}
if !proto.Equal(got, want) {
t.Errorf("Unmarshal returned unexpected result; got:\n%v\nwant:\n%v", marshalText(got), marshalText(want))
}
})
}
}
}
func TestEncodeInvalidUTF8(t *testing.T) {
for _, test := range invalidUTF8TestProtos {
for _, want := range test.decodeTo {
t.Run(fmt.Sprintf("%s (%T)", test.desc, want), func(t *testing.T) {
_, err := proto.Marshal(want)
if err == nil {
t.Errorf("Marshal did not return expected error for invalid UTF8: %v\nMessage:\n%v", err, marshalText(want))
}
})
}
}
}
func TestEncodeNoEnforceUTF8(t *testing.T) {
for _, test := range noEnforceUTF8TestProtos {
for _, want := range test.decodeTo {
t.Run(fmt.Sprintf("%s (%T)", test.desc, want), func(t *testing.T) {
_, err := proto.Marshal(want)
switch {
case flags.ProtoLegacy && err != nil:
t.Errorf("Marshal returned unexpected error: %v\nMessage:\n%v", err, marshalText(want))
case !flags.ProtoLegacy && err == nil:
t.Errorf("Marshal did not return expected error for invalid UTF8: %v\nMessage:\n%v", err, marshalText(want))
}
})
}
}
}
func TestEncodeRequiredFieldChecks(t *testing.T) {
for _, test := range testProtos {
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", marshalText(m))
}
})
}
}
}
func TestEncodeAppend(t *testing.T) {
want := []byte("prefix")
got := append([]byte(nil), want...)
got, err := proto.MarshalOptions{}.MarshalAppend(got, &test3pb.TestAllTypes{
OptionalString: "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 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) {
m := &test3pb.TestAllTypes{OptionalInt32: 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")
}
}