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
74 changes: 74 additions & 0 deletions highest_satisfying_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package vers

import "testing"

func TestHighestSatisfying_Npm(t *testing.T) {
versions := []string{"1.0.0", "1.5.0", "2.0.0", "2.5.0", "3.0.0"}
cases := []struct {
constraint string
want string
}{
{"^1.0", "1.5.0"},
{"^2.0.0", "2.5.0"},
{"~2.0.0", "2.0.0"},
{">=1.0.0 <2.0.0", "1.5.0"},
{"^4.0.0", ""}, // no satisfying version
}
for _, tc := range cases {
got, err := HighestSatisfying(versions, tc.constraint, "npm")
if err != nil {
t.Errorf("HighestSatisfying(%q): %v", tc.constraint, err)
continue
}
if got != tc.want {
t.Errorf("HighestSatisfying(%q) = %q, want %q", tc.constraint, got, tc.want)
}
}
}

func TestHighestSatisfying_OrderIndependent(t *testing.T) {
// Same set, different order — picks the same highest.
a := []string{"1.0.0", "2.0.0", "1.5.0"}
b := []string{"2.0.0", "1.5.0", "1.0.0"}
gotA, _ := HighestSatisfying(a, "^1.0", "npm")
gotB, _ := HighestSatisfying(b, "^1.0", "npm")
if gotA != "1.5.0" || gotB != "1.5.0" {
t.Errorf("order mismatch: a=%q b=%q", gotA, gotB)
}
}

func TestHighestSatisfying_SkipsInvalidVersions(t *testing.T) {
// Garbage versions should be skipped, not stop the walk.
versions := []string{"not-a-version", "1.0.0", "also-bad", "1.5.0"}
got, err := HighestSatisfying(versions, "^1.0", "npm")
if err != nil {
t.Fatal(err)
}
if got != "1.5.0" {
t.Errorf("got %q, want 1.5.0", got)
}
}

func TestHighestSatisfying_VersURI(t *testing.T) {
// Empty scheme → constraint is a vers URI rather than native syntax.
got, err := HighestSatisfying(
[]string{"1.0.0", "1.5.0", "2.0.0"},
"vers:npm/>=1.0.0|<2.0.0",
"")
if err != nil {
t.Fatal(err)
}
if got != "1.5.0" {
t.Errorf("got %q, want 1.5.0", got)
}
}

func TestHighestSatisfying_Empty(t *testing.T) {
got, err := HighestSatisfying(nil, "^1.0", "npm")
if err != nil {
t.Fatal(err)
}
if got != "" {
t.Errorf("got %q, want empty", got)
}
}
35 changes: 35 additions & 0 deletions vers.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,41 @@ func Compare(a, b string) int {
return CompareVersions(a, b)
}

// HighestSatisfying returns the highest version in versions that
// satisfies constraint under the given scheme. Versions that fail to
// parse are skipped. Returns ("", nil) when no version in the list
// satisfies the constraint — a non-nil error is reserved for a
// constraint that itself fails to parse.
//
// Common shape for package-manager resolvers: fetch the list of
// available versions from the registry, then pick the highest one
// that still satisfies the user's manifest constraint.
//
// If scheme is empty, constraint is parsed as a vers URI.
func HighestSatisfying(versions []string, constraint, scheme string) (string, error) {
var r *Range
var err error
if scheme == "" {
r, err = Parse(constraint)
} else {
r, err = ParseNative(constraint, scheme)
}
if err != nil {
return "", err
}

var best string
for _, v := range versions {
if !r.Contains(v) {
continue
}
if best == "" || CompareWithScheme(v, best, scheme) > 0 {
best = v
}
}
return best, nil
}

// Valid checks if a version string is valid.
func Valid(version string) bool {
_, err := ParseVersion(version)
Expand Down