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" 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}}, } for _, tc := range marshalCases { t.Run(tc.name, 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) // Compare each field directly, because proto-generated code has private fields. require.Equal(t, tc.input.FieldA, actual.FieldA) require.Equal(t, tc.input.FieldB, actual.FieldB) require.Equal(t, tc.input.FieldC, actual.FieldC) require.Equal(t, tc.input.FieldD, actual.FieldD) require.Equal(t, tc.input.FieldE, actual.FieldE) require.Equal(t, tc.input.FieldF, actual.FieldF) require.Equal(t, tc.input.FieldG, actual.FieldG) require.Equal(t, tc.input.FieldI, actual.FieldI) require.Equal(t, tc.input.FieldJ, actual.FieldJ) require.Equal(t, tc.input.FieldK, actual.FieldK) require.EqualValues(t, tc.input.FieldH, actual.FieldH) }) } } 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 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)}}, } for _, tc := range marshalCases { t.Run(tc.name, 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)) // Compare each field directly, because proto-generated code has private fields. require.Equal(t, tc.input.FieldA, actual.FieldA) require.Equal(t, tc.input.FieldB, actual.FieldB) require.Equal(t, tc.input.FieldC, actual.FieldC) require.Equal(t, tc.input.FieldD, actual.FieldD) require.Equal(t, tc.input.FieldE, actual.FieldE) require.Equal(t, tc.input.FieldF, actual.FieldF) require.Equal(t, tc.input.FieldFu, actual.FieldFu) }) } }