24df2538db
Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
581 lines
13 KiB
Go
581 lines
13 KiB
Go
package semver
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
type wildcardTypeTest struct {
|
|
input string
|
|
wildcardType wildcardType
|
|
}
|
|
|
|
type comparatorTest struct {
|
|
input string
|
|
comparator func(comparator) bool
|
|
}
|
|
|
|
func TestParseComparator(t *testing.T) {
|
|
compatorTests := []comparatorTest{
|
|
{">", testGT},
|
|
{">=", testGE},
|
|
{"<", testLT},
|
|
{"<=", testLE},
|
|
{"", testEQ},
|
|
{"=", testEQ},
|
|
{"==", testEQ},
|
|
{"!=", testNE},
|
|
{"!", testNE},
|
|
{"-", nil},
|
|
{"<==", nil},
|
|
{"<<", nil},
|
|
{">>", nil},
|
|
}
|
|
|
|
for _, tc := range compatorTests {
|
|
if c := parseComparator(tc.input); c == nil {
|
|
if tc.comparator != nil {
|
|
t.Errorf("Comparator nil for case %q\n", tc.input)
|
|
}
|
|
} else if !tc.comparator(c) {
|
|
t.Errorf("Invalid comparator for case %q\n", tc.input)
|
|
}
|
|
}
|
|
}
|
|
|
|
var (
|
|
v1 = MustParse("1.2.2")
|
|
v2 = MustParse("1.2.3")
|
|
v3 = MustParse("1.2.4")
|
|
)
|
|
|
|
func testEQ(f comparator) bool {
|
|
return f(v1, v1) && !f(v1, v2)
|
|
}
|
|
|
|
func testNE(f comparator) bool {
|
|
return !f(v1, v1) && f(v1, v2)
|
|
}
|
|
|
|
func testGT(f comparator) bool {
|
|
return f(v2, v1) && f(v3, v2) && !f(v1, v2) && !f(v1, v1)
|
|
}
|
|
|
|
func testGE(f comparator) bool {
|
|
return f(v2, v1) && f(v3, v2) && !f(v1, v2)
|
|
}
|
|
|
|
func testLT(f comparator) bool {
|
|
return f(v1, v2) && f(v2, v3) && !f(v2, v1) && !f(v1, v1)
|
|
}
|
|
|
|
func testLE(f comparator) bool {
|
|
return f(v1, v2) && f(v2, v3) && !f(v2, v1)
|
|
}
|
|
|
|
func TestSplitAndTrim(t *testing.T) {
|
|
tests := []struct {
|
|
i string
|
|
s []string
|
|
}{
|
|
{"1.2.3 1.2.3", []string{"1.2.3", "1.2.3"}},
|
|
{" 1.2.3 1.2.3 ", []string{"1.2.3", "1.2.3"}}, // Spaces
|
|
{" >= 1.2.3 <= 1.2.3 ", []string{">=1.2.3", "<=1.2.3"}}, // Spaces between operator and version
|
|
{"1.2.3 || >=1.2.3 <1.2.3", []string{"1.2.3", "||", ">=1.2.3", "<1.2.3"}},
|
|
{" 1.2.3 || >=1.2.3 <1.2.3 ", []string{"1.2.3", "||", ">=1.2.3", "<1.2.3"}},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
p := splitAndTrim(tc.i)
|
|
if !reflect.DeepEqual(p, tc.s) {
|
|
t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSplitComparatorVersion(t *testing.T) {
|
|
tests := []struct {
|
|
i string
|
|
p []string
|
|
}{
|
|
{">1.2.3", []string{">", "1.2.3"}},
|
|
{">=1.2.3", []string{">=", "1.2.3"}},
|
|
{"<1.2.3", []string{"<", "1.2.3"}},
|
|
{"<=1.2.3", []string{"<=", "1.2.3"}},
|
|
{"1.2.3", []string{"", "1.2.3"}},
|
|
{"=1.2.3", []string{"=", "1.2.3"}},
|
|
{"==1.2.3", []string{"==", "1.2.3"}},
|
|
{"!=1.2.3", []string{"!=", "1.2.3"}},
|
|
{"!1.2.3", []string{"!", "1.2.3"}},
|
|
{"error", nil},
|
|
}
|
|
for _, tc := range tests {
|
|
if op, v, err := splitComparatorVersion(tc.i); err != nil {
|
|
if tc.p != nil {
|
|
t.Errorf("Invalid for case %q: Expected %q, got error %q", tc.i, tc.p, err)
|
|
}
|
|
} else if op != tc.p[0] {
|
|
t.Errorf("Invalid operator for case %q: Expected %q, got: %q", tc.i, tc.p[0], op)
|
|
} else if v != tc.p[1] {
|
|
t.Errorf("Invalid version for case %q: Expected %q, got: %q", tc.i, tc.p[1], v)
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func TestBuildVersionRange(t *testing.T) {
|
|
tests := []struct {
|
|
opStr string
|
|
vStr string
|
|
c func(comparator) bool
|
|
v string
|
|
}{
|
|
{">", "1.2.3", testGT, "1.2.3"},
|
|
{">=", "1.2.3", testGE, "1.2.3"},
|
|
{"<", "1.2.3", testLT, "1.2.3"},
|
|
{"<=", "1.2.3", testLE, "1.2.3"},
|
|
{"", "1.2.3", testEQ, "1.2.3"},
|
|
{"=", "1.2.3", testEQ, "1.2.3"},
|
|
{"==", "1.2.3", testEQ, "1.2.3"},
|
|
{"!=", "1.2.3", testNE, "1.2.3"},
|
|
{"!", "1.2.3", testNE, "1.2.3"},
|
|
{">>", "1.2.3", nil, ""}, // Invalid comparator
|
|
{"=", "invalid", nil, ""}, // Invalid version
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
if r, err := buildVersionRange(tc.opStr, tc.vStr); err != nil {
|
|
if tc.c != nil {
|
|
t.Errorf("Invalid for case %q: Expected %q, got error %q", strings.Join([]string{tc.opStr, tc.vStr}, ""), tc.v, err)
|
|
}
|
|
} else if r == nil {
|
|
t.Errorf("Invalid for case %q: got nil", strings.Join([]string{tc.opStr, tc.vStr}, ""))
|
|
} else {
|
|
// test version
|
|
if tv := MustParse(tc.v); !r.v.EQ(tv) {
|
|
t.Errorf("Invalid for case %q: Expected version %q, got: %q", strings.Join([]string{tc.opStr, tc.vStr}, ""), tv, r.v)
|
|
}
|
|
// test comparator
|
|
if r.c == nil {
|
|
t.Errorf("Invalid for case %q: got nil comparator", strings.Join([]string{tc.opStr, tc.vStr}, ""))
|
|
continue
|
|
}
|
|
if !tc.c(r.c) {
|
|
t.Errorf("Invalid comparator for case %q\n", strings.Join([]string{tc.opStr, tc.vStr}, ""))
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func TestSplitORParts(t *testing.T) {
|
|
tests := []struct {
|
|
i []string
|
|
o [][]string
|
|
}{
|
|
{[]string{">1.2.3", "||", "<1.2.3", "||", "=1.2.3"}, [][]string{
|
|
[]string{">1.2.3"},
|
|
[]string{"<1.2.3"},
|
|
[]string{"=1.2.3"},
|
|
}},
|
|
{[]string{">1.2.3", "<1.2.3", "||", "=1.2.3"}, [][]string{
|
|
[]string{">1.2.3", "<1.2.3"},
|
|
[]string{"=1.2.3"},
|
|
}},
|
|
{[]string{">1.2.3", "||"}, nil},
|
|
{[]string{"||", ">1.2.3"}, nil},
|
|
}
|
|
for _, tc := range tests {
|
|
o, err := splitORParts(tc.i)
|
|
if err != nil && tc.o != nil {
|
|
t.Errorf("Unexpected error for case %q: %s", tc.i, err)
|
|
}
|
|
if !reflect.DeepEqual(tc.o, o) {
|
|
t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.o, o)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetWildcardType(t *testing.T) {
|
|
wildcardTypeTests := []wildcardTypeTest{
|
|
{"x", majorWildcard},
|
|
{"1.x", minorWildcard},
|
|
{"1.2.x", patchWildcard},
|
|
{"fo.o.b.ar", noneWildcard},
|
|
}
|
|
|
|
for _, tc := range wildcardTypeTests {
|
|
o := getWildcardType(tc.input)
|
|
if o != tc.wildcardType {
|
|
t.Errorf("Invalid for case: %q: Expected %q, got: %q", tc.input, tc.wildcardType, o)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCreateVersionFromWildcard(t *testing.T) {
|
|
tests := []struct {
|
|
i string
|
|
s string
|
|
}{
|
|
{"1.2.x", "1.2.0"},
|
|
{"1.x", "1.0.0"},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
p := createVersionFromWildcard(tc.i)
|
|
if p != tc.s {
|
|
t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIncrementMajorVersion(t *testing.T) {
|
|
tests := []struct {
|
|
i string
|
|
s string
|
|
}{
|
|
{"1.2.3", "2.2.3"},
|
|
{"1.2", "2.2"},
|
|
{"foo.bar", ""},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
p, _ := incrementMajorVersion(tc.i)
|
|
if p != tc.s {
|
|
t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIncrementMinorVersion(t *testing.T) {
|
|
tests := []struct {
|
|
i string
|
|
s string
|
|
}{
|
|
{"1.2.3", "1.3.3"},
|
|
{"1.2", "1.3"},
|
|
{"foo.bar", ""},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
p, _ := incrementMinorVersion(tc.i)
|
|
if p != tc.s {
|
|
t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExpandWildcardVersion(t *testing.T) {
|
|
tests := []struct {
|
|
i [][]string
|
|
o [][]string
|
|
}{
|
|
{[][]string{[]string{"foox"}}, nil},
|
|
{[][]string{[]string{">=1.2.x"}}, [][]string{[]string{">=1.2.0"}}},
|
|
{[][]string{[]string{"<=1.2.x"}}, [][]string{[]string{"<1.3.0"}}},
|
|
{[][]string{[]string{">1.2.x"}}, [][]string{[]string{">=1.3.0"}}},
|
|
{[][]string{[]string{"<1.2.x"}}, [][]string{[]string{"<1.2.0"}}},
|
|
{[][]string{[]string{"!=1.2.x"}}, [][]string{[]string{"<1.2.0", ">=1.3.0"}}},
|
|
{[][]string{[]string{">=1.x"}}, [][]string{[]string{">=1.0.0"}}},
|
|
{[][]string{[]string{"<=1.x"}}, [][]string{[]string{"<2.0.0"}}},
|
|
{[][]string{[]string{">1.x"}}, [][]string{[]string{">=2.0.0"}}},
|
|
{[][]string{[]string{"<1.x"}}, [][]string{[]string{"<1.0.0"}}},
|
|
{[][]string{[]string{"!=1.x"}}, [][]string{[]string{"<1.0.0", ">=2.0.0"}}},
|
|
{[][]string{[]string{"1.2.x"}}, [][]string{[]string{">=1.2.0", "<1.3.0"}}},
|
|
{[][]string{[]string{"1.x"}}, [][]string{[]string{">=1.0.0", "<2.0.0"}}},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
o, _ := expandWildcardVersion(tc.i)
|
|
if !reflect.DeepEqual(tc.o, o) {
|
|
t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.o, o)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestVersionRangeToRange(t *testing.T) {
|
|
vr := versionRange{
|
|
v: MustParse("1.2.3"),
|
|
c: compLT,
|
|
}
|
|
rf := vr.rangeFunc()
|
|
if !rf(MustParse("1.2.2")) || rf(MustParse("1.2.3")) {
|
|
t.Errorf("Invalid conversion to range func")
|
|
}
|
|
}
|
|
|
|
func TestRangeAND(t *testing.T) {
|
|
v := MustParse("1.2.2")
|
|
v1 := MustParse("1.2.1")
|
|
v2 := MustParse("1.2.3")
|
|
rf1 := Range(func(v Version) bool {
|
|
return v.GT(v1)
|
|
})
|
|
rf2 := Range(func(v Version) bool {
|
|
return v.LT(v2)
|
|
})
|
|
rf := rf1.AND(rf2)
|
|
if rf(v1) {
|
|
t.Errorf("Invalid rangefunc, accepted: %s", v1)
|
|
}
|
|
if rf(v2) {
|
|
t.Errorf("Invalid rangefunc, accepted: %s", v2)
|
|
}
|
|
if !rf(v) {
|
|
t.Errorf("Invalid rangefunc, did not accept: %s", v)
|
|
}
|
|
}
|
|
|
|
func TestRangeOR(t *testing.T) {
|
|
tests := []struct {
|
|
v Version
|
|
b bool
|
|
}{
|
|
{MustParse("1.2.0"), true},
|
|
{MustParse("1.2.2"), false},
|
|
{MustParse("1.2.4"), true},
|
|
}
|
|
v1 := MustParse("1.2.1")
|
|
v2 := MustParse("1.2.3")
|
|
rf1 := Range(func(v Version) bool {
|
|
return v.LT(v1)
|
|
})
|
|
rf2 := Range(func(v Version) bool {
|
|
return v.GT(v2)
|
|
})
|
|
rf := rf1.OR(rf2)
|
|
for _, tc := range tests {
|
|
if r := rf(tc.v); r != tc.b {
|
|
t.Errorf("Invalid for case %q: Expected %t, got %t", tc.v, tc.b, r)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseRange(t *testing.T) {
|
|
type tv struct {
|
|
v string
|
|
b bool
|
|
}
|
|
tests := []struct {
|
|
i string
|
|
t []tv
|
|
}{
|
|
// Simple expressions
|
|
{">1.2.3", []tv{
|
|
{"1.2.2", false},
|
|
{"1.2.3", false},
|
|
{"1.2.4", true},
|
|
}},
|
|
{">=1.2.3", []tv{
|
|
{"1.2.3", true},
|
|
{"1.2.4", true},
|
|
{"1.2.2", false},
|
|
}},
|
|
{"<1.2.3", []tv{
|
|
{"1.2.2", true},
|
|
{"1.2.3", false},
|
|
{"1.2.4", false},
|
|
}},
|
|
{"<=1.2.3", []tv{
|
|
{"1.2.2", true},
|
|
{"1.2.3", true},
|
|
{"1.2.4", false},
|
|
}},
|
|
{"1.2.3", []tv{
|
|
{"1.2.2", false},
|
|
{"1.2.3", true},
|
|
{"1.2.4", false},
|
|
}},
|
|
{"=1.2.3", []tv{
|
|
{"1.2.2", false},
|
|
{"1.2.3", true},
|
|
{"1.2.4", false},
|
|
}},
|
|
{"==1.2.3", []tv{
|
|
{"1.2.2", false},
|
|
{"1.2.3", true},
|
|
{"1.2.4", false},
|
|
}},
|
|
{"!=1.2.3", []tv{
|
|
{"1.2.2", true},
|
|
{"1.2.3", false},
|
|
{"1.2.4", true},
|
|
}},
|
|
{"!1.2.3", []tv{
|
|
{"1.2.2", true},
|
|
{"1.2.3", false},
|
|
{"1.2.4", true},
|
|
}},
|
|
// Simple Expression errors
|
|
{">>1.2.3", nil},
|
|
{"!1.2.3", nil},
|
|
{"1.0", nil},
|
|
{"string", nil},
|
|
{"", nil},
|
|
{"fo.ob.ar.x", nil},
|
|
// AND Expressions
|
|
{">1.2.2 <1.2.4", []tv{
|
|
{"1.2.2", false},
|
|
{"1.2.3", true},
|
|
{"1.2.4", false},
|
|
}},
|
|
{"<1.2.2 <1.2.4", []tv{
|
|
{"1.2.1", true},
|
|
{"1.2.2", false},
|
|
{"1.2.3", false},
|
|
{"1.2.4", false},
|
|
}},
|
|
{">1.2.2 <1.2.5 !=1.2.4", []tv{
|
|
{"1.2.2", false},
|
|
{"1.2.3", true},
|
|
{"1.2.4", false},
|
|
{"1.2.5", false},
|
|
}},
|
|
{">1.2.2 <1.2.5 !1.2.4", []tv{
|
|
{"1.2.2", false},
|
|
{"1.2.3", true},
|
|
{"1.2.4", false},
|
|
{"1.2.5", false},
|
|
}},
|
|
// OR Expressions
|
|
{">1.2.2 || <1.2.4", []tv{
|
|
{"1.2.2", true},
|
|
{"1.2.3", true},
|
|
{"1.2.4", true},
|
|
}},
|
|
{"<1.2.2 || >1.2.4", []tv{
|
|
{"1.2.2", false},
|
|
{"1.2.3", false},
|
|
{"1.2.4", false},
|
|
}},
|
|
// Wildcard expressions
|
|
{">1.x", []tv{
|
|
{"0.1.9", false},
|
|
{"1.2.6", false},
|
|
{"1.9.0", false},
|
|
{"2.0.0", true},
|
|
}},
|
|
{">1.2.x", []tv{
|
|
{"1.1.9", false},
|
|
{"1.2.6", false},
|
|
{"1.3.0", true},
|
|
}},
|
|
// Combined Expressions
|
|
{">1.2.2 <1.2.4 || >=2.0.0", []tv{
|
|
{"1.2.2", false},
|
|
{"1.2.3", true},
|
|
{"1.2.4", false},
|
|
{"2.0.0", true},
|
|
{"2.0.1", true},
|
|
}},
|
|
{"1.x || >=2.0.x <2.2.x", []tv{
|
|
{"0.9.2", false},
|
|
{"1.2.2", true},
|
|
{"2.0.0", true},
|
|
{"2.1.8", true},
|
|
{"2.2.0", false},
|
|
}},
|
|
{">1.2.2 <1.2.4 || >=2.0.0 <3.0.0", []tv{
|
|
{"1.2.2", false},
|
|
{"1.2.3", true},
|
|
{"1.2.4", false},
|
|
{"2.0.0", true},
|
|
{"2.0.1", true},
|
|
{"2.9.9", true},
|
|
{"3.0.0", false},
|
|
}},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
r, err := ParseRange(tc.i)
|
|
if err != nil && tc.t != nil {
|
|
t.Errorf("Error parsing range %q: %s", tc.i, err)
|
|
continue
|
|
}
|
|
for _, tvc := range tc.t {
|
|
v := MustParse(tvc.v)
|
|
if res := r(v); res != tvc.b {
|
|
t.Errorf("Invalid for case %q matching %q: Expected %t, got: %t", tc.i, tvc.v, tvc.b, res)
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func TestMustParseRange(t *testing.T) {
|
|
testCase := ">1.2.2 <1.2.4 || >=2.0.0 <3.0.0"
|
|
r := MustParseRange(testCase)
|
|
if !r(MustParse("1.2.3")) {
|
|
t.Errorf("Unexpected range behavior on MustParseRange")
|
|
}
|
|
}
|
|
|
|
func TestMustParseRange_panic(t *testing.T) {
|
|
defer func() {
|
|
if recover() == nil {
|
|
t.Errorf("Should have panicked")
|
|
}
|
|
}()
|
|
_ = MustParseRange("invalid version")
|
|
}
|
|
|
|
func BenchmarkRangeParseSimple(b *testing.B) {
|
|
const VERSION = ">1.0.0"
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for n := 0; n < b.N; n++ {
|
|
ParseRange(VERSION)
|
|
}
|
|
}
|
|
|
|
func BenchmarkRangeParseAverage(b *testing.B) {
|
|
const VERSION = ">=1.0.0 <2.0.0"
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for n := 0; n < b.N; n++ {
|
|
ParseRange(VERSION)
|
|
}
|
|
}
|
|
|
|
func BenchmarkRangeParseComplex(b *testing.B) {
|
|
const VERSION = ">=1.0.0 <2.0.0 || >=3.0.1 <4.0.0 !=3.0.3 || >=5.0.0"
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for n := 0; n < b.N; n++ {
|
|
ParseRange(VERSION)
|
|
}
|
|
}
|
|
|
|
func BenchmarkRangeMatchSimple(b *testing.B) {
|
|
const VERSION = ">1.0.0"
|
|
r, _ := ParseRange(VERSION)
|
|
v := MustParse("2.0.0")
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for n := 0; n < b.N; n++ {
|
|
r(v)
|
|
}
|
|
}
|
|
|
|
func BenchmarkRangeMatchAverage(b *testing.B) {
|
|
const VERSION = ">=1.0.0 <2.0.0"
|
|
r, _ := ParseRange(VERSION)
|
|
v := MustParse("1.2.3")
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for n := 0; n < b.N; n++ {
|
|
r(v)
|
|
}
|
|
}
|
|
|
|
func BenchmarkRangeMatchComplex(b *testing.B) {
|
|
const VERSION = ">=1.0.0 <2.0.0 || >=3.0.1 <4.0.0 !=3.0.3 || >=5.0.0"
|
|
r, _ := ParseRange(VERSION)
|
|
v := MustParse("5.0.1")
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for n := 0; n < b.N; n++ {
|
|
r(v)
|
|
}
|
|
}
|