package semver import ( "bytes" "encoding/json" "fmt" "reflect" "testing" ) func TestParseConstraint(t *testing.T) { tests := []struct { in string f cfunc v string err bool }{ {">= 1.2", constraintGreaterThanEqual, "1.2.0", false}, {"1.0", constraintTildeOrEqual, "1.0.0", false}, {"foo", nil, "", true}, {"<= 1.2", constraintLessThanEqual, "1.2.0", false}, {"=< 1.2", constraintLessThanEqual, "1.2.0", false}, {"=> 1.2", constraintGreaterThanEqual, "1.2.0", false}, {"v1.2", constraintTildeOrEqual, "1.2.0", false}, {"=1.5", constraintTildeOrEqual, "1.5.0", false}, {"> 1.3", constraintGreaterThan, "1.3.0", false}, {"< 1.4.1", constraintLessThan, "1.4.1", false}, {"< 40.50.10", constraintLessThan, "40.50.10", false}, } for _, tc := range tests { c, err := parseConstraint(tc.in) if tc.err && err == nil { t.Errorf("Expected error for %s didn't occur", tc.in) } else if !tc.err && err != nil { t.Errorf("Unexpected error for %s", tc.in) } // If an error was expected continue the loop and don't try the other // tests as they will cause errors. if tc.err { continue } if tc.v != c.con.String() { t.Errorf("Incorrect version found on %s", tc.in) } f1 := reflect.ValueOf(tc.f) f2 := reflect.ValueOf(constraintOps[c.origfunc]) if f1 != f2 { t.Errorf("Wrong constraint found for %s", tc.in) } } } func TestConstraintCheck(t *testing.T) { tests := []struct { constraint string version string check bool }{ {"=2.0.0", "1.2.3", false}, {"=2.0.0", "2.0.0", true}, {"=2.0", "1.2.3", false}, {"=2.0", "2.0.0", true}, {"=2.0", "2.0.1", true}, {"4.1", "4.1.0", true}, {"!=4.1.0", "4.1.0", false}, {"!=4.1.0", "4.1.1", true}, {"!=4.1", "4.1.0", false}, {"!=4.1", "4.1.1", false}, {"!=4.1", "5.1.0-alpha.1", false}, {"!=4.1-alpha", "4.1.0", true}, {"!=4.1", "5.1.0", true}, {"<11", "0.1.0", true}, {"<11", "11.1.0", false}, {"<1.1", "0.1.0", true}, {"<1.1", "1.1.0", false}, {"<1.1", "1.1.1", false}, {"<=11", "1.2.3", true}, {"<=11", "12.2.3", false}, {"<=11", "11.2.3", true}, {"<=1.1", "1.2.3", false}, {"<=1.1", "0.1.0", true}, {"<=1.1", "1.1.0", true}, {"<=1.1", "1.1.1", true}, {">1.1", "4.1.0", true}, {">1.1", "1.1.0", false}, {">0", "0", false}, {">0", "1", true}, {">0", "0.0.1-alpha", false}, {">0.0", "0.0.1-alpha", false}, {">0-0", "0.0.1-alpha", false}, {">0.0-0", "0.0.1-alpha", false}, {">0", "0.0.0-alpha", false}, {">0-0", "0.0.0-alpha", false}, {">0.0.0-0", "0.0.0-alpha", true}, {">1.2.3-alpha.1", "1.2.3-alpha.2", true}, {">1.2.3-alpha.1", "1.3.3-alpha.2", true}, {">11", "11.1.0", false}, {">11.1", "11.1.0", false}, {">11.1", "11.1.1", false}, {">11.1", "11.2.1", true}, {">=11", "11.1.2", true}, {">=11.1", "11.1.2", true}, {">=11.1", "11.0.2", false}, {">=1.1", "4.1.0", true}, {">=1.1", "1.1.0", true}, {">=1.1", "0.0.9", false}, {">=0", "0.0.1-alpha", false}, {">=0.0", "0.0.1-alpha", false}, {">=0-0", "0.0.1-alpha", true}, {">=0.0-0", "0.0.1-alpha", true}, {">=0", "0.0.0-alpha", false}, {">=0-0", "0.0.0-alpha", true}, {">=0.0.0-0", "0.0.0-alpha", true}, {">=0.0.0-0", "1.2.3", true}, {">=0.0.0-0", "3.4.5-beta.1", true}, {"<0", "0.0.0-alpha", false}, {"<0-z", "0.0.0-alpha", true}, {">=0", "0", true}, {"=0", "1", false}, {"*", "1", true}, {"*", "4.5.6", true}, {"*", "1.2.3-alpha.1", false}, {"*-0", "1.2.3-alpha.1", true}, {"2.*", "1", false}, {"2.*", "3.4.5", false}, {"2.*", "2.1.1", true}, {"2.1.*", "2.1.1", true}, {"2.1.*", "2.2.1", false}, {"", "1", true}, // An empty string is treated as * or wild card {"", "4.5.6", true}, {"", "1.2.3-alpha.1", false}, {"2", "1", false}, {"2", "3.4.5", false}, {"2", "2.1.1", true}, {"2.1", "2.1.1", true}, {"2.1", "2.2.1", false}, {"~1.2.3", "1.2.4", true}, {"~1.2.3", "1.3.4", false}, {"~1.2", "1.2.4", true}, {"~1.2", "1.3.4", false}, {"~1", "1.2.4", true}, {"~1", "2.3.4", false}, {"~0.2.3", "0.2.5", true}, {"~0.2.3", "0.3.5", false}, {"~1.2.3-beta.2", "1.2.3-beta.4", true}, // This next test is a case that is different from npm/js semver handling. // Their prereleases are only range scoped to patch releases. This is // technically not following semver as docs note. In our case we are // following semver. {"~1.2.3-beta.2", "1.2.4-beta.2", true}, {"~1.2.3-beta.2", "1.3.4-beta.2", false}, {"^1.2.3", "1.8.9", true}, {"^1.2.3", "2.8.9", false}, {"^1.2.3", "1.2.1", false}, {"^1.1.0", "2.1.0", false}, {"^1.2.0", "2.2.1", false}, {"^1.2.0", "1.2.1-alpha.1", false}, {"^1.2.0-alpha.0", "1.2.1-alpha.1", true}, {"^1.2.0-alpha.0", "1.2.1-alpha.0", true}, {"^1.2.0-alpha.2", "1.2.0-alpha.1", false}, {"^1.2", "1.8.9", true}, {"^1.2", "2.8.9", false}, {"^1", "1.8.9", true}, {"^1", "2.8.9", false}, {"^0.2.3", "0.2.5", true}, {"^0.2.3", "0.5.6", false}, {"^0.2", "0.2.5", true}, {"^0.2", "0.5.6", false}, {"^0.0.3", "0.0.3", true}, {"^0.0.3", "0.0.4", false}, {"^0.0", "0.0.3", true}, {"^0.0", "0.1.4", false}, {"^0.0", "1.0.4", false}, {"^0", "0.2.3", true}, {"^0", "1.1.4", false}, {"^0.2.3-beta.2", "0.2.3-beta.4", true}, // This next test is a case that is different from npm/js semver handling. // Their prereleases are only range scoped to patch releases. This is // technically not following semver as docs note. In our case we are // following semver. {"^0.2.3-beta.2", "0.2.4-beta.2", true}, {"^0.2.3-beta.2", "0.3.4-beta.2", false}, {"^0.2.3-beta.2", "0.2.3-beta.2", true}, } for _, tc := range tests { c, err := parseConstraint(tc.constraint) if err != nil { t.Errorf("err: %s", err) continue } v, err := NewVersion(tc.version) if err != nil { t.Errorf("err: %s", err) continue } a, _ := c.check(v) if a != tc.check { t.Errorf("Constraint %q failing with %q", tc.constraint, tc.version) } } } func TestNewConstraint(t *testing.T) { tests := []struct { input string ors int count int err bool }{ {">= 1.1", 1, 1, false}, {">40.50.60, < 50.70", 1, 2, false}, {"2.0", 1, 1, false}, {"v2.3.5-20161202202307-sha.e8fc5e5", 1, 1, false}, {">= bar", 0, 0, true}, {"BAR >= 1.2.3", 0, 0, true}, // Test with space separated AND {">= 1.2.3 < 2.0", 1, 2, false}, {">= 1.2.3 < 2.0 || => 3.0 < 4", 2, 2, false}, // Test with commas separating AND {">= 1.2.3, < 2.0", 1, 2, false}, {">= 1.2.3, < 2.0 || => 3.0, < 4", 2, 2, false}, // The 3 - 4 should be broken into 2 by the range rewriting {"3 - 4 || => 3.0, < 4", 2, 2, false}, // Due to having 4 parts these should produce an error. See // https://github.com/Masterminds/semver/issues/185 for the reason for // these tests. {"12.3.4.1234", 0, 0, true}, {"12.23.4.1234", 0, 0, true}, {"12.3.34.1234", 0, 0, true}, {"12.3.34 ~1.2.3", 1, 2, false}, {"12.3.34~ 1.2.3", 0, 0, true}, {"1.0.0 - 2.0.0, <=2.0.0", 1, 3, false}, } for _, tc := range tests { v, err := NewConstraint(tc.input) if tc.err && err == nil { t.Errorf("expected but did not get error for: %s", tc.input) continue } else if !tc.err && err != nil { t.Errorf("unexpectederror for input %s: %s", tc.input, err) continue } if tc.err { continue } l := len(v.constraints) if tc.ors != l { t.Errorf("Expected %s to have %d ORs but got %d", tc.input, tc.ors, l) } l = len(v.constraints[0]) if tc.count != l { t.Errorf("Expected %s to have %d constraints but got %d", tc.input, tc.count, l) } } } func TestConstraintsCheck(t *testing.T) { tests := []struct { constraint string version string check bool }{ {"*", "1.2.3", true}, {"~0.0.0", "1.2.3", true}, {"0.x.x", "1.2.3", false}, {"0.0.x", "1.2.3", false}, {"0.0.0", "1.2.3", false}, {"*", "1.2.3", true}, {"^0.0.0", "1.2.3", false}, {"= 2.0", "1.2.3", false}, {"= 2.0", "2.0.0", true}, {"4.1", "4.1.0", true}, {"4.1.x", "4.1.3", true}, {"1.x", "1.4", true}, {"!=4.1", "4.1.0", false}, {"!=4.1-alpha", "4.1.0-alpha", false}, {"!=4.1-alpha", "4.1.1-alpha", false}, {"!=4.1-alpha", "4.1.0", true}, {"!=4.1", "5.1.0", true}, {"!=4.x", "5.1.0", true}, {"!=4.x", "4.1.0", false}, {"!=4.1.x", "4.2.0", true}, {"!=4.2.x", "4.2.3", false}, {">1.1", "4.1.0", true}, {">1.1", "1.1.0", false}, {"<1.1", "0.1.0", true}, {"<1.1", "1.1.0", false}, {"<1.1", "1.1.1", false}, {"<1.x", "1.1.1", false}, {"<1.x", "0.1.1", true}, {"<1.x", "2.0.0", false}, {"<1.1.x", "1.2.1", false}, {"<1.1.x", "1.1.500", false}, {"<1.1.x", "1.0.500", true}, {"<1.2.x", "1.1.1", true}, {">=1.1", "4.1.0", true}, {">=1.1", "4.1.0-beta", false}, {">=1.1", "1.1.0", true}, {">=1.1", "0.0.9", false}, {"<=1.1", "0.1.0", true}, {"<=1.1", "0.1.0-alpha", false}, {"<=1.1-a", "0.1.0-alpha", true}, {"<=1.1", "1.1.0", true}, {"<=1.x", "1.1.0", true}, {"<=2.x", "3.0.0", false}, {"<=1.1", "1.1.1", true}, {"<=1.1.x", "1.2.500", false}, {"<=4.5", "3.4.0", true}, {"<=4.5", "3.7.0", true}, {"<=4.5", "4.6.3", false}, {">1.1, <2", "1.1.1", false}, {">1.1, <2", "1.2.1", true}, {">1.1, <3", "4.3.2", false}, {">=1.1, <2, !=1.2.3", "1.2.3", false}, {">1.1 <2", "1.1.1", false}, {">1.1 <2", "1.2.1", true}, {">1.1 <3", "4.3.2", false}, {">=1.1 <2 !=1.2.3", "1.2.3", false}, {">=1.1, <2, !=1.2.3 || > 3", "4.1.2", true}, {">=1.1, <2, !=1.2.3 || > 3", "3.1.2", false}, {">=1.1, <2, !=1.2.3 || >= 3", "3.0.0", true}, {">=1.1, <2, !=1.2.3 || > 3", "3.0.0", false}, {">=1.1, <2, !=1.2.3 || > 3", "1.2.3", false}, {">=1.1 <2 !=1.2.3", "1.2.3", false}, {">=1.1 <2 !=1.2.3 || > 3", "4.1.2", true}, {">=1.1 <2 !=1.2.3 || > 3", "3.1.2", false}, {">=1.1 <2 !=1.2.3 || >= 3", "3.0.0", true}, {">=1.1 <2 !=1.2.3 || > 3", "3.0.0", false}, {">=1.1 <2 !=1.2.3 || > 3", "1.2.3", false}, {"> 1.1, < 2", "1.1.1", false}, {"> 1.1, <2", "1.2.1", true}, {">1.1, < 3", "4.3.2", false}, {">= 1.1, < 2, !=1.2.3", "1.2.3", false}, {"> 1.1 < 2", "1.1.1", false}, {">1.1 < 2", "1.2.1", true}, {"> 1.1 <3", "4.3.2", false}, {">=1.1 < 2 != 1.2.3", "1.2.3", false}, {">= 1.1, <2, !=1.2.3 || > 3", "4.1.2", true}, {">= 1.1, <2, != 1.2.3 || > 3", "3.1.2", false}, {">= 1.1, <2, != 1.2.3 || >= 3", "3.0.0", true}, {">= 1.1, <2, !=1.2.3 || > 3", "3.0.0", false}, {">= 1.1, <2, !=1.2.3 || > 3", "1.2.3", false}, {">= 1.1 <2 != 1.2.3", "1.2.3", false}, {">= 1.1 <2 != 1.2.3 || > 3", "4.1.2", true}, {">= 1.1 <2 != 1.2.3 || > 3", "3.1.2", false}, {">= 1.1 <2 != 1.2.3 || >= 3", "3.0.0", true}, {">= 1.1 < 2 !=1.2.3 || > 3", "3.0.0", false}, {">=1.1 < 2 !=1.2.3 || > 3", "1.2.3", false}, {"1.1 - 2", "1.1.1", true}, {"1.5.0 - 4.5", "3.7.0", true}, {"1.1-3", "4.3.2", false}, {"^1.1", "1.1.1", true}, {"^1.1", "4.3.2", false}, {"^1.x", "1.1.1", true}, {"^2.x", "1.1.1", false}, {"^1.x", "2.1.1", false}, {"^1.x", "1.1.1-beta1", false}, {"^1.1.2-alpha", "1.2.1-beta1", true}, {"^1.2.x-alpha", "1.1.1-beta1", false}, {"^0.0.1", "0.0.1", true}, {"^0.0.1", "0.3.1", false}, {"~*", "2.1.1", true}, {"~1", "2.1.1", false}, {"~1", "1.3.5", true}, {"~1", "1.4", true}, {"~1.x", "2.1.1", false}, {"~1.x", "1.3.5", true}, {"~1.x", "1.4", true}, {"~1.1", "1.1.1", true}, {"~1.1", "1.1.1-alpha", false}, {"~1.1-alpha", "1.1.1-beta", true}, {"~1.1.1-beta", "1.1.1-alpha", false}, {"~1.1.1-beta", "1.1.1", true}, {"~1.2.3", "1.2.5", true}, {"~1.2.3", "1.2.2", false}, {"~1.2.3", "1.3.2", false}, {"~1.1", "1.2.3", false}, {"~1.3", "2.4.5", false}, // Ranges should work in conjunction with other constraints anded together. {"1.0.0 - 2.0.0 <=2.0.0", "1.5.0", true}, {"1.0.0 - 2.0.0, <=2.0.0", "1.5.0", true}, } for _, tc := range tests { c, err := NewConstraint(tc.constraint) if err != nil { t.Errorf("err: %s", err) continue } v, err := NewVersion(tc.version) if err != nil { t.Errorf("err: %s", err) continue } a := c.Check(v) if a != tc.check { t.Errorf("Constraint '%s' failing with '%s'", tc.constraint, tc.version) } } } func TestRewriteRange(t *testing.T) { tests := []struct { c string nc string }{ {"2 - 3", ">= 2, <= 3 "}, {"2 - 3, 2 - 3", ">= 2, <= 3 ,>= 2, <= 3 "}, {"2 - 3, 4.0.0 - 5.1", ">= 2, <= 3 ,>= 4.0.0, <= 5.1 "}, {"2 - 3 4.0.0 - 5.1", ">= 2, <= 3 >= 4.0.0, <= 5.1 "}, {"1.0.0 - 2.0.0 <=2.0.0", ">= 1.0.0, <= 2.0.0 <=2.0.0"}, } for _, tc := range tests { o := rewriteRange(tc.c) if o != tc.nc { t.Errorf("Range %s rewritten incorrectly as %q instead of expected %q", tc.c, o, tc.nc) } } } func TestIsX(t *testing.T) { tests := []struct { t string c bool }{ {"A", false}, {"%", false}, {"X", true}, {"x", true}, {"*", true}, } for _, tc := range tests { a := isX(tc.t) if a != tc.c { t.Errorf("Function isX error on %s", tc.t) } } } func TestConstraintsValidate(t *testing.T) { tests := []struct { constraint string version string check bool }{ {"*", "1.2.3", true}, {"~0.0.0", "1.2.3", true}, {"= 2.0", "1.2.3", false}, {"= 2.0", "2.0.0", true}, {"4.1", "4.1.0", true}, {"4.1.x", "4.1.3", true}, {"1.x", "1.4", true}, {"!=4.1", "4.1.0", false}, {"!=4.1", "5.1.0", true}, {"!=4.x", "5.1.0", true}, {"!=4.x", "4.1.0", false}, {"!=4.1.x", "4.2.0", true}, {"!=4.2.x", "4.2.3", false}, {">1.1", "4.1.0", true}, {">1.1", "1.1.0", false}, {"<1.1", "0.1.0", true}, {"<1.1", "1.1.0", false}, {"<1.1", "1.1.1", false}, {"<1.x", "1.1.1", false}, {"<2.x", "1.1.1", true}, {"<1.x", "2.1.1", false}, {"<1.1.x", "1.2.1", false}, {"<1.1.x", "1.1.500", false}, {"<1.2.x", "1.1.1", true}, {">=1.1", "4.1.0", true}, {">=1.1", "1.1.0", true}, {">=1.1", "0.0.9", false}, {"<=1.1", "0.1.0", true}, {"<=1.1", "1.1.0", true}, {"<=1.x", "1.1.0", true}, {"<=2.x", "3.1.0", false}, {"<=1.1", "1.1.1", true}, {"<=1.1.x", "1.2.500", false}, {">1.1, <2", "1.1.1", false}, {">1.1, <2", "1.2.1", true}, {">1.1, <3", "4.3.2", false}, {">=1.1, <2, !=1.2.3", "1.2.3", false}, {">=1.1, <2, !=1.2.3 || > 3", "3.1.2", false}, {">=1.1, <2, !=1.2.3 || > 3", "4.1.2", true}, {">=1.1, <2, !=1.2.3 || >= 3", "3.0.0", true}, {">=1.1, <2, !=1.2.3 || > 3", "3.0.0", false}, {">=1.1, <2, !=1.2.3 || > 3", "1.2.3", false}, {"1.1 - 2", "1.1.1", true}, {"1.1-3", "4.3.2", false}, {"^1.1", "1.1.1", true}, {"^1.1", "1.1.1-alpha", false}, {"^1.1.1-alpha", "1.1.1-beta", true}, {"^1.1.1-beta", "1.1.1-alpha", false}, {"^1.1", "4.3.2", false}, {"^1.x", "1.1.1", true}, {"^2.x", "1.1.1", false}, {"^1.x", "2.1.1", false}, {"^0.0.1", "0.1.3", false}, {"^0.0.1", "0.0.1", true}, {"~*", "2.1.1", true}, {"~1", "2.1.1", false}, {"~1", "1.3.5", true}, {"~1", "1.3.5-beta", false}, {"~1.x", "2.1.1", false}, {"~1.x", "1.3.5", true}, {"~1.x", "1.3.5-beta", false}, {"~1.3.6-alpha", "1.3.5-beta", false}, {"~1.3.5-alpha", "1.3.5-beta", true}, {"~1.3.5-beta", "1.3.5-alpha", false}, {"~1.x", "1.4", true}, {"~1.1", "1.1.1", true}, {"~1.2.3", "1.2.5", true}, {"~1.2.3", "1.2.2", false}, {"~1.2.3", "1.3.2", false}, {"~1.1", "1.2.3", false}, {"~1.3", "2.4.5", false}, } for _, tc := range tests { c, err := NewConstraint(tc.constraint) if err != nil { t.Errorf("err: %s", err) continue } v, err := NewVersion(tc.version) if err != nil { t.Errorf("err: %s", err) continue } a, msgs := c.Validate(v) if a != tc.check { t.Errorf("Constraint '%s' failing with '%s'", tc.constraint, tc.version) } else if !a && len(msgs) == 0 { t.Errorf("%q failed with %q but no errors returned", tc.constraint, tc.version) } // if a == false { // for _, m := range msgs { // t.Errorf("%s", m) // } // } } v, err := StrictNewVersion("1.2.3") if err != nil { t.Errorf("err: %s", err) } c, err := NewConstraint("!= 1.2.5, ^2, <= 1.1.x") if err != nil { t.Errorf("err: %s", err) } _, msgs := c.Validate(v) if len(msgs) != 2 { t.Error("Invalid number of validations found") } e := msgs[0].Error() if e != "1.2.3 is less than 2" { t.Error("Did not get expected message: 1.2.3 is less than 2") } e = msgs[1].Error() if e != "1.2.3 is greater than 1.1.x" { t.Error("Did not get expected message: 1.2.3 is greater than 1.1.x") } tests2 := []struct { constraint, version, msg string }{ {"2.x", "1.2.3", "1.2.3 is less than 2.x"}, {"2", "1.2.3", "1.2.3 is less than 2"}, {"= 2.0", "1.2.3", "1.2.3 is less than 2.0"}, {"!=4.1", "4.1.0", "4.1.0 is equal to 4.1"}, {"!=4.x", "4.1.0", "4.1.0 is equal to 4.x"}, {"!=4.2.x", "4.2.3", "4.2.3 is equal to 4.2.x"}, {">1.1", "1.1.0", "1.1.0 is less than or equal to 1.1"}, {"<1.1", "1.1.0", "1.1.0 is greater than or equal to 1.1"}, {"<1.1", "1.1.1", "1.1.1 is greater than or equal to 1.1"}, {"<1.x", "2.1.1", "2.1.1 is greater than or equal to 1.x"}, {"<1.1.x", "1.2.1", "1.2.1 is greater than or equal to 1.1.x"}, {">=1.1", "0.0.9", "0.0.9 is less than 1.1"}, {"<=2.x", "3.1.0", "3.1.0 is greater than 2.x"}, {"<=1.1", "1.2.1", "1.2.1 is greater than 1.1"}, {"<=1.1.x", "1.2.500", "1.2.500 is greater than 1.1.x"}, {">1.1, <3", "4.3.2", "4.3.2 is greater than or equal to 3"}, {">=1.1, <2, !=1.2.3", "1.2.3", "1.2.3 is equal to 1.2.3"}, {">=1.1, <2, !=1.2.3 || > 3", "3.0.0", "3.0.0 is greater than or equal to 2"}, {">=1.1, <2, !=1.2.3 || > 3", "1.2.3", "1.2.3 is equal to 1.2.3"}, {"1.1 - 3", "4.3.2", "4.3.2 is greater than 3"}, {"^1.1", "4.3.2", "4.3.2 does not have same major version as 1.1"}, {"^1.12.7", "1.6.6", "1.6.6 is less than 1.12.7"}, {"^2.x", "1.1.1", "1.1.1 is less than 2.x"}, {"^1.x", "2.1.1", "2.1.1 does not have same major version as 1.x"}, {"^0.2", "0.3.0", "0.3.0 does not have same minor version as 0.2. Expected minor versions to match when constraint major version is 0"}, {"^0.2", "0.1.1", "0.1.1 is less than 0.2"}, {"^0.0.3", "0.1.1", "0.1.1 does not have same minor version as 0.0.3"}, {"^0.0.3", "0.0.4", "0.0.4 does not equal 0.0.3. Expect version and constraint to equal when major and minor versions are 0"}, {"^0.0.3", "0.0.2", "0.0.2 is less than 0.0.3"}, {"~1", "2.1.2", "2.1.2 does not have same major version as 1"}, {"~1.x", "2.1.1", "2.1.1 does not have same major version as 1.x"}, {"~1.2.3", "1.2.2", "1.2.2 is less than 1.2.3"}, {"~1.2.3", "1.3.2", "1.3.2 does not have same major and minor version as 1.2.3"}, {"~1.1", "1.2.3", "1.2.3 does not have same major and minor version as 1.1"}, {"~1.3", "2.4.5", "2.4.5 does not have same major version as 1.3"}, {"> 1.2.3", "1.2.3-beta.1", "1.2.3-beta.1 is a prerelease version and the constraint is only looking for release versions"}, } for _, tc := range tests2 { c, err := NewConstraint(tc.constraint) if err != nil { t.Errorf("constraint parsing err: %s", err) continue } v, err := StrictNewVersion(tc.version) if err != nil { t.Errorf("version parsing err: %s", err) continue } _, msgs := c.Validate(v) if len(msgs) == 0 { t.Errorf("Did not get error message on constraint %q", tc.constraint) } else { e := msgs[0].Error() if e != tc.msg { t.Errorf("Did not get expected message. Expected %q, got %q", tc.msg, e) } } } } func TestConstraintString(t *testing.T) { tests := []struct { constraint string st string }{ {"*", "*"}, {">=1.2.3", ">=1.2.3"}, {">= 1.2.3", ">=1.2.3"}, {"2.x, >=1.2.3 || >4.5.6, < 5.7", "2.x >=1.2.3 || >4.5.6 <5.7"}, {"2.x, >=1.2.3 || >4.5.6, < 5.7 || >40.50.60, < 50.70", "2.x >=1.2.3 || >4.5.6 <5.7 || >40.50.60 <50.70"}, {"1.2", "1.2"}, } for _, tc := range tests { c, err := NewConstraint(tc.constraint) if err != nil { t.Errorf("cannot create constraint for %q, err: %s", tc.constraint, err) continue } if c.String() != tc.st { t.Errorf("expected constraint from %q to be a string as %q but got %q", tc.constraint, tc.st, c.String()) } if _, err = NewConstraint(c.String()); err != nil { t.Errorf("expected string from constrint %q to parse as valid but got err: %s", tc.constraint, err) } } } func TestTextMarshalConstraints(t *testing.T) { tests := []struct { constraint string want string }{ {"1.2.3", "1.2.3"}, {">=1.2.3", ">=1.2.3"}, {"<=1.2.3", "<=1.2.3"}, {"1 <=1.2.3", "1 <=1.2.3"}, {"1, <=1.2.3", "1 <=1.2.3"}, {">1, <=1.2.3", ">1 <=1.2.3"}, {"> 1 , <=1.2.3", ">1 <=1.2.3"}, } for _, tc := range tests { cs, err := NewConstraint(tc.constraint) if err != nil { t.Errorf("Error creating constraints: %s", err) } out, err2 := cs.MarshalText() if err2 != nil { t.Errorf("Error constraint version: %s", err2) } got := string(out) if got != tc.want { t.Errorf("Error marshaling constraint, unexpected marshaled content: got=%q want=%q", got, tc.want) } // Test that this works for JSON as well as text. When JSON marshaling // functions are missing it falls through to TextMarshal. // NOTE: To not escape the < and > (which json.Marshal does) you need // a custom encoder where html escaping is disabled. This must be done // in the top level encoder being used to marshal the constraints. buf := new(bytes.Buffer) enc := json.NewEncoder(buf) enc.SetEscapeHTML(false) err = enc.Encode(cs) if err != nil { t.Errorf("Error unmarshaling constraint: %s", err) } got = buf.String() // The encoder used here adds a newline so we add that to what we want // so they align. The newline is an artifact of the testing. want := fmt.Sprintf("%q\n", tc.want) if got != want { t.Errorf("Error marshaling constraint, unexpected marshaled content: got=%q want=%q", got, want) } } } func TestTextUnmarshalConstraints(t *testing.T) { tests := []struct { constraint string want string }{ {"1.2.3", "1.2.3"}, {">=1.2.3", ">=1.2.3"}, {"<=1.2.3", "<=1.2.3"}, {">1 <=1.2.3", ">1 <=1.2.3"}, {"> 1 <=1.2.3", ">1 <=1.2.3"}, {">1, <=1.2.3", ">1 <=1.2.3"}, } for _, tc := range tests { cs := Constraints{} err := cs.UnmarshalText([]byte(tc.constraint)) if err != nil { t.Errorf("Error unmarshaling constraints: %s", err) } got := cs.String() if got != tc.want { t.Errorf("Error unmarshaling constraint, unexpected object content: got=%q want=%q", got, tc.want) } // Test that this works for JSON as well as text. When JSON unmarshaling // functions are missing it falls through to TextUnmarshal. err = json.Unmarshal([]byte(fmt.Sprintf("%q", tc.constraint)), &cs) if err != nil { t.Errorf("Error unmarshaling constraints: %s", err) } got = cs.String() if got != tc.want { t.Errorf("Error unmarshaling constraint, unexpected object content: got=%q want=%q", got, tc.want) } } } func FuzzNewConstraint(f *testing.F) { testcases := []string{ "v1.2.3", " ", "......", "1", "1.2.3-beta.1", "1.2.3+foo", "2.3.4-alpha.1+bar", "lorem ipsum", "*", "!=1.2.3", "^4.5", "1.0.0 - 2", "1.2.3.4.5.6", ">= 1", "~9.8.7", "<= 12.13.14", "987654321.123456789.654123789", "1.x", "2.3.x", "9.2-beta.0", } for _, tc := range testcases { f.Add(tc) } f.Fuzz(func(t *testing.T, a string) { _, _ = NewConstraint(a) }) }