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) } }