mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-10-01 01:36:09 -04:00
internal: add custom version type (#1256)
* add custom version type * extend functionality * adapt to requested changes * move to own package * remove duplicate tests, rename package * not handle err
This commit is contained in:
parent
984f0589d2
commit
90ed470178
105
internal/semver/semver.go
Normal file
105
internal/semver/semver.go
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/*
|
||||
Package semver provides functionality to parse and process semantic versions, as they are used in multiple components of Constellation.
|
||||
*/
|
||||
package semver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
// Semver represents a semantic version.
|
||||
type Semver struct {
|
||||
Major int
|
||||
Minor int
|
||||
Patch int
|
||||
}
|
||||
|
||||
// NewSemver returns a Version from a string.
|
||||
func NewSemver(version string) (Semver, error) {
|
||||
// ensure that semver has "v" prefix
|
||||
if !strings.HasPrefix(version, "v") {
|
||||
version = "v" + version
|
||||
}
|
||||
|
||||
if !semver.IsValid(version) {
|
||||
return Semver{}, fmt.Errorf("invalid semver: %s", version)
|
||||
}
|
||||
|
||||
version = semver.Canonical(version)
|
||||
|
||||
var major, minor, patch int
|
||||
_, err := fmt.Sscanf(version, "v%d.%d.%d", &major, &minor, &patch)
|
||||
if err != nil {
|
||||
return Semver{}, fmt.Errorf("parsing semver parts: %w", err)
|
||||
}
|
||||
|
||||
return Semver{
|
||||
Major: major,
|
||||
Minor: minor,
|
||||
Patch: patch,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns the string representation of the version.
|
||||
func (v Semver) String() string {
|
||||
return fmt.Sprintf("v%d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||
}
|
||||
|
||||
// Compare compares two versions. It relies on the semver.Compare function internally.
|
||||
func (v Semver) Compare(other Semver) int {
|
||||
return semver.Compare(v.String(), other.String())
|
||||
}
|
||||
|
||||
// IsUpgradeTo returns if a version is an upgrade to another version.
|
||||
// It checks if the version of v is greater than the version of other and allows a drift of at most one minor version.
|
||||
func (v Semver) IsUpgradeTo(other Semver) bool {
|
||||
return v.Compare(other) > 0 && v.Major == other.Major && v.Minor-other.Minor <= 1
|
||||
}
|
||||
|
||||
// CompatibleWithBinary returns if a version is compatible version of the current built binary.
|
||||
// It checks if the version of the binary is equal or greater than the current version and allows a drift of at most one minor version.
|
||||
func (v Semver) CompatibleWithBinary() bool {
|
||||
binaryVersion, err := NewSemver(constants.VersionInfo)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return v.Compare(binaryVersion) == 0 || binaryVersion.IsUpgradeTo(v)
|
||||
}
|
||||
|
||||
// NextMinor returns the next minor version in the format "vMAJOR.MINOR".
|
||||
func (v Semver) NextMinor() string {
|
||||
return fmt.Sprintf("v%d.%d", v.Major, v.Minor+1)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (v Semver) MarshalJSON() ([]byte, error) {
|
||||
return []byte(fmt.Sprintf(`"%s"`, v.String())), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (v *Semver) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
version, err := NewSemver(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*v = version
|
||||
return nil
|
||||
}
|
207
internal/semver/semver_test.go
Normal file
207
internal/semver/semver_test.go
Normal file
@ -0,0 +1,207 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package semver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
v1_18_0 = Semver{Major: 1, Minor: 18, Patch: 0}
|
||||
v1_19_0 = Semver{Major: 1, Minor: 19, Patch: 0}
|
||||
v1_18_1 = Semver{Major: 1, Minor: 18, Patch: 1}
|
||||
v1_20_0 = Semver{Major: 1, Minor: 20, Patch: 0}
|
||||
v2_0_0 = Semver{Major: 2, Minor: 0, Patch: 0}
|
||||
)
|
||||
|
||||
func TestNewVersion(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
version string
|
||||
wantErr bool
|
||||
}{
|
||||
"valid version": {"v1.18.0", false},
|
||||
"invalid version": {"v1.18. 0", true},
|
||||
"add prefix": {"1.18.0", false},
|
||||
"only major.minor": {"v1.18", false},
|
||||
"only major": {"v1", false},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
_, err := NewSemver(tc.version)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONMarshal(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
version Semver
|
||||
wantString string
|
||||
wantErr bool
|
||||
}{
|
||||
"valid version": {
|
||||
version: v1_18_0,
|
||||
wantString: `"v1.18.0"`,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
b, err := tc.version.MarshalJSON()
|
||||
if tc.wantErr {
|
||||
require.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.wantString, string(b))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONUnmarshal(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
version string
|
||||
wantString string
|
||||
wantErr bool
|
||||
}{
|
||||
"valid version": {
|
||||
version: `"v1.18.0"`,
|
||||
wantString: "v1.18.0",
|
||||
},
|
||||
"invalid version": {
|
||||
version: `"v1. 18.0"`,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
var version Semver
|
||||
err := version.UnmarshalJSON([]byte(tc.version))
|
||||
if tc.wantErr {
|
||||
require.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.wantString, version.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestComparison(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
version1 Semver
|
||||
version2 Semver
|
||||
want int
|
||||
}{
|
||||
"equal": {
|
||||
version1: v1_18_0,
|
||||
version2: v1_18_0,
|
||||
want: 0,
|
||||
},
|
||||
"less than": {
|
||||
version1: v1_18_0,
|
||||
version2: v1_18_1,
|
||||
want: -1,
|
||||
},
|
||||
"greater than": {
|
||||
version1: v1_18_1,
|
||||
version2: v1_18_0,
|
||||
want: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
assert.Equal(tc.want, tc.version1.Compare(tc.version2))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanUpgrade(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
version1 Semver
|
||||
version2 Semver
|
||||
want bool
|
||||
wantErr bool
|
||||
}{
|
||||
"equal": {
|
||||
version1: v1_18_0,
|
||||
version2: v1_18_0,
|
||||
want: false,
|
||||
},
|
||||
"patch less than": {
|
||||
version1: v1_18_0,
|
||||
version2: v1_18_1,
|
||||
want: true,
|
||||
},
|
||||
"minor less then": {
|
||||
version1: v1_18_0,
|
||||
version2: v1_19_0,
|
||||
want: true,
|
||||
},
|
||||
"minor too big drift": {
|
||||
version1: v1_18_0,
|
||||
version2: v1_20_0,
|
||||
want: false,
|
||||
},
|
||||
"major too big drift": {
|
||||
version1: v1_18_0,
|
||||
version2: v2_0_0,
|
||||
want: false,
|
||||
},
|
||||
"greater than": {
|
||||
version1: v1_18_1,
|
||||
version2: v1_18_0,
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
assert.Equal(tc.want, tc.version2.IsUpgradeTo(tc.version1))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextMinor(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
version Semver
|
||||
want string
|
||||
}{
|
||||
"valid version": {
|
||||
version: v1_18_0,
|
||||
want: "v1.19",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
assert.Equal(tc.want, tc.version.NextMinor())
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user