mirror of https://github.com/kubernetes/kops.git
Re-update semver library
This commit is contained in:
parent
b3dedbe287
commit
b3c0772073
|
|
@ -0,0 +1 @@
|
|||
3.4.0: QmZTgGMg34JKEvF1hjr7wwYESvFhg9Khv2WFibDAi5dhno
|
||||
|
|
@ -40,10 +40,52 @@ Features
|
|||
- 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
|
||||
-----
|
||||
|
|
@ -103,23 +145,30 @@ if err != nil {
|
|||
}
|
||||
```
|
||||
|
||||
|
||||
Benchmarks
|
||||
-----
|
||||
|
||||
BenchmarkParseSimple 5000000 328 ns/op 49 B/op 1 allocs/op
|
||||
BenchmarkParseComplex 1000000 2105 ns/op 263 B/op 7 allocs/op
|
||||
BenchmarkParseAverage 1000000 1301 ns/op 168 B/op 4 allocs/op
|
||||
BenchmarkStringSimple 10000000 130 ns/op 5 B/op 1 allocs/op
|
||||
BenchmarkStringLarger 5000000 280 ns/op 32 B/op 2 allocs/op
|
||||
BenchmarkStringComplex 3000000 512 ns/op 80 B/op 3 allocs/op
|
||||
BenchmarkStringAverage 5000000 387 ns/op 47 B/op 2 allocs/op
|
||||
BenchmarkValidateSimple 500000000 7.92 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkValidateComplex 2000000 923 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkValidateAverage 5000000 452 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkCompareSimple 100000000 11.2 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkCompareComplex 50000000 40.9 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkCompareAverage 50000000 43.8 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkSort 5000000 436 ns/op 259 B/op 2 allocs/op
|
||||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -42,4 +42,8 @@ func TestJSONUnmarshal(t *testing.T) {
|
|||
if err := json.Unmarshal([]byte(badVersionString), &v); err == nil {
|
||||
t.Fatal("expected JSON unmarshal error, got nil")
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte("3.1"), &v); err == nil {
|
||||
t.Fatal("expected JSON unmarshal error, got nil")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"author": "blang",
|
||||
"bugs": {
|
||||
"URL": "https://github.com/blang/semver/issues",
|
||||
"url": "https://github.com/blang/semver/issues"
|
||||
},
|
||||
"gx": {
|
||||
"dvcsimport": "github.com/blang/semver"
|
||||
},
|
||||
"gxVersion": "0.10.0",
|
||||
"language": "go",
|
||||
"license": "MIT",
|
||||
"name": "semver",
|
||||
"releaseCmd": "git commit -a -m \"gx publish $VERSION\"",
|
||||
"version": "3.4.0"
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,416 @@
|
|||
package semver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type wildcardType int
|
||||
|
||||
const (
|
||||
noneWildcard wildcardType = iota
|
||||
majorWildcard wildcardType = 1
|
||||
minorWildcard wildcardType = 2
|
||||
patchWildcard wildcardType = 3
|
||||
)
|
||||
|
||||
func wildcardTypefromInt(i int) wildcardType {
|
||||
switch i {
|
||||
case 1:
|
||||
return majorWildcard
|
||||
case 2:
|
||||
return minorWildcard
|
||||
case 3:
|
||||
return patchWildcard
|
||||
default:
|
||||
return noneWildcard
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
expandedParts, err := expandWildcardVersion(orParts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var orFn Range
|
||||
for _, p := range expandedParts {
|
||||
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
|
||||
|
||||
}
|
||||
|
||||
// inArray checks if a byte is contained in an array of bytes
|
||||
func inArray(s byte, list []byte) bool {
|
||||
for _, el := range list {
|
||||
if el == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// splitAndTrim splits a range string by spaces and cleans whitespaces
|
||||
func splitAndTrim(s string) (result []string) {
|
||||
last := 0
|
||||
var lastChar byte
|
||||
excludeFromSplit := []byte{'>', '<', '='}
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) {
|
||||
if last < i-1 {
|
||||
result = append(result, s[last:i])
|
||||
}
|
||||
last = i + 1
|
||||
} else if s[i] != ' ' {
|
||||
lastChar = s[i]
|
||||
}
|
||||
}
|
||||
if last < len(s)-1 {
|
||||
result = append(result, s[last:])
|
||||
}
|
||||
|
||||
for i, v := range result {
|
||||
result[i] = strings.Replace(v, " ", "", -1)
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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
|
||||
}
|
||||
|
||||
// getWildcardType will return the type of wildcard that the
|
||||
// passed version contains
|
||||
func getWildcardType(vStr string) wildcardType {
|
||||
parts := strings.Split(vStr, ".")
|
||||
nparts := len(parts)
|
||||
wildcard := parts[nparts-1]
|
||||
|
||||
possibleWildcardType := wildcardTypefromInt(nparts)
|
||||
if wildcard == "x" {
|
||||
return possibleWildcardType
|
||||
}
|
||||
|
||||
return noneWildcard
|
||||
}
|
||||
|
||||
// createVersionFromWildcard will convert a wildcard version
|
||||
// into a regular version, replacing 'x's with '0's, handling
|
||||
// special cases like '1.x.x' and '1.x'
|
||||
func createVersionFromWildcard(vStr string) string {
|
||||
// handle 1.x.x
|
||||
vStr2 := strings.Replace(vStr, ".x.x", ".x", 1)
|
||||
vStr2 = strings.Replace(vStr2, ".x", ".0", 1)
|
||||
parts := strings.Split(vStr2, ".")
|
||||
|
||||
// handle 1.x
|
||||
if len(parts) == 2 {
|
||||
return vStr2 + ".0"
|
||||
}
|
||||
|
||||
return vStr2
|
||||
}
|
||||
|
||||
// incrementMajorVersion will increment the major version
|
||||
// of the passed version
|
||||
func incrementMajorVersion(vStr string) (string, error) {
|
||||
parts := strings.Split(vStr, ".")
|
||||
i, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
parts[0] = strconv.Itoa(i + 1)
|
||||
|
||||
return strings.Join(parts, "."), nil
|
||||
}
|
||||
|
||||
// incrementMajorVersion will increment the minor version
|
||||
// of the passed version
|
||||
func incrementMinorVersion(vStr string) (string, error) {
|
||||
parts := strings.Split(vStr, ".")
|
||||
i, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
parts[1] = strconv.Itoa(i + 1)
|
||||
|
||||
return strings.Join(parts, "."), nil
|
||||
}
|
||||
|
||||
// expandWildcardVersion will expand wildcards inside versions
|
||||
// following these rules:
|
||||
//
|
||||
// * when dealing with patch wildcards:
|
||||
// >= 1.2.x will become >= 1.2.0
|
||||
// <= 1.2.x will become < 1.3.0
|
||||
// > 1.2.x will become >= 1.3.0
|
||||
// < 1.2.x will become < 1.2.0
|
||||
// != 1.2.x will become < 1.2.0 >= 1.3.0
|
||||
//
|
||||
// * when dealing with minor wildcards:
|
||||
// >= 1.x will become >= 1.0.0
|
||||
// <= 1.x will become < 2.0.0
|
||||
// > 1.x will become >= 2.0.0
|
||||
// < 1.0 will become < 1.0.0
|
||||
// != 1.x will become < 1.0.0 >= 2.0.0
|
||||
//
|
||||
// * when dealing with wildcards without
|
||||
// version operator:
|
||||
// 1.2.x will become >= 1.2.0 < 1.3.0
|
||||
// 1.x will become >= 1.0.0 < 2.0.0
|
||||
func expandWildcardVersion(parts [][]string) ([][]string, error) {
|
||||
var expandedParts [][]string
|
||||
for _, p := range parts {
|
||||
var newParts []string
|
||||
for _, ap := range p {
|
||||
if strings.Index(ap, "x") != -1 {
|
||||
opStr, vStr, err := splitComparatorVersion(ap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
versionWildcardType := getWildcardType(vStr)
|
||||
flatVersion := createVersionFromWildcard(vStr)
|
||||
|
||||
var resultOperator string
|
||||
var shouldIncrementVersion bool
|
||||
switch opStr {
|
||||
case ">":
|
||||
resultOperator = ">="
|
||||
shouldIncrementVersion = true
|
||||
case ">=":
|
||||
resultOperator = ">="
|
||||
case "<":
|
||||
resultOperator = "<"
|
||||
case "<=":
|
||||
resultOperator = "<"
|
||||
shouldIncrementVersion = true
|
||||
case "", "=", "==":
|
||||
newParts = append(newParts, ">="+flatVersion)
|
||||
resultOperator = "<"
|
||||
shouldIncrementVersion = true
|
||||
case "!=", "!":
|
||||
newParts = append(newParts, "<"+flatVersion)
|
||||
resultOperator = ">="
|
||||
shouldIncrementVersion = true
|
||||
}
|
||||
|
||||
var resultVersion string
|
||||
if shouldIncrementVersion {
|
||||
switch versionWildcardType {
|
||||
case patchWildcard:
|
||||
resultVersion, _ = incrementMinorVersion(flatVersion)
|
||||
case minorWildcard:
|
||||
resultVersion, _ = incrementMajorVersion(flatVersion)
|
||||
}
|
||||
} else {
|
||||
resultVersion = flatVersion
|
||||
}
|
||||
|
||||
ap = resultOperator + resultVersion
|
||||
}
|
||||
newParts = append(newParts, ap)
|
||||
}
|
||||
expandedParts = append(expandedParts, newParts)
|
||||
}
|
||||
|
||||
return expandedParts, 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,581 @@
|
|||
package semver
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type wildcardTypeTest struct {
|
||||
input string
|
||||
wildcardType wildcardType
|
||||
}
|
||||
|
||||
type comparatorTest struct {
|
||||
input string
|
||||
comparator func(comparator) bool
|
||||
}
|
||||
|
||||
func TestParseComparator(t *testing.T) {
|
||||
compatorTests := []comparatorTest{
|
||||
{">", testGT},
|
||||
{">=", testGE},
|
||||
{"<", testLT},
|
||||
{"<=", testLE},
|
||||
{"", testEQ},
|
||||
{"=", testEQ},
|
||||
{"==", testEQ},
|
||||
{"!=", testNE},
|
||||
{"!", testNE},
|
||||
{"-", nil},
|
||||
{"<==", nil},
|
||||
{"<<", nil},
|
||||
{">>", nil},
|
||||
}
|
||||
|
||||
for _, tc := range compatorTests {
|
||||
if c := parseComparator(tc.input); c == nil {
|
||||
if tc.comparator != nil {
|
||||
t.Errorf("Comparator nil for case %q\n", tc.input)
|
||||
}
|
||||
} else if !tc.comparator(c) {
|
||||
t.Errorf("Invalid comparator for case %q\n", tc.input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
v1 = MustParse("1.2.2")
|
||||
v2 = MustParse("1.2.3")
|
||||
v3 = MustParse("1.2.4")
|
||||
)
|
||||
|
||||
func testEQ(f comparator) bool {
|
||||
return f(v1, v1) && !f(v1, v2)
|
||||
}
|
||||
|
||||
func testNE(f comparator) bool {
|
||||
return !f(v1, v1) && f(v1, v2)
|
||||
}
|
||||
|
||||
func testGT(f comparator) bool {
|
||||
return f(v2, v1) && f(v3, v2) && !f(v1, v2) && !f(v1, v1)
|
||||
}
|
||||
|
||||
func testGE(f comparator) bool {
|
||||
return f(v2, v1) && f(v3, v2) && !f(v1, v2)
|
||||
}
|
||||
|
||||
func testLT(f comparator) bool {
|
||||
return f(v1, v2) && f(v2, v3) && !f(v2, v1) && !f(v1, v1)
|
||||
}
|
||||
|
||||
func testLE(f comparator) bool {
|
||||
return f(v1, v2) && f(v2, v3) && !f(v2, v1)
|
||||
}
|
||||
|
||||
func TestSplitAndTrim(t *testing.T) {
|
||||
tests := []struct {
|
||||
i string
|
||||
s []string
|
||||
}{
|
||||
{"1.2.3 1.2.3", []string{"1.2.3", "1.2.3"}},
|
||||
{" 1.2.3 1.2.3 ", []string{"1.2.3", "1.2.3"}}, // Spaces
|
||||
{" >= 1.2.3 <= 1.2.3 ", []string{">=1.2.3", "<=1.2.3"}}, // Spaces between operator and version
|
||||
{"1.2.3 || >=1.2.3 <1.2.3", []string{"1.2.3", "||", ">=1.2.3", "<1.2.3"}},
|
||||
{" 1.2.3 || >=1.2.3 <1.2.3 ", []string{"1.2.3", "||", ">=1.2.3", "<1.2.3"}},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
p := splitAndTrim(tc.i)
|
||||
if !reflect.DeepEqual(p, tc.s) {
|
||||
t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitComparatorVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
i string
|
||||
p []string
|
||||
}{
|
||||
{">1.2.3", []string{">", "1.2.3"}},
|
||||
{">=1.2.3", []string{">=", "1.2.3"}},
|
||||
{"<1.2.3", []string{"<", "1.2.3"}},
|
||||
{"<=1.2.3", []string{"<=", "1.2.3"}},
|
||||
{"1.2.3", []string{"", "1.2.3"}},
|
||||
{"=1.2.3", []string{"=", "1.2.3"}},
|
||||
{"==1.2.3", []string{"==", "1.2.3"}},
|
||||
{"!=1.2.3", []string{"!=", "1.2.3"}},
|
||||
{"!1.2.3", []string{"!", "1.2.3"}},
|
||||
{"error", nil},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
if op, v, err := splitComparatorVersion(tc.i); err != nil {
|
||||
if tc.p != nil {
|
||||
t.Errorf("Invalid for case %q: Expected %q, got error %q", tc.i, tc.p, err)
|
||||
}
|
||||
} else if op != tc.p[0] {
|
||||
t.Errorf("Invalid operator for case %q: Expected %q, got: %q", tc.i, tc.p[0], op)
|
||||
} else if v != tc.p[1] {
|
||||
t.Errorf("Invalid version for case %q: Expected %q, got: %q", tc.i, tc.p[1], v)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildVersionRange(t *testing.T) {
|
||||
tests := []struct {
|
||||
opStr string
|
||||
vStr string
|
||||
c func(comparator) bool
|
||||
v string
|
||||
}{
|
||||
{">", "1.2.3", testGT, "1.2.3"},
|
||||
{">=", "1.2.3", testGE, "1.2.3"},
|
||||
{"<", "1.2.3", testLT, "1.2.3"},
|
||||
{"<=", "1.2.3", testLE, "1.2.3"},
|
||||
{"", "1.2.3", testEQ, "1.2.3"},
|
||||
{"=", "1.2.3", testEQ, "1.2.3"},
|
||||
{"==", "1.2.3", testEQ, "1.2.3"},
|
||||
{"!=", "1.2.3", testNE, "1.2.3"},
|
||||
{"!", "1.2.3", testNE, "1.2.3"},
|
||||
{">>", "1.2.3", nil, ""}, // Invalid comparator
|
||||
{"=", "invalid", nil, ""}, // Invalid version
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
if r, err := buildVersionRange(tc.opStr, tc.vStr); err != nil {
|
||||
if tc.c != nil {
|
||||
t.Errorf("Invalid for case %q: Expected %q, got error %q", strings.Join([]string{tc.opStr, tc.vStr}, ""), tc.v, err)
|
||||
}
|
||||
} else if r == nil {
|
||||
t.Errorf("Invalid for case %q: got nil", strings.Join([]string{tc.opStr, tc.vStr}, ""))
|
||||
} else {
|
||||
// test version
|
||||
if tv := MustParse(tc.v); !r.v.EQ(tv) {
|
||||
t.Errorf("Invalid for case %q: Expected version %q, got: %q", strings.Join([]string{tc.opStr, tc.vStr}, ""), tv, r.v)
|
||||
}
|
||||
// test comparator
|
||||
if r.c == nil {
|
||||
t.Errorf("Invalid for case %q: got nil comparator", strings.Join([]string{tc.opStr, tc.vStr}, ""))
|
||||
continue
|
||||
}
|
||||
if !tc.c(r.c) {
|
||||
t.Errorf("Invalid comparator for case %q\n", strings.Join([]string{tc.opStr, tc.vStr}, ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestSplitORParts(t *testing.T) {
|
||||
tests := []struct {
|
||||
i []string
|
||||
o [][]string
|
||||
}{
|
||||
{[]string{">1.2.3", "||", "<1.2.3", "||", "=1.2.3"}, [][]string{
|
||||
[]string{">1.2.3"},
|
||||
[]string{"<1.2.3"},
|
||||
[]string{"=1.2.3"},
|
||||
}},
|
||||
{[]string{">1.2.3", "<1.2.3", "||", "=1.2.3"}, [][]string{
|
||||
[]string{">1.2.3", "<1.2.3"},
|
||||
[]string{"=1.2.3"},
|
||||
}},
|
||||
{[]string{">1.2.3", "||"}, nil},
|
||||
{[]string{"||", ">1.2.3"}, nil},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
o, err := splitORParts(tc.i)
|
||||
if err != nil && tc.o != nil {
|
||||
t.Errorf("Unexpected error for case %q: %s", tc.i, err)
|
||||
}
|
||||
if !reflect.DeepEqual(tc.o, o) {
|
||||
t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.o, o)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetWildcardType(t *testing.T) {
|
||||
wildcardTypeTests := []wildcardTypeTest{
|
||||
{"x", majorWildcard},
|
||||
{"1.x", minorWildcard},
|
||||
{"1.2.x", patchWildcard},
|
||||
{"fo.o.b.ar", noneWildcard},
|
||||
}
|
||||
|
||||
for _, tc := range wildcardTypeTests {
|
||||
o := getWildcardType(tc.input)
|
||||
if o != tc.wildcardType {
|
||||
t.Errorf("Invalid for case: %q: Expected %q, got: %q", tc.input, tc.wildcardType, o)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateVersionFromWildcard(t *testing.T) {
|
||||
tests := []struct {
|
||||
i string
|
||||
s string
|
||||
}{
|
||||
{"1.2.x", "1.2.0"},
|
||||
{"1.x", "1.0.0"},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
p := createVersionFromWildcard(tc.i)
|
||||
if p != tc.s {
|
||||
t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncrementMajorVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
i string
|
||||
s string
|
||||
}{
|
||||
{"1.2.3", "2.2.3"},
|
||||
{"1.2", "2.2"},
|
||||
{"foo.bar", ""},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
p, _ := incrementMajorVersion(tc.i)
|
||||
if p != tc.s {
|
||||
t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncrementMinorVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
i string
|
||||
s string
|
||||
}{
|
||||
{"1.2.3", "1.3.3"},
|
||||
{"1.2", "1.3"},
|
||||
{"foo.bar", ""},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
p, _ := incrementMinorVersion(tc.i)
|
||||
if p != tc.s {
|
||||
t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpandWildcardVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
i [][]string
|
||||
o [][]string
|
||||
}{
|
||||
{[][]string{[]string{"foox"}}, nil},
|
||||
{[][]string{[]string{">=1.2.x"}}, [][]string{[]string{">=1.2.0"}}},
|
||||
{[][]string{[]string{"<=1.2.x"}}, [][]string{[]string{"<1.3.0"}}},
|
||||
{[][]string{[]string{">1.2.x"}}, [][]string{[]string{">=1.3.0"}}},
|
||||
{[][]string{[]string{"<1.2.x"}}, [][]string{[]string{"<1.2.0"}}},
|
||||
{[][]string{[]string{"!=1.2.x"}}, [][]string{[]string{"<1.2.0", ">=1.3.0"}}},
|
||||
{[][]string{[]string{">=1.x"}}, [][]string{[]string{">=1.0.0"}}},
|
||||
{[][]string{[]string{"<=1.x"}}, [][]string{[]string{"<2.0.0"}}},
|
||||
{[][]string{[]string{">1.x"}}, [][]string{[]string{">=2.0.0"}}},
|
||||
{[][]string{[]string{"<1.x"}}, [][]string{[]string{"<1.0.0"}}},
|
||||
{[][]string{[]string{"!=1.x"}}, [][]string{[]string{"<1.0.0", ">=2.0.0"}}},
|
||||
{[][]string{[]string{"1.2.x"}}, [][]string{[]string{">=1.2.0", "<1.3.0"}}},
|
||||
{[][]string{[]string{"1.x"}}, [][]string{[]string{">=1.0.0", "<2.0.0"}}},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
o, _ := expandWildcardVersion(tc.i)
|
||||
if !reflect.DeepEqual(tc.o, o) {
|
||||
t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.o, o)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionRangeToRange(t *testing.T) {
|
||||
vr := versionRange{
|
||||
v: MustParse("1.2.3"),
|
||||
c: compLT,
|
||||
}
|
||||
rf := vr.rangeFunc()
|
||||
if !rf(MustParse("1.2.2")) || rf(MustParse("1.2.3")) {
|
||||
t.Errorf("Invalid conversion to range func")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRangeAND(t *testing.T) {
|
||||
v := MustParse("1.2.2")
|
||||
v1 := MustParse("1.2.1")
|
||||
v2 := MustParse("1.2.3")
|
||||
rf1 := Range(func(v Version) bool {
|
||||
return v.GT(v1)
|
||||
})
|
||||
rf2 := Range(func(v Version) bool {
|
||||
return v.LT(v2)
|
||||
})
|
||||
rf := rf1.AND(rf2)
|
||||
if rf(v1) {
|
||||
t.Errorf("Invalid rangefunc, accepted: %s", v1)
|
||||
}
|
||||
if rf(v2) {
|
||||
t.Errorf("Invalid rangefunc, accepted: %s", v2)
|
||||
}
|
||||
if !rf(v) {
|
||||
t.Errorf("Invalid rangefunc, did not accept: %s", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRangeOR(t *testing.T) {
|
||||
tests := []struct {
|
||||
v Version
|
||||
b bool
|
||||
}{
|
||||
{MustParse("1.2.0"), true},
|
||||
{MustParse("1.2.2"), false},
|
||||
{MustParse("1.2.4"), true},
|
||||
}
|
||||
v1 := MustParse("1.2.1")
|
||||
v2 := MustParse("1.2.3")
|
||||
rf1 := Range(func(v Version) bool {
|
||||
return v.LT(v1)
|
||||
})
|
||||
rf2 := Range(func(v Version) bool {
|
||||
return v.GT(v2)
|
||||
})
|
||||
rf := rf1.OR(rf2)
|
||||
for _, tc := range tests {
|
||||
if r := rf(tc.v); r != tc.b {
|
||||
t.Errorf("Invalid for case %q: Expected %t, got %t", tc.v, tc.b, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRange(t *testing.T) {
|
||||
type tv struct {
|
||||
v string
|
||||
b bool
|
||||
}
|
||||
tests := []struct {
|
||||
i string
|
||||
t []tv
|
||||
}{
|
||||
// Simple expressions
|
||||
{">1.2.3", []tv{
|
||||
{"1.2.2", false},
|
||||
{"1.2.3", false},
|
||||
{"1.2.4", true},
|
||||
}},
|
||||
{">=1.2.3", []tv{
|
||||
{"1.2.3", true},
|
||||
{"1.2.4", true},
|
||||
{"1.2.2", false},
|
||||
}},
|
||||
{"<1.2.3", []tv{
|
||||
{"1.2.2", true},
|
||||
{"1.2.3", false},
|
||||
{"1.2.4", false},
|
||||
}},
|
||||
{"<=1.2.3", []tv{
|
||||
{"1.2.2", true},
|
||||
{"1.2.3", true},
|
||||
{"1.2.4", false},
|
||||
}},
|
||||
{"1.2.3", []tv{
|
||||
{"1.2.2", false},
|
||||
{"1.2.3", true},
|
||||
{"1.2.4", false},
|
||||
}},
|
||||
{"=1.2.3", []tv{
|
||||
{"1.2.2", false},
|
||||
{"1.2.3", true},
|
||||
{"1.2.4", false},
|
||||
}},
|
||||
{"==1.2.3", []tv{
|
||||
{"1.2.2", false},
|
||||
{"1.2.3", true},
|
||||
{"1.2.4", false},
|
||||
}},
|
||||
{"!=1.2.3", []tv{
|
||||
{"1.2.2", true},
|
||||
{"1.2.3", false},
|
||||
{"1.2.4", true},
|
||||
}},
|
||||
{"!1.2.3", []tv{
|
||||
{"1.2.2", true},
|
||||
{"1.2.3", false},
|
||||
{"1.2.4", true},
|
||||
}},
|
||||
// Simple Expression errors
|
||||
{">>1.2.3", nil},
|
||||
{"!1.2.3", nil},
|
||||
{"1.0", nil},
|
||||
{"string", nil},
|
||||
{"", nil},
|
||||
{"fo.ob.ar.x", nil},
|
||||
// AND Expressions
|
||||
{">1.2.2 <1.2.4", []tv{
|
||||
{"1.2.2", false},
|
||||
{"1.2.3", true},
|
||||
{"1.2.4", false},
|
||||
}},
|
||||
{"<1.2.2 <1.2.4", []tv{
|
||||
{"1.2.1", true},
|
||||
{"1.2.2", false},
|
||||
{"1.2.3", false},
|
||||
{"1.2.4", false},
|
||||
}},
|
||||
{">1.2.2 <1.2.5 !=1.2.4", []tv{
|
||||
{"1.2.2", false},
|
||||
{"1.2.3", true},
|
||||
{"1.2.4", false},
|
||||
{"1.2.5", false},
|
||||
}},
|
||||
{">1.2.2 <1.2.5 !1.2.4", []tv{
|
||||
{"1.2.2", false},
|
||||
{"1.2.3", true},
|
||||
{"1.2.4", false},
|
||||
{"1.2.5", false},
|
||||
}},
|
||||
// OR Expressions
|
||||
{">1.2.2 || <1.2.4", []tv{
|
||||
{"1.2.2", true},
|
||||
{"1.2.3", true},
|
||||
{"1.2.4", true},
|
||||
}},
|
||||
{"<1.2.2 || >1.2.4", []tv{
|
||||
{"1.2.2", false},
|
||||
{"1.2.3", false},
|
||||
{"1.2.4", false},
|
||||
}},
|
||||
// Wildcard expressions
|
||||
{">1.x", []tv{
|
||||
{"0.1.9", false},
|
||||
{"1.2.6", false},
|
||||
{"1.9.0", false},
|
||||
{"2.0.0", true},
|
||||
}},
|
||||
{">1.2.x", []tv{
|
||||
{"1.1.9", false},
|
||||
{"1.2.6", false},
|
||||
{"1.3.0", true},
|
||||
}},
|
||||
// Combined Expressions
|
||||
{">1.2.2 <1.2.4 || >=2.0.0", []tv{
|
||||
{"1.2.2", false},
|
||||
{"1.2.3", true},
|
||||
{"1.2.4", false},
|
||||
{"2.0.0", true},
|
||||
{"2.0.1", true},
|
||||
}},
|
||||
{"1.x || >=2.0.x <2.2.x", []tv{
|
||||
{"0.9.2", false},
|
||||
{"1.2.2", true},
|
||||
{"2.0.0", true},
|
||||
{"2.1.8", true},
|
||||
{"2.2.0", false},
|
||||
}},
|
||||
{">1.2.2 <1.2.4 || >=2.0.0 <3.0.0", []tv{
|
||||
{"1.2.2", false},
|
||||
{"1.2.3", true},
|
||||
{"1.2.4", false},
|
||||
{"2.0.0", true},
|
||||
{"2.0.1", true},
|
||||
{"2.9.9", true},
|
||||
{"3.0.0", false},
|
||||
}},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
r, err := ParseRange(tc.i)
|
||||
if err != nil && tc.t != nil {
|
||||
t.Errorf("Error parsing range %q: %s", tc.i, err)
|
||||
continue
|
||||
}
|
||||
for _, tvc := range tc.t {
|
||||
v := MustParse(tvc.v)
|
||||
if res := r(v); res != tvc.b {
|
||||
t.Errorf("Invalid for case %q matching %q: Expected %t, got: %t", tc.i, tvc.v, tvc.b, res)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestMustParseRange(t *testing.T) {
|
||||
testCase := ">1.2.2 <1.2.4 || >=2.0.0 <3.0.0"
|
||||
r := MustParseRange(testCase)
|
||||
if !r(MustParse("1.2.3")) {
|
||||
t.Errorf("Unexpected range behavior on MustParseRange")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMustParseRange_panic(t *testing.T) {
|
||||
defer func() {
|
||||
if recover() == nil {
|
||||
t.Errorf("Should have panicked")
|
||||
}
|
||||
}()
|
||||
_ = MustParseRange("invalid version")
|
||||
}
|
||||
|
||||
func BenchmarkRangeParseSimple(b *testing.B) {
|
||||
const VERSION = ">1.0.0"
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
ParseRange(VERSION)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRangeParseAverage(b *testing.B) {
|
||||
const VERSION = ">=1.0.0 <2.0.0"
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
ParseRange(VERSION)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRangeParseComplex(b *testing.B) {
|
||||
const VERSION = ">=1.0.0 <2.0.0 || >=3.0.1 <4.0.0 !=3.0.3 || >=5.0.0"
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
ParseRange(VERSION)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRangeMatchSimple(b *testing.B) {
|
||||
const VERSION = ">1.0.0"
|
||||
r, _ := ParseRange(VERSION)
|
||||
v := MustParse("2.0.0")
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
r(v)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRangeMatchAverage(b *testing.B) {
|
||||
const VERSION = ">=1.0.0 <2.0.0"
|
||||
r, _ := ParseRange(VERSION)
|
||||
v := MustParse("1.2.3")
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
r(v)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRangeMatchComplex(b *testing.B) {
|
||||
const VERSION = ">=1.0.0 <2.0.0 || >=3.0.1 <4.0.0 !=3.0.3 || >=5.0.0"
|
||||
r, _ := ParseRange(VERSION)
|
||||
v := MustParse("5.0.1")
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
r(v)
|
||||
}
|
||||
}
|
||||
|
|
@ -200,6 +200,29 @@ 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 {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,13 @@ var formatTests = []formatTest{
|
|||
{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 {
|
||||
|
|
@ -50,6 +57,18 @@ func TestParse(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
|
@ -184,6 +203,19 @@ func TestWrongFormat(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
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}
|
||||
|
|
@ -319,6 +351,15 @@ func BenchmarkParseAverage(b *testing.B) {
|
|||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue