internal: semver support for pseudoversions (#1564)

* support for prerelease tag / pseudoversion

* build version first

Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com>

* use strings.Cut

Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com>

---------

Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com>
This commit is contained in:
Moritz Sanft 2023-04-03 10:48:28 +02:00 committed by GitHub
parent 62c165750f
commit 2d41a19fbf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 124 additions and 18 deletions

View File

@ -23,6 +23,7 @@ type Semver struct {
Major int Major int
Minor int Minor int
Patch int Patch int
Prerelease string
} }
// New returns a Version from a string. // New returns a Version from a string.
@ -39,6 +40,7 @@ func New(version string) (Semver, error) {
version = semver.Canonical(version) version = semver.Canonical(version)
var major, minor, patch int var major, minor, patch int
_, pre, _ := strings.Cut(version, "-")
_, err := fmt.Sscanf(version, "v%d.%d.%d", &major, &minor, &patch) _, err := fmt.Sscanf(version, "v%d.%d.%d", &major, &minor, &patch)
if err != nil { if err != nil {
return Semver{}, fmt.Errorf("parsing semver parts: %w", err) return Semver{}, fmt.Errorf("parsing semver parts: %w", err)
@ -48,12 +50,17 @@ func New(version string) (Semver, error) {
Major: major, Major: major,
Minor: minor, Minor: minor,
Patch: patch, Patch: patch,
Prerelease: pre,
}, nil }, nil
} }
// String returns the string representation of the version. // String returns the string representation of the version.
func (v Semver) String() string { func (v Semver) String() string {
return fmt.Sprintf("v%d.%d.%d", v.Major, v.Minor, v.Patch) version := fmt.Sprintf("v%d.%d.%d", v.Major, v.Minor, v.Patch)
if v.Prerelease != "" {
return fmt.Sprintf("%s-%s", version, v.Prerelease)
}
return version
} }
// Compare compares two versions. It relies on the semver.Compare function internally. // Compare compares two versions. It relies on the semver.Compare function internally.

View File

@ -15,6 +15,8 @@ import (
var ( var (
v1_18_0 = Semver{Major: 1, Minor: 18, Patch: 0} 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_19_0 = Semver{Major: 1, Minor: 19, Patch: 0}
v1_18_1 = Semver{Major: 1, Minor: 18, Patch: 1} v1_18_1 = Semver{Major: 1, Minor: 18, Patch: 1}
v1_20_0 = Semver{Major: 1, Minor: 20, Patch: 0} v1_20_0 = Semver{Major: 1, Minor: 20, Patch: 0}
@ -24,24 +26,69 @@ var (
func TestNewVersion(t *testing.T) { func TestNewVersion(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
version string version string
want Semver
wantErr bool wantErr bool
}{ }{
"valid version": {"v1.18.0", false}, "valid version": {
"invalid version": {"v1.18. 0", true}, version: "v1.18.0",
"add prefix": {"1.18.0", false}, want: Semver{
"only major.minor": {"v1.18", false}, Major: 1,
"only major": {"v1", false}, 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 { for name, tc := range testCases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
_, err := New(tc.version) ver, err := New(tc.version)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
} else { } else {
assert.NoError(err) assert.NoError(err)
assert.Equal(tc.want, ver)
} }
}) })
} }
@ -57,6 +104,10 @@ func TestJSONMarshal(t *testing.T) {
version: v1_18_0, version: v1_18_0,
wantString: `"v1.18.0"`, wantString: `"v1.18.0"`,
}, },
"prerelease": {
version: v1_18_0Pre,
wantString: `"v1.18.0-pre"`,
},
} }
for name, tc := range testCases { for name, tc := range testCases {
@ -89,6 +140,10 @@ func TestJSONUnmarshal(t *testing.T) {
version: `"v1. 18.0"`, version: `"v1. 18.0"`,
wantErr: true, wantErr: true,
}, },
"prerelease": {
version: `"v1.18.0-pre"`,
wantString: "v1.18.0-pre",
},
} }
for name, tc := range testCases { for name, tc := range testCases {
@ -129,6 +184,26 @@ func TestComparison(t *testing.T) {
version2: v1_18_0, version2: v1_18_0,
want: 1, 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 { for name, tc := range testCases {
@ -176,6 +251,26 @@ func TestCanUpgrade(t *testing.T) {
version2: v1_18_0, version2: v1_18_0,
want: false, want: false,
}, },
"prerelease less than": {
version1: v1_18_0Pre,
version2: v1_18_0,
want: true,
},
"prerelease greater than": {
version1: v1_18_0,
version2: v1_18_0Pre,
want: false,
},
"prerelease equal": {
version1: v1_18_0Pre,
version2: v1_18_0Pre,
want: false,
},
"prerelease extra": {
version1: v1_18_0Pre,
version2: v1_18_0PreExtra,
want: true,
},
} }
for name, tc := range testCases { for name, tc := range testCases {
@ -196,6 +291,10 @@ func TestNextMinor(t *testing.T) {
version: v1_18_0, version: v1_18_0,
want: "v1.19", want: "v1.19",
}, },
"prerelease": {
version: v1_18_0Pre,
want: "v1.19",
},
} }
for name, tc := range testCases { for name, tc := range testCases {