git-subtree-dir: vendor/github.com/blang/semver git-subtree-mainline:c40bbe1f7fgit-subtree-split:60ec3488bf
| @ -0,0 +1,22 @@ | |||
| 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. | |||
| @ -0,0 +1,191 @@ | |||
| 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. | |||
| @ -0,0 +1,83 @@ | |||
| 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) | |||
| } | |||
| } | |||
| @ -0,0 +1,23 @@ | |||
| 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 | |||
| } | |||
| @ -0,0 +1,45 @@ | |||
| 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") | |||
| } | |||
| } | |||
| @ -0,0 +1,233 @@ | |||
| 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 | |||
| } | |||
| @ -0,0 +1,459 @@ | |||
| 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) | |||
| } | |||
| } | |||
| @ -0,0 +1,418 @@ | |||
| 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 | |||
| } | |||
| @ -0,0 +1,458 @@ | |||
| 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)) | |||
| } | |||
| } | |||
| @ -0,0 +1,28 @@ | |||
| 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)) | |||
| } | |||
| @ -0,0 +1,30 @@ | |||
| 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}) | |||
| } | |||
| } | |||
| @ -0,0 +1,30 @@ | |||
| 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 | |||
| } | |||
| @ -0,0 +1,38 @@ | |||
| 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) | |||
| } | |||
| } | |||
| } | |||
| } | |||