| @ -1,22 +0,0 @@ | |||||
| The MIT License | |||||
| Copyright (c) 2014 Benedikt Lang <github at benediktlang.de> | |||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| of this software and associated documentation files (the "Software"), to deal | |||||
| in the Software without restriction, including without limitation the rights | |||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
| copies of the Software, and to permit persons to whom the Software is | |||||
| furnished to do so, subject to the following conditions: | |||||
| The above copyright notice and this permission notice shall be included in | |||||
| all copies or substantial portions of the Software. | |||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
| THE SOFTWARE. | |||||
| @ -1,191 +0,0 @@ | |||||
| semver for golang [](https://drone.io/github.com/blang/semver/latest) [](https://godoc.org/github.com/blang/semver) [](https://coveralls.io/r/blang/semver?branch=master) | |||||
| ====== | |||||
| semver is a [Semantic Versioning](http://semver.org/) library written in golang. It fully covers spec version `2.0.0`. | |||||
| Usage | |||||
| ----- | |||||
| ```bash | |||||
| $ go get github.com/blang/semver | |||||
| ``` | |||||
| Note: Always vendor your dependencies or fix on a specific version tag. | |||||
| ```go | |||||
| import github.com/blang/semver | |||||
| v1, err := semver.Make("1.0.0-beta") | |||||
| v2, err := semver.Make("2.0.0-beta") | |||||
| v1.Compare(v2) | |||||
| ``` | |||||
| Also check the [GoDocs](http://godoc.org/github.com/blang/semver). | |||||
| Why should I use this lib? | |||||
| ----- | |||||
| - Fully spec compatible | |||||
| - No reflection | |||||
| - No regex | |||||
| - Fully tested (Coverage >99%) | |||||
| - Readable parsing/validation errors | |||||
| - Fast (See [Benchmarks](#benchmarks)) | |||||
| - Only Stdlib | |||||
| - Uses values instead of pointers | |||||
| - Many features, see below | |||||
| Features | |||||
| ----- | |||||
| - Parsing and validation at all levels | |||||
| - Comparator-like comparisons | |||||
| - Compare Helper Methods | |||||
| - InPlace manipulation | |||||
| - Ranges `>=1.0.0 <2.0.0 || >=3.0.0 !3.0.1-beta.1` | |||||
| - Sortable (implements sort.Interface) | |||||
| - database/sql compatible (sql.Scanner/Valuer) | |||||
| - encoding/json compatible (json.Marshaler/Unmarshaler) | |||||
| Ranges | |||||
| ------ | |||||
| A `Range` is a set of conditions which specify which versions satisfy the range. | |||||
| A condition is composed of an operator and a version. The supported operators are: | |||||
| - `<1.0.0` Less than `1.0.0` | |||||
| - `<=1.0.0` Less than or equal to `1.0.0` | |||||
| - `>1.0.0` Greater than `1.0.0` | |||||
| - `>=1.0.0` Greater than or equal to `1.0.0` | |||||
| - `1.0.0`, `=1.0.0`, `==1.0.0` Equal to `1.0.0` | |||||
| - `!1.0.0`, `!=1.0.0` Not equal to `1.0.0`. Excludes version `1.0.0`. | |||||
| A `Range` can link multiple `Ranges` separated by space: | |||||
| Ranges can be linked by logical AND: | |||||
| - `>1.0.0 <2.0.0` would match between both ranges, so `1.1.1` and `1.8.7` but not `1.0.0` or `2.0.0` | |||||
| - `>1.0.0 <3.0.0 !2.0.3-beta.2` would match every version between `1.0.0` and `3.0.0` except `2.0.3-beta.2` | |||||
| Ranges can also be linked by logical OR: | |||||
| - `<2.0.0 || >=3.0.0` would match `1.x.x` and `3.x.x` but not `2.x.x` | |||||
| AND has a higher precedence than OR. It's not possible to use brackets. | |||||
| Ranges can be combined by both AND and OR | |||||
| - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1` | |||||
| Range usage: | |||||
| ``` | |||||
| v, err := semver.Parse("1.2.3") | |||||
| range, err := semver.ParseRange(">1.0.0 <2.0.0 || >=3.0.0") | |||||
| if range(v) { | |||||
| //valid | |||||
| } | |||||
| ``` | |||||
| Example | |||||
| ----- | |||||
| Have a look at full examples in [examples/main.go](examples/main.go) | |||||
| ```go | |||||
| import github.com/blang/semver | |||||
| v, err := semver.Make("0.0.1-alpha.preview+123.github") | |||||
| fmt.Printf("Major: %d\n", v.Major) | |||||
| fmt.Printf("Minor: %d\n", v.Minor) | |||||
| fmt.Printf("Patch: %d\n", v.Patch) | |||||
| fmt.Printf("Pre: %s\n", v.Pre) | |||||
| fmt.Printf("Build: %s\n", v.Build) | |||||
| // Prerelease versions array | |||||
| if len(v.Pre) > 0 { | |||||
| fmt.Println("Prerelease versions:") | |||||
| for i, pre := range v.Pre { | |||||
| fmt.Printf("%d: %q\n", i, pre) | |||||
| } | |||||
| } | |||||
| // Build meta data array | |||||
| if len(v.Build) > 0 { | |||||
| fmt.Println("Build meta data:") | |||||
| for i, build := range v.Build { | |||||
| fmt.Printf("%d: %q\n", i, build) | |||||
| } | |||||
| } | |||||
| v001, err := semver.Make("0.0.1") | |||||
| // Compare using helpers: v.GT(v2), v.LT, v.GTE, v.LTE | |||||
| v001.GT(v) == true | |||||
| v.LT(v001) == true | |||||
| v.GTE(v) == true | |||||
| v.LTE(v) == true | |||||
| // Or use v.Compare(v2) for comparisons (-1, 0, 1): | |||||
| v001.Compare(v) == 1 | |||||
| v.Compare(v001) == -1 | |||||
| v.Compare(v) == 0 | |||||
| // Manipulate Version in place: | |||||
| v.Pre[0], err = semver.NewPRVersion("beta") | |||||
| if err != nil { | |||||
| fmt.Printf("Error parsing pre release version: %q", err) | |||||
| } | |||||
| fmt.Println("\nValidate versions:") | |||||
| v.Build[0] = "?" | |||||
| err = v.Validate() | |||||
| if err != nil { | |||||
| fmt.Printf("Validation failed: %s\n", err) | |||||
| } | |||||
| ``` | |||||
| Benchmarks | |||||
| ----- | |||||
| BenchmarkParseSimple-4 5000000 390 ns/op 48 B/op 1 allocs/op | |||||
| BenchmarkParseComplex-4 1000000 1813 ns/op 256 B/op 7 allocs/op | |||||
| BenchmarkParseAverage-4 1000000 1171 ns/op 163 B/op 4 allocs/op | |||||
| BenchmarkStringSimple-4 20000000 119 ns/op 16 B/op 1 allocs/op | |||||
| BenchmarkStringLarger-4 10000000 206 ns/op 32 B/op 2 allocs/op | |||||
| BenchmarkStringComplex-4 5000000 324 ns/op 80 B/op 3 allocs/op | |||||
| BenchmarkStringAverage-4 5000000 273 ns/op 53 B/op 2 allocs/op | |||||
| BenchmarkValidateSimple-4 200000000 9.33 ns/op 0 B/op 0 allocs/op | |||||
| BenchmarkValidateComplex-4 3000000 469 ns/op 0 B/op 0 allocs/op | |||||
| BenchmarkValidateAverage-4 5000000 256 ns/op 0 B/op 0 allocs/op | |||||
| BenchmarkCompareSimple-4 100000000 11.8 ns/op 0 B/op 0 allocs/op | |||||
| BenchmarkCompareComplex-4 50000000 30.8 ns/op 0 B/op 0 allocs/op | |||||
| BenchmarkCompareAverage-4 30000000 41.5 ns/op 0 B/op 0 allocs/op | |||||
| BenchmarkSort-4 3000000 419 ns/op 256 B/op 2 allocs/op | |||||
| BenchmarkRangeParseSimple-4 2000000 850 ns/op 192 B/op 5 allocs/op | |||||
| BenchmarkRangeParseAverage-4 1000000 1677 ns/op 400 B/op 10 allocs/op | |||||
| BenchmarkRangeParseComplex-4 300000 5214 ns/op 1440 B/op 30 allocs/op | |||||
| BenchmarkRangeMatchSimple-4 50000000 25.6 ns/op 0 B/op 0 allocs/op | |||||
| BenchmarkRangeMatchAverage-4 30000000 56.4 ns/op 0 B/op 0 allocs/op | |||||
| BenchmarkRangeMatchComplex-4 10000000 153 ns/op 0 B/op 0 allocs/op | |||||
| See benchmark cases at [semver_test.go](semver_test.go) | |||||
| Motivation | |||||
| ----- | |||||
| I simply couldn't find any lib supporting the full spec. Others were just wrong or used reflection and regex which i don't like. | |||||
| Contribution | |||||
| ----- | |||||
| Feel free to make a pull request. For bigger changes create a issue first to discuss about it. | |||||
| License | |||||
| ----- | |||||
| See [LICENSE](LICENSE) file. | |||||
| @ -1,83 +0,0 @@ | |||||
| package main | |||||
| import ( | |||||
| "fmt" | |||||
| "github.com/blang/semver" | |||||
| ) | |||||
| func main() { | |||||
| v, err := semver.Parse("0.0.1-alpha.preview.222+123.github") | |||||
| if err != nil { | |||||
| fmt.Printf("Error while parsing (not valid): %q", err) | |||||
| } | |||||
| fmt.Printf("Version to string: %q\n", v) | |||||
| fmt.Printf("Major: %d\n", v.Major) | |||||
| fmt.Printf("Minor: %d\n", v.Minor) | |||||
| fmt.Printf("Patch: %d\n", v.Patch) | |||||
| // Prerelease versions | |||||
| if len(v.Pre) > 0 { | |||||
| fmt.Println("Prerelease versions:") | |||||
| for i, pre := range v.Pre { | |||||
| fmt.Printf("%d: %q\n", i, pre) | |||||
| } | |||||
| } | |||||
| // Build meta data | |||||
| if len(v.Build) > 0 { | |||||
| fmt.Println("Build meta data:") | |||||
| for i, build := range v.Build { | |||||
| fmt.Printf("%d: %q\n", i, build) | |||||
| } | |||||
| } | |||||
| // Make == Parse (Value), New for Pointer | |||||
| v001, err := semver.Make("0.0.1") | |||||
| fmt.Println("\nUse Version.Compare for comparisons (-1, 0, 1):") | |||||
| fmt.Printf("%q is greater than %q: Compare == %d\n", v001, v, v001.Compare(v)) | |||||
| fmt.Printf("%q is less than %q: Compare == %d\n", v, v001, v.Compare(v001)) | |||||
| fmt.Printf("%q is equal to %q: Compare == %d\n", v, v, v.Compare(v)) | |||||
| fmt.Println("\nUse comparison helpers returning booleans:") | |||||
| fmt.Printf("%q is greater than %q: %t\n", v001, v, v001.GT(v)) | |||||
| fmt.Printf("%q is greater than equal %q: %t\n", v001, v, v001.GTE(v)) | |||||
| fmt.Printf("%q is greater than equal %q: %t\n", v, v, v.GTE(v)) | |||||
| fmt.Printf("%q is less than %q: %t\n", v, v001, v.LT(v001)) | |||||
| fmt.Printf("%q is less than equal %q: %t\n", v, v001, v.LTE(v001)) | |||||
| fmt.Printf("%q is less than equal %q: %t\n", v, v, v.LTE(v)) | |||||
| fmt.Println("\nManipulate Version in place:") | |||||
| v.Pre[0], err = semver.NewPRVersion("beta") | |||||
| if err != nil { | |||||
| fmt.Printf("Error parsing pre release version: %q", err) | |||||
| } | |||||
| fmt.Printf("Version to string: %q\n", v) | |||||
| fmt.Println("\nCompare Prerelease versions:") | |||||
| pre1, _ := semver.NewPRVersion("123") | |||||
| pre2, _ := semver.NewPRVersion("alpha") | |||||
| pre3, _ := semver.NewPRVersion("124") | |||||
| fmt.Printf("%q is less than %q: Compare == %d\n", pre1, pre2, pre1.Compare(pre2)) | |||||
| fmt.Printf("%q is greater than %q: Compare == %d\n", pre3, pre1, pre3.Compare(pre1)) | |||||
| fmt.Printf("%q is equal to %q: Compare == %d\n", pre1, pre1, pre1.Compare(pre1)) | |||||
| fmt.Println("\nValidate versions:") | |||||
| v.Build[0] = "?" | |||||
| err = v.Validate() | |||||
| if err != nil { | |||||
| fmt.Printf("Validation failed: %s\n", err) | |||||
| } | |||||
| fmt.Println("Create valid build meta data:") | |||||
| b1, _ := semver.NewBuildVersion("build123") | |||||
| v.Build[0] = b1 | |||||
| fmt.Printf("Version with new build version %q\n", v) | |||||
| _, err = semver.NewBuildVersion("build?123") | |||||
| if err != nil { | |||||
| fmt.Printf("Create build version failed: %s\n", err) | |||||
| } | |||||
| } | |||||
| @ -1,23 +0,0 @@ | |||||
| package semver | |||||
| import ( | |||||
| "encoding/json" | |||||
| ) | |||||
| // MarshalJSON implements the encoding/json.Marshaler interface. | |||||
| func (v Version) MarshalJSON() ([]byte, error) { | |||||
| return json.Marshal(v.String()) | |||||
| } | |||||
| // UnmarshalJSON implements the encoding/json.Unmarshaler interface. | |||||
| func (v *Version) UnmarshalJSON(data []byte) (err error) { | |||||
| var versionString string | |||||
| if err = json.Unmarshal(data, &versionString); err != nil { | |||||
| return | |||||
| } | |||||
| *v, err = Parse(versionString) | |||||
| return | |||||
| } | |||||
| @ -1,45 +0,0 @@ | |||||
| package semver | |||||
| import ( | |||||
| "encoding/json" | |||||
| "strconv" | |||||
| "testing" | |||||
| ) | |||||
| func TestJSONMarshal(t *testing.T) { | |||||
| versionString := "3.1.4-alpha.1.5.9+build.2.6.5" | |||||
| v, err := Parse(versionString) | |||||
| if err != nil { | |||||
| t.Fatal(err) | |||||
| } | |||||
| versionJSON, err := json.Marshal(v) | |||||
| if err != nil { | |||||
| t.Fatal(err) | |||||
| } | |||||
| quotedVersionString := strconv.Quote(versionString) | |||||
| if string(versionJSON) != quotedVersionString { | |||||
| t.Fatalf("JSON marshaled semantic version not equal: expected %q, got %q", quotedVersionString, string(versionJSON)) | |||||
| } | |||||
| } | |||||
| func TestJSONUnmarshal(t *testing.T) { | |||||
| versionString := "3.1.4-alpha.1.5.9+build.2.6.5" | |||||
| quotedVersionString := strconv.Quote(versionString) | |||||
| var v Version | |||||
| if err := json.Unmarshal([]byte(quotedVersionString), &v); err != nil { | |||||
| t.Fatal(err) | |||||
| } | |||||
| if v.String() != versionString { | |||||
| t.Fatalf("JSON unmarshaled semantic version not equal: expected %q, got %q", versionString, v.String()) | |||||
| } | |||||
| badVersionString := strconv.Quote("3.1.4.1.5.9.2.6.5-other-digits-of-pi") | |||||
| if err := json.Unmarshal([]byte(badVersionString), &v); err == nil { | |||||
| t.Fatal("expected JSON unmarshal error, got nil") | |||||
| } | |||||
| } | |||||
| @ -1,233 +0,0 @@ | |||||
| package semver | |||||
| import ( | |||||
| "fmt" | |||||
| "strings" | |||||
| "unicode" | |||||
| ) | |||||
| type comparator func(Version, Version) bool | |||||
| var ( | |||||
| compEQ comparator = func(v1 Version, v2 Version) bool { | |||||
| return v1.Compare(v2) == 0 | |||||
| } | |||||
| compNE = func(v1 Version, v2 Version) bool { | |||||
| return v1.Compare(v2) != 0 | |||||
| } | |||||
| compGT = func(v1 Version, v2 Version) bool { | |||||
| return v1.Compare(v2) == 1 | |||||
| } | |||||
| compGE = func(v1 Version, v2 Version) bool { | |||||
| return v1.Compare(v2) >= 0 | |||||
| } | |||||
| compLT = func(v1 Version, v2 Version) bool { | |||||
| return v1.Compare(v2) == -1 | |||||
| } | |||||
| compLE = func(v1 Version, v2 Version) bool { | |||||
| return v1.Compare(v2) <= 0 | |||||
| } | |||||
| ) | |||||
| type versionRange struct { | |||||
| v Version | |||||
| c comparator | |||||
| } | |||||
| // rangeFunc creates a Range from the given versionRange. | |||||
| func (vr *versionRange) rangeFunc() Range { | |||||
| return Range(func(v Version) bool { | |||||
| return vr.c(v, vr.v) | |||||
| }) | |||||
| } | |||||
| // Range represents a range of versions. | |||||
| // A Range can be used to check if a Version satisfies it: | |||||
| // | |||||
| // range, err := semver.ParseRange(">1.0.0 <2.0.0") | |||||
| // range(semver.MustParse("1.1.1") // returns true | |||||
| type Range func(Version) bool | |||||
| // OR combines the existing Range with another Range using logical OR. | |||||
| func (rf Range) OR(f Range) Range { | |||||
| return Range(func(v Version) bool { | |||||
| return rf(v) || f(v) | |||||
| }) | |||||
| } | |||||
| // AND combines the existing Range with another Range using logical AND. | |||||
| func (rf Range) AND(f Range) Range { | |||||
| return Range(func(v Version) bool { | |||||
| return rf(v) && f(v) | |||||
| }) | |||||
| } | |||||
| // ParseRange parses a range and returns a Range. | |||||
| // If the range could not be parsed an error is returned. | |||||
| // | |||||
| // Valid ranges are: | |||||
| // - "<1.0.0" | |||||
| // - "<=1.0.0" | |||||
| // - ">1.0.0" | |||||
| // - ">=1.0.0" | |||||
| // - "1.0.0", "=1.0.0", "==1.0.0" | |||||
| // - "!1.0.0", "!=1.0.0" | |||||
| // | |||||
| // A Range can consist of multiple ranges separated by space: | |||||
| // Ranges can be linked by logical AND: | |||||
| // - ">1.0.0 <2.0.0" would match between both ranges, so "1.1.1" and "1.8.7" but not "1.0.0" or "2.0.0" | |||||
| // - ">1.0.0 <3.0.0 !2.0.3-beta.2" would match every version between 1.0.0 and 3.0.0 except 2.0.3-beta.2 | |||||
| // | |||||
| // Ranges can also be linked by logical OR: | |||||
| // - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x" | |||||
| // | |||||
| // AND has a higher precedence than OR. It's not possible to use brackets. | |||||
| // | |||||
| // Ranges can be combined by both AND and OR | |||||
| // | |||||
| // - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1` | |||||
| func ParseRange(s string) (Range, error) { | |||||
| parts := splitAndTrim(s) | |||||
| orParts, err := splitORParts(parts) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| var orFn Range | |||||
| for _, p := range orParts { | |||||
| var andFn Range | |||||
| for _, ap := range p { | |||||
| opStr, vStr, err := splitComparatorVersion(ap) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| vr, err := buildVersionRange(opStr, vStr) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err) | |||||
| } | |||||
| rf := vr.rangeFunc() | |||||
| // Set function | |||||
| if andFn == nil { | |||||
| andFn = rf | |||||
| } else { // Combine with existing function | |||||
| andFn = andFn.AND(rf) | |||||
| } | |||||
| } | |||||
| if orFn == nil { | |||||
| orFn = andFn | |||||
| } else { | |||||
| orFn = orFn.OR(andFn) | |||||
| } | |||||
| } | |||||
| return orFn, nil | |||||
| } | |||||
| // splitORParts splits the already cleaned parts by '||'. | |||||
| // Checks for invalid positions of the operator and returns an | |||||
| // error if found. | |||||
| func splitORParts(parts []string) ([][]string, error) { | |||||
| var ORparts [][]string | |||||
| last := 0 | |||||
| for i, p := range parts { | |||||
| if p == "||" { | |||||
| if i == 0 { | |||||
| return nil, fmt.Errorf("First element in range is '||'") | |||||
| } | |||||
| ORparts = append(ORparts, parts[last:i]) | |||||
| last = i + 1 | |||||
| } | |||||
| } | |||||
| if last == len(parts) { | |||||
| return nil, fmt.Errorf("Last element in range is '||'") | |||||
| } | |||||
| ORparts = append(ORparts, parts[last:]) | |||||
| return ORparts, nil | |||||
| } | |||||
| // buildVersionRange takes a slice of 2: operator and version | |||||
| // and builds a versionRange, otherwise an error. | |||||
| func buildVersionRange(opStr, vStr string) (*versionRange, error) { | |||||
| c := parseComparator(opStr) | |||||
| if c == nil { | |||||
| return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, "")) | |||||
| } | |||||
| v, err := Parse(vStr) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("Could not parse version %q in %q: %s", vStr, strings.Join([]string{opStr, vStr}, ""), err) | |||||
| } | |||||
| return &versionRange{ | |||||
| v: v, | |||||
| c: c, | |||||
| }, nil | |||||
| } | |||||
| // splitAndTrim splits a range string by spaces and cleans leading and trailing spaces | |||||
| func splitAndTrim(s string) (result []string) { | |||||
| last := 0 | |||||
| for i := 0; i < len(s); i++ { | |||||
| if s[i] == ' ' { | |||||
| if last < i-1 { | |||||
| result = append(result, s[last:i]) | |||||
| } | |||||
| last = i + 1 | |||||
| } | |||||
| } | |||||
| if last < len(s)-1 { | |||||
| result = append(result, s[last:]) | |||||
| } | |||||
| // parts := strings.Split(s, " ") | |||||
| // for _, x := range parts { | |||||
| // if s := strings.TrimSpace(x); len(s) != 0 { | |||||
| // result = append(result, s) | |||||
| // } | |||||
| // } | |||||
| return | |||||
| } | |||||
| // splitComparatorVersion splits the comparator from the version. | |||||
| // Spaces between the comparator and the version are not allowed. | |||||
| // Input must be free of leading or trailing spaces. | |||||
| func splitComparatorVersion(s string) (string, string, error) { | |||||
| i := strings.IndexFunc(s, unicode.IsDigit) | |||||
| if i == -1 { | |||||
| return "", "", fmt.Errorf("Could not get version from string: %q", s) | |||||
| } | |||||
| return strings.TrimSpace(s[0:i]), s[i:], nil | |||||
| } | |||||
| func parseComparator(s string) comparator { | |||||
| switch s { | |||||
| case "==": | |||||
| fallthrough | |||||
| case "": | |||||
| fallthrough | |||||
| case "=": | |||||
| return compEQ | |||||
| case ">": | |||||
| return compGT | |||||
| case ">=": | |||||
| return compGE | |||||
| case "<": | |||||
| return compLT | |||||
| case "<=": | |||||
| return compLE | |||||
| case "!": | |||||
| fallthrough | |||||
| case "!=": | |||||
| return compNE | |||||
| } | |||||
| return nil | |||||
| } | |||||
| // MustParseRange is like ParseRange but panics if the range cannot be parsed. | |||||
| func MustParseRange(s string) Range { | |||||
| r, err := ParseRange(s) | |||||
| if err != nil { | |||||
| panic(`semver: ParseRange(` + s + `): ` + err.Error()) | |||||
| } | |||||
| return r | |||||
| } | |||||
| @ -1,459 +0,0 @@ | |||||
| package semver | |||||
| import ( | |||||
| "reflect" | |||||
| "strings" | |||||
| "testing" | |||||
| ) | |||||
| 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 <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 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}, | |||||
| // 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}, | |||||
| }}, | |||||
| // 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.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) | |||||
| } | |||||
| } | |||||
| @ -1,418 +0,0 @@ | |||||
| package semver | |||||
| import ( | |||||
| "errors" | |||||
| "fmt" | |||||
| "strconv" | |||||
| "strings" | |||||
| ) | |||||
| const ( | |||||
| numbers string = "0123456789" | |||||
| alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" | |||||
| alphanum = alphas + numbers | |||||
| ) | |||||
| // SpecVersion is the latest fully supported spec version of semver | |||||
| var SpecVersion = Version{ | |||||
| Major: 2, | |||||
| Minor: 0, | |||||
| Patch: 0, | |||||
| } | |||||
| // Version represents a semver compatible version | |||||
| type Version struct { | |||||
| Major uint64 | |||||
| Minor uint64 | |||||
| Patch uint64 | |||||
| Pre []PRVersion | |||||
| Build []string //No Precendence | |||||
| } | |||||
| // Version to string | |||||
| func (v Version) String() string { | |||||
| b := make([]byte, 0, 5) | |||||
| b = strconv.AppendUint(b, v.Major, 10) | |||||
| b = append(b, '.') | |||||
| b = strconv.AppendUint(b, v.Minor, 10) | |||||
| b = append(b, '.') | |||||
| b = strconv.AppendUint(b, v.Patch, 10) | |||||
| if len(v.Pre) > 0 { | |||||
| b = append(b, '-') | |||||
| b = append(b, v.Pre[0].String()...) | |||||
| for _, pre := range v.Pre[1:] { | |||||
| b = append(b, '.') | |||||
| b = append(b, pre.String()...) | |||||
| } | |||||
| } | |||||
| if len(v.Build) > 0 { | |||||
| b = append(b, '+') | |||||
| b = append(b, v.Build[0]...) | |||||
| for _, build := range v.Build[1:] { | |||||
| b = append(b, '.') | |||||
| b = append(b, build...) | |||||
| } | |||||
| } | |||||
| return string(b) | |||||
| } | |||||
| // Equals checks if v is equal to o. | |||||
| func (v Version) Equals(o Version) bool { | |||||
| return (v.Compare(o) == 0) | |||||
| } | |||||
| // EQ checks if v is equal to o. | |||||
| func (v Version) EQ(o Version) bool { | |||||
| return (v.Compare(o) == 0) | |||||
| } | |||||
| // NE checks if v is not equal to o. | |||||
| func (v Version) NE(o Version) bool { | |||||
| return (v.Compare(o) != 0) | |||||
| } | |||||
| // GT checks if v is greater than o. | |||||
| func (v Version) GT(o Version) bool { | |||||
| return (v.Compare(o) == 1) | |||||
| } | |||||
| // GTE checks if v is greater than or equal to o. | |||||
| func (v Version) GTE(o Version) bool { | |||||
| return (v.Compare(o) >= 0) | |||||
| } | |||||
| // GE checks if v is greater than or equal to o. | |||||
| func (v Version) GE(o Version) bool { | |||||
| return (v.Compare(o) >= 0) | |||||
| } | |||||
| // LT checks if v is less than o. | |||||
| func (v Version) LT(o Version) bool { | |||||
| return (v.Compare(o) == -1) | |||||
| } | |||||
| // LTE checks if v is less than or equal to o. | |||||
| func (v Version) LTE(o Version) bool { | |||||
| return (v.Compare(o) <= 0) | |||||
| } | |||||
| // LE checks if v is less than or equal to o. | |||||
| func (v Version) LE(o Version) bool { | |||||
| return (v.Compare(o) <= 0) | |||||
| } | |||||
| // Compare compares Versions v to o: | |||||
| // -1 == v is less than o | |||||
| // 0 == v is equal to o | |||||
| // 1 == v is greater than o | |||||
| func (v Version) Compare(o Version) int { | |||||
| if v.Major != o.Major { | |||||
| if v.Major > o.Major { | |||||
| return 1 | |||||
| } | |||||
| return -1 | |||||
| } | |||||
| if v.Minor != o.Minor { | |||||
| if v.Minor > o.Minor { | |||||
| return 1 | |||||
| } | |||||
| return -1 | |||||
| } | |||||
| if v.Patch != o.Patch { | |||||
| if v.Patch > o.Patch { | |||||
| return 1 | |||||
| } | |||||
| return -1 | |||||
| } | |||||
| // Quick comparison if a version has no prerelease versions | |||||
| if len(v.Pre) == 0 && len(o.Pre) == 0 { | |||||
| return 0 | |||||
| } else if len(v.Pre) == 0 && len(o.Pre) > 0 { | |||||
| return 1 | |||||
| } else if len(v.Pre) > 0 && len(o.Pre) == 0 { | |||||
| return -1 | |||||
| } | |||||
| i := 0 | |||||
| for ; i < len(v.Pre) && i < len(o.Pre); i++ { | |||||
| if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 { | |||||
| continue | |||||
| } else if comp == 1 { | |||||
| return 1 | |||||
| } else { | |||||
| return -1 | |||||
| } | |||||
| } | |||||
| // If all pr versions are the equal but one has further prversion, this one greater | |||||
| if i == len(v.Pre) && i == len(o.Pre) { | |||||
| return 0 | |||||
| } else if i == len(v.Pre) && i < len(o.Pre) { | |||||
| return -1 | |||||
| } else { | |||||
| return 1 | |||||
| } | |||||
| } | |||||
| // Validate validates v and returns error in case | |||||
| func (v Version) Validate() error { | |||||
| // Major, Minor, Patch already validated using uint64 | |||||
| for _, pre := range v.Pre { | |||||
| if !pre.IsNum { //Numeric prerelease versions already uint64 | |||||
| if len(pre.VersionStr) == 0 { | |||||
| return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr) | |||||
| } | |||||
| if !containsOnly(pre.VersionStr, alphanum) { | |||||
| return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr) | |||||
| } | |||||
| } | |||||
| } | |||||
| for _, build := range v.Build { | |||||
| if len(build) == 0 { | |||||
| return fmt.Errorf("Build meta data can not be empty %q", build) | |||||
| } | |||||
| if !containsOnly(build, alphanum) { | |||||
| return fmt.Errorf("Invalid character(s) found in build meta data %q", build) | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| // New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error | |||||
| func New(s string) (vp *Version, err error) { | |||||
| v, err := Parse(s) | |||||
| vp = &v | |||||
| return | |||||
| } | |||||
| // Make is an alias for Parse, parses version string and returns a validated Version or error | |||||
| func Make(s string) (Version, error) { | |||||
| return Parse(s) | |||||
| } | |||||
| // ParseTolerant allows for certain version specifications that do not strictly adhere to semver | |||||
| // specs to be parsed by this library. It does so by normalizing versions before passing them to | |||||
| // Parse(). It currently trims spaces, removes a "v" prefix, and adds a 0 patch number to versions | |||||
| // with only major and minor components specified | |||||
| func ParseTolerant(s string) (Version, error) { | |||||
| s = strings.TrimSpace(s) | |||||
| s = strings.TrimPrefix(s, "v") | |||||
| // Split into major.minor.(patch+pr+meta) | |||||
| parts := strings.SplitN(s, ".", 3) | |||||
| if len(parts) < 3 { | |||||
| if strings.ContainsAny(parts[len(parts)-1], "+-") { | |||||
| return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data") | |||||
| } | |||||
| for len(parts) < 3 { | |||||
| parts = append(parts, "0") | |||||
| } | |||||
| s = strings.Join(parts, ".") | |||||
| } | |||||
| return Parse(s) | |||||
| } | |||||
| // Parse parses version string and returns a validated Version or error | |||||
| func Parse(s string) (Version, error) { | |||||
| if len(s) == 0 { | |||||
| return Version{}, errors.New("Version string empty") | |||||
| } | |||||
| // Split into major.minor.(patch+pr+meta) | |||||
| parts := strings.SplitN(s, ".", 3) | |||||
| if len(parts) != 3 { | |||||
| return Version{}, errors.New("No Major.Minor.Patch elements found") | |||||
| } | |||||
| // Major | |||||
| if !containsOnly(parts[0], numbers) { | |||||
| return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0]) | |||||
| } | |||||
| if hasLeadingZeroes(parts[0]) { | |||||
| return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0]) | |||||
| } | |||||
| major, err := strconv.ParseUint(parts[0], 10, 64) | |||||
| if err != nil { | |||||
| return Version{}, err | |||||
| } | |||||
| // Minor | |||||
| if !containsOnly(parts[1], numbers) { | |||||
| return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1]) | |||||
| } | |||||
| if hasLeadingZeroes(parts[1]) { | |||||
| return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1]) | |||||
| } | |||||
| minor, err := strconv.ParseUint(parts[1], 10, 64) | |||||
| if err != nil { | |||||
| return Version{}, err | |||||
| } | |||||
| v := Version{} | |||||
| v.Major = major | |||||
| v.Minor = minor | |||||
| var build, prerelease []string | |||||
| patchStr := parts[2] | |||||
| if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 { | |||||
| build = strings.Split(patchStr[buildIndex+1:], ".") | |||||
| patchStr = patchStr[:buildIndex] | |||||
| } | |||||
| if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 { | |||||
| prerelease = strings.Split(patchStr[preIndex+1:], ".") | |||||
| patchStr = patchStr[:preIndex] | |||||
| } | |||||
| if !containsOnly(patchStr, numbers) { | |||||
| return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr) | |||||
| } | |||||
| if hasLeadingZeroes(patchStr) { | |||||
| return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr) | |||||
| } | |||||
| patch, err := strconv.ParseUint(patchStr, 10, 64) | |||||
| if err != nil { | |||||
| return Version{}, err | |||||
| } | |||||
| v.Patch = patch | |||||
| // Prerelease | |||||
| for _, prstr := range prerelease { | |||||
| parsedPR, err := NewPRVersion(prstr) | |||||
| if err != nil { | |||||
| return Version{}, err | |||||
| } | |||||
| v.Pre = append(v.Pre, parsedPR) | |||||
| } | |||||
| // Build meta data | |||||
| for _, str := range build { | |||||
| if len(str) == 0 { | |||||
| return Version{}, errors.New("Build meta data is empty") | |||||
| } | |||||
| if !containsOnly(str, alphanum) { | |||||
| return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str) | |||||
| } | |||||
| v.Build = append(v.Build, str) | |||||
| } | |||||
| return v, nil | |||||
| } | |||||
| // MustParse is like Parse but panics if the version cannot be parsed. | |||||
| func MustParse(s string) Version { | |||||
| v, err := Parse(s) | |||||
| if err != nil { | |||||
| panic(`semver: Parse(` + s + `): ` + err.Error()) | |||||
| } | |||||
| return v | |||||
| } | |||||
| // PRVersion represents a PreRelease Version | |||||
| type PRVersion struct { | |||||
| VersionStr string | |||||
| VersionNum uint64 | |||||
| IsNum bool | |||||
| } | |||||
| // NewPRVersion creates a new valid prerelease version | |||||
| func NewPRVersion(s string) (PRVersion, error) { | |||||
| if len(s) == 0 { | |||||
| return PRVersion{}, errors.New("Prerelease is empty") | |||||
| } | |||||
| v := PRVersion{} | |||||
| if containsOnly(s, numbers) { | |||||
| if hasLeadingZeroes(s) { | |||||
| return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s) | |||||
| } | |||||
| num, err := strconv.ParseUint(s, 10, 64) | |||||
| // Might never be hit, but just in case | |||||
| if err != nil { | |||||
| return PRVersion{}, err | |||||
| } | |||||
| v.VersionNum = num | |||||
| v.IsNum = true | |||||
| } else if containsOnly(s, alphanum) { | |||||
| v.VersionStr = s | |||||
| v.IsNum = false | |||||
| } else { | |||||
| return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s) | |||||
| } | |||||
| return v, nil | |||||
| } | |||||
| // IsNumeric checks if prerelease-version is numeric | |||||
| func (v PRVersion) IsNumeric() bool { | |||||
| return v.IsNum | |||||
| } | |||||
| // Compare compares two PreRelease Versions v and o: | |||||
| // -1 == v is less than o | |||||
| // 0 == v is equal to o | |||||
| // 1 == v is greater than o | |||||
| func (v PRVersion) Compare(o PRVersion) int { | |||||
| if v.IsNum && !o.IsNum { | |||||
| return -1 | |||||
| } else if !v.IsNum && o.IsNum { | |||||
| return 1 | |||||
| } else if v.IsNum && o.IsNum { | |||||
| if v.VersionNum == o.VersionNum { | |||||
| return 0 | |||||
| } else if v.VersionNum > o.VersionNum { | |||||
| return 1 | |||||
| } else { | |||||
| return -1 | |||||
| } | |||||
| } else { // both are Alphas | |||||
| if v.VersionStr == o.VersionStr { | |||||
| return 0 | |||||
| } else if v.VersionStr > o.VersionStr { | |||||
| return 1 | |||||
| } else { | |||||
| return -1 | |||||
| } | |||||
| } | |||||
| } | |||||
| // PreRelease version to string | |||||
| func (v PRVersion) String() string { | |||||
| if v.IsNum { | |||||
| return strconv.FormatUint(v.VersionNum, 10) | |||||
| } | |||||
| return v.VersionStr | |||||
| } | |||||
| func containsOnly(s string, set string) bool { | |||||
| return strings.IndexFunc(s, func(r rune) bool { | |||||
| return !strings.ContainsRune(set, r) | |||||
| }) == -1 | |||||
| } | |||||
| func hasLeadingZeroes(s string) bool { | |||||
| return len(s) > 1 && s[0] == '0' | |||||
| } | |||||
| // NewBuildVersion creates a new valid build version | |||||
| func NewBuildVersion(s string) (string, error) { | |||||
| if len(s) == 0 { | |||||
| return "", errors.New("Buildversion is empty") | |||||
| } | |||||
| if !containsOnly(s, alphanum) { | |||||
| return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s) | |||||
| } | |||||
| return s, nil | |||||
| } | |||||
| @ -1,458 +0,0 @@ | |||||
| package semver | |||||
| import ( | |||||
| "testing" | |||||
| ) | |||||
| func prstr(s string) PRVersion { | |||||
| return PRVersion{s, 0, false} | |||||
| } | |||||
| func prnum(i uint64) PRVersion { | |||||
| return PRVersion{"", i, true} | |||||
| } | |||||
| type formatTest struct { | |||||
| v Version | |||||
| result string | |||||
| } | |||||
| var formatTests = []formatTest{ | |||||
| {Version{1, 2, 3, nil, nil}, "1.2.3"}, | |||||
| {Version{0, 0, 1, nil, nil}, "0.0.1"}, | |||||
| {Version{0, 0, 1, []PRVersion{prstr("alpha"), prstr("preview")}, []string{"123", "456"}}, "0.0.1-alpha.preview+123.456"}, | |||||
| {Version{1, 2, 3, []PRVersion{prstr("alpha"), prnum(1)}, []string{"123", "456"}}, "1.2.3-alpha.1+123.456"}, | |||||
| {Version{1, 2, 3, []PRVersion{prstr("alpha"), prnum(1)}, nil}, "1.2.3-alpha.1"}, | |||||
| {Version{1, 2, 3, nil, []string{"123", "456"}}, "1.2.3+123.456"}, | |||||
| // Prereleases and build metadata hyphens | |||||
| {Version{1, 2, 3, []PRVersion{prstr("alpha"), prstr("b-eta")}, []string{"123", "b-uild"}}, "1.2.3-alpha.b-eta+123.b-uild"}, | |||||
| {Version{1, 2, 3, nil, []string{"123", "b-uild"}}, "1.2.3+123.b-uild"}, | |||||
| {Version{1, 2, 3, []PRVersion{prstr("alpha"), prstr("b-eta")}, nil}, "1.2.3-alpha.b-eta"}, | |||||
| } | |||||
| var tolerantFormatTests = []formatTest{ | |||||
| {Version{1, 2, 3, nil, nil}, "v1.2.3"}, | |||||
| {Version{1, 2, 3, nil, nil}, " 1.2.3 "}, | |||||
| {Version{1, 2, 0, nil, nil}, "1.2"}, | |||||
| {Version{1, 0, 0, nil, nil}, "1"}, | |||||
| } | |||||
| func TestStringer(t *testing.T) { | |||||
| for _, test := range formatTests { | |||||
| if res := test.v.String(); res != test.result { | |||||
| t.Errorf("Stringer, expected %q but got %q", test.result, res) | |||||
| } | |||||
| } | |||||
| } | |||||
| func TestParse(t *testing.T) { | |||||
| for _, test := range formatTests { | |||||
| if v, err := Parse(test.result); err != nil { | |||||
| t.Errorf("Error parsing %q: %q", test.result, err) | |||||
| } else if comp := v.Compare(test.v); comp != 0 { | |||||
| t.Errorf("Parsing, expected %q but got %q, comp: %d ", test.v, v, comp) | |||||
| } else if err := v.Validate(); err != nil { | |||||
| t.Errorf("Error validating parsed version %q: %q", test.v, err) | |||||
| } | |||||
| } | |||||
| } | |||||
| func TestParseTolerant(t *testing.T) { | |||||
| for _, test := range tolerantFormatTests { | |||||
| if v, err := ParseTolerant(test.result); err != nil { | |||||
| t.Errorf("Error parsing %q: %q", test.result, err) | |||||
| } else if comp := v.Compare(test.v); comp != 0 { | |||||
| t.Errorf("Parsing, expected %q but got %q, comp: %d ", test.v, v, comp) | |||||
| } else if err := v.Validate(); err != nil { | |||||
| t.Errorf("Error validating parsed version %q: %q", test.v, err) | |||||
| } | |||||
| } | |||||
| } | |||||
| func TestMustParse(t *testing.T) { | |||||
| _ = MustParse("32.2.1-alpha") | |||||
| } | |||||
| func TestMustParse_panic(t *testing.T) { | |||||
| defer func() { | |||||
| if recover() == nil { | |||||
| t.Errorf("Should have panicked") | |||||
| } | |||||
| }() | |||||
| _ = MustParse("invalid version") | |||||
| } | |||||
| func TestValidate(t *testing.T) { | |||||
| for _, test := range formatTests { | |||||
| if err := test.v.Validate(); err != nil { | |||||
| t.Errorf("Error validating %q: %q", test.v, err) | |||||
| } | |||||
| } | |||||
| } | |||||
| type compareTest struct { | |||||
| v1 Version | |||||
| v2 Version | |||||
| result int | |||||
| } | |||||
| var compareTests = []compareTest{ | |||||
| {Version{1, 0, 0, nil, nil}, Version{1, 0, 0, nil, nil}, 0}, | |||||
| {Version{2, 0, 0, nil, nil}, Version{1, 0, 0, nil, nil}, 1}, | |||||
| {Version{0, 1, 0, nil, nil}, Version{0, 1, 0, nil, nil}, 0}, | |||||
| {Version{0, 2, 0, nil, nil}, Version{0, 1, 0, nil, nil}, 1}, | |||||
| {Version{0, 0, 1, nil, nil}, Version{0, 0, 1, nil, nil}, 0}, | |||||
| {Version{0, 0, 2, nil, nil}, Version{0, 0, 1, nil, nil}, 1}, | |||||
| {Version{1, 2, 3, nil, nil}, Version{1, 2, 3, nil, nil}, 0}, | |||||
| {Version{2, 2, 4, nil, nil}, Version{1, 2, 4, nil, nil}, 1}, | |||||
| {Version{1, 3, 3, nil, nil}, Version{1, 2, 3, nil, nil}, 1}, | |||||
| {Version{1, 2, 4, nil, nil}, Version{1, 2, 3, nil, nil}, 1}, | |||||
| // Spec Examples #11 | |||||
| {Version{1, 0, 0, nil, nil}, Version{2, 0, 0, nil, nil}, -1}, | |||||
| {Version{2, 0, 0, nil, nil}, Version{2, 1, 0, nil, nil}, -1}, | |||||
| {Version{2, 1, 0, nil, nil}, Version{2, 1, 1, nil, nil}, -1}, | |||||
| // Spec Examples #9 | |||||
| {Version{1, 0, 0, nil, nil}, Version{1, 0, 0, []PRVersion{prstr("alpha")}, nil}, 1}, | |||||
| {Version{1, 0, 0, []PRVersion{prstr("alpha")}, nil}, Version{1, 0, 0, []PRVersion{prstr("alpha"), prnum(1)}, nil}, -1}, | |||||
| {Version{1, 0, 0, []PRVersion{prstr("alpha"), prnum(1)}, nil}, Version{1, 0, 0, []PRVersion{prstr("alpha"), prstr("beta")}, nil}, -1}, | |||||
| {Version{1, 0, 0, []PRVersion{prstr("alpha"), prstr("beta")}, nil}, Version{1, 0, 0, []PRVersion{prstr("beta")}, nil}, -1}, | |||||
| {Version{1, 0, 0, []PRVersion{prstr("beta")}, nil}, Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(2)}, nil}, -1}, | |||||
| {Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(2)}, nil}, Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(11)}, nil}, -1}, | |||||
| {Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(11)}, nil}, Version{1, 0, 0, []PRVersion{prstr("rc"), prnum(1)}, nil}, -1}, | |||||
| {Version{1, 0, 0, []PRVersion{prstr("rc"), prnum(1)}, nil}, Version{1, 0, 0, nil, nil}, -1}, | |||||
| // Ignore Build metadata | |||||
| {Version{1, 0, 0, nil, []string{"1", "2", "3"}}, Version{1, 0, 0, nil, nil}, 0}, | |||||
| } | |||||
| func TestCompare(t *testing.T) { | |||||
| for _, test := range compareTests { | |||||
| if res := test.v1.Compare(test.v2); res != test.result { | |||||
| t.Errorf("Comparing %q : %q, expected %d but got %d", test.v1, test.v2, test.result, res) | |||||
| } | |||||
| //Test counterpart | |||||
| if res := test.v2.Compare(test.v1); res != -test.result { | |||||
| t.Errorf("Comparing %q : %q, expected %d but got %d", test.v2, test.v1, -test.result, res) | |||||
| } | |||||
| } | |||||
| } | |||||
| type wrongformatTest struct { | |||||
| v *Version | |||||
| str string | |||||
| } | |||||
| var wrongformatTests = []wrongformatTest{ | |||||
| {nil, ""}, | |||||
| {nil, "."}, | |||||
| {nil, "1."}, | |||||
| {nil, ".1"}, | |||||
| {nil, "a.b.c"}, | |||||
| {nil, "1.a.b"}, | |||||
| {nil, "1.1.a"}, | |||||
| {nil, "1.a.1"}, | |||||
| {nil, "a.1.1"}, | |||||
| {nil, ".."}, | |||||
| {nil, "1.."}, | |||||
| {nil, "1.1."}, | |||||
| {nil, "1..1"}, | |||||
| {nil, "1.1.+123"}, | |||||
| {nil, "1.1.-beta"}, | |||||
| {nil, "-1.1.1"}, | |||||
| {nil, "1.-1.1"}, | |||||
| {nil, "1.1.-1"}, | |||||
| // giant numbers | |||||
| {nil, "20000000000000000000.1.1"}, | |||||
| {nil, "1.20000000000000000000.1"}, | |||||
| {nil, "1.1.20000000000000000000"}, | |||||
| {nil, "1.1.1-20000000000000000000"}, | |||||
| // Leading zeroes | |||||
| {nil, "01.1.1"}, | |||||
| {nil, "001.1.1"}, | |||||
| {nil, "1.01.1"}, | |||||
| {nil, "1.001.1"}, | |||||
| {nil, "1.1.01"}, | |||||
| {nil, "1.1.001"}, | |||||
| {nil, "1.1.1-01"}, | |||||
| {nil, "1.1.1-001"}, | |||||
| {nil, "1.1.1-beta.01"}, | |||||
| {nil, "1.1.1-beta.001"}, | |||||
| {&Version{0, 0, 0, []PRVersion{prstr("!")}, nil}, "0.0.0-!"}, | |||||
| {&Version{0, 0, 0, nil, []string{"!"}}, "0.0.0+!"}, | |||||
| // empty prversion | |||||
| {&Version{0, 0, 0, []PRVersion{prstr(""), prstr("alpha")}, nil}, "0.0.0-.alpha"}, | |||||
| // empty build meta data | |||||
| {&Version{0, 0, 0, []PRVersion{prstr("alpha")}, []string{""}}, "0.0.0-alpha+"}, | |||||
| {&Version{0, 0, 0, []PRVersion{prstr("alpha")}, []string{"test", ""}}, "0.0.0-alpha+test."}, | |||||
| } | |||||
| func TestWrongFormat(t *testing.T) { | |||||
| for _, test := range wrongformatTests { | |||||
| if res, err := Parse(test.str); err == nil { | |||||
| t.Errorf("Parsing wrong format version %q, expected error but got %q", test.str, res) | |||||
| } | |||||
| if test.v != nil { | |||||
| if err := test.v.Validate(); err == nil { | |||||
| t.Errorf("Validating wrong format version %q (%q), expected error", test.v, test.str) | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| var wrongTolerantFormatTests = []wrongformatTest{ | |||||
| {nil, "1.0+abc"}, | |||||
| {nil, "1.0-rc.1"}, | |||||
| } | |||||
| func TestWrongTolerantFormat(t *testing.T) { | |||||
| for _, test := range wrongTolerantFormatTests { | |||||
| if res, err := ParseTolerant(test.str); err == nil { | |||||
| t.Errorf("Parsing wrong format version %q, expected error but got %q", test.str, res) | |||||
| } | |||||
| } | |||||
| } | |||||
| func TestCompareHelper(t *testing.T) { | |||||
| v := Version{1, 0, 0, []PRVersion{prstr("alpha")}, nil} | |||||
| v1 := Version{1, 0, 0, nil, nil} | |||||
| if !v.EQ(v) { | |||||
| t.Errorf("%q should be equal to %q", v, v) | |||||
| } | |||||
| if !v.Equals(v) { | |||||
| t.Errorf("%q should be equal to %q", v, v) | |||||
| } | |||||
| if !v1.NE(v) { | |||||
| t.Errorf("%q should not be equal to %q", v1, v) | |||||
| } | |||||
| if !v.GTE(v) { | |||||
| t.Errorf("%q should be greater than or equal to %q", v, v) | |||||
| } | |||||
| if !v.LTE(v) { | |||||
| t.Errorf("%q should be less than or equal to %q", v, v) | |||||
| } | |||||
| if !v.LT(v1) { | |||||
| t.Errorf("%q should be less than %q", v, v1) | |||||
| } | |||||
| if !v.LTE(v1) { | |||||
| t.Errorf("%q should be less than or equal %q", v, v1) | |||||
| } | |||||
| if !v.LE(v1) { | |||||
| t.Errorf("%q should be less than or equal %q", v, v1) | |||||
| } | |||||
| if !v1.GT(v) { | |||||
| t.Errorf("%q should be greater than %q", v1, v) | |||||
| } | |||||
| if !v1.GTE(v) { | |||||
| t.Errorf("%q should be greater than or equal %q", v1, v) | |||||
| } | |||||
| if !v1.GE(v) { | |||||
| t.Errorf("%q should be greater than or equal %q", v1, v) | |||||
| } | |||||
| } | |||||
| func TestPreReleaseVersions(t *testing.T) { | |||||
| p1, err := NewPRVersion("123") | |||||
| if !p1.IsNumeric() { | |||||
| t.Errorf("Expected numeric prversion, got %q", p1) | |||||
| } | |||||
| if p1.VersionNum != 123 { | |||||
| t.Error("Wrong prversion number") | |||||
| } | |||||
| if err != nil { | |||||
| t.Errorf("Not expected error %q", err) | |||||
| } | |||||
| p2, err := NewPRVersion("alpha") | |||||
| if p2.IsNumeric() { | |||||
| t.Errorf("Expected non-numeric prversion, got %q", p2) | |||||
| } | |||||
| if p2.VersionStr != "alpha" { | |||||
| t.Error("Wrong prversion string") | |||||
| } | |||||
| if err != nil { | |||||
| t.Errorf("Not expected error %q", err) | |||||
| } | |||||
| } | |||||
| func TestBuildMetaDataVersions(t *testing.T) { | |||||
| _, err := NewBuildVersion("123") | |||||
| if err != nil { | |||||
| t.Errorf("Unexpected error %q", err) | |||||
| } | |||||
| _, err = NewBuildVersion("build") | |||||
| if err != nil { | |||||
| t.Errorf("Unexpected error %q", err) | |||||
| } | |||||
| _, err = NewBuildVersion("test?") | |||||
| if err == nil { | |||||
| t.Error("Expected error, got none") | |||||
| } | |||||
| _, err = NewBuildVersion("") | |||||
| if err == nil { | |||||
| t.Error("Expected error, got none") | |||||
| } | |||||
| } | |||||
| func TestNewHelper(t *testing.T) { | |||||
| v, err := New("1.2.3") | |||||
| if err != nil { | |||||
| t.Fatalf("Unexpected error %q", err) | |||||
| } | |||||
| // New returns pointer | |||||
| if v == nil { | |||||
| t.Fatal("Version is nil") | |||||
| } | |||||
| if v.Compare(Version{1, 2, 3, nil, nil}) != 0 { | |||||
| t.Fatal("Unexpected comparison problem") | |||||
| } | |||||
| } | |||||
| func TestMakeHelper(t *testing.T) { | |||||
| v, err := Make("1.2.3") | |||||
| if err != nil { | |||||
| t.Fatalf("Unexpected error %q", err) | |||||
| } | |||||
| if v.Compare(Version{1, 2, 3, nil, nil}) != 0 { | |||||
| t.Fatal("Unexpected comparison problem") | |||||
| } | |||||
| } | |||||
| func BenchmarkParseSimple(b *testing.B) { | |||||
| const VERSION = "0.0.1" | |||||
| b.ReportAllocs() | |||||
| b.ResetTimer() | |||||
| for n := 0; n < b.N; n++ { | |||||
| Parse(VERSION) | |||||
| } | |||||
| } | |||||
| func BenchmarkParseComplex(b *testing.B) { | |||||
| const VERSION = "0.0.1-alpha.preview+123.456" | |||||
| b.ReportAllocs() | |||||
| b.ResetTimer() | |||||
| for n := 0; n < b.N; n++ { | |||||
| Parse(VERSION) | |||||
| } | |||||
| } | |||||
| func BenchmarkParseAverage(b *testing.B) { | |||||
| l := len(formatTests) | |||||
| b.ReportAllocs() | |||||
| b.ResetTimer() | |||||
| for n := 0; n < b.N; n++ { | |||||
| Parse(formatTests[n%l].result) | |||||
| } | |||||
| } | |||||
| func BenchmarkParseTolerantAverage(b *testing.B) { | |||||
| l := len(tolerantFormatTests) | |||||
| b.ReportAllocs() | |||||
| b.ResetTimer() | |||||
| for n := 0; n < b.N; n++ { | |||||
| ParseTolerant(tolerantFormatTests[n%l].result) | |||||
| } | |||||
| } | |||||
| func BenchmarkStringSimple(b *testing.B) { | |||||
| const VERSION = "0.0.1" | |||||
| v, _ := Parse(VERSION) | |||||
| b.ReportAllocs() | |||||
| b.ResetTimer() | |||||
| for n := 0; n < b.N; n++ { | |||||
| v.String() | |||||
| } | |||||
| } | |||||
| func BenchmarkStringLarger(b *testing.B) { | |||||
| const VERSION = "11.15.2012" | |||||
| v, _ := Parse(VERSION) | |||||
| b.ReportAllocs() | |||||
| b.ResetTimer() | |||||
| for n := 0; n < b.N; n++ { | |||||
| v.String() | |||||
| } | |||||
| } | |||||
| func BenchmarkStringComplex(b *testing.B) { | |||||
| const VERSION = "0.0.1-alpha.preview+123.456" | |||||
| v, _ := Parse(VERSION) | |||||
| b.ReportAllocs() | |||||
| b.ResetTimer() | |||||
| for n := 0; n < b.N; n++ { | |||||
| v.String() | |||||
| } | |||||
| } | |||||
| func BenchmarkStringAverage(b *testing.B) { | |||||
| l := len(formatTests) | |||||
| b.ReportAllocs() | |||||
| b.ResetTimer() | |||||
| for n := 0; n < b.N; n++ { | |||||
| formatTests[n%l].v.String() | |||||
| } | |||||
| } | |||||
| func BenchmarkValidateSimple(b *testing.B) { | |||||
| const VERSION = "0.0.1" | |||||
| v, _ := Parse(VERSION) | |||||
| b.ReportAllocs() | |||||
| b.ResetTimer() | |||||
| for n := 0; n < b.N; n++ { | |||||
| v.Validate() | |||||
| } | |||||
| } | |||||
| func BenchmarkValidateComplex(b *testing.B) { | |||||
| const VERSION = "0.0.1-alpha.preview+123.456" | |||||
| v, _ := Parse(VERSION) | |||||
| b.ReportAllocs() | |||||
| b.ResetTimer() | |||||
| for n := 0; n < b.N; n++ { | |||||
| v.Validate() | |||||
| } | |||||
| } | |||||
| func BenchmarkValidateAverage(b *testing.B) { | |||||
| l := len(formatTests) | |||||
| b.ReportAllocs() | |||||
| b.ResetTimer() | |||||
| for n := 0; n < b.N; n++ { | |||||
| formatTests[n%l].v.Validate() | |||||
| } | |||||
| } | |||||
| func BenchmarkCompareSimple(b *testing.B) { | |||||
| const VERSION = "0.0.1" | |||||
| v, _ := Parse(VERSION) | |||||
| b.ReportAllocs() | |||||
| b.ResetTimer() | |||||
| for n := 0; n < b.N; n++ { | |||||
| v.Compare(v) | |||||
| } | |||||
| } | |||||
| func BenchmarkCompareComplex(b *testing.B) { | |||||
| const VERSION = "0.0.1-alpha.preview+123.456" | |||||
| v, _ := Parse(VERSION) | |||||
| b.ReportAllocs() | |||||
| b.ResetTimer() | |||||
| for n := 0; n < b.N; n++ { | |||||
| v.Compare(v) | |||||
| } | |||||
| } | |||||
| func BenchmarkCompareAverage(b *testing.B) { | |||||
| l := len(compareTests) | |||||
| b.ReportAllocs() | |||||
| b.ResetTimer() | |||||
| for n := 0; n < b.N; n++ { | |||||
| compareTests[n%l].v1.Compare((compareTests[n%l].v2)) | |||||
| } | |||||
| } | |||||
| @ -1,28 +0,0 @@ | |||||
| package semver | |||||
| import ( | |||||
| "sort" | |||||
| ) | |||||
| // Versions represents multiple versions. | |||||
| type Versions []Version | |||||
| // Len returns length of version collection | |||||
| func (s Versions) Len() int { | |||||
| return len(s) | |||||
| } | |||||
| // Swap swaps two versions inside the collection by its indices | |||||
| func (s Versions) Swap(i, j int) { | |||||
| s[i], s[j] = s[j], s[i] | |||||
| } | |||||
| // Less checks if version at index i is less than version at index j | |||||
| func (s Versions) Less(i, j int) bool { | |||||
| return s[i].LT(s[j]) | |||||
| } | |||||
| // Sort sorts a slice of versions | |||||
| func Sort(versions []Version) { | |||||
| sort.Sort(Versions(versions)) | |||||
| } | |||||
| @ -1,30 +0,0 @@ | |||||
| package semver | |||||
| import ( | |||||
| "reflect" | |||||
| "testing" | |||||
| ) | |||||
| func TestSort(t *testing.T) { | |||||
| v100, _ := Parse("1.0.0") | |||||
| v010, _ := Parse("0.1.0") | |||||
| v001, _ := Parse("0.0.1") | |||||
| versions := []Version{v010, v100, v001} | |||||
| Sort(versions) | |||||
| correct := []Version{v001, v010, v100} | |||||
| if !reflect.DeepEqual(versions, correct) { | |||||
| t.Fatalf("Sort returned wrong order: %s", versions) | |||||
| } | |||||
| } | |||||
| func BenchmarkSort(b *testing.B) { | |||||
| v100, _ := Parse("1.0.0") | |||||
| v010, _ := Parse("0.1.0") | |||||
| v001, _ := Parse("0.0.1") | |||||
| b.ReportAllocs() | |||||
| b.ResetTimer() | |||||
| for n := 0; n < b.N; n++ { | |||||
| Sort([]Version{v010, v100, v001}) | |||||
| } | |||||
| } | |||||
| @ -1,30 +0,0 @@ | |||||
| package semver | |||||
| import ( | |||||
| "database/sql/driver" | |||||
| "fmt" | |||||
| ) | |||||
| // Scan implements the database/sql.Scanner interface. | |||||
| func (v *Version) Scan(src interface{}) (err error) { | |||||
| var str string | |||||
| switch src := src.(type) { | |||||
| case string: | |||||
| str = src | |||||
| case []byte: | |||||
| str = string(src) | |||||
| default: | |||||
| return fmt.Errorf("Version.Scan: cannot convert %T to string.", src) | |||||
| } | |||||
| if t, err := Parse(str); err == nil { | |||||
| *v = t | |||||
| } | |||||
| return | |||||
| } | |||||
| // Value implements the database/sql/driver.Valuer interface. | |||||
| func (v Version) Value() (driver.Value, error) { | |||||
| return v.String(), nil | |||||
| } | |||||
| @ -1,38 +0,0 @@ | |||||
| package semver | |||||
| import ( | |||||
| "testing" | |||||
| ) | |||||
| type scanTest struct { | |||||
| val interface{} | |||||
| shouldError bool | |||||
| expected string | |||||
| } | |||||
| var scanTests = []scanTest{ | |||||
| {"1.2.3", false, "1.2.3"}, | |||||
| {[]byte("1.2.3"), false, "1.2.3"}, | |||||
| {7, true, ""}, | |||||
| {7e4, true, ""}, | |||||
| {true, true, ""}, | |||||
| } | |||||
| func TestScanString(t *testing.T) { | |||||
| for _, tc := range scanTests { | |||||
| s := &Version{} | |||||
| err := s.Scan(tc.val) | |||||
| if tc.shouldError { | |||||
| if err == nil { | |||||
| t.Fatalf("Scan did not return an error on %v (%T)", tc.val, tc.val) | |||||
| } | |||||
| } else { | |||||
| if err != nil { | |||||
| t.Fatalf("Scan returned an unexpected error: %s (%T) on %v (%T)", tc.val, tc.val, tc.val, tc.val) | |||||
| } | |||||
| if val, _ := s.Value(); val != tc.expected { | |||||
| t.Errorf("Wrong Value returned, expected %q, got %q", tc.expected, val) | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||