Implement the functions of getting chart version details and chart lists

getting chart list returns chart list instead of map and provides total versions info
getting chart details will return dependencies and values besides the metatdata of the chart version
This commit is contained in:
Steven Zou 2018-07-06 20:53:13 +08:00
parent b65cc34c70
commit 7d782c41fc
24 changed files with 2796 additions and 60 deletions

8
src/Gopkg.lock generated
View File

@ -58,6 +58,12 @@
packages = ["."]
revision = "e87155e8f0c05bf323d0b13470e1b97af0cb5652"
[[projects]]
name = "github.com/blang/semver"
packages = ["."]
revision = "2ee87856327ba09384cabd113bc6b5d174e9ec0f"
version = "v3.5.1"
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
@ -371,6 +377,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "db970de67fb6d82511e2fb991860473786678b3c1dfc4fb835046fafbaa61f57"
inputs-digest = "0392e2659013ae00be46cd4f0b9d56aa0765b6297d672a56175249c465e98a8d"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -90,4 +90,8 @@ ignored = ["github.com/vmware/harbor/tests*"]
[[constraint]]
name = "github.com/ghodss/yaml"
version = "=1.0.0"
version = "=1.0.0"
[[constraint]]
name = "github.com/blang/semver"
version = "=3.5.1"

View File

@ -1,12 +1,182 @@
package chartserver
import "k8s.io/helm/pkg/proto/hapi/chart"
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/Masterminds/semver"
"k8s.io/helm/pkg/chartutil"
helm_repo "k8s.io/helm/pkg/repo"
)
const (
readmeFileName = "README.md"
)
//ChartVersionDetails keeps the detailed data info of the chart version
type ChartVersionDetails struct {
Metadata *helm_repo.ChartVersion `json:"metadata"`
Dependencies []*chartutil.Dependency `json:"dependencies"`
Values map[string]interface{} `json:"values"`
Files map[string]string `json:"files"`
}
//ChartInfo keeps the information of the chart
type ChartInfo struct {
Name string
TotalVersions uint32 `json:"total_versions"`
Created time.Time
Icon string
Home string
}
//ChartOperator is designed to process the contents of
//the specified chart version to get more details
type ChartOperator struct{}
//GetChartDetails parse the details from the provided content bytes
func (cho *ChartOperator) GetChartDetails(content []byte) *chart.Chart {
return nil
func (cho *ChartOperator) GetChartDetails(content []byte) (*ChartVersionDetails, error) {
if content == nil || len(content) == 0 {
return nil, errors.New("zero content")
}
//Load chart from in-memory content
reader := bytes.NewReader(content)
chartData, err := chartutil.LoadArchive(reader)
if err != nil {
return nil, err
}
//Parse the requirements of chart
requirements, err := chartutil.LoadRequirements(chartData)
if err != nil {
return nil, err
}
var values map[string]interface{}
files := make(map[string]string)
//Parse values
if chartData.Values != nil {
values = parseRawValues([]byte(chartData.Values.GetRaw()))
//Append values.yaml file
files["values.yaml"] = chartData.Values.Raw
}
//Append other files like 'README.md'
for _, v := range chartData.GetFiles() {
if v.TypeUrl == readmeFileName {
files[readmeFileName] = string(v.GetValue())
break
}
}
theChart := &ChartVersionDetails{
Dependencies: requirements.Dependencies,
Values: values,
Files: files,
}
return theChart, nil
}
//GetChartList returns a reorganized chart list
func (cho *ChartOperator) GetChartList(content []byte) ([]*ChartInfo, error) {
if content == nil || len(content) == 0 {
return nil, errors.New("zero content")
}
allCharts := make(map[string]helm_repo.ChartVersions)
if err := json.Unmarshal(content, &allCharts); err != nil {
return nil, err
}
chartList := make([]*ChartInfo, 0)
for key, chartVersions := range allCharts {
lVersion, oVersion := getTheTwoCharts(chartVersions)
if lVersion != nil && oVersion != nil {
chartInfo := &ChartInfo{
Name: key,
TotalVersions: uint32(len(chartVersions)),
}
chartInfo.Created = oVersion.Created
chartInfo.Home = lVersion.Home
chartInfo.Icon = lVersion.Icon
chartList = append(chartList, chartInfo)
}
}
return chartList, nil
}
//Get the latest and oldest chart versions
func getTheTwoCharts(chartVersions helm_repo.ChartVersions) (latestChart *helm_repo.ChartVersion, oldestChart *helm_repo.ChartVersion) {
if len(chartVersions) == 1 {
return chartVersions[0], chartVersions[0]
}
for _, chartVersion := range chartVersions {
currentV, err := semver.NewVersion(chartVersion.Version)
if err != nil {
//ignore it
continue
}
//Find latest chart
if latestChart == nil {
latestChart = chartVersion
} else {
lVersion, _ := semver.NewVersion(latestChart.Version)
if lVersion.LessThan(currentV) {
latestChart = chartVersion
}
}
if oldestChart == nil {
oldestChart = chartVersion
} else {
if oldestChart.Created.After(chartVersion.Created) {
oldestChart = chartVersion
}
}
}
return latestChart, oldestChart
}
//Parse the raw values to value map
func parseRawValues(rawValue []byte) map[string]interface{} {
valueMap := make(map[string]interface{})
if len(rawValue) == 0 {
return valueMap
}
values, err := chartutil.ReadValues(rawValue)
if err != nil || len(values) == 0 {
return valueMap
}
readValue(values, "", valueMap)
return valueMap
}
//Recursively read value
func readValue(values map[string]interface{}, keyPrefix string, valueMap map[string]interface{}) {
for key, value := range values {
longKey := key
if keyPrefix != "" {
longKey = fmt.Sprintf("%s.%s", keyPrefix, key)
}
if subValues, ok := value.(map[string]interface{}); ok {
readValue(subValues, longKey, valueMap)
} else {
valueMap[longKey] = value
}
}
}

67
src/chartserver/client.go Normal file
View File

@ -0,0 +1,67 @@
package chartserver
import (
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"time"
)
const (
clientTimeout = 10 * time.Second
maxIdleConnections = 10
idleConnectionTimeout = 30 * time.Second
)
//ChartClient is a http client to get the content from the external http server
type ChartClient struct {
httpClient *http.Client
}
//NewChartClient is constructor of ChartClient
func NewChartClient() *ChartClient { //Create http client with customized timeouts
client := &http.Client{
Timeout: clientTimeout,
Transport: &http.Transport{
MaxIdleConns: maxIdleConnections,
IdleConnTimeout: idleConnectionTimeout,
},
}
return &ChartClient{client}
}
//GetContent get the bytes from the specified url
func (cc *ChartClient) GetContent(url string) ([]byte, error) {
if len(strings.TrimSpace(url)) == 0 {
return nil, errors.New("empty url is not allowed")
}
request, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
//Set basic auth
request.SetBasicAuth(userName, os.Getenv(passwordKey))
response, err := cc.httpClient.Do(request)
if err != nil {
return nil, err
}
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to retrieve content from url %s", url)
}
content, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
defer response.Body.Close()
return content, nil
}

