forked from TrueCloudLab/frostfs-api-go
Evgenii Stratonikov
5e1c6a908f
``` goos: linux goarch: amd64 pkg: git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs cpu: 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz │ old │ new │ │ sec/op │ sec/op vs base │ ObjectIDSlice/0_elements/to_grpc_message-8 3.193n ± 2% 3.242n ± 0% +1.50% (p=0.034 n=10) ObjectIDSlice/0_elements/from_grpc_message-8 3.197n ± 2% 3.343n ± 1% +4.57% (p=0.000 n=10) ObjectIDSlice/0_elements/marshal-8 5.666n ± 3% 5.642n ± 0% -0.42% (p=0.000 n=10) ObjectIDSlice/1_elements/to_grpc_message-8 53.10n ± 6% 29.78n ± 12% -43.92% (p=0.000 n=10) ObjectIDSlice/1_elements/from_grpc_message-8 28.99n ± 5% 29.77n ± 7% ~ (p=0.165 n=10) ObjectIDSlice/1_elements/marshal-8 49.08n ± 7% 50.72n ± 6% ~ (p=0.218 n=10) ObjectIDSlice/50_elements/to_grpc_message-8 1652.5n ± 7% 277.2n ± 1% -83.22% (p=0.000 n=10) ObjectIDSlice/50_elements/from_grpc_message-8 261.2n ± 11% 226.7n ± 15% -13.19% (p=0.003 n=10) ObjectIDSlice/50_elements/marshal-8 1.512µ ± 6% 1.514µ ± 6% ~ (p=0.955 n=10) geomean 52.15n 39.99n -23.31% │ old │ new │ │ B/op │ B/op vs base │ ObjectIDSlice/0_elements/to_grpc_message-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ObjectIDSlice/0_elements/from_grpc_message-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ObjectIDSlice/0_elements/marshal-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ObjectIDSlice/1_elements/to_grpc_message-8 32.00 ± 0% 24.00 ± 0% -25.00% (p=0.000 n=10) ObjectIDSlice/1_elements/from_grpc_message-8 24.00 ± 0% 24.00 ± 0% ~ (p=1.000 n=10) ¹ ObjectIDSlice/1_elements/marshal-8 48.00 ± 0% 48.00 ± 0% ~ (p=1.000 n=10) ¹ ObjectIDSlice/50_elements/to_grpc_message-8 1.578Ki ± 0% 1.250Ki ± 0% -20.79% (p=0.000 n=10) ObjectIDSlice/50_elements/from_grpc_message-8 1.250Ki ± 0% 1.250Ki ± 0% ~ (p=1.000 n=10) ¹ ObjectIDSlice/50_elements/marshal-8 2.000Ki ± 0% 2.000Ki ± 0% ~ (p=1.000 n=10) ¹ geomean ² -5.62% ² ¹ all samples are equal ² summaries must be >0 to compute geomean │ old │ new │ │ allocs/op │ allocs/op vs base │ ObjectIDSlice/0_elements/to_grpc_message-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ObjectIDSlice/0_elements/from_grpc_message-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ObjectIDSlice/0_elements/marshal-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ObjectIDSlice/1_elements/to_grpc_message-8 2.000 ± 0% 1.000 ± 0% -50.00% (p=0.000 n=10) ObjectIDSlice/1_elements/from_grpc_message-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ ObjectIDSlice/1_elements/marshal-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ ObjectIDSlice/50_elements/to_grpc_message-8 51.000 ± 0% 1.000 ± 0% -98.04% (p=0.000 n=10) ObjectIDSlice/50_elements/from_grpc_message-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ ObjectIDSlice/50_elements/marshal-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ geomean ² -40.18% ² ¹ all samples are equal ² summaries must be >0 to compute geomean ``` Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
217 lines
8.1 KiB
Go
217 lines
8.1 KiB
Go
package proto_test
|
|
|
|
import (
|
|
"math"
|
|
"math/rand"
|
|
"testing"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/util/proto/test"
|
|
generated "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/util/proto/test/custom"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/protobuf/encoding/protojson"
|
|
goproto "google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
type protoInt interface {
|
|
~int32 | ~uint32 | ~int64 | ~uint64
|
|
}
|
|
|
|
func nonZero[T protoInt]() T {
|
|
var r T
|
|
for r == 0 {
|
|
r = T(rand.Uint64())
|
|
}
|
|
return r
|
|
}
|
|
|
|
func TestStableMarshalSingle(t *testing.T) {
|
|
t.Run("empty", func(t *testing.T) {
|
|
input := &generated.Primitives{}
|
|
require.Zero(t, input.StableSize())
|
|
|
|
r := input.MarshalProtobuf(nil)
|
|
require.Empty(t, r)
|
|
})
|
|
|
|
marshalCases := []struct {
|
|
name string
|
|
input *generated.Primitives
|
|
}{
|
|
{name: "bytes", input: &generated.Primitives{FieldA: []byte{1, 2, 3}}},
|
|
{name: "string", input: &generated.Primitives{FieldB: "123"}},
|
|
{name: "bool", input: &generated.Primitives{FieldC: true}},
|
|
{name: "int32", input: &generated.Primitives{FieldD: -10}},
|
|
{name: "uint32", input: &generated.Primitives{FieldE: nonZero[uint32]()}},
|
|
{name: "int64", input: &generated.Primitives{FieldF: nonZero[int64]()}},
|
|
{name: "uint64", input: &generated.Primitives{FieldG: nonZero[uint64]()}},
|
|
{name: "uint64", input: &generated.Primitives{FieldI: nonZero[uint64]()}},
|
|
{name: "float64", input: &generated.Primitives{FieldJ: math.Float64frombits(12345677890)}},
|
|
{name: "fixed32", input: &generated.Primitives{FieldK: nonZero[uint32]()}},
|
|
{name: "enum, positive", input: &generated.Primitives{FieldH: generated.Primitives_POSITIVE}},
|
|
{name: "enum, negative", input: &generated.Primitives{FieldH: generated.Primitives_NEGATIVE}},
|
|
{name: "oneof, first", input: &generated.Primitives{FieldM: &generated.Primitives_FieldMa{FieldMa: []byte{4, 2}}}},
|
|
{name: "oneof, second", input: &generated.Primitives{FieldM: &generated.Primitives_FieldMe{FieldMe: nonZero[uint32]()}}},
|
|
}
|
|
for _, tc := range marshalCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Run("proto", func(t *testing.T) {
|
|
r := tc.input.MarshalProtobuf(nil)
|
|
require.Equal(t, len(r), tc.input.StableSize())
|
|
require.NotEmpty(t, r)
|
|
|
|
var actual test.Primitives
|
|
require.NoError(t, goproto.Unmarshal(r, &actual))
|
|
|
|
var actualFrostfs generated.Primitives
|
|
require.NoError(t, actualFrostfs.UnmarshalProtobuf(r))
|
|
require.Equal(t, tc.input, &actualFrostfs)
|
|
|
|
primitivesEqual(t, tc.input, &actual)
|
|
})
|
|
t.Run("json", func(t *testing.T) {
|
|
r, err := tc.input.MarshalJSON()
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, r)
|
|
|
|
var actual test.Primitives
|
|
require.NoError(t, protojson.Unmarshal(r, &actual))
|
|
|
|
var actualFrostfs generated.Primitives
|
|
require.NoError(t, actualFrostfs.UnmarshalJSON(r))
|
|
require.Equal(t, tc.input, &actualFrostfs)
|
|
|
|
primitivesEqual(t, tc.input, &actual)
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func primitivesEqual(t *testing.T, a *generated.Primitives, b *test.Primitives) {
|
|
// Compare each field directly, because proto-generated code has private fields.
|
|
require.Equal(t, a.FieldA, b.FieldA)
|
|
require.Equal(t, a.FieldB, b.FieldB)
|
|
require.Equal(t, a.FieldC, b.FieldC)
|
|
require.Equal(t, a.FieldD, b.FieldD)
|
|
require.Equal(t, a.FieldE, b.FieldE)
|
|
require.Equal(t, a.FieldF, b.FieldF)
|
|
require.Equal(t, a.FieldG, b.FieldG)
|
|
require.Equal(t, a.FieldI, b.FieldI)
|
|
require.Equal(t, a.FieldJ, b.FieldJ)
|
|
require.Equal(t, a.FieldK, b.FieldK)
|
|
require.EqualValues(t, a.FieldH, b.FieldH)
|
|
require.Equal(t, a.GetFieldMa(), b.GetFieldMa())
|
|
require.Equal(t, a.GetFieldMe(), b.GetFieldMe())
|
|
require.Equal(t, a.GetFieldAux().GetInnerField(), b.GetFieldAux().GetInnerField())
|
|
}
|
|
|
|
func repPrimitivesEqual(t *testing.T, a *generated.RepPrimitives, b *test.RepPrimitives) {
|
|
// Compare each field directly, because proto-generated code has private fields.
|
|
require.Equal(t, a.FieldA, b.FieldA)
|
|
require.Equal(t, a.FieldB, b.FieldB)
|
|
require.Equal(t, a.FieldC, b.FieldC)
|
|
require.Equal(t, a.FieldD, b.FieldD)
|
|
require.Equal(t, a.FieldE, b.FieldE)
|
|
require.Equal(t, a.FieldF, b.FieldF)
|
|
require.Equal(t, a.FieldFu, b.FieldFu)
|
|
require.Equal(t, len(a.GetFieldAux()), len(b.GetFieldAux()))
|
|
for i := range a.FieldAux {
|
|
require.Equal(t, a.GetFieldAux()[i].GetInnerField(), b.GetFieldAux()[i].GetInnerField())
|
|
}
|
|
}
|
|
|
|
func randIntSlice[T protoInt](n int, includeZero bool) []T {
|
|
r := make([]T, n)
|
|
if n == 0 {
|
|
return r
|
|
}
|
|
for i := range r {
|
|
r[i] = T(rand.Uint64())
|
|
}
|
|
if includeZero {
|
|
r[0] = 0
|
|
}
|
|
return r
|
|
}
|
|
|
|
func uint32SliceToAux(s []uint32) []generated.RepPrimitives_Aux {
|
|
r := make([]generated.RepPrimitives_Aux, len(s))
|
|
for i := range s {
|
|
r[i] = generated.RepPrimitives_Aux{InnerField: s[i]}
|
|
}
|
|
return r
|
|
}
|
|
|
|
func TestStableMarshalRep(t *testing.T) {
|
|
t.Run("empty", func(t *testing.T) {
|
|
marshalCases := []struct {
|
|
name string
|
|
input *generated.RepPrimitives
|
|
}{
|
|
{name: "default", input: &generated.RepPrimitives{}},
|
|
{name: "bytes", input: &generated.RepPrimitives{FieldA: [][]byte{}}},
|
|
{name: "string", input: &generated.RepPrimitives{FieldB: []string{}}},
|
|
{name: "int32", input: &generated.RepPrimitives{FieldC: []int32{}}},
|
|
{name: "uint32", input: &generated.RepPrimitives{FieldD: []uint32{}}},
|
|
{name: "int64", input: &generated.RepPrimitives{FieldE: []int64{}}},
|
|
{name: "uint64", input: &generated.RepPrimitives{FieldF: []uint64{}}},
|
|
{name: "uint64", input: &generated.RepPrimitives{FieldFu: []uint64{}}},
|
|
}
|
|
|
|
for _, tc := range marshalCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
require.Zero(t, tc.input.StableSize())
|
|
|
|
r := tc.input.MarshalProtobuf(nil)
|
|
require.Empty(t, r)
|
|
})
|
|
}
|
|
})
|
|
|
|
marshalCases := []struct {
|
|
name string
|
|
input *generated.RepPrimitives
|
|
}{
|
|
{name: "bytes", input: &generated.RepPrimitives{FieldA: [][]byte{{1, 2, 3}}}},
|
|
{name: "string", input: &generated.RepPrimitives{FieldB: []string{"123"}}},
|
|
{name: "int32", input: &generated.RepPrimitives{FieldC: randIntSlice[int32](1, true)}},
|
|
{name: "int32", input: &generated.RepPrimitives{FieldC: randIntSlice[int32](2, true)}},
|
|
{name: "int32", input: &generated.RepPrimitives{FieldC: randIntSlice[int32](2, false)}},
|
|
{name: "uint32", input: &generated.RepPrimitives{FieldD: randIntSlice[uint32](1, true)}},
|
|
{name: "uint32", input: &generated.RepPrimitives{FieldD: randIntSlice[uint32](2, true)}},
|
|
{name: "uint32", input: &generated.RepPrimitives{FieldD: randIntSlice[uint32](2, false)}},
|
|
{name: "int64", input: &generated.RepPrimitives{FieldE: randIntSlice[int64](1, true)}},
|
|
{name: "int64", input: &generated.RepPrimitives{FieldE: randIntSlice[int64](2, true)}},
|
|
{name: "int64", input: &generated.RepPrimitives{FieldE: randIntSlice[int64](2, false)}},
|
|
{name: "uint64", input: &generated.RepPrimitives{FieldF: randIntSlice[uint64](1, true)}},
|
|
{name: "uint64", input: &generated.RepPrimitives{FieldF: randIntSlice[uint64](2, true)}},
|
|
{name: "uint64", input: &generated.RepPrimitives{FieldF: randIntSlice[uint64](2, false)}},
|
|
{name: "uint64", input: &generated.RepPrimitives{FieldFu: randIntSlice[uint64](1, true)}},
|
|
{name: "uint64", input: &generated.RepPrimitives{FieldFu: randIntSlice[uint64](2, true)}},
|
|
{name: "uint64", input: &generated.RepPrimitives{FieldFu: randIntSlice[uint64](2, false)}},
|
|
{name: "message", input: &generated.RepPrimitives{FieldAux: uint32SliceToAux(randIntSlice[uint32](1, true))}},
|
|
{name: "message", input: &generated.RepPrimitives{FieldAux: uint32SliceToAux(randIntSlice[uint32](2, true))}},
|
|
{name: "message", input: &generated.RepPrimitives{FieldAux: uint32SliceToAux(randIntSlice[uint32](2, false))}},
|
|
}
|
|
for _, tc := range marshalCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Run("proto", func(t *testing.T) {
|
|
r := tc.input.MarshalProtobuf(nil)
|
|
require.Equal(t, len(r), tc.input.StableSize())
|
|
require.NotEmpty(t, r)
|
|
|
|
var actual test.RepPrimitives
|
|
require.NoError(t, goproto.Unmarshal(r, &actual))
|
|
repPrimitivesEqual(t, tc.input, &actual)
|
|
})
|
|
t.Run("json", func(t *testing.T) {
|
|
r, err := tc.input.MarshalJSON()
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, r)
|
|
|
|
var actual test.RepPrimitives
|
|
require.NoError(t, protojson.Unmarshal(r, &actual))
|
|
repPrimitivesEqual(t, tc.input, &actual)
|
|
})
|
|
})
|
|
}
|
|
}
|