package semver import ( "database/sql" "encoding/json" "fmt" "testing" ) func TestStrictNewVersion(t *testing.T) { tests := []struct { version string err bool }{ {"1.2.3", false}, {"1.2.3-alpha.01", true}, {"1.2.3+test.01", false}, {"1.2.3-alpha.-1", false}, {"v1.2.3", true}, {"1.0", true}, {"v1.0", true}, {"1", true}, {"v1", true}, {"1.2.beta", true}, {"v1.2.beta", true}, {"foo", true}, {"1.2-5", true}, {"v1.2-5", true}, {"1.2-beta.5", true}, {"v1.2-beta.5", true}, {"\n1.2", true}, {"\nv1.2", true}, {"1.2.0-x.Y.0+metadata", false}, {"v1.2.0-x.Y.0+metadata", true}, {"1.2.0-x.Y.0+metadata-width-hypen", false}, {"v1.2.0-x.Y.0+metadata-width-hypen", true}, {"1.2.3-rc1-with-hypen", false}, {"v1.2.3-rc1-with-hypen", true}, {"1.2.3.4", true}, {"v1.2.3.4", true}, {"1.2.2147483648", false}, {"1.2147483648.3", false}, {"2147483648.3.0", false}, // The SemVer spec in a pre-release expects to allow [0-9A-Za-z-]. But, // the lack of all 3 parts in this version should produce an error. {"20221209-update-renovatejson-v4", true}, } for _, tc := range tests { _, err := StrictNewVersion(tc.version) if tc.err && err == nil { t.Fatalf("expected error for version: %s", tc.version) } else if !tc.err && err != nil { t.Fatalf("error for version %s: %s", tc.version, err) } } } func TestNewVersion(t *testing.T) { tests := []struct { version string err bool }{ {"1.2.3", false}, {"1.2.3-alpha.01", true}, {"1.2.3+test.01", false}, {"1.2.3-alpha.-1", false}, {"v1.2.3", false}, {"1.0", false}, {"v1.0", false}, {"1", false}, {"v1", false}, {"1.2.beta", true}, {"v1.2.beta", true}, {"foo", true}, {"1.2-5", false}, {"v1.2-5", false}, {"1.2-beta.5", false}, {"v1.2-beta.5", false}, {"\n1.2", true}, {"\nv1.2", true}, {"1.2.0-x.Y.0+metadata", false}, {"v1.2.0-x.Y.0+metadata", false}, {"1.2.0-x.Y.0+metadata-width-hypen", false}, {"v1.2.0-x.Y.0+metadata-width-hypen", false}, {"1.2.3-rc1-with-hypen", false}, {"v1.2.3-rc1-with-hypen", false}, {"1.2.3.4", true}, {"v1.2.3.4", true}, {"1.2.2147483648", false}, {"1.2147483648.3", false}, {"2147483648.3.0", 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", true}, {"12.23.4.1234", true}, {"12.3.34.1234", true}, // The SemVer spec in a pre-release expects to allow [0-9A-Za-z-]. {"20221209-update-renovatejson-v4", false}, } for _, tc := range tests { _, err := NewVersion(tc.version) if tc.err && err == nil { t.Fatalf("expected error for version: %s", tc.version) } else if !tc.err && err != nil { t.Fatalf("error for version %s: %s", tc.version, err) } } } func TestNew(t *testing.T) { // v0.1.2 v := New(0, 1, 2, "", "") if v.String() != "0.1.2" { t.Errorf("expected version 0.1.2 but got %q", v.String()) } // v1.2.3-alpha.1+foo.bar v = New(1, 2, 3, "alpha.1", "foo.bar") if v.String() != "1.2.3-alpha.1+foo.bar" { t.Errorf("expected version 1.2.3-alpha.1+foo.bar but got %q", v.String()) } } func TestOriginal(t *testing.T) { tests := []string{ "1.2.3", "v1.2.3", "1.0", "v1.0", "1", "v1", "1.2-5", "v1.2-5", "1.2-beta.5", "v1.2-beta.5", "1.2.0-x.Y.0+metadata", "v1.2.0-x.Y.0+metadata", "1.2.0-x.Y.0+metadata-width-hypen", "v1.2.0-x.Y.0+metadata-width-hypen", "1.2.3-rc1-with-hypen", "v1.2.3-rc1-with-hypen", } for _, tc := range tests { v, err := NewVersion(tc) if err != nil { t.Errorf("Error parsing version %s", tc) } o := v.Original() if o != tc { t.Errorf("Error retrieving original. Expected '%s' but got '%v'", tc, v) } } } func TestParts(t *testing.T) { v, err := NewVersion("1.2.3-beta.1+build.123") if err != nil { t.Error("Error parsing version 1.2.3-beta.1+build.123") } if v.Major() != 1 { t.Error("Major() returning wrong value") } if v.Minor() != 2 { t.Error("Minor() returning wrong value") } if v.Patch() != 3 { t.Error("Patch() returning wrong value") } if v.Prerelease() != "beta.1" { t.Error("Prerelease() returning wrong value") } if v.Metadata() != "build.123" { t.Error("Metadata() returning wrong value") } } func TestCoerceString(t *testing.T) { tests := []struct { version string expected string }{ {"1.2.3", "1.2.3"}, {"v1.2.3", "1.2.3"}, {"1.0", "1.0.0"}, {"v1.0", "1.0.0"}, {"1", "1.0.0"}, {"v1", "1.0.0"}, {"1.2-5", "1.2.0-5"}, {"v1.2-5", "1.2.0-5"}, {"1.2-beta.5", "1.2.0-beta.5"}, {"v1.2-beta.5", "1.2.0-beta.5"}, {"1.2.0-x.Y.0+metadata", "1.2.0-x.Y.0+metadata"}, {"v1.2.0-x.Y.0+metadata", "1.2.0-x.Y.0+metadata"}, {"1.2.0-x.Y.0+metadata-width-hypen", "1.2.0-x.Y.0+metadata-width-hypen"}, {"v1.2.0-x.Y.0+metadata-width-hypen", "1.2.0-x.Y.0+metadata-width-hypen"}, {"1.2.3-rc1-with-hypen", "1.2.3-rc1-with-hypen"}, {"v1.2.3-rc1-with-hypen", "1.2.3-rc1-with-hypen"}, } for _, tc := range tests { v, err := NewVersion(tc.version) if err != nil { t.Errorf("Error parsing version %s", tc) } s := v.String() if s != tc.expected { t.Errorf("Error generating string. Expected '%s' but got '%s'", tc.expected, s) } } } func TestCompare(t *testing.T) { tests := []struct { v1 string v2 string expected int }{ {"1.2.3", "1.5.1", -1}, {"2.2.3", "1.5.1", 1}, {"2.2.3", "2.2.2", 1}, {"3.2-beta", "3.2-beta", 0}, {"1.3", "1.1.4", 1}, {"4.2", "4.2-beta", 1}, {"4.2-beta", "4.2", -1}, {"4.2-alpha", "4.2-beta", -1}, {"4.2-alpha", "4.2-alpha", 0}, {"4.2-beta.2", "4.2-beta.1", 1}, {"4.2-beta2", "4.2-beta1", 1}, {"4.2-beta", "4.2-beta.2", -1}, {"4.2-beta", "4.2-beta.foo", -1}, {"4.2-beta.2", "4.2-beta", 1}, {"4.2-beta.foo", "4.2-beta", 1}, {"1.2+bar", "1.2+baz", 0}, {"1.0.0-beta.4", "1.0.0-beta.-2", -1}, {"1.0.0-beta.-2", "1.0.0-beta.-3", -1}, {"1.0.0-beta.-3", "1.0.0-beta.5", 1}, } for _, tc := range tests { v1, err := NewVersion(tc.v1) if err != nil { t.Errorf("Error parsing version: %s", err) } v2, err := NewVersion(tc.v2) if err != nil { t.Errorf("Error parsing version: %s", err) } a := v1.Compare(v2) e := tc.expected if a != e { t.Errorf( "Comparison of '%s' and '%s' failed. Expected '%d', got '%d'", tc.v1, tc.v2, e, a, ) } } } func TestLessThan(t *testing.T) { tests := []struct { v1 string v2 string expected bool }{ {"1.2.3", "1.5.1", true}, {"2.2.3", "1.5.1", false}, {"3.2-beta", "3.2-beta", false}, } for _, tc := range tests { v1, err := NewVersion(tc.v1) if err != nil { t.Errorf("Error parsing version: %s", err) } v2, err := NewVersion(tc.v2) if err != nil { t.Errorf("Error parsing version: %s", err) } a := v1.LessThan(v2) e := tc.expected if a != e { t.Errorf( "Comparison of '%s' and '%s' failed. Expected '%t', got '%t'", tc.v1, tc.v2, e, a, ) } } } func TestLessThanEqual(t *testing.T) { tests := []struct { v1 string v2 string expected bool }{ {"1.2.3", "1.5.1", true}, {"2.2.3", "1.5.1", false}, {"1.5.1", "1.5.1", true}, } for _, tc := range tests { v1, err := NewVersion(tc.v1) if err != nil { t.Errorf("Error parsing version: %s", err) } v2, err := NewVersion(tc.v2) if err != nil { t.Errorf("Error parsing version: %s", err) } a := v1.LessThanEqual(v2) e := tc.expected if a != e { t.Errorf( "Comparison of '%s' and '%s' failed. Expected '%t', got '%t'", tc.v1, tc.v2, e, a, ) } } } func TestGreaterThan(t *testing.T) { tests := []struct { v1 string v2 string expected bool }{ {"1.2.3", "1.5.1", false}, {"2.2.3", "1.5.1", true}, {"3.2-beta", "3.2-beta", false}, {"3.2.0-beta.1", "3.2.0-beta.5", false}, {"3.2-beta.4", "3.2-beta.2", true}, {"7.43.0-SNAPSHOT.99", "7.43.0-SNAPSHOT.103", false}, {"7.43.0-SNAPSHOT.FOO", "7.43.0-SNAPSHOT.103", true}, {"7.43.0-SNAPSHOT.99", "7.43.0-SNAPSHOT.BAR", false}, } for _, tc := range tests { v1, err := NewVersion(tc.v1) if err != nil { t.Errorf("Error parsing version: %s", err) } v2, err := NewVersion(tc.v2) if err != nil { t.Errorf("Error parsing version: %s", err) } a := v1.GreaterThan(v2) e := tc.expected if a != e { t.Errorf( "Comparison of '%s' and '%s' failed. Expected '%t', got '%t'", tc.v1, tc.v2, e, a, ) } } } func TestGreaterThanEqual(t *testing.T) { tests := []struct { v1 string v2 string expected bool }{ {"1.2.3", "1.5.1", false}, {"2.2.3", "1.5.1", true}, {"1.5.1", "1.5.1", true}, } for _, tc := range tests { v1, err := NewVersion(tc.v1) if err != nil { t.Errorf("Error parsing version: %s", err) } v2, err := NewVersion(tc.v2) if err != nil { t.Errorf("Error parsing version: %s", err) } a := v1.GreaterThanEqual(v2) e := tc.expected if a != e { t.Errorf( "Comparison of '%s' and '%s' failed. Expected '%t', got '%t'", tc.v1, tc.v2, e, a, ) } } } func TestEqual(t *testing.T) { tests := []struct { v1 *Version v2 *Version expected bool }{ {MustParse("1.2.3"), MustParse("1.5.1"), false}, {MustParse("2.2.3"), MustParse("1.5.1"), false}, {MustParse("3.2-beta"), MustParse("3.2-beta"), true}, {MustParse("3.2-beta+foo"), MustParse("3.2-beta+bar"), true}, {nil, nil, true}, {nil, MustParse("1.2.3"), false}, {MustParse("1.2.3"), nil, false}, } for _, tc := range tests { a := tc.v1.Equal(tc.v2) e := tc.expected if a != e { t.Errorf( "Comparison of '%s' and '%s' failed. Expected '%t', got '%t'", tc.v1, tc.v2, e, a, ) } } } func TestInc(t *testing.T) { tests := []struct { v1 string expected string how string expectedOriginal string }{ {"1.2.3", "1.2.4", "patch", "1.2.4"}, {"v1.2.4", "1.2.5", "patch", "v1.2.5"}, {"1.2.3", "1.3.0", "minor", "1.3.0"}, {"v1.2.4", "1.3.0", "minor", "v1.3.0"}, {"1.2.3", "2.0.0", "major", "2.0.0"}, {"v1.2.4", "2.0.0", "major", "v2.0.0"}, {"1.2.3+meta", "1.2.4", "patch", "1.2.4"}, {"1.2.3-beta+meta", "1.2.3", "patch", "1.2.3"}, {"v1.2.4-beta+meta", "1.2.4", "patch", "v1.2.4"}, {"1.2.3-beta+meta", "1.3.0", "minor", "1.3.0"}, {"v1.2.4-beta+meta", "1.3.0", "minor", "v1.3.0"}, {"1.2.3-beta+meta", "2.0.0", "major", "2.0.0"}, {"v1.2.4-beta+meta", "2.0.0", "major", "v2.0.0"}, } for _, tc := range tests { v1, err := NewVersion(tc.v1) if err != nil { t.Errorf("Error parsing version: %s", err) } var v2 Version switch tc.how { case "patch": v2 = v1.IncPatch() case "minor": v2 = v1.IncMinor() case "major": v2 = v1.IncMajor() } a := v2.String() e := tc.expected if a != e { t.Errorf( "Inc %q failed. Expected %q got %q", tc.how, e, a, ) } a = v2.Original() e = tc.expectedOriginal if a != e { t.Errorf( "Inc %q failed. Expected original %q got %q", tc.how, e, a, ) } } } func TestSetPrerelease(t *testing.T) { tests := []struct { v1 string prerelease string expectedVersion string expectedPrerelease string expectedOriginal string expectedErr error }{ {"1.2.3", "**", "1.2.3", "", "1.2.3", ErrInvalidPrerelease}, {"1.2.3", "beta", "1.2.3-beta", "beta", "1.2.3-beta", nil}, {"v1.2.4", "beta", "1.2.4-beta", "beta", "v1.2.4-beta", nil}, } for _, tc := range tests { v1, err := NewVersion(tc.v1) if err != nil { t.Errorf("Error parsing version: %s", err) } v2, err := v1.SetPrerelease(tc.prerelease) if err != tc.expectedErr { t.Errorf("Expected to get err=%s, but got err=%s", tc.expectedErr, err) } a := v2.Prerelease() e := tc.expectedPrerelease if a != e { t.Errorf("Expected prerelease value=%q, but got %q", e, a) } a = v2.String() e = tc.expectedVersion if a != e { t.Errorf("Expected version string=%q, but got %q", e, a) } a = v2.Original() e = tc.expectedOriginal if a != e { t.Errorf("Expected version original=%q, but got %q", e, a) } } } func TestSetMetadata(t *testing.T) { tests := []struct { v1 string metadata string expectedVersion string expectedMetadata string expectedOriginal string expectedErr error }{ {"1.2.3", "**", "1.2.3", "", "1.2.3", ErrInvalidMetadata}, {"1.2.3", "meta", "1.2.3+meta", "meta", "1.2.3+meta", nil}, {"v1.2.4", "meta", "1.2.4+meta", "meta", "v1.2.4+meta", nil}, } for _, tc := range tests { v1, err := NewVersion(tc.v1) if err != nil { t.Errorf("Error parsing version: %s", err) } v2, err := v1.SetMetadata(tc.metadata) if err != tc.expectedErr { t.Errorf("Expected to get err=%s, but got err=%s", tc.expectedErr, err) } a := v2.Metadata() e := tc.expectedMetadata if a != e { t.Errorf("Expected metadata value=%q, but got %q", e, a) } a = v2.String() e = tc.expectedVersion if e != a { t.Errorf("Expected version string=%q, but got %q", e, a) } a = v2.Original() e = tc.expectedOriginal if a != e { t.Errorf("Expected version original=%q, but got %q", e, a) } } } func TestOriginalVPrefix(t *testing.T) { tests := []struct { version string vprefix string }{ {"1.2.3", ""}, {"v1.2.4", "v"}, } for _, tc := range tests { v1, _ := NewVersion(tc.version) a := v1.originalVPrefix() e := tc.vprefix if a != e { t.Errorf("Expected vprefix=%q, but got %q", e, a) } } } func TestJsonMarshal(t *testing.T) { sVer := "1.1.1" x, err := StrictNewVersion(sVer) if err != nil { t.Errorf("Error creating version: %s", err) } out, err2 := json.Marshal(x) if err2 != nil { t.Errorf("Error marshaling version: %s", err2) } got := string(out) want := fmt.Sprintf("%q", sVer) if got != want { t.Errorf("Error marshaling unexpected marshaled content: got=%q want=%q", got, want) } } func TestJsonUnmarshal(t *testing.T) { sVer := "1.1.1" ver := &Version{} err := json.Unmarshal([]byte(fmt.Sprintf("%q", sVer)), ver) if err != nil { t.Errorf("Error unmarshaling version: %s", err) } got := ver.String() want := sVer if got != want { t.Errorf("Error unmarshaling unexpected object content: got=%q want=%q", got, want) } } func TestTextMarshal(t *testing.T) { sVer := "1.1.1" x, err := StrictNewVersion(sVer) if err != nil { t.Errorf("Error creating version: %s", err) } out, err2 := x.MarshalText() if err2 != nil { t.Errorf("Error marshaling version: %s", err2) } got := string(out) want := sVer if got != want { t.Errorf("Error marshaling unexpected marshaled content: got=%q want=%q", got, want) } } func TestTextUnmarshal(t *testing.T) { sVer := "1.1.1" ver := &Version{} err := ver.UnmarshalText([]byte(sVer)) if err != nil { t.Errorf("Error unmarshaling version: %s", err) } got := ver.String() want := sVer if got != want { t.Errorf("Error unmarshaling unexpected object content: got=%q want=%q", got, want) } } func TestSQLScanner(t *testing.T) { sVer := "1.1.1" x, err := StrictNewVersion(sVer) if err != nil { t.Errorf("Error creating version: %s", err) } var s sql.Scanner = x var out *Version var ok bool if out, ok = s.(*Version); !ok { t.Errorf("Error expected Version type, got=%T want=%T", s, Version{}) } got := out.String() want := sVer if got != want { t.Errorf("Error sql scanner unexpected scan content: got=%q want=%q", got, want) } } func TestDriverValuer(t *testing.T) { sVer := "1.1.1" x, err := StrictNewVersion(sVer) if err != nil { t.Errorf("Error creating version: %s", err) } got, err := x.Value() if err != nil { t.Fatalf("Error getting value, got %v", err) } want := sVer if got != want { t.Errorf("Error driver valuer unexpected value content: got=%q want=%q", got, want) } } func TestValidatePrerelease(t *testing.T) { tests := []struct { pre string expected error }{ {"foo", nil}, {"alpha.1", nil}, {"alpha.01", ErrSegmentStartsZero}, {"foo☃︎", ErrInvalidPrerelease}, {"alpha.0-1", nil}, } for _, tc := range tests { if err := validatePrerelease(tc.pre); err != tc.expected { t.Errorf("Unexpected error %q for prerelease %q", err, tc.pre) } } } func TestValidateMetadata(t *testing.T) { tests := []struct { meta string expected error }{ {"foo", nil}, {"alpha.1", nil}, {"alpha.01", nil}, {"foo☃︎", ErrInvalidMetadata}, {"alpha.0-1", nil}, {"al-pha.1Phe70CgWe050H9K1mJwRUqTNQXZRERwLOEg37wpXUb4JgzgaD5YkL52ABnoyiE", nil}, } for _, tc := range tests { if err := validateMetadata(tc.meta); err != tc.expected { t.Errorf("Unexpected error %q for metadata %q", err, tc.meta) } } } func FuzzNewVersion(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"} for _, tc := range testcases { f.Add(tc) } f.Fuzz(func(_ *testing.T, a string) { _, _ = NewVersion(a) }) } func FuzzStrictNewVersion(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"} for _, tc := range testcases { f.Add(tc) } f.Fuzz(func(_ *testing.T, a string) { _, _ = StrictNewVersion(a) }) }