View File

@ -2,15 +2,7 @@ package chartserver
import (
"errors"
"net/http"
"net/url"
"time"
)
const (
clientTimeout = 10 * time.Second
maxIdleConnections = 10
idleConnectionTimeout = 30 * time.Second
)
//Controller is used to handle flows of related requests based on the corresponding handlers
@ -40,13 +32,7 @@ func NewController(backendServer *url.URL) (*Controller, error) {
proxy := NewProxyEngine(backendServer)
//Create http client with customized timeouts
client := &http.Client{
Timeout: clientTimeout,
Transport: &http.Transport{
MaxIdleConns: maxIdleConnections,
IdleConnTimeout: idleConnectionTimeout,
},
}
client := NewChartClient()
//Initialize chart operator for use
operator := &ChartOperator{}
@ -60,8 +46,10 @@ func NewController(backendServer *url.URL) (*Controller, error) {
backendServerAddress: backendServer,
},
manipulationHandler: &ManipulationHandler{
trafficProxy: proxy,
chartOperator: operator,
trafficProxy: proxy,
chartOperator: operator,
apiClient: client,
backendServerAddress: backendServer,
},
}, nil
}

View File

@ -1,7 +1,14 @@
package chartserver
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/ghodss/yaml"
helm_repo "k8s.io/helm/pkg/repo"
)
//ManipulationHandler includes all the handler methods for the purpose of manipulating the
@ -13,11 +20,38 @@ type ManipulationHandler struct {
//Parse and process the chart version to provide required info data
chartOperator *ChartOperator
//HTTP client used to call the realted APIs of the backend chart repositories
apiClient *ChartClient
//Point to the url of the backend server
backendServerAddress *url.URL
}
//ListCharts lists all the charts under the specified namespace
func (mh *ManipulationHandler) ListCharts(w http.ResponseWriter, req *http.Request) {
mh.trafficProxy.ServeHTTP(w, req)
rootURL := strings.TrimSuffix(mh.backendServerAddress.String(), "/")
fullURL := fmt.Sprintf("%s%s", rootURL, req.RequestURI)
content, err := mh.apiClient.GetContent(fullURL)
if err != nil {
writeInternalError(w, err)
return
}
chartList, err := mh.chartOperator.GetChartList(content)
if err != nil {
writeInternalError(w, err)
return
}
jsonData, err := json.Marshal(chartList)
if err != nil {
writeInternalError(w, err)
return
}
writeJSONData(w, jsonData)
}
//GetChart returns all the chart versions under the specified chart
@ -29,8 +63,35 @@ func (mh *ManipulationHandler) GetChart(w http.ResponseWriter, req *http.Request
//This handler should return the details of the chart version,
//maybe including metadata,dependencies and values etc.
func (mh *ManipulationHandler) GetChartVersion(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
w.Write([]byte("not implemented"))
chartV, err := mh.getChartVersion(req.RequestURI)
if err != nil {
writeInternalError(w, err)
return
}
//TODO:
namespace := "repo2"
content, err := mh.getChartVersionContent(namespace, chartV.URLs[0])
if err != nil {
writeInternalError(w, err)
return
}
//Process bytes and get more details of chart version
chartDetails, err := mh.chartOperator.GetChartDetails(content)
if err != nil {
writeInternalError(w, err)
return
}
chartDetails.Metadata = chartV
bytes, err := json.Marshal(chartDetails)
if err != nil {
writeInternalError(w, err)
return
}
writeJSONData(w, bytes)
}
//UploadChartVersion will save the new version of the chart to the backend storage
@ -47,3 +108,29 @@ func (mh *ManipulationHandler) UploadProvenanceFile(w http.ResponseWriter, req *
func (mh *ManipulationHandler) DeleteChartVersion(w http.ResponseWriter, req *http.Request) {
mh.trafficProxy.ServeHTTP(w, req)
}
//Get the basic metadata of chart version
func (mh *ManipulationHandler) getChartVersion(path string) (*helm_repo.ChartVersion, error) {
rootURL := strings.TrimSuffix(mh.backendServerAddress.String(), "/")
fullURL := fmt.Sprintf("%s%s", rootURL, path)
content, err := mh.apiClient.GetContent(fullURL)
if err != nil {
return nil, err
}
chartVersion := &helm_repo.ChartVersion{}
if err := yaml.Unmarshal(content, chartVersion); err != nil {
return nil, err
}
return chartVersion, nil
}
//Get the content bytes of the chart version
func (mh *ManipulationHandler) getChartVersionContent(namespace string, path string) ([]byte, error) {
rootURL := strings.TrimSuffix(mh.backendServerAddress.String(), "/")
fullPath := fmt.Sprintf("%s/%s/%s", rootURL, namespace, path)
return mh.apiClient.GetContent(fullPath)
}

View File

@ -4,10 +4,8 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"strings"
"sync"
"sync/atomic"
@ -30,7 +28,7 @@ type RepositoryHandler struct {
trafficProxy *ProxyEngine
//HTTP client used to call the realted APIs of the backend chart repositories
apiClient *http.Client
apiClient *ChartClient
//Point to the url of the backend server
backendServerAddress *url.URL
@ -60,14 +58,14 @@ func (rh *RepositoryHandler) GetIndexFile(w http.ResponseWriter, req *http.Reque
//Get project manager references
projectMgr, err := filter.GetProjectManager(req)
if err != nil {
writeError(w, http.StatusInternalServerError, err)
writeInternalError(w, err)
return
}
//Get all the projects
results, err := projectMgr.List(nil)
if err != nil {
writeError(w, http.StatusInternalServerError, err)
writeInternalError(w, err)
return
}
@ -161,7 +159,7 @@ LOOP:
writeError(w, http.StatusInternalServerError, err)
return
case <-req.Context().Done():
writeError(w, http.StatusInternalServerError, errors.New("request aborted"))
writeInternalError(w, errors.New("request aborted"))
return
}
}
@ -187,7 +185,7 @@ LOOP:
bytes, err := yaml.Marshal(mergedIndexFile)
if err != nil {
writeError(w, http.StatusInternalServerError, err)
writeInternalError(w, err)
return
}
@ -206,29 +204,12 @@ func (rh *RepositoryHandler) getIndexYamlWithNS(namespace string) (*helm_repo.In
rootURL := strings.TrimSuffix(rh.backendServerAddress.String(), "/")
url := fmt.Sprintf("%s/%s/index.yaml", rootURL, namespace)
request, err := http.NewRequest(http.MethodGet, url, nil)
content, err := rh.apiClient.GetContent(url)
if err != nil {
return nil, err
}
//Set basic auth
request.SetBasicAuth(userName, os.Getenv(passwordKey))
response, err := rh.apiClient.Do(request)
if err != nil {
return nil, err
}
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to retrieve the index.yaml under repo %s", namespace)
}
content, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
defer response.Body.Close()
//Traverse to index file object for merging
indexFile := helm_repo.NewIndexFile()
if err := yaml.Unmarshal(content, indexFile); err != nil {
return nil, err
@ -276,12 +257,3 @@ func emptyIndexFile() []byte {
return rawData
}
func writeError(w http.ResponseWriter, code int, err error) {
errorObj := make(map[string]string)
errorObj["error"] = err.Error()
errorContent, _ := json.Marshal(errorObj)
w.WriteHeader(code)
w.Write(errorContent)
}

33
src/chartserver/utils.go Normal file
View File

@ -0,0 +1,33 @@
package chartserver
import (
"encoding/json"
"net/http"
)
const (
contentTypeHeader = "content-type"
contentTypeJSON = "application/json"
)
//Write error to http client
func writeError(w http.ResponseWriter, code int, err error) {
errorObj := make(map[string]string)
errorObj["error"] = err.Error()
errorContent, _ := json.Marshal(errorObj)
w.WriteHeader(code)
w.Write(errorContent)
}
//StatusCode == 500
func writeInternalError(w http.ResponseWriter, err error) {
writeError(w, http.StatusInternalServerError, err)
}
//Write JSON data to http client
func writeJSONData(w http.ResponseWriter, data []byte) {
w.Header().Set(contentTypeHeader, contentTypeJSON)
w.WriteHeader(http.StatusOK)
w.Write(data)
}

1
src/vendor/github.com/blang/semver/.gx/lastpubver generated vendored Normal file
View File

@ -0,0 +1 @@
3.5.1: QmYRGECuvQnRX73fcvPnGbYijBcGN2HbKZQ7jh26qmLiHG

21
src/vendor/github.com/blang/semver/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,21 @@
language: go
matrix:
include:
- go: 1.4.3
- go: 1.5.4
- go: 1.6.3
- go: 1.7
- go: tip
allow_failures:
- go: tip
install:
- go get golang.org/x/tools/cmd/cover
- go get github.com/mattn/goveralls
script:
- echo "Test and track coverage" ; $HOME/gopath/bin/goveralls -package "." -service=travis-ci
-repotoken $COVERALLS_TOKEN
- echo "Build examples" ; cd examples && go build
- echo "Check if gofmt'd" ; diff -u <(echo -n) <(gofmt -d -s .)
env:
global:
secure: HroGEAUQpVq9zX1b1VIkraLiywhGbzvNnTZq2TMxgK7JHP8xqNplAeF1izrR2i4QLL9nsY+9WtYss4QuPvEtZcVHUobw6XnL6radF7jS1LgfYZ9Y7oF+zogZ2I5QUMRLGA7rcxQ05s7mKq3XZQfeqaNts4bms/eZRefWuaFZbkw=

22
src/vendor/github.com/blang/semver/LICENSE generated vendored Normal file
View File

@ -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.

194
src/vendor/github.com/blang/semver/README.md generated vendored Normal file
View File

@ -0,0 +1,194 @@
semver for golang [![Build Status](https://travis-ci.org/blang/semver.svg?branch=master)](https://travis-ci.org/blang/semver) [![GoDoc](https://godoc.org/github.com/blang/semver?status.png)](https://godoc.org/github.com/blang/semver) [![Coverage Status](https://img.shields.io/coveralls/blang/semver.svg)](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`
- Wildcards `>=1.x`, `<=2.5.x`
- 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`.
Note that spaces between the operator and the version will be gracefully tolerated.
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.

83
src/vendor/github.com/blang/semver/examples/main.go generated vendored Normal file
View 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)
}
}

23
src/vendor/github.com/blang/semver/json.go generated vendored Normal file
View File

@ -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
}

49
src/vendor/github.com/blang/semver/json_test.go generated vendored Normal file
View File

@ -0,0 +1,49 @@
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")
}
if err := json.Unmarshal([]byte("3.1"), &v); err == nil {
t.Fatal("expected JSON unmarshal error, got nil")
}
}

17
src/vendor/github.com/blang/semver/package.json generated vendored Normal file
View File

@ -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.5.1"
}

416
src/vendor/github.com/blang/semver/range.go generated vendored Normal file
View File

@ -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
}

581
src/vendor/github.com/blang/semver/range_test.go generated vendored Normal file
View File

@ -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)
}
}

418
src/vendor/github.com/blang/semver/semver.go generated vendored Normal file
View File

@ -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
}

458
src/vendor/github.com/blang/semver/semver_test.go generated vendored Normal file
View File

@ -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))
}
}

28
src/vendor/github.com/blang/semver/sort.go generated vendored Normal file
View File

@ -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))
}

30
src/vendor/github.com/blang/semver/sort_test.go generated vendored Normal file
View File

@ -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})
}
}

30
src/vendor/github.com/blang/semver/sql.go generated vendored Normal file
View File

@ -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
}

38
src/vendor/github.com/blang/semver/sql_test.go generated vendored Normal file
View File

@ -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)
}
}
}
}