From 1d8b6c1dab1dd9d4ff6cac0e004435039878b796 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Thu, 26 May 2022 15:09:54 +0200 Subject: [PATCH 1/2] Added binary/gob encoder implementation --- binary.go | 118 +++++++++++++++++++++++++++++++++++++++++++++++++ binary_test.go | 77 ++++++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 binary.go create mode 100644 binary_test.go diff --git a/binary.go b/binary.go new file mode 100644 index 0000000..ee698e8 --- /dev/null +++ b/binary.go @@ -0,0 +1,118 @@ +// Copyright 2018-2022 Cristian Maglie. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// + +package semver + +import ( + "bytes" + "encoding/binary" +) + +func marshalByteArray(b []byte) []byte { + l := len(b) + res := make([]byte, l+4) + binary.BigEndian.PutUint32(res, uint32(l)) + copy(res[4:], b) + return res +} + +func marshalInt(i int) []byte { + res := make([]byte, 4) + binary.BigEndian.PutUint32(res, uint32(i)) + return res +} + +// MarshalBinary implements binary custom encoding +func (v *Version) MarshalBinary() ([]byte, error) { + res := new(bytes.Buffer) + _, _ = res.Write(marshalByteArray(v.major)) + _, _ = res.Write(marshalByteArray(v.minor)) + _, _ = res.Write(marshalByteArray(v.patch)) + _, _ = res.Write(marshalInt(len(v.prerelases))) + for _, pre := range v.prerelases { + _, _ = res.Write(marshalByteArray(pre)) + } + _, _ = res.Write(marshalInt(len(v.numericPrereleases))) + for _, npre := range v.numericPrereleases { + v := []byte{0} + if npre { + v[0] = 1 + } + _, _ = res.Write(v) + } + _, _ = res.Write(marshalInt(len(v.builds))) + for _, build := range v.builds { + _, _ = res.Write(marshalByteArray(build)) + } + return res.Bytes(), nil +} + +func decodeArray(data []byte) ([]byte, []byte) { + l, data := int(binary.BigEndian.Uint32(data)), data[4:] + return data[:l], data[l:] +} + +func decodeInt(data []byte) (int, []byte) { + return int(binary.BigEndian.Uint32(data)), data[4:] +} + +// UnmarshalJSON implements binary custom decoding +func (v *Version) UnmarshalBinary(data []byte) error { + var buff []byte + + v.major, data = decodeArray(data) + v.minor, data = decodeArray(data) + v.patch, data = decodeArray(data) + n, data := decodeInt(data) + v.prerelases = nil + for i := 0; i < n; i++ { + buff, data = decodeArray(data) + v.prerelases = append(v.prerelases, buff) + } + v.numericPrereleases = nil + n, data = decodeInt(data) + for i := 0; i < n; i++ { + num := false + if data[0] == 1 { + num = true + } + v.numericPrereleases = append(v.numericPrereleases, num) + data = data[1:] + } + v.builds = nil + n, data = decodeInt(data) + for i := 0; i < n; i++ { + buff, data = decodeArray(data) + v.builds = append(v.builds, buff) + } + return nil +} + +// MarshalBinary implements json.Marshaler +func (v *RelaxedVersion) MarshalBinary() ([]byte, error) { + res := new(bytes.Buffer) + if len(v.customversion) > 0 { + _, _ = res.Write([]byte{0}) + _, _ = res.Write(marshalByteArray(v.customversion)) + return res.Bytes(), nil + } + res.Write([]byte{1}) + d, _ := v.version.MarshalBinary() // can't fail + _, _ = res.Write(d) + return res.Bytes(), nil +} + +// UnmarshalBinary implements json.Unmarshaler +func (v *RelaxedVersion) UnmarshalBinary(data []byte) error { + if data[0] == 0 { + v.customversion, _ = decodeArray(data[1:]) + v.version = nil + return nil + } + + v.customversion = nil + v.version = &Version{} + return v.version.UnmarshalBinary(data[1:]) +} diff --git a/binary_test.go b/binary_test.go new file mode 100644 index 0000000..c3fdda3 --- /dev/null +++ b/binary_test.go @@ -0,0 +1,77 @@ +// Copyright 2018-2022 Cristian Maglie. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// + +package semver + +import ( + "bytes" + "encoding/gob" + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGOBEncoderVersion(t *testing.T) { + testVersion := "1.2.3-aaa.4.5.6+bbb.7.8.9" + + v, err := Parse(testVersion) + require.NoError(t, err) + dumpV := fmt.Sprintf("%s,%s,%s,%s,%v,%s", v.major, v.minor, v.patch, v.prerelases, v.numericPrereleases, v.builds) + require.Equal(t, "1,2,3,[aaa 4 5 6],[false true true true],[bbb 7 8 9]", dumpV) + require.Equal(t, testVersion, v.String()) + + dataV := new(bytes.Buffer) + err = gob.NewEncoder(dataV).Encode(v) + require.NoError(t, err) + + var u Version + err = gob.NewDecoder(dataV).Decode(&u) + require.NoError(t, err) + dumpU := fmt.Sprintf("%s,%s,%s,%s,%v,%s", u.major, u.minor, u.patch, u.prerelases, u.numericPrereleases, u.builds) + + require.Equal(t, dumpV, dumpU) + require.Equal(t, testVersion, u.String()) +} + +func TestGOBEncoderRelaxedVersion(t *testing.T) { + check := func(testVersion string) { + v := ParseRelaxed(testVersion) + + dataV := new(bytes.Buffer) + err := gob.NewEncoder(dataV).Encode(v) + require.NoError(t, err) + + var u RelaxedVersion + err = gob.NewDecoder(dataV).Decode(&u) + require.NoError(t, err) + + require.Equal(t, testVersion, u.String()) + } + check("1.2.3-aaa.4.5.6+bbb.7.8.9") + check("asdasdasd-1.2.3-aaa.4.5.6+bbb.7.8.9") +} + +func BenchmarkBinaryDecoding(b *testing.B) { + testVersion := "1.2.3-aaa.4.5.6+bbb.7.8.9" + v := MustParse(testVersion) + + data, _ := v.MarshalBinary() + var u Version + for i := 0; i < b.N; i++ { + u.UnmarshalBinary(data) + } +} + +func BenchmarkBinaryDecodingRelaxed(b *testing.B) { + testVersion := "1.2.3-aaa.4.5.6+bbb.7.8.9" + v := ParseRelaxed(testVersion) + + data, _ := v.MarshalBinary() + var u RelaxedVersion + for i := 0; i < b.N; i++ { + u.UnmarshalBinary(data) + } +} From 3d72585e924c003116a87f7e7a9ce536274dbfad Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Thu, 26 May 2022 15:10:05 +0200 Subject: [PATCH 2/2] Added JSON benchmarks --- json_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/json_test.go b/json_test.go index 92db16a..e30c34a 100644 --- a/json_test.go +++ b/json_test.go @@ -62,3 +62,23 @@ func TestJSONParseRelaxedVersion(t *testing.T) { err = json.Unmarshal([]byte(`123`), &u) require.Error(t, err) } + +func BenchmarkJSONDecoding(b *testing.B) { + testVersion := "1.2.3-aaa.4.5.6+bbb.7.8.9" + v, _ := Parse(testVersion) + data, _ := json.Marshal(v) + var u Version + for i := 0; i < b.N; i++ { + json.Unmarshal(data, &u) + } +} + +func BenchmarkJSONDecodingRelaxed(b *testing.B) { + testVersion := "1.2.3-aaa.4.5.6+bbb.7.8.9" + v := ParseRelaxed(testVersion) + data, _ := json.Marshal(v) + var u RelaxedVersion + for i := 0; i < b.N; i++ { + json.Unmarshal(data, &u) + } +}