Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 6 additions & 10 deletions cmd/feature/outdated.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,11 +211,13 @@ func checkFeatureVersion(featureID string) (outdatedEntry, bool) {
// findLatestVersion finds the highest semver tag from the list that is newer
// than the current tag. Returns empty string if current is already the latest.
func findLatestVersion(current string, tags []string) string {
currentVer, currentErr := parseSemver(current)
currentVer, err := parseSemver(current)
if err != nil {
return ""
}

var best semver.Version
best := currentVer
var bestTag string
hasBest := false

for _, t := range tags {
if t == tagLatest {
Expand All @@ -227,15 +229,9 @@ func findLatestVersion(current string, tags []string) string {
continue
}

// If current tag is not valid semver, we cannot compare
if currentErr != nil {
continue
}

if ver.GT(currentVer) && (!hasBest || ver.GT(best)) {
if ver.GT(best) {
best = ver
bestTag = t
hasBest = true
}
}

Expand Down
125 changes: 125 additions & 0 deletions cmd/feature/outdated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,131 @@ func TestFindLatestVersion_MixedValidInvalid(t *testing.T) {
assert.Equal(t, "3", result)
}

func TestFindLatestVersion_EmptyAndZero(t *testing.T) {
const zeroVersion = "0.0.0"

tests := []struct {
name string
current string
tags []string
want string
}{
{
name: "nil tags",
current: "1",
},
{
name: "empty tags",
current: "1",
tags: []string{},
},
{
name: "zero version equality",
current: "0",
tags: []string{"0.0", zeroVersion},
},
{
name: "zero version upgrade",
current: zeroVersion,
tags: []string{"0", "0.0.1", zeroVersion},
want: "0.0.1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, findLatestVersion(tt.current, tt.tags))
})
}
}

func TestFindLatestVersion_OrderingAndTies(t *testing.T) {
const (
majorMinorVersion = "2.0"
fullVersion = "2.0.0"
)

tests := []struct {
name string
current string
tags []string
want string
}{
{
name: "equal normalized current",
current: "2",
tags: []string{"1.9.9", majorMinorVersion, fullVersion},
},
{
name: "unsorted maximum",
current: "1",
tags: []string{"2", "4.1", "3.9.9", "1", "abc", tagLatest},
want: "4.1",
},
{
name: "normalized tie keeps bare major first",
current: "1",
tags: []string{"2", majorMinorVersion, fullVersion},
want: "2",
},
{
name: "normalized tie keeps full version first",
current: "1",
tags: []string{fullVersion, majorMinorVersion, "2"},
want: fullVersion,
},
{
name: "build metadata tie keeps first tag",
current: "1",
tags: []string{"2.0.0+first", "2.0.0+second", fullVersion},
want: "2.0.0+first",
},
{
name: "build metadata does not upgrade current",
current: "2.0.0+current",
tags: []string{"2.0.0+other", fullVersion},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, findLatestVersion(tt.current, tt.tags))
})
}
}

func TestFindLatestVersion_Prereleases(t *testing.T) {
const releaseVersion = "2.0.0"

tests := []struct {
name string
current string
tags []string
want string
}{
{
name: "prerelease ordering",
current: "2.0.0-alpha",
tags: []string{"2.0.0-beta.2", "2.0.0-beta.10", "2.0.0-beta.1"},
want: "2.0.0-beta.10",
},
{
name: "release outranks prerelease",
current: "2.0.0-beta",
tags: []string{releaseVersion, "2.0.0-rc.1", "2.0.0-alpha"},
want: releaseVersion,
},
{
name: "prerelease below current release",
current: releaseVersion,
tags: []string{"2.0.0-rc.1", "1.9.9", releaseVersion},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, findLatestVersion(tt.current, tt.tags))
})
}
}

func TestCheckFeatureVersion_SkipsLocalPath(t *testing.T) {
_, ok := checkFeatureVersion("./local-feature")
assert.False(t, ok)
Expand Down
Loading