constellation/internal/semver/semver_test.go
Otto Bittner 1d5a8283e0
cli: use Semver type to represent microservice versions (#2125)
Previously we used strings to pass microservice versions. This invited
bugs due to missing input validation.
2023-07-25 14:20:25 +02:00

394 lines
7.8 KiB
Go

/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package semver
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
var (
v1_18_0 = Semver{major: 1, minor: 18, patch: 0}
v1_18_0Pre = Semver{major: 1, minor: 18, patch: 0, prerelease: "pre"}
v1_18_0PreExtra = Semver{major: 1, minor: 18, patch: 0, prerelease: "pre.1"}
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
want Semver
wantErr bool
}{
"valid version": {
version: "v1.18.0",
want: Semver{
major: 1,
minor: 18,
patch: 0,
},
wantErr: false,
},
"valid version prerelease": {
version: "v1.18.0-pre+yyyymmddhhmmss-abcdefabcdef",
want: Semver{
major: 1,
minor: 18,
patch: 0,
prerelease: "pre",
},
wantErr: false,
},
"only prerelease": {version: "v-pre.0.yyyymmddhhmmss-abcdefabcdef", wantErr: true},
"invalid version": {version: "v1.18. 0", wantErr: true},
"add prefix": {
version: "1.18.0",
want: Semver{
major: 1,
minor: 18,
patch: 0,
},
wantErr: false,
},
"only major.minor": {
version: "v1.18",
want: Semver{
major: 1,
minor: 18,
patch: 0,
},
wantErr: false,
},
"only major": {
version: "v1",
want: Semver{
major: 1,
minor: 0,
patch: 0,
},
wantErr: false,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
ver, err := New(tc.version)
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
assert.Equal(tc.want, ver)
}
})
}
}
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"`,
},
"prerelease": {
version: v1_18_0Pre,
wantString: `"v1.18.0-pre"`,
},
}
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,
},
"prerelease": {
version: `"v1.18.0-pre"`,
wantString: "v1.18.0-pre",
},
}
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,
},
"prerelease less than": {
version1: v1_18_0Pre,
version2: v1_18_0,
want: -1,
},
"prerelease greater than": {
version1: v1_18_0,
version2: v1_18_0Pre,
want: 1,
},
"prerelease equal": {
version1: v1_18_0Pre,
version2: v1_18_0Pre,
want: 0,
},
"prerelease extra less than": {
version1: v1_18_0Pre,
version2: v1_18_0PreExtra,
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
wantUpgrade bool
}{
"equal": {
version1: v1_18_0,
version2: v1_18_0,
wantUpgrade: false,
},
"patch less than": {
version1: v1_18_0,
version2: v1_18_1,
wantUpgrade: true,
},
"minor less then": {
version1: v1_18_0,
version2: v1_19_0,
wantUpgrade: true,
},
"minor too big drift": {
version1: v1_18_0,
version2: v1_20_0,
wantUpgrade: false,
},
"major too big drift": {
version1: v1_18_0,
version2: v2_0_0,
wantUpgrade: false,
},
"greater than": {
version1: v1_18_1,
version2: v1_18_0,
wantUpgrade: false,
},
"prerelease less than": {
version1: v1_18_0Pre,
version2: v1_18_0,
wantUpgrade: true,
},
"prerelease greater than": {
version1: v1_18_0,
version2: v1_18_0Pre,
wantUpgrade: false,
},
"prerelease equal": {
version1: v1_18_0Pre,
version2: v1_18_0Pre,
wantUpgrade: false,
},
"prerelease extra": {
version1: v1_18_0Pre,
version2: v1_18_0PreExtra,
wantUpgrade: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
assert.Equal(tc.wantUpgrade, tc.version2.IsUpgradeTo(tc.version1) == nil)
})
}
}
func TestNextMinor(t *testing.T) {
testCases := map[string]struct {
version Semver
want string
}{
"valid version": {
version: v1_18_0,
want: "v1.19",
},
"prerelease": {
version: v1_18_0Pre,
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())
})
}
}
func TestVersionMarshalYAML(t *testing.T) {
testCases := map[string]struct {
version Semver
want string
}{
"simple": {
version: Semver{
major: 1,
minor: 18,
patch: 0,
prerelease: "",
},
want: "v1.18.0\n",
},
"with prerelease": {
version: Semver{
major: 1,
minor: 18,
patch: 0,
prerelease: "pre",
},
want: "v1.18.0-pre\n",
},
"empty semver": {
version: Semver{},
want: "v0.0.0\n",
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
marshalled, err := yaml.Marshal(tc.version)
require.NoError(t, err)
require.Equal(t, tc.want, string(marshalled))
var unmarshalled Semver
err = yaml.Unmarshal(marshalled, &unmarshalled)
require.NoError(t, err)
require.Equal(t, tc.version, unmarshalled)
})
}
}
func TestVersionUnmarshalYAML(t *testing.T) {
testCases := map[string]struct {
version []byte
want Semver
wantError bool
}{
"empty string": {
version: []byte(""),
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
var actual Semver
err := yaml.Unmarshal(tc.version, &actual)
if tc.wantError {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tc.want.Compare(actual), 0, fmt.Sprintf("expected %s, got %s", tc.want, actual))
})
}
}
func TestSort(t *testing.T) {
testCases := map[string]struct {
input []Semver
want []Semver
}{
"": {
input: []Semver{NewFromInt(2, 0, 0, ""), NewFromInt(0, 0, 0, ""), NewFromInt(1, 5, 0, "aa"), NewFromInt(1, 5, 0, "bb"), NewFromInt(1, 0, 0, "")},
want: []Semver{NewFromInt(0, 0, 0, ""), NewFromInt(1, 0, 0, ""), NewFromInt(1, 5, 0, "aa"), NewFromInt(1, 5, 0, "bb"), NewFromInt(2, 0, 0, "")},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
Sort(tc.input)
require.Equal(t, tc.want, tc.input, fmt.Sprintf("expected %s, got %s", tc.want, tc.input))
})
}
}