diff --git a/Gopkg.lock b/Gopkg.lock index 79a985dc..7d93ac01 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,10 +2,10 @@ [[projects]] + branch = "2.x" name = "github.com/Masterminds/semver" packages = ["."] - revision = "59c29afe1a994eacb71c833025ca7acf874bb1da" - version = "v1.2.2" + revision = "c2e7f6c2f49a1613362aa774884c17e395dd85b7" [[projects]] name = "github.com/Microsoft/go-winio" @@ -141,6 +141,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "42499d5f9a57fad258f61fc4c095b1ad62e33bc71b0cd99f52aecb6e1f11e1b0" + inputs-digest = "73b93048f02cd027ec4cdc288e714a986701be6e9ab57cb2df588d627b523179" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 5359b972..7263bf2b 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -23,7 +23,7 @@ [[constraint]] name = "github.com/Masterminds/semver" - version = "1.2.2" + branch = "2.x" [[constraint]] name = "github.com/codegangsta/cli" diff --git a/dvm-helper/dockerversion/dockerversion.go b/dvm-helper/dockerversion/dockerversion.go index 1f2d29ef..eb3163a6 100644 --- a/dvm-helper/dockerversion/dockerversion.go +++ b/dvm-helper/dockerversion/dockerversion.go @@ -30,13 +30,69 @@ func Parse(value string) Version { v := Version{raw: value} semver, err := semver.NewVersion(value) if err == nil { - v.semver = semver + v.semver = &semver } else { v.alias = value } return v } +func (version Version) BuildDownloadURL(mirrorURL string) (url string, archived bool) { + var releaseSlug, versionSlug, extSlug string + + archivedReleaseCutoff, _ := semver.NewVersion("1.11.0-rc1") + dockerStoreCutoff, _ := semver.NewVersion("17.06.0-ce") + + var edgeVersion Version + if version.IsExperimental() { + // TODO: Figure out the latest edge version + edgeVersion = Parse("17.06.0-ce") + } + + // Docker Store Download + if version.IsExperimental() || !version.semver.LessThan(dockerStoreCutoff) { + archived = true + extSlug = archiveFileExt + if mirrorURL == "" { + mirrorURL = "download.docker.com" + } + if version.IsExperimental() { + releaseSlug = "edge" + versionSlug = edgeVersion.String() + } else if version.IsPrerelease() { + releaseSlug = "test" + versionSlug = version.String() + } else { + releaseSlug = "stable" + versionSlug = version.String() + } + + url = fmt.Sprintf("https://%s/%s/static/%s/%s/docker-%s%s", + mirrorURL, mobyOS, releaseSlug, dockerArch, versionSlug, extSlug) + return + } else { // Original Download + archived = !version.semver.LessThan(archivedReleaseCutoff) + versionSlug = version.String() + if archived { + extSlug = archiveFileExt + } else { + extSlug = binaryFileExt + } + if mirrorURL == "" { + mirrorURL = "docker.com" + } + if version.IsPrerelease() { + releaseSlug = "test" + } else { + releaseSlug = "get" + } + + url = fmt.Sprintf("https://%s.%s/builds/%s/%s/docker-%s%s", + releaseSlug, mirrorURL, dockerOS, dockerArch, versionSlug, extSlug) + return + } +} + func (version Version) IsPrerelease() bool { if version.semver == nil { return false @@ -78,11 +134,6 @@ func (version *Version) SetAsExperimental() { version.alias = ExperimentalAlias } -func (version Version) ShouldUseArchivedRelease() bool { - cutoff, _ := semver.NewConstraint(">= 1.11.0-rc1") - return version.IsExperimental() || cutoff.Check(version.semver) -} - func (version Version) String() string { if version.alias != "" && version.semver != nil { return fmt.Sprintf("%s (%s)", version.alias, version.formatRaw()) @@ -129,7 +180,10 @@ func (version Version) InRange(r string) (bool, error) { if err != nil { return false, errors.Wrapf(err, "Unable to parse range constraint: %s", r) } - return c.Check(version.semver), nil + if version.semver == nil { + return false, nil + } + return c.Matches(*version.semver) == nil, nil } // Compare compares Versions v to o: @@ -138,7 +192,7 @@ func (version Version) InRange(r string) (bool, error) { // 1 == v is greater than o func (v Version) Compare(o Version) int { if v.semver != nil && o.semver != nil { - return v.semver.Compare(o.semver) + return v.semver.Compare(*o.semver) } return strings.Compare(v.alias, o.alias) diff --git a/dvm-helper/dockerversion/dockerversion.nix.go b/dvm-helper/dockerversion/dockerversion.nix.go new file mode 100644 index 00000000..dc68e70f --- /dev/null +++ b/dvm-helper/dockerversion/dockerversion.nix.go @@ -0,0 +1,6 @@ +// +build !windows + +package dockerversion + +const archiveFileExt string = ".tgz" +const binaryFileExt string = "" diff --git a/dvm-helper/dockerversion/dockerversion_386.go b/dvm-helper/dockerversion/dockerversion_386.go new file mode 100644 index 00000000..5f450371 --- /dev/null +++ b/dvm-helper/dockerversion/dockerversion_386.go @@ -0,0 +1,3 @@ +package dockerversion + +const dockerArch string = "i386" diff --git a/dvm-helper/dockerversion/dockerversion_amd64.go b/dvm-helper/dockerversion/dockerversion_amd64.go new file mode 100644 index 00000000..e2867e2e --- /dev/null +++ b/dvm-helper/dockerversion/dockerversion_amd64.go @@ -0,0 +1,3 @@ +package dockerversion + +const dockerArch string = "x86_64" diff --git a/dvm-helper/dockerversion/dockerversion_darwin.go b/dvm-helper/dockerversion/dockerversion_darwin.go new file mode 100644 index 00000000..f487368d --- /dev/null +++ b/dvm-helper/dockerversion/dockerversion_darwin.go @@ -0,0 +1,4 @@ +package dockerversion + +const dockerOS string = "Darwin" +const mobyOS string = "mac" diff --git a/dvm-helper/dockerversion/dockerversion_linux.go b/dvm-helper/dockerversion/dockerversion_linux.go new file mode 100644 index 00000000..1c3685ce --- /dev/null +++ b/dvm-helper/dockerversion/dockerversion_linux.go @@ -0,0 +1,4 @@ +package dockerversion + +const dockerOS string = "Linux" +const mobyOS string = "linux" diff --git a/dvm-helper/dockerversion/dockerversion_test.go b/dvm-helper/dockerversion/dockerversion_test.go index 3dce29b4..87e49d35 100644 --- a/dvm-helper/dockerversion/dockerversion_test.go +++ b/dvm-helper/dockerversion/dockerversion_test.go @@ -1,76 +1,72 @@ -package dockerversion_test +package dockerversion import ( + "fmt" + "net/http" "testing" - "github.com/howtowhale/dvm/dvm-helper/dockerversion" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) func TestStripLeadingV(t *testing.T) { - v := dockerversion.Parse("v1.0.0") + v := Parse("v1.0.0") assert.Equal(t, "1.0.0", v.String(), "Leading v should be stripped from the string representation") assert.Equal(t, "1.0.0", v.Name(), "Leading v should be stripped from the name") assert.Equal(t, "1.0.0", v.Value(), "Leading v should be stripped from the version value") } func TestIsPrerelease(t *testing.T) { - var v dockerversion.Version + var v Version - v = dockerversion.Parse("17.3.0-ce-rc1") + v = Parse("17.3.0-ce-rc1") assert.True(t, v.IsPrerelease(), "%s should be a prerelease", v) - v = dockerversion.Parse("1.12.4-rc1") + v = Parse("1.12.4-rc1") assert.True(t, v.IsPrerelease(), "%s should be a prerelease", v) - v = dockerversion.Parse("1.12.4-beta.1") + v = Parse("1.12.4-beta.1") assert.True(t, v.IsPrerelease(), "%s should be a prerelease", v) - v = dockerversion.Parse("1.12.4-alpha-2") + v = Parse("1.12.4-alpha-2") assert.True(t, v.IsPrerelease(), "%s should be a prerelease", v) - v = dockerversion.Parse("17.3.0-ce") + v = Parse("17.3.0-ce") assert.False(t, v.IsPrerelease(), "%s should NOT be a prerelease", v) } -func TestPrereleaseUsesArchivedReleases(t *testing.T) { - v := dockerversion.Parse("v1.12.5-rc1") - - assert.True(t, v.ShouldUseArchivedRelease()) -} - func TestLeadingZeroInVersion(t *testing.T) { - v := dockerversion.Parse("v17.03.0-ce") + v := Parse("v17.03.0-ce") assert.Equal(t, "17.03.0-ce", v.String(), "Leading zeroes in the version should be preserved") } func TestSystemAlias(t *testing.T) { - v := dockerversion.Parse(dockerversion.SystemAlias) + v := Parse(SystemAlias) assert.Empty(t, v.Slug(), "The system alias should not have a slug") - assert.Equal(t, dockerversion.SystemAlias, v.String(), + assert.Equal(t, SystemAlias, v.String(), "An empty alias should only print the alias") - assert.Equal(t, dockerversion.SystemAlias, v.Name(), + assert.Equal(t, SystemAlias, v.Name(), "The name for an aliased version should be its alias") assert.Equal(t, "", v.Value(), "The value for an empty aliased version should be empty") } func TestExperimentalAlias(t *testing.T) { - v := dockerversion.Parse(dockerversion.ExperimentalAlias) - assert.Equal(t, dockerversion.ExperimentalAlias, v.Slug(), + v := Parse(ExperimentalAlias) + assert.Equal(t, ExperimentalAlias, v.Slug(), "The slug for the experimental version should be 'experimental'") - assert.Equal(t, dockerversion.ExperimentalAlias, v.String(), + assert.Equal(t, ExperimentalAlias, v.String(), "An empty alias should only print the alias") - assert.Equal(t, dockerversion.ExperimentalAlias, v.Name(), + assert.Equal(t, ExperimentalAlias, v.Name(), "The name for an aliased version should be its alias") assert.Equal(t, "", v.Value(), "The value for an empty aliased version should be empty") } func TestAlias(t *testing.T) { - v := dockerversion.NewAlias("prod", "1.2.3") + v := NewAlias("prod", "1.2.3") assert.Equal(t, "1.2.3", v.Slug(), "The slug for an aliased version should be its semver value") assert.Equal(t, "prod (1.2.3)", v.String(), @@ -82,7 +78,7 @@ func TestAlias(t *testing.T) { } func TestSemanticVersion(t *testing.T) { - v := dockerversion.Parse("1.2.3") + v := Parse("1.2.3") assert.Equal(t, "1.2.3", v.Slug(), "The slug for a a semantic version should be its semver value") assert.Equal(t, "1.2.3", v.String(), @@ -94,13 +90,65 @@ func TestSemanticVersion(t *testing.T) { } func TestSetAsExperimental(t *testing.T) { - v := dockerversion.Parse("1.2.3") + v := Parse("1.2.3") v.SetAsExperimental() assert.True(t, v.IsExperimental()) } func TestSetAsSystem(t *testing.T) { - v := dockerversion.Parse("1.2.3") + v := Parse("1.2.3") v.SetAsSystem() assert.True(t, v.IsSystem()) -} \ No newline at end of file +} + +func TestVersion_BuildDownloadURL(t *testing.T) { + testcases := map[Version]struct { + wantURL string + wantArchived bool + }{ + // original download location, without compression + Parse("1.10.3"): {fmt.Sprintf("https://get.docker.com/builds/%s/%s/docker-1.10.3", dockerOS, dockerArch), false}, + + // original download location, without compression, prerelease + Parse("1.10.0-rc1"): {fmt.Sprintf("https://test.docker.com/builds/%s/%s/docker-1.10.0-rc1", dockerOS, dockerArch), false}, + + // compressed binaries + Parse("1.11.0-rc1"): {fmt.Sprintf("https://test.docker.com/builds/%s/%s/docker-1.11.0-rc1.tgz", dockerOS, dockerArch), true}, + + // original version scheme, prerelease binaries + Parse("1.13.0-rc1"): {fmt.Sprintf("https://test.docker.com/builds/%s/%s/docker-1.13.0-rc1.tgz", dockerOS, dockerArch), true}, + + // yearly notation, original download location, release location + Parse("17.03.0-ce"): {fmt.Sprintf("https://get.docker.com/builds/%s/%s/docker-17.03.0-ce%s", dockerOS, dockerArch, archiveFileExt), true}, + + // docker store download + Parse("17.06.0-ce"): {fmt.Sprintf("https://download.docker.com/%s/static/stable/%s/docker-17.06.0-ce.tgz", mobyOS, dockerArch), true}, + + // docker store download, prerelease + Parse("17.07.0-ce-rc1"): {fmt.Sprintf("https://download.docker.com/%s/static/test/%s/docker-17.07.0-ce-rc1.tgz", mobyOS, dockerArch), true}, + + // latest edge/experimental + Parse("experimental"): {fmt.Sprintf("https://download.docker.com/%s/static/edge/%s/docker-17.06.0-ce.tgz", mobyOS, dockerArch), true}, + } + + for version, testcase := range testcases { + t.Run(version.String(), func(t *testing.T) { + gotURL, gotArchived := version.BuildDownloadURL("") + if testcase.wantURL != gotURL { + t.Fatalf("Expected %s to be downloaded from '%s', but got '%s'", version, testcase.wantURL, gotURL) + } + if testcase.wantArchived != gotArchived { + t.Fatalf("Expected %s to use an archived download strategy", version) + } + + response, err := http.DefaultClient.Head(gotURL) + if err != nil { + t.Fatalf("%#v", errors.Wrapf(err, "Unable to download release from %s", gotURL)) + } + + if response.StatusCode != 200 { + t.Fatalf("Unexpected status code (%d) when downloading %s", response.StatusCode, gotURL) + } + }) + } +} diff --git a/dvm-helper/dockerversion/dockerversion_windows.go b/dvm-helper/dockerversion/dockerversion_windows.go new file mode 100644 index 00000000..79e6232e --- /dev/null +++ b/dvm-helper/dockerversion/dockerversion_windows.go @@ -0,0 +1,6 @@ +package dockerversion + +const dockerOS string = "Windows" +const mobyOS string = "win" +const archiveFileExt string = ".zip" +const binaryFileExt string = ".exe" diff --git a/dvm-helper/dvm-helper.386.go b/dvm-helper/dvm-helper.386.go index 3d30608d..60eff004 100644 --- a/dvm-helper/dvm-helper.386.go +++ b/dvm-helper/dvm-helper.386.go @@ -2,5 +2,4 @@ package main -const dockerArch string = "i386" const dvmArch string = "i386" diff --git a/dvm-helper/dvm-helper.amd64.go b/dvm-helper/dvm-helper.amd64.go index 1622de24..e4fad6a6 100644 --- a/dvm-helper/dvm-helper.amd64.go +++ b/dvm-helper/dvm-helper.amd64.go @@ -2,5 +2,4 @@ package main -const dockerArch string = "x86_64" const dvmArch string = "x86_64" diff --git a/dvm-helper/dvm-helper.darwin.go b/dvm-helper/dvm-helper.darwin.go index 5d359461..623921c2 100644 --- a/dvm-helper/dvm-helper.darwin.go +++ b/dvm-helper/dvm-helper.darwin.go @@ -2,5 +2,4 @@ package main -const dockerOS string = "Darwin" const dvmOS string = "Darwin" diff --git a/dvm-helper/dvm-helper.go b/dvm-helper/dvm-helper.go index dd81356a..971f0f78 100644 --- a/dvm-helper/dvm-helper.go +++ b/dvm-helper/dvm-helper.go @@ -441,37 +441,11 @@ func install(version dockerversion.Version) { } } -func buildDownloadURL(version dockerversion.Version) string { - dockerVersion := version.Value() - if version.IsExperimental() { - dockerVersion = "latest" - } - - if mirrorURL == "" { - mirrorURL = "https://get.docker.com/builds" - if version.IsExperimental() { - writeDebug("Downloading from experimental builds mirror") - mirrorURL = "https://experimental.docker.com/builds" - } - if version.IsPrerelease() { - writeDebug("Downloading from prerelease builds mirror") - mirrorURL = "https://test.docker.com/builds" - } - } - - // New Docker versions are released in a zip file, vs. the old way of releasing the client binary only - if version.ShouldUseArchivedRelease() { - return fmt.Sprintf("%s/%s/%s/docker-%s%s", mirrorURL, dockerOS, dockerArch, dockerVersion, archiveFileExt) - } - - return fmt.Sprintf("%s/%s/%s/docker-%s%s", mirrorURL, dockerOS, dockerArch, dockerVersion, binaryFileExt) -} - func downloadRelease(version dockerversion.Version) { - url := buildDownloadURL(version) + url, archived := version.BuildDownloadURL(mirrorURL) binaryName := getBinaryName() binaryPath := filepath.Join(getVersionDir(version), binaryName) - if version.ShouldUseArchivedRelease() { + if archived { archivedFile := path.Join("docker", binaryName) downloadArchivedFileWithChecksum(url, archivedFile, binaryPath) } else { diff --git a/dvm-helper/dvm-helper.linux.go b/dvm-helper/dvm-helper.linux.go index e94d6bc9..ec647a3b 100644 --- a/dvm-helper/dvm-helper.linux.go +++ b/dvm-helper/dvm-helper.linux.go @@ -2,5 +2,4 @@ package main -const dockerOS string = "Linux" const dvmOS string = "Linux" diff --git a/dvm-helper/dvm-helper.nix.go b/dvm-helper/dvm-helper.nix.go index fe08938c..3e742dc4 100644 --- a/dvm-helper/dvm-helper.nix.go +++ b/dvm-helper/dvm-helper.nix.go @@ -8,7 +8,6 @@ import ( ) const binaryFileExt string = "" -const archiveFileExt string = ".tgz" func upgradeSelf(version string) { binaryURL := buildDvmReleaseURL(version, dvmOS, dvmArch, "dvm-helper") diff --git a/dvm-helper/dvm-helper.windows.go b/dvm-helper/dvm-helper.windows.go index b063800c..8fdab414 100644 --- a/dvm-helper/dvm-helper.windows.go +++ b/dvm-helper/dvm-helper.windows.go @@ -9,10 +9,8 @@ import ( ) import "strings" -const dockerOS string = "Windows" const dvmOS string = "Windows" const binaryFileExt string = ".exe" -const archiveFileExt string = ".zip" func upgradeSelf(version string) { binaryURL := buildDvmReleaseURL(version, dvmOS, dvmArch, "dvm-helper.exe") diff --git a/vendor/github.com/Masterminds/semver/.travis.yml b/vendor/github.com/Masterminds/semver/.travis.yml index 32318407..fa92a5a3 100644 --- a/vendor/github.com/Masterminds/semver/.travis.yml +++ b/vendor/github.com/Masterminds/semver/.travis.yml @@ -1,7 +1,6 @@ language: go go: - - 1.5 - 1.6 - 1.7 - tip @@ -13,8 +12,8 @@ go: sudo: false script: - - GO15VENDOREXPERIMENT=1 make setup - - GO15VENDOREXPERIMENT=1 make test + - make setup + - make test notifications: webhooks: diff --git a/vendor/github.com/Masterminds/semver/CHANGELOG.md b/vendor/github.com/Masterminds/semver/CHANGELOG.md index 237d53a4..25550675 100644 --- a/vendor/github.com/Masterminds/semver/CHANGELOG.md +++ b/vendor/github.com/Masterminds/semver/CHANGELOG.md @@ -1,37 +1,9 @@ -# Release 1.2.2 (2016-12-13) +# Release 1.x.x (xxxx-xx-xx) -## Fixed -- #34: Fixed issue where hyphen range was not working with pre-release parsing. - -# Release 1.2.1 (2016-11-28) - -## Fixed -- #24: Fixed edge case issue where constraint "> 0" does not handle "0.0.1-alpha" - properly. - -# Release 1.2.0 (2016-11-04) - -## Added -- #20: Added MustParse function for versions (thanks @adamreese) -- #15: Added increment methods on versions (thanks @mh-cbon) - -## Fixed -- Issue #21: Per the SemVer spec (section 9) a pre-release is unstable and - might not satisfy the intended compatibility. The change here ignores pre-releases - on constraint checks (e.g., ~ or ^) when a pre-release is not part of the - constraint. For example, `^1.2.3` will ignore pre-releases while - `^1.2.3-alpha` will include them. - -# Release 1.1.1 (2016-06-30) - -## Changed - Issue #9: Speed up version comparison performance (thanks @sdboyer) - Issue #8: Added benchmarks (thanks @sdboyer) -- Updated Go Report Card URL to new location -- Updated Readme to add code snippet formatting (thanks @mh-cbon) -- Updating tagging to v[SemVer] structure for compatibility with other tools. -# Release 1.1.0 (2016-03-11) +# Release 1.1.0 (2015-03-11) - Issue #2: Implemented validation to provide reasons a versions failed a constraint. diff --git a/vendor/github.com/Masterminds/semver/README.md b/vendor/github.com/Masterminds/semver/README.md index 9a9ed453..aa133eac 100644 --- a/vendor/github.com/Masterminds/semver/README.md +++ b/vendor/github.com/Masterminds/semver/README.md @@ -7,15 +7,13 @@ The `semver` package provides the ability to work with [Semantic Versions](http: * Check if a semantic version fits within a set of constraints * Optionally work with a `v` prefix -[![Build Status](https://travis-ci.org/Masterminds/semver.svg)](https://travis-ci.org/Masterminds/semver) [![Build status](https://ci.appveyor.com/api/projects/status/jfk66lib7hb985k8/branch/master?svg=true&passingText=windows%20build%20passing&failingText=windows%20build%20failing)](https://ci.appveyor.com/project/mattfarina/semver/branch/master) [![GoDoc](https://godoc.org/github.com/Masterminds/semver?status.png)](https://godoc.org/github.com/Masterminds/semver) [![Go Report Card](https://goreportcard.com/badge/github.com/Masterminds/semver)](https://goreportcard.com/report/github.com/Masterminds/semver) +[![Build Status](https://travis-ci.org/Masterminds/semver.svg)](https://travis-ci.org/Masterminds/semver) [![Build status](https://ci.appveyor.com/api/projects/status/jfk66lib7hb985k8/branch/master?svg=true&passingText=windows%20build%20passing&failingText=windows%20build%20failing)](https://ci.appveyor.com/project/mattfarina/semver/branch/master) [![GoDoc](https://godoc.org/github.com/Masterminds/semver?status.png)](https://godoc.org/github.com/Masterminds/semver) [![Go Report Card](http://goreportcard.com/badge/Masterminds/semver)](http://goreportcard.com/report/Masterminds/semver) ## Parsing Semantic Versions To parse a semantic version use the `NewVersion` function. For example, -```go v, err := semver.NewVersion("1.2.3-beta.1+build345") -``` If there is an error the version wasn't parseable. The version object has methods to get the parts of the version, compare it to other versions, convert the @@ -27,7 +25,6 @@ please see the [documentation](https://godoc.org/github.com/Masterminds/semver). A set of versions can be sorted using the [`sort`](https://golang.org/pkg/sort/) package from the standard library. For example, -```go raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",} vs := make([]*semver.Version, len(raw)) for i, r := range raw { @@ -40,14 +37,12 @@ package from the standard library. For example, } sort.Sort(semver.Collection(vs)) -``` ## Checking Version Constraints Checking a version against version constraints is one of the most featureful parts of the package. -```go c, err := semver.NewConstraint(">= 1.2.3") if err != nil { // Handle constraint not being parseable. @@ -59,7 +54,6 @@ parts of the package. } // Check if the version meets the constraints. The a variable will be true. a := c.Check(v) -``` ## Basic Comparisons @@ -78,15 +72,6 @@ The basic comparisons are: * `>=`: greater than or equal to * `<=`: less than or equal to -_Note, according to the Semantic Version specification pre-releases may not be -API compliant with their release counterpart. It says,_ - -> _A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal version._ - -_SemVer comparisons without a pre-release value will skip pre-release versions. -For example, `>1.2.3` will skip pre-releases when looking at a list of values -while `>1.2.3-alpha.1` will evaluate pre-releases._ - ## Hyphen Range Comparisons There are multiple methods to handle ranges and the first is hyphens ranges. @@ -134,7 +119,6 @@ In addition to testing a version against a constraint, a version can be validate against a constraint. When validation fails a slice of errors containing why a version didn't meet the constraint is returned. For example, -```go c, err := semver.NewConstraint("<= 1.2.3, >= 1.4") if err != nil { // Handle constraint not being parseable. @@ -155,7 +139,6 @@ version didn't meet the constraint is returned. For example, // "1.3 is greater than 1.2.3" // "1.3 is less than 1.4" } -``` # Contribute diff --git a/vendor/github.com/Masterminds/semver/appveyor.yml b/vendor/github.com/Masterminds/semver/appveyor.yml index b2778df1..08d60708 100644 --- a/vendor/github.com/Masterminds/semver/appveyor.yml +++ b/vendor/github.com/Masterminds/semver/appveyor.yml @@ -32,13 +32,13 @@ test_script: --tests \ --vendor \ --deadline 60s \ - ./... || exit_code=1" + ./... || cmd /C EXIT 0" - "gometalinter.v1 \ --disable-all \ --enable golint \ --vendor \ --deadline 60s \ - ./... || :" + ./... || cmd /C EXIT 0" - go test -v deploy: off diff --git a/vendor/github.com/Masterminds/semver/benchmark_test.go b/vendor/github.com/Masterminds/semver/benchmark_test.go index 58a5c289..5a76f6a9 100644 --- a/vendor/github.com/Masterminds/semver/benchmark_test.go +++ b/vendor/github.com/Masterminds/semver/benchmark_test.go @@ -1,16 +1,53 @@ -package semver_test +package semver -import ( - "testing" +import "testing" - "github.com/Masterminds/semver" +func init() { + // disable constraint and version creation caching + CacheConstraints = false + CacheVersions = false +} + +var ( + rc1 = rangeConstraint{ + min: newV(1, 5, 0), + max: newV(2, 0, 0), + includeMax: true, + } + rc2 = rangeConstraint{ + min: newV(2, 0, 0), + max: newV(3, 0, 0), + } + rc3 = rangeConstraint{ + min: newV(1, 5, 0), + max: newV(2, 0, 0), + } + rc4 = rangeConstraint{ + min: newV(1, 7, 0), + max: newV(4, 0, 0), + } + rc5 = rangeConstraint{ + min: newV(2, 7, 0), + max: newV(3, 0, 0), + } + rc6 = rangeConstraint{ + min: newV(3, 0, 1), + max: newV(3, 0, 4), + } + rc7 = rangeConstraint{ + min: newV(1, 0, 0), + max: newV(1, 2, 0), + } + // Two fully non-overlapping unions + u1 = rc1.Union(rc7) + u2 = rc5.Union(rc6) ) /* Constraint creation benchmarks */ func benchNewConstraint(c string, b *testing.B) { for i := 0; i < b.N; i++ { - semver.NewConstraint(c) + NewConstraint(c) } } @@ -38,52 +75,17 @@ func BenchmarkNewConstraintUnion(b *testing.B) { benchNewConstraint("~2.0.0 || =3.1.0", b) } -/* Check benchmarks */ - -func benchCheckVersion(c, v string, b *testing.B) { - version, _ := semver.NewVersion(v) - constraint, _ := semver.NewConstraint(c) - - for i := 0; i < b.N; i++ { - constraint.Check(version) - } -} - -func BenchmarkCheckVersionUnary(b *testing.B) { - benchCheckVersion("=2.0", "2.0.0", b) -} - -func BenchmarkCheckVersionTilde(b *testing.B) { - benchCheckVersion("~2.0.0", "2.0.5", b) -} - -func BenchmarkCheckVersionCaret(b *testing.B) { - benchCheckVersion("^2.0.0", "2.1.0", b) -} - -func BenchmarkCheckVersionWildcard(b *testing.B) { - benchCheckVersion("1.x", "1.4.0", b) -} - -func BenchmarkCheckVersionRange(b *testing.B) { - benchCheckVersion(">=2.1.x, <3.1.0", "2.4.5", b) -} - -func BenchmarkCheckVersionUnion(b *testing.B) { - benchCheckVersion("~2.0.0 || =3.1.0", "3.1.0", b) -} +/* Validate benchmarks, including fails */ func benchValidateVersion(c, v string, b *testing.B) { - version, _ := semver.NewVersion(v) - constraint, _ := semver.NewConstraint(c) + version, _ := NewVersion(v) + constraint, _ := NewConstraint(c) for i := 0; i < b.N; i++ { - constraint.Validate(version) + constraint.Matches(version) } } -/* Validate benchmarks, including fails */ - func BenchmarkValidateVersionUnary(b *testing.B) { benchValidateVersion("=2.0", "2.0.0", b) } @@ -136,7 +138,7 @@ func BenchmarkValidateVersionUnionFail(b *testing.B) { func benchNewVersion(v string, b *testing.B) { for i := 0; i < b.N; i++ { - semver.NewVersion(v) + NewVersion(v) } } @@ -155,3 +157,103 @@ func BenchmarkNewVersionMeta(b *testing.B) { func BenchmarkNewVersionMetaDash(b *testing.B) { benchNewVersion("1.0.0+metadata-dash", b) } + +/* Union benchmarks */ + +func BenchmarkAdjacentRangeUnion(b *testing.B) { + for i := 0; i < b.N; i++ { + Union(rc1, rc2) + } +} + +func BenchmarkAdjacentRangeUnionMethod(b *testing.B) { + for i := 0; i < b.N; i++ { + rc1.Union(rc2) + } +} + +func BenchmarkDisjointRangeUnion(b *testing.B) { + for i := 0; i < b.N; i++ { + Union(rc2, rc3) + } +} + +func BenchmarkDisjointRangeUnionMethod(b *testing.B) { + for i := 0; i < b.N; i++ { + rc2.Union(rc3) + } +} + +func BenchmarkOverlappingRangeUnion(b *testing.B) { + for i := 0; i < b.N; i++ { + Union(rc1, rc4) + } +} + +func BenchmarkOverlappingRangeUnionMethod(b *testing.B) { + for i := 0; i < b.N; i++ { + rc1.Union(rc4) + } +} + +func BenchmarkUnionUnion(b *testing.B) { + for i := 0; i < b.N; i++ { + Union(u1, u2) + } +} + +func BenchmarkUnionUnionMethod(b *testing.B) { + for i := 0; i < b.N; i++ { + u1.Union(u2) + } +} + +/* Intersection benchmarks */ + +func BenchmarkSubsetRangeIntersection(b *testing.B) { + for i := 0; i < b.N; i++ { + Intersection(rc2, rc4) + } +} + +func BenchmarkSubsetRangeIntersectionMethod(b *testing.B) { + for i := 0; i < b.N; i++ { + rc2.Intersect(rc4) + } +} + +func BenchmarkDisjointRangeIntersection(b *testing.B) { + for i := 0; i < b.N; i++ { + Intersection(rc2, rc3) + } +} + +func BenchmarkDisjointRangeIntersectionMethod(b *testing.B) { + for i := 0; i < b.N; i++ { + rc2.Intersect(rc3) + } +} + +func BenchmarkOverlappingRangeIntersection(b *testing.B) { + for i := 0; i < b.N; i++ { + Intersection(rc1, rc4) + } +} + +func BenchmarkOverlappingRangeIntersectionMethod(b *testing.B) { + for i := 0; i < b.N; i++ { + rc1.Intersect(rc4) + } +} + +func BenchmarkUnionIntersection(b *testing.B) { + for i := 0; i < b.N; i++ { + Intersection(u1, u2) + } +} + +func BenchmarkUnionIntersectionMethod(b *testing.B) { + for i := 0; i < b.N; i++ { + u1.Intersect(u2) + } +} diff --git a/vendor/github.com/Masterminds/semver/collection.go b/vendor/github.com/Masterminds/semver/collection.go index a7823589..459fbe0e 100644 --- a/vendor/github.com/Masterminds/semver/collection.go +++ b/vendor/github.com/Masterminds/semver/collection.go @@ -3,7 +3,7 @@ package semver // Collection is a collection of Version instances and implements the sort // interface. See the sort package for more details. // https://golang.org/pkg/sort/ -type Collection []*Version +type Collection []Version // Len returns the length of a collection. The number of Version instances // on the slice. diff --git a/vendor/github.com/Masterminds/semver/collection_test.go b/vendor/github.com/Masterminds/semver/collection_test.go index 71b909c4..a1d745f4 100644 --- a/vendor/github.com/Masterminds/semver/collection_test.go +++ b/vendor/github.com/Masterminds/semver/collection_test.go @@ -15,7 +15,7 @@ func TestCollection(t *testing.T) { "0.4.2", } - vs := make([]*Version, len(raw)) + vs := make([]Version, len(raw)) for i, r := range raw { v, err := NewVersion(r) if err != nil { diff --git a/vendor/github.com/Masterminds/semver/constraints.go b/vendor/github.com/Masterminds/semver/constraints.go index 4c4fac3b..164c0116 100644 --- a/vendor/github.com/Masterminds/semver/constraints.go +++ b/vendor/github.com/Masterminds/semver/constraints.go @@ -1,126 +1,39 @@ package semver import ( - "errors" "fmt" "regexp" + "sort" "strings" + "sync" ) -// Constraints is one or more constraint that a semantic version can be -// checked against. -type Constraints struct { - constraints [][]*constraint -} - -// NewConstraint returns a Constraints instance that a Version instance can -// be checked against. If there is a parse error it will be returned. -func NewConstraint(c string) (*Constraints, error) { - - // Rewrite - ranges into a comparison operation. - c = rewriteRange(c) - - ors := strings.Split(c, "||") - or := make([][]*constraint, len(ors)) - for k, v := range ors { - cs := strings.Split(v, ",") - result := make([]*constraint, len(cs)) - for i, s := range cs { - pc, err := parseConstraint(s) - if err != nil { - return nil, err - } - - result[i] = pc - } - or[k] = result - } - - o := &Constraints{constraints: or} - return o, nil -} - -// Check tests if a version satisfies the constraints. -func (cs Constraints) Check(v *Version) bool { - // loop over the ORs and check the inner ANDs - for _, o := range cs.constraints { - joy := true - for _, c := range o { - if !c.check(v) { - joy = false - break - } - } - - if joy { - return true - } - } - - return false -} - -// Validate checks if a version satisfies a constraint. If not a slice of -// reasons for the failure are returned in addition to a bool. -func (cs Constraints) Validate(v *Version) (bool, []error) { - // loop over the ORs and check the inner ANDs - var e []error - for _, o := range cs.constraints { - joy := true - for _, c := range o { - if !c.check(v) { - em := fmt.Errorf(c.msg, v, c.orig) - e = append(e, em) - joy = false - } - } - - if joy { - return true, []error{} - } - } - - return false, e -} - -var constraintOps map[string]cfunc -var constraintMsg map[string]string var constraintRegex *regexp.Regexp +var constraintRangeRegex *regexp.Regexp -func init() { - constraintOps = map[string]cfunc{ - "": constraintTildeOrEqual, - "=": constraintTildeOrEqual, - "!=": constraintNotEqual, - ">": constraintGreaterThan, - "<": constraintLessThan, - ">=": constraintGreaterThanEqual, - "=>": constraintGreaterThanEqual, - "<=": constraintLessThanEqual, - "=<": constraintLessThanEqual, - "~": constraintTilde, - "~>": constraintTilde, - "^": constraintCaret, - } +const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` + + `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + + `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` - constraintMsg = map[string]string{ - "": "%s is not equal to %s", - "=": "%s is not equal to %s", - "!=": "%s is equal to %s", - ">": "%s is less than or equal to %s", - "<": "%s is greater than or equal to %s", - ">=": "%s is less than %s", - "=>": "%s is less than %s", - "<=": "%s is greater than %s", - "=<": "%s is greater than %s", - "~": "%s does not have same major and minor version as %s", - "~>": "%s does not have same major and minor version as %s", - "^": "%s does not have same major version as %s", +func init() { + constraintOps := []string{ + "", + "=", + "!=", + ">", + "<", + ">=", + "=>", + "<=", + "=<", + "~", + "~>", + "^", } ops := make([]string, 0, len(constraintOps)) - for k := range constraintOps { - ops = append(ops, regexp.QuoteMeta(k)) + for _, op := range constraintOps { + ops = append(ops, regexp.QuoteMeta(op)) } constraintRegex = regexp.MustCompile(fmt.Sprintf( @@ -129,293 +42,274 @@ func init() { cvRegex)) constraintRangeRegex = regexp.MustCompile(fmt.Sprintf( - `\s*(%s)\s+-\s+(%s)\s*`, + `\s*(%s)\s*-\s*(%s)\s*`, cvRegex, cvRegex)) } -// An individual constraint -type constraint struct { - // The callback function for the restraint. It performs the logic for - // the constraint. - function cfunc - - msg string - - // The version used in the constraint check. For example, if a constraint - // is '<= 2.0.0' the con a version instance representing 2.0.0. - con *Version - - // The original parsed version (e.g., 4.x from != 4.x) - orig string - - // When an x is used as part of the version (e.g., 1.x) - minorDirty bool - dirty bool -} - -// Check if a version meets the constraint -func (c *constraint) check(v *Version) bool { - return c.function(v, c) +// Constraint is the interface that wraps checking a semantic version against +// one or more constraints to find a match. +type Constraint interface { + // Constraints compose the fmt.Stringer interface. This method is the + // bijective inverse of NewConstraint(): if a string yielded from this + // method is passed to NewConstraint(), a byte-identical instance of the + // original Constraint will be returend. + fmt.Stringer + + // ImpliedCaretString converts the Constraint to a string in the same manner + // as String(), but treats the empty operator as equivalent to ^, rather + // than =. + // + // In the same way that String() is the inverse of NewConstraint(), this + // method is the inverse of to NewConstraintIC(). + ImpliedCaretString() string + + // Matches checks that a version satisfies the constraint. If it does not, + // an error is returned indcating the problem; if it does, the error is nil. + Matches(v Version) error + + // Intersect computes the intersection between the receiving Constraint and + // passed Constraint, and returns a new Constraint representing the result. + Intersect(Constraint) Constraint + + // Union computes the union between the receiving Constraint and the passed + // Constraint, and returns a new Constraint representing the result. + Union(Constraint) Constraint + + // MatchesAny returns a bool indicating whether there exists any version that + // satisfies both the receiver constraint, and the passed Constraint. + // + // In other words, this reports whether an intersection would be non-empty. + MatchesAny(Constraint) bool + + // Restrict implementation of this interface to this package. We need the + // flexibility of an interface, but we cover all possibilities here; closing + // off the interface to external implementation lets us safely do tricks + // with types for magic types (none and any) + _private() } -type cfunc func(v *Version, c *constraint) bool - -func parseConstraint(c string) (*constraint, error) { - m := constraintRegex.FindStringSubmatch(c) - if m == nil { - return nil, fmt.Errorf("improper constraint: %s", c) - } - - ver := m[2] - orig := ver - minorDirty := false - dirty := false - if isX(m[3]) { - ver = "0.0.0" - dirty = true - } else if isX(strings.TrimPrefix(m[4], ".")) { - minorDirty = true - dirty = true - ver = fmt.Sprintf("%s.0.0%s", m[3], m[6]) - } else if isX(strings.TrimPrefix(m[5], ".")) { - dirty = true - ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6]) - } - - con, err := NewVersion(ver) - if err != nil { - - // The constraintRegex should catch any regex parsing errors. So, - // we should never get here. - return nil, errors.New("constraint Parser Error") - } - - cs := &constraint{ - function: constraintOps[m[1]], - msg: constraintMsg[m[1]], - con: con, - orig: orig, - minorDirty: minorDirty, - dirty: dirty, - } - return cs, nil -} - -// Constraint functions -func constraintNotEqual(v *Version, c *constraint) bool { - if c.dirty { - - // If there is a pre-release on the version but the constraint isn't looking - // for them assume that pre-releases are not compatible. See issue 21 for - // more details. - if v.Prerelease() != "" && c.con.Prerelease() == "" { - return false - } - - if c.con.Major() != v.Major() { - return true - } - if c.con.Minor() != v.Minor() && !c.minorDirty { - return true - } else if c.minorDirty { - return false - } - - return false - } - - return !v.Equal(c.con) +// realConstraint is used internally to differentiate between any, none, and +// unionConstraints, vs. Version and rangeConstraints. +type realConstraint interface { + Constraint + _real() } -func constraintGreaterThan(v *Version, c *constraint) bool { - - // An edge case the constraint is 0.0.0 and the version is 0.0.0-someprerelease - // exists. This that case. - if !isNonZero(c.con) && isNonZero(v) { - return true - } - - // If there is a pre-release on the version but the constraint isn't looking - // for them assume that pre-releases are not compatible. See issue 21 for - // more details. - if v.Prerelease() != "" && c.con.Prerelease() == "" { - return false - } +// CacheConstraints controls whether or not parsed constraints are cached +var CacheConstraints = true +var constraintCache = make(map[string]ccache) +var constraintCacheIC = make(map[string]ccache) +var constraintCacheLock sync.RWMutex - return v.Compare(c.con) == 1 +type ccache struct { + c Constraint + err error } -func constraintLessThan(v *Version, c *constraint) bool { - // If there is a pre-release on the version but the constraint isn't looking - // for them assume that pre-releases are not compatible. See issue 21 for - // more details. - if v.Prerelease() != "" && c.con.Prerelease() == "" { - return false - } - - if !c.dirty { - return v.Compare(c.con) < 0 - } - - if v.Major() > c.con.Major() { - return false - } else if v.Minor() > c.con.Minor() && !c.minorDirty { - return false - } - - return true +// NewConstraint takes a string representing a set of semver constraints, and +// returns a corresponding Constraint object. Constraints are suitable +// for checking Versions for admissibility, or combining with other Constraint +// objects. +// +// If an invalid constraint string is passed, more information is provided in +// the returned error string. +func NewConstraint(in string) (Constraint, error) { + return newConstraint(in, false, constraintCache) } -func constraintGreaterThanEqual(v *Version, c *constraint) bool { - // An edge case the constraint is 0.0.0 and the version is 0.0.0-someprerelease - // exists. This that case. - if !isNonZero(c.con) && isNonZero(v) { - return true - } - - // If there is a pre-release on the version but the constraint isn't looking - // for them assume that pre-releases are not compatible. See issue 21 for - // more details. - if v.Prerelease() != "" && c.con.Prerelease() == "" { - return false - } - - return v.Compare(c.con) >= 0 +// NewConstraintIC ("Implied Caret") is the same as NewConstraint, except that +// it treats an absent operator as being equivalent to ^ instead of =. +func NewConstraintIC(in string) (Constraint, error) { + return newConstraint(in, true, constraintCacheIC) } -func constraintLessThanEqual(v *Version, c *constraint) bool { - // If there is a pre-release on the version but the constraint isn't looking - // for them assume that pre-releases are not compatible. See issue 21 for - // more details. - if v.Prerelease() != "" && c.con.Prerelease() == "" { - return false - } - - if !c.dirty { - return v.Compare(c.con) <= 0 - } - - if v.Major() > c.con.Major() { - return false - } else if v.Minor() > c.con.Minor() && !c.minorDirty { - return false +func newConstraint(in string, ic bool, cache map[string]ccache) (Constraint, error) { + if CacheConstraints { + constraintCacheLock.RLock() + if final, exists := cache[in]; exists { + constraintCacheLock.RUnlock() + return final.c, final.err + } + constraintCacheLock.RUnlock() } - return true -} - -// ~*, ~>* --> >= 0.0.0 (any) -// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0, <3.0.0 -// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0, <2.1.0 -// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0, <1.3.0 -// ~1.2.3, ~>1.2.3 --> >=1.2.3, <1.3.0 -// ~1.2.0, ~>1.2.0 --> >=1.2.0, <1.3.0 -func constraintTilde(v *Version, c *constraint) bool { - // If there is a pre-release on the version but the constraint isn't looking - // for them assume that pre-releases are not compatible. See issue 21 for - // more details. - if v.Prerelease() != "" && c.con.Prerelease() == "" { - return false - } + // Rewrite - ranges into a comparison operation. + c := rewriteRange(in) - if v.LessThan(c.con) { - return false - } + ors := strings.Split(c, "||") + or := make([]Constraint, len(ors)) + for k, v := range ors { + cs := strings.Split(v, ",") + result := make([]Constraint, len(cs)) + for i, s := range cs { + pc, err := parseConstraint(s, ic) + if err != nil { + if CacheConstraints { + constraintCacheLock.Lock() + cache[in] = ccache{err: err} + constraintCacheLock.Unlock() + } + return nil, err + } - // ~0.0.0 is a special case where all constraints are accepted. It's - // equivalent to >= 0.0.0. - if c.con.Major() == 0 && c.con.Minor() == 0 && c.con.Patch() == 0 { - return true + result[i] = pc + } + or[k] = Intersection(result...) } - if v.Major() != c.con.Major() { - return false - } + final := Union(or...) - if v.Minor() != c.con.Minor() && !c.minorDirty { - return false + if CacheConstraints { + constraintCacheLock.Lock() + cache[in] = ccache{c: final} + constraintCacheLock.Unlock() } - return true + return final, nil } -// When there is a .x (dirty) status it automatically opts in to ~. Otherwise -// it's a straight = -func constraintTildeOrEqual(v *Version, c *constraint) bool { - // If there is a pre-release on the version but the constraint isn't looking - // for them assume that pre-releases are not compatible. See issue 21 for - // more details. - if v.Prerelease() != "" && c.con.Prerelease() == "" { - return false - } - - if c.dirty { - c.msg = constraintMsg["~"] - return constraintTilde(v, c) +// Intersection computes the intersection between N Constraints, returning as +// compact a representation of the intersection as possible. +// +// No error is indicated if all the sets are collectively disjoint; you must inspect the +// return value to see if the result is the empty set (by calling IsNone() on +// it). +func Intersection(cg ...Constraint) Constraint { + // If there's zero or one constraints in the group, we can quit fast + switch len(cg) { + case 0: + // Zero members, only sane thing to do is return none + return None() + case 1: + // Just one member means that's our final constraint + return cg[0] + } + + car, cdr := cg[0], cg[1:] + for _, c := range cdr { + if IsNone(car) { + return None() + } + car = car.Intersect(c) } - return v.Equal(c.con) + return car } -// ^* --> (any) -// ^2, ^2.x, ^2.x.x --> >=2.0.0, <3.0.0 -// ^2.0, ^2.0.x --> >=2.0.0, <3.0.0 -// ^1.2, ^1.2.x --> >=1.2.0, <2.0.0 -// ^1.2.3 --> >=1.2.3, <2.0.0 -// ^1.2.0 --> >=1.2.0, <2.0.0 -func constraintCaret(v *Version, c *constraint) bool { - // If there is a pre-release on the version but the constraint isn't looking - // for them assume that pre-releases are not compatible. See issue 21 for - // more details. - if v.Prerelease() != "" && c.con.Prerelease() == "" { - return false - } - - if v.LessThan(c.con) { - return false - } - - if v.Major() != c.con.Major() { - return false +// Union takes a variable number of constraints, and returns the most compact +// possible representation of those constraints. +// +// This effectively ORs together all the provided constraints. If any of the +// included constraints are the set of all versions (any), that supercedes +// everything else. +func Union(cg ...Constraint) Constraint { + // If there's zero or one constraints in the group, we can quit fast + switch len(cg) { + case 0: + // Zero members, only sane thing to do is return none + return None() + case 1: + // One member, so the result will just be that + return cg[0] + } + + // Preliminary pass to look for 'any' in the current set (and bail out early + // if found), but also construct a []realConstraint for everything else + var real constraintList + + for _, c := range cg { + switch tc := c.(type) { + case any: + return c + case none: + continue + case Version: + //heap.Push(&real, tc) + real = append(real, tc) + case rangeConstraint: + //heap.Push(&real, tc) + real = append(real, tc) + case unionConstraint: + real = append(real, tc...) + //for _, c2 := range tc { + //heap.Push(&real, c2) + //} + default: + panic("unknown constraint type") + } } + // TODO wtf why isn't heap working...so, ugh, have to do this - return true -} + // Sort both the versions and ranges into ascending order + sort.Sort(real) -var constraintRangeRegex *regexp.Regexp - -const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` + - `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + - `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` - -func isX(x string) bool { - switch x { - case "x", "*", "X": - return true - default: - return false - } -} + // Iteratively merge the constraintList elements + var nuc unionConstraint + for _, c := range real { + if len(nuc) == 0 { + nuc = append(nuc, c) + continue + } -func rewriteRange(i string) string { - m := constraintRangeRegex.FindAllStringSubmatch(i, -1) - if m == nil { - return i - } - o := i - for _, v := range m { - t := fmt.Sprintf(">= %s, <= %s", v[1], v[11]) - o = strings.Replace(o, v[0], t, 1) + last := nuc[len(nuc)-1] + switch lt := last.(type) { + case Version: + switch ct := c.(type) { + case Version: + // Two versions in a row; only append if they're not equal + if !lt.Equal(ct) { + nuc = append(nuc, ct) + } + case rangeConstraint: + // Last was version, current is range. constraintList sorts by + // min version, so it's guaranteed that the version will be less + // than the range's min, guaranteeing that these are disjoint. + // + // ...almost. If the min of the range is the same as the + // version, then a union should merge the two by making the + // range inclusive at the bottom. + if lt.Equal(ct.min) { + ct.includeMin = true + nuc[len(nuc)-1] = ct + } else { + nuc = append(nuc, c) + } + } + case rangeConstraint: + switch ct := c.(type) { + case Version: + // Last was range, current is version. constraintList sort invariants guarantee + // that the version will be greater than the min, so we have to + // determine if the version is less than the max. If it is, we + // subsume it into the range with a Union call. + // + // Lazy version: just union them and let rangeConstraint figure + // it out, then switch on the result type. + c2 := lt.Union(ct) + if crc, ok := c2.(realConstraint); ok { + nuc[len(nuc)-1] = crc + } else { + // Otherwise, all it can be is a union constraint. First + // item in the union will be the same range, second will be the + // version, so append onto nuc from one back from the end + nuc = append(nuc[:len(nuc)-1], c2.(unionConstraint)...) + } + case rangeConstraint: + if lt.MatchesAny(ct) || areAdjacent(lt, ct) { + // If the previous range overlaps or is adjacent to the + // current range, we know they'll be able to merge together, + // so overwrite the last item in nuc with the result of that + // merge (which is what Union will produce) + nuc[len(nuc)-1] = lt.Union(ct).(realConstraint) + } else { + nuc = append(nuc, c) + } + } + } } - return o -} - -// Detect if a version is not zero (0.0.0) -func isNonZero(v *Version) bool { - if v.Major() != 0 || v.Minor() != 0 || v.Patch() != 0 || v.Prerelease() != "" { - return true + if len(nuc) == 1 { + return nuc[0] } - - return false + return nuc } diff --git a/vendor/github.com/Masterminds/semver/constraints_test.go b/vendor/github.com/Masterminds/semver/constraints_test.go index 59106227..a45714d7 100644 --- a/vendor/github.com/Masterminds/semver/constraints_test.go +++ b/vendor/github.com/Masterminds/semver/constraints_test.go @@ -1,35 +1,66 @@ package semver -import ( - "reflect" - "testing" -) +import "testing" func TestParseConstraint(t *testing.T) { tests := []struct { in string - f cfunc - v string + c Constraint err bool }{ - {">= 1.2", constraintGreaterThanEqual, "1.2.0", false}, - {"1.0", constraintTildeOrEqual, "1.0.0", false}, - {"foo", nil, "", true}, - {"<= 1.2", constraintLessThanEqual, "1.2.0", false}, - {"=< 1.2", constraintLessThanEqual, "1.2.0", false}, - {"=> 1.2", constraintGreaterThanEqual, "1.2.0", false}, - {"v1.2", constraintTildeOrEqual, "1.2.0", false}, - {"=1.5", constraintTildeOrEqual, "1.5.0", false}, - {"> 1.3", constraintGreaterThan, "1.3.0", false}, - {"< 1.4.1", constraintLessThan, "1.4.1", false}, + {"*", Any(), false}, + {">= 1.2", rangeConstraint{ + min: newV(1, 2, 0), + max: Version{special: infiniteVersion}, + includeMin: true, + }, false}, + {"1.0", newV(1, 0, 0), false}, + {"foo", nil, true}, + {"<= 1.2", rangeConstraint{ + min: Version{special: zeroVersion}, + max: newV(1, 2, 0), + includeMax: true, + }, false}, + {"=< 1.2", rangeConstraint{ + min: Version{special: zeroVersion}, + max: newV(1, 2, 0), + includeMax: true, + }, false}, + {"=> 1.2", rangeConstraint{ + min: newV(1, 2, 0), + max: Version{special: infiniteVersion}, + includeMin: true, + }, false}, + {"v1.2", newV(1, 2, 0), false}, + {"=1.5", newV(1, 5, 0), false}, + {"> 1.3", rangeConstraint{ + min: newV(1, 3, 0), + max: Version{special: infiniteVersion}, + }, false}, + {"< 1.4.1", rangeConstraint{ + min: Version{special: zeroVersion}, + max: newV(1, 4, 1), + }, false}, + {"~1.1.0", rangeConstraint{ + min: newV(1, 1, 0), + max: newV(1, 2, 0), + includeMin: true, + includeMax: false, + }, false}, + {"^1.1.0", rangeConstraint{ + min: newV(1, 1, 0), + max: newV(2, 0, 0), + includeMin: true, + includeMax: false, + }, false}, } for _, tc := range tests { - c, err := parseConstraint(tc.in) + c, err := parseConstraint(tc.in, false) if tc.err && err == nil { t.Errorf("Expected error for %s didn't occur", tc.in) } else if !tc.err && err != nil { - t.Errorf("Unexpected error for %s", tc.in) + t.Errorf("Unexpected error %q for %s", err, tc.in) } // If an error was expected continue the loop and don't try the other @@ -38,15 +69,84 @@ func TestParseConstraint(t *testing.T) { continue } - if tc.v != c.con.String() { + if !constraintEq(tc.c, c) { t.Errorf("Incorrect version found on %s", tc.in) } + } +} - f1 := reflect.ValueOf(tc.f) - f2 := reflect.ValueOf(c.function) - if f1 != f2 { - t.Errorf("Wrong constraint found for %s", tc.in) +func constraintEq(c1, c2 Constraint) bool { + switch tc1 := c1.(type) { + case any: + if _, ok := c2.(any); !ok { + return false + } + return true + case none: + if _, ok := c2.(none); !ok { + return false + } + return true + case Version: + if tc2, ok := c2.(Version); ok { + return tc1.Equal(tc2) + } + return false + case rangeConstraint: + if tc2, ok := c2.(rangeConstraint); ok { + if len(tc1.excl) != len(tc2.excl) { + return false + } + + if !tc1.minIsZero() { + if !(tc1.includeMin == tc2.includeMin && tc1.min.Equal(tc2.min)) { + return false + } + } else if !tc2.minIsZero() { + return false + } + + if !tc1.maxIsInf() { + if !(tc1.includeMax == tc2.includeMax && tc1.max.Equal(tc2.max)) { + return false + } + } else if !tc2.maxIsInf() { + return false + } + + for k, e := range tc1.excl { + if !e.Equal(tc2.excl[k]) { + return false + } + } + return true } + return false + case unionConstraint: + if tc2, ok := c2.(unionConstraint); ok { + if len(tc1) != len(tc2) { + return false + } + + for k, c := range tc1 { + if !constraintEq(c, tc2[k]) { + return false + } + } + return true + } + return false + } + + panic("unknown type") +} + +// newV is a helper to create a new Version object. +func newV(major, minor, patch uint64) Version { + return Version{ + major: major, + minor: minor, + patch: patch, } } @@ -72,14 +172,33 @@ func TestConstraintCheck(t *testing.T) { {"<=1.1", "0.1.0", true}, {"<=1.1", "1.1.0", true}, {"<=1.1", "1.1.1", false}, - {">0", "0.0.1-alpha", true}, - {">=0", "0.0.1-alpha", true}, - {">0", "0", false}, - {">=0", "0", true}, + {"<=1.1-alpha1", "1.1", false}, + {"<=2.x", "3.0.0", false}, + {"<=2.x", "2.9.9", true}, + {"<2.x", "2.0.0", false}, + {"<2.x", "1.9.9", true}, + {">=2.x", "3.0.0", true}, + {">=2.x", "2.9.9", true}, + {">=2.x", "1.9.9", false}, + {">2.x", "3.0.0", true}, + {">2.x", "2.9.9", false}, + {">2.x", "1.9.9", false}, + {"<=2.x-alpha2", "3.0.0-alpha3", false}, + {"<=2.0.0", "2.0.0-alpha1", false}, + {">2.x-beta1", "3.0.0-alpha2", false}, + {"^2.0.0", "3.0.0-alpha2", false}, + {"^2.0.0", "2.0.0-alpha1", false}, + {"^2.1.0-alpha1", "2.1.0-alpha2", true}, // allow prerelease match within same major/minor/patch + {"^2.1.0-alpha1", "2.1.1-alpha2", false}, // but ONLY within same major/minor/patch + {"^2.1.0-alpha3", "2.1.0-alpha2", false}, // still respect prerelease ordering + {"^2.0.0", "2.0.0-alpha2", false}, // and only if the min has a prerelease } for _, tc := range tests { - c, err := parseConstraint(tc.constraint) + if testing.Verbose() { + t.Logf("Testing if %q allows %q", tc.constraint, tc.version) + } + c, err := parseConstraint(tc.constraint, false) if err != nil { t.Errorf("err: %s", err) continue @@ -91,9 +210,13 @@ func TestConstraintCheck(t *testing.T) { continue } - a := c.check(v) + a := c.Matches(v) == nil if a != tc.check { - t.Errorf("Constraint %q failing with %q", tc.constraint, tc.version) + if tc.check { + t.Errorf("%q should have matched %q", tc.constraint, tc.version) + } else { + t.Errorf("%q should not have matched %q", tc.constraint, tc.version) + } } } } @@ -101,23 +224,78 @@ func TestConstraintCheck(t *testing.T) { func TestNewConstraint(t *testing.T) { tests := []struct { input string - ors int - count int + c Constraint err bool }{ - {">= 1.1", 1, 1, false}, - {"2.0", 1, 1, false}, - {"v2.3.5-20161202202307-sha.e8fc5e5", 1, 1, false}, - {">= bar", 0, 0, true}, - {">= 1.2.3, < 2.0", 1, 2, false}, - {">= 1.2.3, < 2.0 || => 3.0, < 4", 2, 2, false}, - - // The 3 - 4 should be broken into 2 by the range rewriting - {"3 - 4 || => 3.0, < 4", 2, 2, false}, + {">= 1.1", rangeConstraint{ + min: newV(1, 1, 0), + max: Version{special: infiniteVersion}, + includeMin: true, + }, false}, + {"2.0", newV(2, 0, 0), false}, + {">= bar", nil, true}, + {"^1.1.0", rangeConstraint{ + min: newV(1, 1, 0), + max: newV(2, 0, 0), + includeMin: true, + }, false}, + {">= 1.2.3, < 2.0 || => 3.0, < 4", unionConstraint{ + rangeConstraint{ + min: newV(1, 2, 3), + max: newV(2, 0, 0), + includeMin: true, + }, + rangeConstraint{ + min: newV(3, 0, 0), + max: newV(4, 0, 0), + includeMin: true, + }, + }, false}, + {"3-4 || => 1.0, < 2", Union( + rangeConstraint{ + min: newV(3, 0, 0), + max: newV(4, 0, 0), + includeMin: true, + includeMax: true, + }, + rangeConstraint{ + min: newV(1, 0, 0), + max: newV(2, 0, 0), + includeMin: true, + }, + ), false}, + // demonstrates union compression + {"3-4 || => 3.0, < 4", rangeConstraint{ + min: newV(3, 0, 0), + max: newV(4, 0, 0), + includeMin: true, + includeMax: true, + }, false}, + {">=1.1.0, <2.0.0", rangeConstraint{ + min: newV(1, 1, 0), + max: newV(2, 0, 0), + includeMin: true, + includeMax: false, + }, false}, + {"!=1.4.0", rangeConstraint{ + min: Version{special: zeroVersion}, + max: Version{special: infiniteVersion}, + excl: []Version{ + newV(1, 4, 0), + }, + }, false}, + {">=1.1.0, !=1.4.0", rangeConstraint{ + min: newV(1, 1, 0), + max: Version{special: infiniteVersion}, + includeMin: true, + excl: []Version{ + newV(1, 4, 0), + }, + }, false}, } for _, tc := range tests { - v, err := NewConstraint(tc.input) + c, err := NewConstraint(tc.input) if tc.err && err == nil { t.Errorf("expected but did not get error for: %s", tc.input) continue @@ -129,16 +307,47 @@ func TestNewConstraint(t *testing.T) { continue } - l := len(v.constraints) - if tc.ors != l { - t.Errorf("Expected %s to have %d ORs but got %d", - tc.input, tc.ors, l) + if !constraintEq(tc.c, c) { + t.Errorf("%q produced constraint %q, but expected %q", tc.input, c, tc.c) } + } +} + +func TestNewConstraintIC(t *testing.T) { + tests := []struct { + input string + c Constraint + err bool + }{ + {"=2.0", newV(2, 0, 0), false}, + {"= 2.0", newV(2, 0, 0), false}, + {"1.1.0", rangeConstraint{ + min: newV(1, 1, 0), + max: newV(2, 0, 0), + includeMin: true, + }, false}, + {"1.1", rangeConstraint{ + min: newV(1, 1, 0), + max: newV(2, 0, 0), + includeMin: true, + }, false}, + } - l = len(v.constraints[0]) - if tc.count != l { - t.Errorf("Expected %s to have %d constraints but got %d", - tc.input, tc.count, l) + for _, tc := range tests { + c, err := NewConstraintIC(tc.input) + if tc.err && err == nil { + t.Errorf("expected but did not get error for: %s", tc.input) + continue + } else if !tc.err && err != nil { + t.Errorf("unexpectederror for input %s: %s", tc.input, err) + continue + } + if tc.err { + continue + } + + if !constraintEq(tc.c, c) { + t.Errorf("%q produced constraint %q, but expected %q", tc.input, c, tc.c) } } } @@ -150,15 +359,19 @@ func TestConstraintsCheck(t *testing.T) { check bool }{ {"*", "1.2.3", true}, - {"~0.0.0", "1.2.3", true}, + {"~0.0.0", "1.2.3", false}, + {"0.x.x", "1.2.3", false}, + {"0.0.x", "1.2.3", false}, + {"~0.0.0", "0.1.9", false}, + {"~0.0.0", "0.0.9", true}, + {"^0.0.0", "0.0.9", true}, + {"^0.0.0", "0.1.9", false}, // caret behaves like tilde below 1.0.0 {"= 2.0", "1.2.3", false}, {"= 2.0", "2.0.0", true}, {"4.1", "4.1.0", true}, {"4.1.x", "4.1.3", true}, {"1.x", "1.4", true}, {"!=4.1", "4.1.0", false}, - {"!=4.1-alpha", "4.1.0-alpha", false}, - {"!=4.1-alpha", "4.1.0", true}, {"!=4.1", "5.1.0", true}, {"!=4.x", "5.1.0", true}, {"!=4.x", "4.1.0", false}, @@ -169,18 +382,17 @@ func TestConstraintsCheck(t *testing.T) { {"<1.1", "0.1.0", true}, {"<1.1", "1.1.0", false}, {"<1.1", "1.1.1", false}, - {"<1.x", "1.1.1", true}, + {"<1.x", "1.1.1", false}, + {"<1.x", "0.9.1", true}, {"<1.x", "2.1.1", false}, {"<1.1.x", "1.2.1", false}, - {"<1.1.x", "1.1.500", true}, + {"<1.1.x", "1.1.500", false}, + {"<1.1.x", "1.0.500", true}, {"<1.2.x", "1.1.1", true}, {">=1.1", "4.1.0", true}, - {">=1.1", "4.1.0-beta", false}, {">=1.1", "1.1.0", true}, {">=1.1", "0.0.9", false}, {"<=1.1", "0.1.0", true}, - {"<=1.1", "0.1.0-alpha", false}, - {"<=1.1-a", "0.1.0-alpha", true}, {"<=1.1", "1.1.0", true}, {"<=1.x", "1.1.0", true}, {"<=2.x", "3.1.0", false}, @@ -200,18 +412,11 @@ func TestConstraintsCheck(t *testing.T) { {"^1.x", "1.1.1", true}, {"^2.x", "1.1.1", false}, {"^1.x", "2.1.1", false}, - {"^1.x", "1.1.1-beta1", false}, - {"^1.1.2-alpha", "1.2.1-beta1", true}, - {"^1.2.x-alpha", "1.1.1-beta1", false}, {"~*", "2.1.1", true}, {"~1.x", "2.1.1", false}, {"~1.x", "1.3.5", true}, {"~1.x", "1.4", true}, {"~1.1", "1.1.1", true}, - {"~1.1", "1.1.1-alpha", false}, - {"~1.1-alpha", "1.1.1-beta", true}, - {"~1.1.1-beta", "1.1.1-alpha", false}, - {"~1.1.1-beta", "1.1.1", true}, {"~1.2.3", "1.2.5", true}, {"~1.2.3", "1.2.2", false}, {"~1.2.3", "1.3.2", false}, @@ -232,9 +437,105 @@ func TestConstraintsCheck(t *testing.T) { continue } - a := c.Check(v) + a := c.Matches(v) == nil if a != tc.check { - t.Errorf("Constraint '%s' failing with '%s'", tc.constraint, tc.version) + if a { + t.Errorf("Input %q produced constraint %q; should not have admitted %q, but did", tc.constraint, c, tc.version) + } else { + t.Errorf("Input %q produced constraint %q; should have admitted %q, but did not", tc.constraint, c, tc.version) + } + } + } +} + +func TestBidirectionalSerialization(t *testing.T) { + tests := []struct { + io string + eq bool + }{ + {"*", true}, // any + {"~0.0.0", false}, // tildes expand into ranges + {"=2.0", false}, // abbreviated versions print as full + {"4.1.x", false}, // wildcards expand into ranges + {">= 1.1.0", false}, // does not produce spaces on ranges + {"4.1.0", true}, + {"!=4.1.0", true}, + {">=1.1.0", true}, + {">1.0.0, <=1.1.0", true}, + {"<=1.1.0", true}, + {">=1.1.7, <1.3.0", true}, // tilde width + {">=1.1.0, <=2.0.0", true}, // no unary op on lte max + {">1.1.3, <2.0.0", true}, // no unary op on gt min + {">1.1.0, <=2.0.0", true}, // no unary op on gt min and lte max + {">=1.1.0, <=1.2.0", true}, // no unary op on lte max + {">1.1.1, <1.2.0", true}, // no unary op on gt min + {">1.1.7, <=2.0.0", true}, // no unary op on gt min and lte max + {">1.1.7, <=2.0.0", true}, // no unary op on gt min and lte max + {">=0.1.7, <1.0.0", true}, // caret shifting below 1.0.0 + {">=0.1.7, <0.3.0", true}, // caret shifting width below 1.0.0 + } + + for _, fix := range tests { + c, err := NewConstraint(fix.io) + if err != nil { + t.Errorf("Valid constraint string produced unexpected error: %s", err) + } + + eq := fix.io == c.String() + if eq != fix.eq { + if eq { + t.Errorf("Constraint %q should not have reproduced input string %q, but did", c, fix.io) + } else { + t.Errorf("Constraint should have reproduced input string %q, but instead produced %q", fix.io, c) + } + } + } +} + +func TestBidirectionalSerializationIC(t *testing.T) { + tests := []struct { + io string + eq bool + }{ + {"*", true}, // any + {"=2.0.0", true}, // versions retain leading = + {"2.0.0", true}, // (no) caret in, (no) caret out + } + + for _, fix := range tests { + c, err := NewConstraintIC(fix.io) + if err != nil { + t.Errorf("Valid constraint string produced unexpected error: %s", err) + } + + eq := fix.io == c.ImpliedCaretString() + if eq != fix.eq { + if eq { + t.Errorf("Constraint %q should not have reproduced input string %q, but did", c, fix.io) + } else { + t.Errorf("Constraint should have reproduced input string %q, but instead produced %q", fix.io, c) + } + } + } +} + +func TestPreferUnaryOpForm(t *testing.T) { + tests := []struct { + in, out string + }{ + {">=0.1.7, <0.2.0", "^0.1.7"}, // caret shifting below 1.0.0 + {">=1.1.0, <2.0.0", "^1.1.0"}, + {">=1.1.0, <2.0.0, !=1.2.3", "^1.1.0, !=1.2.3"}, + } + + for _, fix := range tests { + c, err := NewConstraint(fix.in) + if err != nil { + t.Errorf("Valid constraint string produced unexpected error: %s", err) + } + + if fix.out != c.String() { + t.Errorf("Constraint %q was not transformed into expected output string %q", fix.in, fix.out) } } } @@ -244,9 +545,9 @@ func TestRewriteRange(t *testing.T) { c string nc string }{ - {"2 - 3", ">= 2, <= 3"}, - {"2 - 3, 2 - 3", ">= 2, <= 3,>= 2, <= 3"}, - {"2 - 3, 4.0.0 - 5.1", ">= 2, <= 3,>= 4.0.0, <= 5.1"}, + {"2-3", ">= 2, <= 3"}, + {"2-3, 2-3", ">= 2, <= 3,>= 2, <= 3"}, + {"2-3, 4.0.0-5.1", ">= 2, <= 3,>= 4.0.0, <= 5.1"}, } for _, tc := range tests { @@ -278,175 +579,121 @@ func TestIsX(t *testing.T) { } } -func TestConstraintsValidate(t *testing.T) { - tests := []struct { - constraint string - version string - check bool - }{ - {"*", "1.2.3", true}, - {"~0.0.0", "1.2.3", true}, - {"= 2.0", "1.2.3", false}, - {"= 2.0", "2.0.0", true}, - {"4.1", "4.1.0", true}, - {"4.1.x", "4.1.3", true}, - {"1.x", "1.4", true}, - {"!=4.1", "4.1.0", false}, - {"!=4.1", "5.1.0", true}, - {"!=4.x", "5.1.0", true}, - {"!=4.x", "4.1.0", false}, - {"!=4.1.x", "4.2.0", true}, - {"!=4.2.x", "4.2.3", false}, - {">1.1", "4.1.0", true}, - {">1.1", "1.1.0", false}, - {"<1.1", "0.1.0", true}, - {"<1.1", "1.1.0", false}, - {"<1.1", "1.1.1", false}, - {"<1.x", "1.1.1", true}, - {"<1.x", "2.1.1", false}, - {"<1.1.x", "1.2.1", false}, - {"<1.1.x", "1.1.500", true}, - {"<1.2.x", "1.1.1", true}, - {">=1.1", "4.1.0", true}, - {">=1.1", "1.1.0", true}, - {">=1.1", "0.0.9", false}, - {"<=1.1", "0.1.0", true}, - {"<=1.1", "1.1.0", true}, - {"<=1.x", "1.1.0", true}, - {"<=2.x", "3.1.0", false}, - {"<=1.1", "1.1.1", false}, - {"<=1.1.x", "1.2.500", false}, - {">1.1, <2", "1.1.1", true}, - {">1.1, <3", "4.3.2", false}, - {">=1.1, <2, !=1.2.3", "1.2.3", false}, - {">=1.1, <2, !=1.2.3 || > 3", "3.1.2", true}, - {">=1.1, <2, !=1.2.3 || >= 3", "3.0.0", true}, - {">=1.1, <2, !=1.2.3 || > 3", "3.0.0", false}, - {">=1.1, <2, !=1.2.3 || > 3", "1.2.3", false}, - {"1.1 - 2", "1.1.1", true}, - {"1.1-3", "4.3.2", false}, - {"^1.1", "1.1.1", true}, - {"^1.1", "1.1.1-alpha", false}, - {"^1.1.1-alpha", "1.1.1-beta", true}, - {"^1.1.1-beta", "1.1.1-alpha", false}, - {"^1.1", "4.3.2", false}, - {"^1.x", "1.1.1", true}, - {"^2.x", "1.1.1", false}, - {"^1.x", "2.1.1", false}, - {"~*", "2.1.1", true}, - {"~1.x", "2.1.1", false}, - {"~1.x", "1.3.5", true}, - {"~1.x", "1.3.5-beta", false}, - {"~1.3.6-alpha", "1.3.5-beta", false}, - {"~1.3.5-alpha", "1.3.5-beta", true}, - {"~1.3.5-beta", "1.3.5-alpha", false}, - {"~1.x", "1.4", true}, - {"~1.1", "1.1.1", true}, - {"~1.2.3", "1.2.5", true}, - {"~1.2.3", "1.2.2", false}, - {"~1.2.3", "1.3.2", false}, - {"~1.1", "1.2.3", false}, - {"~1.3", "2.4.5", false}, +func TestUnionErr(t *testing.T) { + u1 := Union( + rangeConstraint{ + min: newV(3, 0, 0), + max: newV(4, 0, 0), + includeMin: true, + includeMax: true, + }, + rangeConstraint{ + min: newV(1, 0, 0), + max: newV(2, 0, 0), + includeMin: true, + }, + ) + fail := u1.Matches(newV(2, 5, 0)) + failstr := `2.5.0 is greater than or equal to the maximum of ^1.0.0 +2.5.0 is less than the minimum of >=3.0.0, <=4.0.0` + if fail.Error() != failstr { + t.Errorf("Did not get expected failure message from union, got %q", fail) } +} - for _, tc := range tests { - c, err := NewConstraint(tc.constraint) - if err != nil { - t.Errorf("err: %s", err) - continue - } +func TestIsSuperset(t *testing.T) { + rc := []rangeConstraint{ + { + min: newV(1, 2, 0), + max: newV(2, 0, 0), + includeMin: true, + }, + { + min: newV(1, 2, 0), + max: newV(2, 1, 0), + }, + { + min: Version{special: zeroVersion}, + max: newV(1, 10, 0), + }, + { + min: newV(2, 0, 0), + max: Version{special: infiniteVersion}, + }, + { + min: newV(1, 2, 0), + max: newV(2, 0, 0), + includeMax: true, + }, + } - v, err := NewVersion(tc.version) - if err != nil { - t.Errorf("err: %s", err) - continue - } + for _, c := range rc { - a, msgs := c.Validate(v) - if a != tc.check { - t.Errorf("Constraint '%s' failing with '%s'", tc.constraint, tc.version) - } else if !a && len(msgs) == 0 { - t.Errorf("%q failed with %q but no errors returned", tc.constraint, tc.version) + // Superset comparison is not strict, so a range should always be a superset + // of itself. + if !c.isSupersetOf(c) { + t.Errorf("Ranges should be supersets of themselves; %s indicated it was not", c) } - - // if a == false { - // for _, m := range msgs { - // t.Errorf("%s", m) - // } - // } } - v, err := NewVersion("1.2.3") - if err != nil { - t.Errorf("err: %s", err) + pairs := []struct{ l, r rangeConstraint }{ + { + // ensures lte is handled correctly (min side) + l: rc[0], + r: rc[1], + }, + { + // ensures nil on min side works well + l: rc[0], + r: rc[2], + }, + { + // ensures nil on max side works well + l: rc[0], + r: rc[3], + }, + { + // ensures nils on both sides work well + l: rc[2], + r: rc[3], + }, + { + // ensures gte is handled correctly (max side) + l: rc[2], + r: rc[4], + }, } - c, err := NewConstraint("!= 1.2.5, ^2, <= 1.1.x") - if err != nil { - t.Errorf("err: %s", err) + for _, p := range pairs { + if p.l.isSupersetOf(p.r) { + t.Errorf("%s is not a superset of %s", p.l, p.r) + } + if p.r.isSupersetOf(p.l) { + t.Errorf("%s is not a superset of %s", p.r, p.l) + } } - _, msgs := c.Validate(v) - if len(msgs) != 2 { - t.Error("Invalid number of validations found") + rc[1].max.minor = 0 + + if !rc[0].isSupersetOf(rc[1]) { + t.Errorf("%s is a superset of %s", rc[0], rc[1]) } - e := msgs[0].Error() - if e != "1.2.3 does not have same major version as 2" { - t.Error("Did not get expected message: 1.2.3 does not have same major version as 2") + rc[1].includeMax = true + if rc[1].isSupersetOf(rc[0]) { + t.Errorf("%s is not a superset of %s", rc[1], rc[0]) } - e = msgs[1].Error() - if e != "1.2.3 is greater than 1.1.x" { - t.Error("Did not get expected message: 1.2.3 is greater than 1.1.x") + rc[0].includeMin = false + if !rc[1].isSupersetOf(rc[0]) { + t.Errorf("%s is a superset of %s", rc[1], rc[0]) } - tests2 := []struct { - constraint, version, msg string - }{ - {"= 2.0", "1.2.3", "1.2.3 is not equal to 2.0"}, - {"!=4.1", "4.1.0", "4.1.0 is equal to 4.1"}, - {"!=4.x", "4.1.0", "4.1.0 is equal to 4.x"}, - {"!=4.2.x", "4.2.3", "4.2.3 is equal to 4.2.x"}, - {">1.1", "1.1.0", "1.1.0 is less than or equal to 1.1"}, - {"<1.1", "1.1.0", "1.1.0 is greater than or equal to 1.1"}, - {"<1.1", "1.1.1", "1.1.1 is greater than or equal to 1.1"}, - {"<1.x", "2.1.1", "2.1.1 is greater than or equal to 1.x"}, - {"<1.1.x", "1.2.1", "1.2.1 is greater than or equal to 1.1.x"}, - {">=1.1", "0.0.9", "0.0.9 is less than 1.1"}, - {"<=2.x", "3.1.0", "3.1.0 is greater than 2.x"}, - {"<=1.1", "1.1.1", "1.1.1 is greater than 1.1"}, - {"<=1.1.x", "1.2.500", "1.2.500 is greater than 1.1.x"}, - {">1.1, <3", "4.3.2", "4.3.2 is greater than or equal to 3"}, - {">=1.1, <2, !=1.2.3", "1.2.3", "1.2.3 is equal to 1.2.3"}, - {">=1.1, <2, !=1.2.3 || > 3", "3.0.0", "3.0.0 is greater than or equal to 2"}, - {">=1.1, <2, !=1.2.3 || > 3", "1.2.3", "1.2.3 is equal to 1.2.3"}, - {"1.1 - 3", "4.3.2", "4.3.2 is greater than 3"}, - {"^1.1", "4.3.2", "4.3.2 does not have same major version as 1.1"}, - {"^2.x", "1.1.1", "1.1.1 does not have same major version as 2.x"}, - {"^1.x", "2.1.1", "2.1.1 does not have same major version as 1.x"}, - {"~1.x", "2.1.1", "2.1.1 does not have same major and minor version as 1.x"}, - {"~1.2.3", "1.2.2", "1.2.2 does not have same major and minor version as 1.2.3"}, - {"~1.2.3", "1.3.2", "1.3.2 does not have same major and minor version as 1.2.3"}, - {"~1.1", "1.2.3", "1.2.3 does not have same major and minor version as 1.1"}, - {"~1.3", "2.4.5", "2.4.5 does not have same major and minor version as 1.3"}, - } - - for _, tc := range tests2 { - c, err := NewConstraint(tc.constraint) - if err != nil { - t.Errorf("err: %s", err) - continue - } - - v, err := NewVersion(tc.version) - if err != nil { - t.Errorf("err: %s", err) - continue - } + // isSupersetOf ignores excludes, so even though this would make rc[1] not a + // superset of rc[0] anymore, it should still say it is. + rc[1].excl = []Version{ + newV(1, 5, 0), + } - _, msgs := c.Validate(v) - e := msgs[0].Error() - if e != tc.msg { - t.Errorf("Did not get expected message %q: %s", tc.msg, e) - } + if !rc[1].isSupersetOf(rc[0]) { + t.Errorf("%s is still a superset of %s, because isSupersetOf is supposed to ignore excluded versions", rc[1], rc[0]) } } diff --git a/vendor/github.com/Masterminds/semver/error.go b/vendor/github.com/Masterminds/semver/error.go new file mode 100644 index 00000000..9eb33b39 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/error.go @@ -0,0 +1,83 @@ +package semver + +import ( + "bytes" + "fmt" +) + +var rangeErrs = [...]string{ + "%s is less than the minimum of %s", + "%s is less than or equal to the minimum of %s", + "%s is greater than the maximum of %s", + "%s is greater than or equal to the maximum of %s", + "%s is specifically disallowed in %s", + "%s has prerelease data, so is omitted by the range %s", +} + +const ( + rerrLT = iota + rerrLTE + rerrGT + rerrGTE + rerrNE + rerrPre +) + +// MatchFailure is an interface for failures to find a Constraint match. +type MatchFailure interface { + error + + // Pair returns the version and constraint that did not match prompting + // the error. + Pair() (v Version, c Constraint) +} + +// RangeMatchFailure occurs when a version is not within a constraint range. +type RangeMatchFailure struct { + v Version + rc rangeConstraint + typ int8 +} + +func (rce RangeMatchFailure) Error() string { + return fmt.Sprintf(rangeErrs[rce.typ], rce.v, rce.rc) +} + +// Pair returns the version and constraint that did not match. Part of the +// MatchFailure interface. +func (rce RangeMatchFailure) Pair() (v Version, r Constraint) { + return rce.v, rce.rc +} + +// VersionMatchFailure occurs when two versions do not match each other. +type VersionMatchFailure struct { + v, other Version +} + +func (vce VersionMatchFailure) Error() string { + return fmt.Sprintf("%s is not equal to %s", vce.v, vce.other) +} + +// Pair returns the two versions that did not match. Part of the +// MatchFailure interface. +func (vce VersionMatchFailure) Pair() (v Version, r Constraint) { + return vce.v, vce.other +} + +// MultiMatchFailure errors occur when there are multiple constraints a version +// is being checked against and there are failures. +type MultiMatchFailure []MatchFailure + +func (mmf MultiMatchFailure) Error() string { + var buf bytes.Buffer + + for k, e := range mmf { + if k < len(mmf)-1 { + fmt.Fprintf(&buf, "%s\n", e) + } else { + fmt.Fprintf(&buf, e.Error()) + } + } + + return buf.String() +} diff --git a/vendor/github.com/Masterminds/semver/magic.go b/vendor/github.com/Masterminds/semver/magic.go new file mode 100644 index 00000000..7eee64f1 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/magic.go @@ -0,0 +1,107 @@ +package semver + +import "errors" + +var errNone = errors.New("The 'None' constraint admits no versions.") + +// Any is a constraint that is satisfied by any valid semantic version. +type any struct{} + +// Any creates a constraint that will match any version. +func Any() Constraint { + return any{} +} + +func (any) String() string { + return "*" +} + +func (any) ImpliedCaretString() string { + return "*" +} + +// Matches checks that a version satisfies the constraint. As all versions +// satisfy Any, this always returns nil. +func (any) Matches(v Version) error { + return nil +} + +// Intersect computes the intersection between two constraints. +// +// As Any is the set of all possible versions, any intersection with that +// infinite set will necessarily be the entirety of the second set. Thus, this +// simply returns the passed constraint. +func (any) Intersect(c Constraint) Constraint { + return c +} + +// MatchesAny indicates whether there exists any version that can satisfy both +// this constraint, and the passed constraint. As all versions +// satisfy Any, this is always true - unless none is passed. +func (any) MatchesAny(c Constraint) bool { + if _, ok := c.(none); ok { + return false + } + return true +} + +func (any) Union(c Constraint) Constraint { + return Any() +} + +func (any) _private() {} + +// None is an unsatisfiable constraint - it represents the empty set. +type none struct{} + +// None creates a constraint that matches no versions (the empty set). +func None() Constraint { + return none{} +} + +func (none) String() string { + return "" +} + +func (none) ImpliedCaretString() string { + return "" +} + +// Matches checks that a version satisfies the constraint. As no version can +// satisfy None, this always fails (returns an error). +func (none) Matches(v Version) error { + return errNone +} + +// Intersect computes the intersection between two constraints. +// +// None is the empty set of versions, and any intersection with the empty set is +// necessarily the empty set. Thus, this always returns None. +func (none) Intersect(Constraint) Constraint { + return None() +} + +func (none) Union(c Constraint) Constraint { + return c +} + +// MatchesAny indicates whether there exists any version that can satisfy the +// constraint. As no versions satisfy None, this is always false. +func (none) MatchesAny(c Constraint) bool { + return false +} + +func (none) _private() {} + +// IsNone indicates if a constraint will match no versions - that is, the +// constraint represents the empty set. +func IsNone(c Constraint) bool { + _, ok := c.(none) + return ok +} + +// IsAny indicates if a constraint will match any and all versions. +func IsAny(c Constraint) bool { + _, ok := c.(any) + return ok +} diff --git a/vendor/github.com/Masterminds/semver/parse.go b/vendor/github.com/Masterminds/semver/parse.go new file mode 100644 index 00000000..d6afa6c9 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/parse.go @@ -0,0 +1,237 @@ +package semver + +import ( + "errors" + "fmt" + "strings" +) + +func rewriteRange(i string) string { + m := constraintRangeRegex.FindAllStringSubmatch(i, -1) + if m == nil { + return i + } + o := i + for _, v := range m { + t := fmt.Sprintf(">= %s, <= %s", v[1], v[11]) + o = strings.Replace(o, v[0], t, 1) + } + + return o +} + +func parseConstraint(c string, cbd bool) (Constraint, error) { + m := constraintRegex.FindStringSubmatch(c) + if m == nil { + return nil, fmt.Errorf("Malformed constraint: %s", c) + } + + // Handle the full wildcard case first - easy! + if isX(m[3]) { + return any{}, nil + } + + ver := m[2] + var wildPatch, wildMinor bool + if isX(strings.TrimPrefix(m[4], ".")) { + wildPatch = true + wildMinor = true + ver = fmt.Sprintf("%s.0.0%s", m[3], m[6]) + } else if isX(strings.TrimPrefix(m[5], ".")) { + wildPatch = true + ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6]) + } + + v, err := NewVersion(ver) + if err != nil { + // The constraintRegex should catch any regex parsing errors. So, + // we should never get here. + return nil, errors.New("constraint Parser Error") + } + + // We never want to keep the "original" data in a constraint, and keeping it + // around can disrupt simple equality comparisons. So, strip it out. + v.original = "" + + // If caret-by-default flag is on and there's no operator, convert the + // operator to a caret. + if cbd && m[1] == "" { + m[1] = "^" + } + + switch m[1] { + case "^": + // Caret always expands to a range + return expandCaret(v), nil + case "~": + // Tilde always expands to a range + return expandTilde(v, wildMinor), nil + case "!=": + // Not equals expands to a range if no element isX(); otherwise expands + // to a union of ranges + return expandNeq(v, wildMinor, wildPatch), nil + case "", "=": + if wildPatch || wildMinor { + // Equalling a wildcard has the same behavior as expanding tilde + return expandTilde(v, wildMinor), nil + } + return v, nil + case ">": + return expandGreater(v, wildMinor, wildPatch, false), nil + case ">=", "=>": + return expandGreater(v, wildMinor, wildPatch, true), nil + case "<": + return expandLess(v, wildMinor, wildPatch, false), nil + case "<=", "=<": + return expandLess(v, wildMinor, wildPatch, true), nil + default: + // Shouldn't be possible to get here, unless the regex is allowing + // predicate we don't know about... + return nil, fmt.Errorf("Unrecognized predicate %q", m[1]) + } +} + +func expandCaret(v Version) Constraint { + var maxv Version + // Caret behaves like tilde below 1.0.0 + if v.major == 0 { + maxv.minor = v.minor + 1 + } else { + maxv.major = v.major + 1 + } + + return rangeConstraint{ + min: v, + max: maxv, + includeMin: true, + includeMax: false, + } +} + +func expandTilde(v Version, wildMinor bool) Constraint { + if wildMinor { + // When minor is wild on a tilde, behavior is same as caret + return expandCaret(v) + } + + maxv := Version{ + major: v.major, + minor: v.minor + 1, + patch: 0, + } + + return rangeConstraint{ + min: v, + max: maxv, + includeMin: true, + includeMax: false, + } +} + +// expandNeq expands a "not-equals" constraint. +// +// If the constraint has any wildcards, it will expand into a unionConstraint +// (which is how we represent a disjoint set). If there are no wildcards, it +// will expand to a rangeConstraint with no min or max, but having the one +// exception. +func expandNeq(v Version, wildMinor, wildPatch bool) Constraint { + if !(wildMinor || wildPatch) { + return rangeConstraint{ + min: Version{special: zeroVersion}, + max: Version{special: infiniteVersion}, + excl: []Version{v}, + } + } + + // Create the low range with no min, and the max as the floor admitted by + // the wildcard + lr := rangeConstraint{ + min: Version{special: zeroVersion}, + max: v, + includeMax: false, + } + + // The high range uses the derived version (bumped depending on where the + // wildcards were) as the min, and is inclusive + minv := Version{ + major: v.major, + minor: v.minor, + patch: v.patch, + } + + if wildMinor { + minv.major++ + } else { + minv.minor++ + } + + hr := rangeConstraint{ + min: minv, + max: Version{special: infiniteVersion}, + includeMin: true, + } + + return Union(lr, hr) +} + +func expandGreater(v Version, wildMinor, wildPatch, eq bool) Constraint { + if (wildMinor || wildPatch) && !eq { + // wildcards negate the meaning of prerelease and other info + v = Version{ + major: v.major, + minor: v.minor, + patch: v.patch, + } + + // Not equal but with wildcards is the weird case - we have to bump up + // the next version AND make it equal + if wildMinor { + v.major++ + } else { + v.minor++ + } + return rangeConstraint{ + min: v, + max: Version{special: infiniteVersion}, + includeMin: true, + } + } + + return rangeConstraint{ + min: v, + max: Version{special: infiniteVersion}, + includeMin: eq, + } +} + +func expandLess(v Version, wildMinor, wildPatch, eq bool) Constraint { + if eq && (wildMinor || wildPatch) { + // wildcards negate the meaning of prerelease and other info + v = Version{ + major: v.major, + minor: v.minor, + patch: v.patch, + } + if wildMinor { + v.major++ + } else if wildPatch { + v.minor++ + } + return rangeConstraint{ + min: Version{special: zeroVersion}, + max: v, + includeMax: false, + } + } + + return rangeConstraint{ + min: Version{special: zeroVersion}, + max: v, + includeMax: eq, + } +} + +func isX(x string) bool { + l := strings.ToLower(x) + return l == "x" || l == "*" +} diff --git a/vendor/github.com/Masterminds/semver/range.go b/vendor/github.com/Masterminds/semver/range.go new file mode 100644 index 00000000..bcfdfcf9 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/range.go @@ -0,0 +1,519 @@ +package semver + +import ( + "fmt" + "sort" + "strings" +) + +type rangeConstraint struct { + min, max Version + includeMin, includeMax bool + excl []Version +} + +func (rc rangeConstraint) Matches(v Version) error { + var fail bool + ispre := v.Prerelease() != "" + + rce := RangeMatchFailure{ + v: v, + rc: rc, + } + + if !rc.minIsZero() { + cmp := rc.min.Compare(v) + if rc.includeMin { + rce.typ = rerrLT + fail = cmp == 1 + } else { + rce.typ = rerrLTE + fail = cmp != -1 + } + + if fail { + return rce + } + } + + if !rc.maxIsInf() { + cmp := rc.max.Compare(v) + if rc.includeMax { + rce.typ = rerrGT + fail = cmp == -1 + } else { + rce.typ = rerrGTE + fail = cmp != 1 + + } + + if fail { + return rce + } + } + + for _, excl := range rc.excl { + if excl.Equal(v) { + rce.typ = rerrNE + return rce + } + } + + // If the incoming version has prerelease info, it's usually a match failure + // - unless all the numeric parts are equal between the incoming and the + // minimum. + if !fail && ispre && !numPartsEq(rc.min, v) { + rce.typ = rerrPre + return rce + } + + return nil +} + +func (rc rangeConstraint) dup() rangeConstraint { + // Only need to do anything if there are some excludes + if len(rc.excl) == 0 { + return rc + } + + var excl []Version + excl = make([]Version, len(rc.excl)) + copy(excl, rc.excl) + + return rangeConstraint{ + min: rc.min, + max: rc.max, + includeMin: rc.includeMin, + includeMax: rc.includeMax, + excl: excl, + } +} + +func (rc rangeConstraint) minIsZero() bool { + return rc.min.special == zeroVersion +} + +func (rc rangeConstraint) maxIsInf() bool { + return rc.max.special == infiniteVersion +} + +func (rc rangeConstraint) Intersect(c Constraint) Constraint { + switch oc := c.(type) { + case any: + return rc + case none: + return None() + case unionConstraint: + return oc.Intersect(rc) + case Version: + if err := rc.Matches(oc); err != nil { + return None() + } + return c + case rangeConstraint: + nr := rangeConstraint{ + min: rc.min, + max: rc.max, + includeMin: rc.includeMin, + includeMax: rc.includeMax, + } + + if !oc.minIsZero() { + if nr.minIsZero() || nr.min.LessThan(oc.min) { + nr.min = oc.min + nr.includeMin = oc.includeMin + } else if oc.min.Equal(nr.min) && !oc.includeMin { + // intersection means we must follow the least inclusive + nr.includeMin = false + } + } + + if !oc.maxIsInf() { + if nr.maxIsInf() || nr.max.GreaterThan(oc.max) { + nr.max = oc.max + nr.includeMax = oc.includeMax + } else if oc.max.Equal(nr.max) && !oc.includeMax { + // intersection means we must follow the least inclusive + nr.includeMax = false + } + } + + // Ensure any applicable excls from oc are included in nc + for _, e := range append(rc.excl, oc.excl...) { + if nr.Matches(e) == nil { + nr.excl = append(nr.excl, e) + } + } + + if nr.minIsZero() || nr.maxIsInf() { + return nr + } + + if nr.min.Equal(nr.max) { + // min and max are equal. if range is inclusive, return that + // version; otherwise, none + if nr.includeMin && nr.includeMax { + return nr.min + } + return None() + } + + if nr.min.GreaterThan(nr.max) { + // min is greater than max - not possible, so we return none + return None() + } + + // range now fully validated, return what we have + return nr + + default: + panic("unknown type") + } +} + +func (rc rangeConstraint) Union(c Constraint) Constraint { + switch oc := c.(type) { + case any: + return Any() + case none: + return rc + case unionConstraint: + return Union(rc, oc) + case Version: + if err := rc.Matches(oc); err == nil { + return rc + } else if len(rc.excl) > 0 { // TODO (re)checking like this is wasteful + // ensure we don't have an excl-specific mismatch; if we do, remove + // it and return that + for k, e := range rc.excl { + if e.Equal(oc) { + excl := make([]Version, len(rc.excl)-1) + + if k == len(rc.excl)-1 { + copy(excl, rc.excl[:k]) + } else { + copy(excl, append(rc.excl[:k], rc.excl[k+1:]...)) + } + + return rangeConstraint{ + min: rc.min, + max: rc.max, + includeMin: rc.includeMin, + includeMax: rc.includeMax, + excl: excl, + } + } + } + } + + if oc.LessThan(rc.min) { + return unionConstraint{oc, rc.dup()} + } + if oc.Equal(rc.min) { + ret := rc.dup() + ret.includeMin = true + return ret + } + if oc.Equal(rc.max) { + ret := rc.dup() + ret.includeMax = true + return ret + } + // Only possibility left is gt + return unionConstraint{rc.dup(), oc} + case rangeConstraint: + if (rc.minIsZero() && oc.maxIsInf()) || (rc.maxIsInf() && oc.minIsZero()) { + rcl, ocl := len(rc.excl), len(oc.excl) + // Quick check for open case + if rcl == 0 && ocl == 0 { + return Any() + } + + // This is inefficient, but it's such an absurdly corner case... + if len(dedupeExcls(rc.excl, oc.excl)) == rcl+ocl { + // If deduped excludes are the same length as the individual + // excludes, then they have no overlapping elements, so the + // union knocks out the excludes and we're back to Any. + return Any() + } + + // There's at least some dupes, which are all we need to include + nc := rangeConstraint{ + min: Version{special: zeroVersion}, + max: Version{special: infiniteVersion}, + } + for _, e1 := range rc.excl { + for _, e2 := range oc.excl { + if e1.Equal(e2) { + nc.excl = append(nc.excl, e1) + } + } + } + + return nc + } else if areAdjacent(rc, oc) { + // Receiver adjoins the input from below + nc := rc.dup() + + nc.max = oc.max + nc.includeMax = oc.includeMax + nc.excl = append(nc.excl, oc.excl...) + + return nc + } else if areAdjacent(oc, rc) { + // Input adjoins the receiver from below + nc := oc.dup() + + nc.max = rc.max + nc.includeMax = rc.includeMax + nc.excl = append(nc.excl, rc.excl...) + + return nc + + } else if rc.MatchesAny(oc) { + // Receiver and input overlap; form a new range accordingly. + nc := rangeConstraint{ + min: Version{special: zeroVersion}, + max: Version{special: infiniteVersion}, + } + + // For efficiency, we simultaneously determine if either of the + // ranges are supersets of the other, while also selecting the min + // and max of the new range + var info uint8 + + const ( + lminlt uint8 = 1 << iota // left (rc) min less than right + rminlt // right (oc) min less than left + lmaxgt // left max greater than right + rmaxgt // right max greater than left + lsupr = lminlt | lmaxgt // left is superset of right + rsupl = rminlt | rmaxgt // right is superset of left + ) + + // Pick the min + if !rc.minIsZero() { + if oc.minIsZero() || rc.min.GreaterThan(oc.min) || (rc.min.Equal(oc.min) && !rc.includeMin && oc.includeMin) { + info |= rminlt + nc.min = oc.min + nc.includeMin = oc.includeMin + } else { + info |= lminlt + nc.min = rc.min + nc.includeMin = rc.includeMin + } + } else if !oc.minIsZero() { + info |= lminlt + nc.min = rc.min + nc.includeMin = rc.includeMin + } + + // Pick the max + if !rc.maxIsInf() { + if oc.maxIsInf() || rc.max.LessThan(oc.max) || (rc.max.Equal(oc.max) && !rc.includeMax && oc.includeMax) { + info |= rmaxgt + nc.max = oc.max + nc.includeMax = oc.includeMax + } else { + info |= lmaxgt + nc.max = rc.max + nc.includeMax = rc.includeMax + } + } else if oc.maxIsInf() { + info |= lmaxgt + nc.max = rc.max + nc.includeMax = rc.includeMax + } + + // Reincorporate any excluded versions + if info&lsupr != lsupr { + // rc is not superset of oc, so must walk oc.excl + for _, e := range oc.excl { + if rc.Matches(e) != nil { + nc.excl = append(nc.excl, e) + } + } + } + + if info&rsupl != rsupl { + // oc is not superset of rc, so must walk rc.excl + for _, e := range rc.excl { + if oc.Matches(e) != nil { + nc.excl = append(nc.excl, e) + } + } + } + + return nc + } else { + // Don't call Union() here b/c it would duplicate work + uc := constraintList{rc, oc} + sort.Sort(uc) + return unionConstraint(uc) + } + } + + panic("unknown type") +} + +// isSupersetOf computes whether the receiver rangeConstraint is a superset of +// the passed rangeConstraint. +// +// This is NOT a strict superset comparison, so identical ranges will both +// report being supersets of each other. +// +// Note also that this does *not* compare excluded versions - it only compares +// range endpoints. +func (rc rangeConstraint) isSupersetOf(rc2 rangeConstraint) bool { + if !rc.minIsZero() { + if rc2.minIsZero() || rc.min.GreaterThan(rc2.min) || (rc.min.Equal(rc2.min) && !rc.includeMin && rc2.includeMin) { + return false + } + } + + if !rc.maxIsInf() { + if rc2.maxIsInf() || rc.max.LessThan(rc2.max) || (rc.max.Equal(rc2.max) && !rc.includeMax && rc2.includeMax) { + return false + } + } + + return true +} + +func (rc rangeConstraint) String() string { + return rc.toString(false) +} + +func (rc rangeConstraint) ImpliedCaretString() string { + return rc.toString(true) +} + +func (rc rangeConstraint) toString(impliedCaret bool) string { + var pieces []string + + // We need to trigger the standard verbose handling from various points, so + // wrap it in a function. + noshort := func() { + if !rc.minIsZero() { + if rc.includeMin { + pieces = append(pieces, fmt.Sprintf(">=%s", rc.min)) + } else { + pieces = append(pieces, fmt.Sprintf(">%s", rc.min)) + } + } + + if !rc.maxIsInf() { + if rc.includeMax { + pieces = append(pieces, fmt.Sprintf("<=%s", rc.max)) + } else { + pieces = append(pieces, fmt.Sprintf("<%s", rc.max)) + } + } + } + + // Handle the possibility that we might be able to express the range + // with a caret or tilde, as we prefer those forms. + var caretstr string + if impliedCaret { + caretstr = "%s" + } else { + caretstr = "^%s" + } + + switch { + case rc.minIsZero() && rc.maxIsInf(): + // This if is internal because it's useful to know for the other cases + // that we don't have special values at both bounds + if len(rc.excl) == 0 { + // Shouldn't be possible to reach from anything that can be done + // outside the package, but best to cover it and be safe + return "*" + } + case rc.minIsZero(), rc.includeMax, !rc.includeMin: + // tilde and caret could never apply here + noshort() + case !rc.maxIsInf() && rc.max.Minor() == 0 && rc.max.Patch() == 0: // basic caret + if rc.min.Major() == rc.max.Major()-1 && rc.min.Major() != 0 { + pieces = append(pieces, fmt.Sprintf(caretstr, rc.min)) + } else { + // range is too wide for caret, need standard operators + noshort() + } + case !rc.maxIsInf() && rc.max.Major() != 0 && rc.max.Patch() == 0: // basic tilde + if rc.min.Minor() == rc.max.Minor()-1 && rc.min.Major() == rc.max.Major() { + pieces = append(pieces, fmt.Sprintf("~%s", rc.min)) + } else { + // range is too wide for tilde, need standard operators + noshort() + } + case !rc.maxIsInf() && rc.max.Major() == 0 && rc.max.Patch() == 0 && rc.max.Minor() != 0: + // below 1.0.0, tilde is meaningless but caret is shifted to the + // right (so it basically behaves the same as tilde does above 1.0.0) + if rc.min.Minor() == rc.max.Minor()-1 { + pieces = append(pieces, fmt.Sprintf(caretstr, rc.min)) + } else { + noshort() + } + default: + noshort() + } + + for _, e := range rc.excl { + pieces = append(pieces, fmt.Sprintf("!=%s", e)) + } + + return strings.Join(pieces, ", ") +} + +// areAdjacent tests two constraints to determine if they are adjacent, +// but non-overlapping. +// +// If either constraint is not a range, returns false. We still allow it at the +// type level, however, to make the check convenient elsewhere. +// +// Assumes the first range is less than the second; it is incumbent on the +// caller to arrange the inputs appropriately. +func areAdjacent(c1, c2 Constraint) bool { + var rc1, rc2 rangeConstraint + var ok bool + if rc1, ok = c1.(rangeConstraint); !ok { + return false + } + if rc2, ok = c2.(rangeConstraint); !ok { + return false + } + + if !rc1.max.Equal(rc2.min) { + return false + } + + return (rc1.includeMax && !rc2.includeMin) || + (!rc1.includeMax && rc2.includeMin) +} + +func (rc rangeConstraint) MatchesAny(c Constraint) bool { + if _, ok := rc.Intersect(c).(none); ok { + return false + } + return true +} + +func dedupeExcls(ex1, ex2 []Version) []Version { + // TODO stupid inefficient, but these are really only ever going to be + // small, so not worth optimizing right now + var ret []Version +oloop: + for _, e1 := range ex1 { + for _, e2 := range ex2 { + if e1.Equal(e2) { + continue oloop + } + } + ret = append(ret, e1) + } + + return append(ret, ex2...) +} + +func (rangeConstraint) _private() {} +func (rangeConstraint) _real() {} diff --git a/vendor/github.com/Masterminds/semver/set_ops_test.go b/vendor/github.com/Masterminds/semver/set_ops_test.go new file mode 100644 index 00000000..c08f2761 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/set_ops_test.go @@ -0,0 +1,932 @@ +package semver + +import "testing" + +func TestIntersection(t *testing.T) { + var actual Constraint + rc1 := rangeConstraint{ + min: newV(1, 0, 0), + max: newV(2, 0, 0), + } + + if actual = Intersection(); !IsNone(actual) { + t.Errorf("Intersection of nothing should always produce None; got %q", actual) + } + + if actual = Intersection(rc1); !constraintEq(actual, rc1) { + t.Errorf("Intersection of one item should always return that item; got %q", actual) + } + + if actual = Intersection(rc1, None()); !IsNone(actual) { + t.Errorf("Intersection of anything with None should always produce None; got %q", actual) + } + + if actual = Intersection(rc1, Any()); !constraintEq(actual, rc1) { + t.Errorf("Intersection of anything with Any should return self; got %q", actual) + } + + v1 := newV(1, 5, 0) + if actual = Intersection(rc1, v1); !constraintEq(actual, v1) { + t.Errorf("Got constraint %q, but expected %q", actual, v1) + } + + rc2 := rangeConstraint{ + min: newV(1, 2, 0), + max: newV(2, 2, 0), + } + result := rangeConstraint{ + min: newV(1, 2, 0), + max: newV(2, 0, 0), + } + + if actual = Intersection(rc1, rc2); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + + u1 := unionConstraint{ + rangeConstraint{ + min: newV(1, 2, 0), + max: newV(3, 0, 0), + }, + newV(3, 1, 0), + } + + if actual = Intersection(u1, rc1); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + if actual = Intersection(rc1, newV(2, 0, 5), u1); !IsNone(actual) { + t.Errorf("First two are disjoint, should have gotten None but got %q", actual) + } +} + +func TestRangeIntersection(t *testing.T) { + var actual Constraint + // Test magic cases + rc1 := rangeConstraint{ + min: newV(1, 0, 0), + max: newV(2, 0, 0), + } + if actual = rc1.Intersect(Any()); !constraintEq(actual, rc1) { + t.Errorf("Intersection of anything with Any should return self; got %q", actual) + } + if actual = rc1.Intersect(None()); !IsNone(actual) { + t.Errorf("Intersection of anything with None should always produce None; got %q", actual) + } + + // Test single version cases + + // single v, in range + v1 := newV(1, 5, 0) + + if actual = rc1.Intersect(v1); !constraintEq(actual, v1) { + t.Errorf("Intersection of version with matching range should return the version; got %q", actual) + } + + // now exclude just that version + rc1.excl = []Version{v1} + if actual = rc1.Intersect(v1); !IsNone(actual) { + t.Errorf("Intersection of version with range having specific exclude for that version should produce None; got %q", actual) + } + + // and, of course, none if the version is out of range + v2 := newV(0, 5, 0) + if actual = rc1.Intersect(v2); !IsNone(actual) { + t.Errorf("Intersection of version with non-matching range should produce None; got %q", actual) + } + + // Test basic overlap case + rc1 = rangeConstraint{ + min: newV(1, 0, 0), + max: newV(2, 0, 0), + } + rc2 := rangeConstraint{ + min: newV(1, 2, 0), + max: newV(2, 2, 0), + } + result := rangeConstraint{ + min: newV(1, 2, 0), + max: newV(2, 0, 0), + } + + if actual = rc1.Intersect(rc2); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + if actual = rc2.Intersect(rc1); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + + // And with includes + rc1.includeMin = true + rc1.includeMax = true + rc2.includeMin = true + rc2.includeMax = true + result.includeMin = true + result.includeMax = true + + if actual = rc1.Intersect(rc2); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + if actual = rc2.Intersect(rc1); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + + // Overlaps with nils + rc1 = rangeConstraint{ + min: newV(1, 0, 0), + max: Version{special: infiniteVersion}, + } + rc2 = rangeConstraint{ + min: Version{special: zeroVersion}, + max: newV(2, 2, 0), + } + result = rangeConstraint{ + min: newV(1, 0, 0), + max: newV(2, 2, 0), + } + + if actual = rc1.Intersect(rc2); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + if actual = rc2.Intersect(rc1); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + + // And with includes + rc1.includeMin = true + rc2.includeMax = true + result.includeMin = true + result.includeMax = true + + if actual = rc1.Intersect(rc2); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + if actual = rc2.Intersect(rc1); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + + // Test superset overlap case + rc1 = rangeConstraint{ + min: newV(1, 5, 0), + max: newV(2, 0, 0), + } + rc2 = rangeConstraint{ + min: newV(1, 0, 0), + max: newV(3, 0, 0), + } + result = rangeConstraint{ + min: newV(1, 5, 0), + max: newV(2, 0, 0), + } + + if actual = rc1.Intersect(rc2); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + if actual = rc2.Intersect(rc1); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + + // Make sure irrelevant includes don't leak in + rc2.includeMin = true + rc2.includeMax = true + + if actual = rc1.Intersect(rc2); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + if actual = rc2.Intersect(rc1); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + + // But relevant includes get used + rc1.includeMin = true + rc1.includeMax = true + result.includeMin = true + result.includeMax = true + + if actual = rc1.Intersect(rc2); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + if actual = rc2.Intersect(rc1); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + + // Test disjoint case + rc1 = rangeConstraint{ + min: newV(1, 5, 0), + max: newV(1, 6, 0), + } + rc2 = rangeConstraint{ + min: newV(2, 0, 0), + max: newV(3, 0, 0), + } + + if actual = rc1.Intersect(rc2); !constraintEq(actual, None()) { + t.Errorf("Got constraint %q, but expected %q", actual, None()) + } + if actual = rc2.Intersect(rc1); !constraintEq(actual, None()) { + t.Errorf("Got constraint %q, but expected %q", actual, None()) + } + + // Test disjoint at gt/lt boundary (non-adjacent) + rc1 = rangeConstraint{ + min: newV(1, 5, 0), + max: newV(2, 0, 0), + } + rc2 = rangeConstraint{ + min: newV(2, 0, 0), + max: newV(3, 0, 0), + } + + if actual = rc1.Intersect(rc2); !constraintEq(actual, None()) { + t.Errorf("Got constraint %q, but expected %q", actual, None()) + } + if actual = rc2.Intersect(rc1); !constraintEq(actual, None()) { + t.Errorf("Got constraint %q, but expected %q", actual, None()) + } + + // Now, just have them touch at a single version + rc1.includeMax = true + rc2.includeMin = true + + vresult := newV(2, 0, 0) + if actual = rc1.Intersect(rc2); !constraintEq(actual, vresult) { + t.Errorf("Got constraint %q, but expected %q", actual, vresult) + } + if actual = rc2.Intersect(rc1); !constraintEq(actual, vresult) { + t.Errorf("Got constraint %q, but expected %q", actual, vresult) + } + + // Test excludes in intersection range + rc1 = rangeConstraint{ + min: newV(1, 5, 0), + max: newV(2, 0, 0), + excl: []Version{ + newV(1, 6, 0), + }, + } + rc2 = rangeConstraint{ + min: newV(1, 0, 0), + max: newV(3, 0, 0), + } + + if actual = rc1.Intersect(rc2); !constraintEq(actual, rc1) { + t.Errorf("Got constraint %q, but expected %q", actual, rc1) + } + if actual = rc2.Intersect(rc1); !constraintEq(actual, rc1) { + t.Errorf("Got constraint %q, but expected %q", actual, rc1) + } + + // Test excludes not in intersection range + rc1 = rangeConstraint{ + min: newV(1, 5, 0), + max: newV(2, 0, 0), + } + rc2 = rangeConstraint{ + min: newV(1, 0, 0), + max: newV(3, 0, 0), + excl: []Version{ + newV(1, 1, 0), + }, + } + + if actual = rc1.Intersect(rc2); !constraintEq(actual, rc1) { + t.Errorf("Got constraint %q, but expected %q", actual, rc1) + } + if actual = rc2.Intersect(rc1); !constraintEq(actual, rc1) { + t.Errorf("Got constraint %q, but expected %q", actual, rc1) + } + + // Test min, and greater min + rc1 = rangeConstraint{ + min: newV(1, 0, 0), + max: Version{special: infiniteVersion}, + } + rc2 = rangeConstraint{ + min: newV(1, 5, 0), + max: Version{special: infiniteVersion}, + includeMin: true, + } + + if actual = rc1.Intersect(rc2); !constraintEq(actual, rc2) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + if actual = rc2.Intersect(rc1); !constraintEq(actual, rc2) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + + // Test max, and lesser max + rc1 = rangeConstraint{ + max: newV(1, 0, 0), + } + rc2 = rangeConstraint{ + max: newV(1, 5, 0), + } + result = rangeConstraint{ + max: newV(1, 0, 0), + } + + if actual = rc1.Intersect(rc2); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + if actual = rc2.Intersect(rc1); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + + // Ensure pure excludes come through as they should + rc1 = rangeConstraint{ + min: Version{special: zeroVersion}, + max: Version{special: infiniteVersion}, + excl: []Version{ + newV(1, 6, 0), + }, + } + + rc2 = rangeConstraint{ + min: Version{special: zeroVersion}, + max: Version{special: infiniteVersion}, + excl: []Version{ + newV(1, 6, 0), + newV(1, 7, 0), + }, + } + + if actual = Any().Intersect(rc1); !constraintEq(actual, rc1) { + t.Errorf("Got constraint %q, but expected %q", actual, rc1) + } + if actual = rc1.Intersect(Any()); !constraintEq(actual, rc1) { + t.Errorf("Got constraint %q, but expected %q", actual, rc1) + } + if actual = rc1.Intersect(rc2); !constraintEq(actual, rc2) { + t.Errorf("Got constraint %q, but expected %q", actual, rc2) + } + + // TODO test the pre-release special range stuff +} + +func TestRangeUnion(t *testing.T) { + var actual Constraint + // Test magic cases + rc1 := rangeConstraint{ + min: newV(1, 0, 0), + max: newV(2, 0, 0), + } + if actual = rc1.Union(Any()); !IsAny(actual) { + t.Errorf("Union of anything with Any should always produce Any; got %q", actual) + } + if actual = rc1.Union(None()); !constraintEq(actual, rc1) { + t.Errorf("Union of anything with None should return self; got %q", actual) + } + + // Test single version cases + + // single v, in range + v1 := newV(1, 5, 0) + + if actual = rc1.Union(v1); !constraintEq(actual, rc1) { + t.Errorf("Union of version with matching range should return the range; got %q", actual) + } + + // now exclude just that version + rc2 := rc1.dup() + rc2.excl = []Version{v1} + if actual = rc2.Union(v1); !constraintEq(actual, rc1) { + t.Errorf("Union of version with range having specific exclude for that version should produce the range without that exclude; got %q", actual) + } + + // and a union if the version is not within the range + v2 := newV(0, 5, 0) + uresult := unionConstraint{v2, rc1} + if actual = rc1.Union(v2); !constraintEq(actual, uresult) { + t.Errorf("Union of version with non-matching range should produce a unionConstraint with those two; got %q", actual) + } + + // union with version at the min should ensure "oreq" + v2 = newV(1, 0, 0) + rc3 := rc1 + rc3.includeMin = true + + if actual = rc1.Union(v2); !constraintEq(actual, rc3) { + t.Errorf("Union of range with version at min end should add includeMin (%q), but got %q", rc3, actual) + } + if actual = v2.Union(rc1); !constraintEq(actual, rc3) { + t.Errorf("Union of range with version at min end should add includeMin (%q), but got %q", rc3, actual) + } + + // same at max end + v2 = newV(2, 0, 0) + rc3.includeMin = false + rc3.includeMax = true + + if actual = rc1.Union(v2); !constraintEq(actual, rc3) { + t.Errorf("Union of range with version at max end should add includeMax (%q), but got %q", rc3, actual) + } + if actual = v2.Union(rc1); !constraintEq(actual, rc3) { + t.Errorf("Union of range with version at max end should add includeMax (%q), but got %q", rc3, actual) + } + + // Test basic overlap case + rc1 = rangeConstraint{ + min: newV(1, 0, 0), + max: newV(2, 0, 0), + } + rc2 = rangeConstraint{ + min: newV(1, 2, 0), + max: newV(2, 2, 0), + } + result := rangeConstraint{ + min: newV(1, 0, 0), + max: newV(2, 2, 0), + } + + if actual = rc1.Union(rc2); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + if actual = rc2.Union(rc1); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + + // And with includes + rc1.includeMin = true + rc1.includeMax = true + rc2.includeMin = true + rc2.includeMax = true + result.includeMin = true + result.includeMax = true + + if actual = rc1.Union(rc2); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + if actual = rc2.Union(rc1); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + + // Overlaps with nils + rc1 = rangeConstraint{ + min: newV(1, 0, 0), + max: Version{special: infiniteVersion}, + } + rc2 = rangeConstraint{ + min: Version{special: zeroVersion}, + max: newV(2, 2, 0), + } + + if actual = rc1.Union(rc2); !constraintEq(actual, Any()) { + t.Errorf("Got constraint %q, but expected %q", actual, Any()) + } + if actual = rc2.Union(rc1); !constraintEq(actual, Any()) { + t.Errorf("Got constraint %q, but expected %q", actual, Any()) + } + + // Just one nil in overlap + rc1.max = newV(2, 0, 0) + result = rangeConstraint{ + min: Version{special: zeroVersion}, + max: newV(2, 2, 0), + } + + if actual = rc1.Union(rc2); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + if actual = rc2.Union(rc1); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + + rc1.max = Version{special: infiniteVersion} + rc2.min = newV(1, 5, 0) + result = rangeConstraint{ + min: newV(1, 0, 0), + max: Version{special: infiniteVersion}, + } + + if actual = rc1.Union(rc2); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + if actual = rc2.Union(rc1); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + + // Test superset overlap case + rc1 = rangeConstraint{ + min: newV(1, 5, 0), + max: newV(2, 0, 0), + } + rc2 = rangeConstraint{ + min: newV(1, 0, 0), + max: newV(3, 0, 0), + } + + if actual = rc1.Union(rc2); !constraintEq(actual, rc2) { + t.Errorf("Got constraint %q, but expected %q", actual, rc2) + } + if actual = rc2.Union(rc1); !constraintEq(actual, rc2) { + t.Errorf("Got constraint %q, but expected %q", actual, rc2) + } + + // Test disjoint case + rc1 = rangeConstraint{ + min: newV(1, 5, 0), + max: newV(1, 6, 0), + } + rc2 = rangeConstraint{ + min: newV(2, 0, 0), + max: newV(3, 0, 0), + } + uresult = unionConstraint{rc1, rc2} + + if actual = rc1.Union(rc2); !constraintEq(actual, uresult) { + t.Errorf("Got constraint %q, but expected %q", actual, uresult) + } + if actual = rc2.Union(rc1); !constraintEq(actual, uresult) { + t.Errorf("Got constraint %q, but expected %q", actual, uresult) + } + + // Test disjoint at gt/lt boundary (non-adjacent) + rc1 = rangeConstraint{ + min: newV(1, 5, 0), + max: newV(2, 0, 0), + } + rc2 = rangeConstraint{ + min: newV(2, 0, 0), + max: newV(3, 0, 0), + } + uresult = unionConstraint{rc1, rc2} + + if actual = rc1.Union(rc2); !constraintEq(actual, uresult) { + t.Errorf("Got constraint %q, but expected %q", actual, uresult) + } + if actual = rc2.Union(rc1); !constraintEq(actual, uresult) { + t.Errorf("Got constraint %q, but expected %q", actual, uresult) + } + + // Now, just have them touch at a single version + rc1.includeMax = true + rc2.includeMin = true + result = rangeConstraint{ + min: newV(1, 5, 0), + max: newV(3, 0, 0), + } + + if actual = rc1.Union(rc2); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + if actual = rc2.Union(rc1); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + + // And top-adjacent at that version + rc2.includeMin = false + if actual = rc1.Union(rc2); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + if actual = rc2.Union(rc1); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + // And bottom-adjacent at that version + rc1.includeMax = false + rc2.includeMin = true + if actual = rc1.Union(rc2); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + if actual = rc2.Union(rc1); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + + // Test excludes in overlapping range + rc1 = rangeConstraint{ + min: newV(1, 5, 0), + max: newV(2, 0, 0), + excl: []Version{ + newV(1, 6, 0), + }, + } + rc2 = rangeConstraint{ + min: newV(1, 0, 0), + max: newV(3, 0, 0), + } + + if actual = rc1.Union(rc2); !constraintEq(actual, rc2) { + t.Errorf("Got constraint %q, but expected %q", actual, rc2) + } + if actual = rc2.Union(rc1); !constraintEq(actual, rc2) { + t.Errorf("Got constraint %q, but expected %q", actual, rc2) + } + + // Test excludes not in non-overlapping range + rc1 = rangeConstraint{ + min: newV(1, 5, 0), + max: newV(2, 0, 0), + } + rc2 = rangeConstraint{ + min: newV(1, 0, 0), + max: newV(3, 0, 0), + excl: []Version{ + newV(1, 1, 0), + }, + } + + if actual = rc1.Union(rc2); !constraintEq(actual, rc2) { + t.Errorf("Got constraint %q, but expected %q", actual, rc2) + } + if actual = rc2.Union(rc1); !constraintEq(actual, rc2) { + t.Errorf("Got constraint %q, but expected %q", actual, rc2) + } + + // Ensure pure excludes come through as they should + rc1 = rangeConstraint{ + min: Version{special: zeroVersion}, + max: Version{special: infiniteVersion}, + excl: []Version{ + newV(1, 6, 0), + }, + } + + rc2 = rangeConstraint{ + min: Version{special: zeroVersion}, + max: Version{special: infiniteVersion}, + excl: []Version{ + newV(1, 6, 0), + newV(1, 7, 0), + }, + } + + if actual = rc1.Union(rc2); !constraintEq(actual, rc1) { + t.Errorf("Got constraint %q, but expected %q", actual, rc1) + } + if actual = rc2.Union(rc1); !constraintEq(actual, rc1) { + t.Errorf("Got constraint %q, but expected %q", actual, rc1) + } + + rc1 = rangeConstraint{ + min: Version{special: zeroVersion}, + max: Version{special: infiniteVersion}, + excl: []Version{ + newV(1, 5, 0), + }, + } + + if actual = rc1.Union(rc2); !constraintEq(actual, Any()) { + t.Errorf("Got constraint %q, but expected %q", actual, Any()) + } + if actual = rc2.Union(rc1); !constraintEq(actual, Any()) { + t.Errorf("Got constraint %q, but expected %q", actual, Any()) + } + + // TODO test the pre-release special range stuff +} + +func TestUnionIntersection(t *testing.T) { + var actual Constraint + // magic first + u1 := unionConstraint{ + newV(1, 1, 0), + newV(1, 2, 0), + newV(1, 3, 0), + } + if actual = u1.Intersect(Any()); !constraintEq(actual, u1) { + t.Errorf("Intersection of anything with Any should return self; got %s", actual) + } + if actual = u1.Intersect(None()); !IsNone(actual) { + t.Errorf("Intersection of anything with None should always produce None; got %s", actual) + } + if u1.MatchesAny(None()) { + t.Errorf("Can't match any when intersected with None") + } + + // intersect of unions with single versions + v1 := newV(1, 1, 0) + if actual = u1.Intersect(v1); !constraintEq(actual, v1) { + t.Errorf("Got constraint %q, but expected %q", actual, v1) + } + if actual = v1.Intersect(u1); !constraintEq(actual, v1) { + t.Errorf("Got constraint %q, but expected %q", actual, v1) + } + + // intersect of range with union of versions + u1 = unionConstraint{ + newV(1, 1, 0), + newV(1, 2, 0), + newV(1, 3, 0), + } + rc1 := rangeConstraint{ + min: newV(1, 0, 0), + max: newV(2, 0, 0), + } + + if actual = u1.Intersect(rc1); !constraintEq(actual, u1) { + t.Errorf("Got constraint %q, but expected %q", actual, u1) + } + if actual = rc1.Intersect(u1); !constraintEq(actual, u1) { + t.Errorf("Got constraint %q, but expected %q", actual, u1) + } + + u2 := unionConstraint{ + newV(1, 1, 0), + newV(1, 2, 0), + } + + if actual = u1.Intersect(u2); !constraintEq(actual, u2) { + t.Errorf("Got constraint %q, but expected %q", actual, u2) + } + + // Overlapping sub/supersets + rc1 = rangeConstraint{ + min: newV(1, 5, 0), + max: newV(1, 6, 0), + } + rc2 := rangeConstraint{ + min: newV(2, 0, 0), + max: newV(3, 0, 0), + } + rc3 = rangeConstraint{ + min: newV(1, 0, 0), + max: newV(2, 0, 0), + } + rc4 := rangeConstraint{ + min: newV(2, 5, 0), + max: newV(2, 6, 0), + } + u1 = unionConstraint{rc1, rc2} + u2 = unionConstraint{rc3, rc4} + ur := unionConstraint{rc1, rc4} + + if actual = u1.Intersect(u2); !constraintEq(actual, ur) { + t.Errorf("Got constraint %q, but expected %q", actual, ur) + } + if actual = u2.Intersect(u1); !constraintEq(actual, ur) { + t.Errorf("Got constraint %q, but expected %q", actual, ur) + } + + // Ensure excludes carry as they should + rc1.excl = []Version{newV(1, 5, 5)} + u1 = unionConstraint{rc1, rc2} + ur = unionConstraint{rc1, rc4} + + if actual = u1.Intersect(u2); !constraintEq(actual, ur) { + t.Errorf("Got constraint %q, but expected %q", actual, ur) + } + if actual = u2.Intersect(u1); !constraintEq(actual, ur) { + t.Errorf("Got constraint %q, but expected %q", actual, ur) + } +} + +func TestUnionUnion(t *testing.T) { + var actual Constraint + // magic first + u1 := unionConstraint{ + newV(1, 1, 0), + newV(1, 2, 0), + newV(1, 3, 0), + } + if actual = u1.Union(Any()); !IsAny(actual) { + t.Errorf("Union of anything with Any should always return Any; got %s", actual) + } + if actual = u1.Union(None()); !constraintEq(actual, u1) { + t.Errorf("Union of anything with None should always return self; got %s", actual) + } + + // union of uc with single versions + // already present + v1 := newV(1, 2, 0) + if actual = u1.Union(v1); !constraintEq(actual, u1) { + t.Errorf("Got constraint %q, but expected %q", actual, u1) + } + if actual = v1.Union(u1); !constraintEq(actual, u1) { + t.Errorf("Got constraint %q, but expected %q", actual, u1) + } + + // not present + v2 := newV(1, 4, 0) + ur := append(u1, v2) + if actual = u1.Union(v2); !constraintEq(actual, ur) { + t.Errorf("Got constraint %q, but expected %q", actual, ur) + } + if actual = v2.Union(u1); !constraintEq(actual, ur) { + t.Errorf("Got constraint %q, but expected %q", actual, ur) + } + + // union of uc with uc, all versions + u2 := unionConstraint{ + newV(1, 3, 0), + newV(1, 4, 0), + newV(1, 5, 0), + } + ur = unionConstraint{ + newV(1, 1, 0), + newV(1, 2, 0), + newV(1, 3, 0), + newV(1, 4, 0), + newV(1, 5, 0), + } + + if actual = u1.Union(u2); !constraintEq(actual, ur) { + t.Errorf("Got constraint %q, but expected %q", actual, ur) + } + if actual = u2.Union(u1); !constraintEq(actual, ur) { + t.Errorf("Got constraint %q, but expected %q", actual, ur) + } + + // union that should compress versions into range + rc1 := rangeConstraint{ + min: newV(1, 0, 0), + max: newV(2, 0, 0), + } + + if actual = u1.Union(rc1); !constraintEq(actual, rc1) { + t.Errorf("Got constraint %q, but expected %q", actual, rc1) + } + if actual = rc1.Union(u1); !constraintEq(actual, rc1) { + t.Errorf("Got constraint %q, but expected %q", actual, rc1) + } + + rc1.max = newV(1, 4, 5) + u3 := append(u2, newV(1, 7, 0)) + ur = unionConstraint{ + rc1, + newV(1, 5, 0), + newV(1, 7, 0), + } + + if actual = u3.Union(rc1); !constraintEq(actual, ur) { + t.Errorf("Got constraint %q, but expected %q", actual, ur) + } + if actual = rc1.Union(u3); !constraintEq(actual, ur) { + t.Errorf("Got constraint %q, but expected %q", actual, ur) + } +} + +// Most version stuff got tested by range and/or union b/c most tests were +// repeated bidirectionally (set operations are commutative; testing in pairs +// helps us catch any situation where we fail to maintain that invariant) +func TestVersionSetOps(t *testing.T) { + var actual Constraint + + v1 := newV(1, 0, 0) + + if actual = v1.Intersect(v1); !constraintEq(actual, v1) { + t.Errorf("Version intersected with itself should be itself, got %q", actual) + } + if !v1.MatchesAny(v1) { + t.Errorf("MatchesAny should work with a version against itself") + } + + v2 := newV(2, 0, 0) + if actual = v1.Intersect(v2); !IsNone(actual) { + t.Errorf("Versions should only intersect with themselves, got %q", actual) + } + if v1.MatchesAny(v2) { + t.Errorf("MatchesAny should not work when combined with anything other than itself") + } + + result := unionConstraint{v1, v2} + + if actual = v1.Union(v1); !constraintEq(actual, v1) { + t.Errorf("Version union with itself should return self, got %q", actual) + } + + if actual = v1.Union(v2); !constraintEq(actual, result) { + t.Errorf("Got constraint %q, but expected %q", actual, result) + } + if actual = v1.Union(v2); !constraintEq(actual, result) { + // Duplicate just to make sure ordering works right + t.Errorf("Got constraint %q, but expected %q", actual, result) + } +} + +func TestAreAdjacent(t *testing.T) { + rc1 := rangeConstraint{ + min: newV(1, 0, 0), + max: newV(2, 0, 0), + } + rc2 := rangeConstraint{ + min: newV(1, 2, 0), + max: newV(2, 2, 0), + } + + if areAdjacent(rc1, rc2) { + t.Errorf("Ranges overlap, should not indicate as adjacent") + } + + rc2 = rangeConstraint{ + min: newV(2, 0, 0), + } + + if areAdjacent(rc1, rc2) { + t.Errorf("Ranges are non-overlapping and non-adjacent, but reported as adjacent") + } + + rc2.includeMin = true + + if !areAdjacent(rc1, rc2) { + t.Errorf("Ranges are non-overlapping and adjacent, but reported as non-adjacent") + } + + rc1.includeMax = true + + if areAdjacent(rc1, rc2) { + t.Errorf("Ranges are overlapping at a single version, but reported as adjacent") + } + + rc2.includeMin = false + if !areAdjacent(rc1, rc2) { + t.Errorf("Ranges are non-overlapping and adjacent, but reported as non-adjacent") + } +} diff --git a/vendor/github.com/Masterminds/semver/union.go b/vendor/github.com/Masterminds/semver/union.go new file mode 100644 index 00000000..bc794f88 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/union.go @@ -0,0 +1,152 @@ +package semver + +import "strings" + +type unionConstraint []realConstraint + +func (uc unionConstraint) Matches(v Version) error { + var uce MultiMatchFailure + for _, c := range uc { + err := c.Matches(v) + if err == nil { + return nil + } + uce = append(uce, err.(MatchFailure)) + + } + + return uce +} + +func (uc unionConstraint) Intersect(c2 Constraint) Constraint { + var other []realConstraint + + switch tc2 := c2.(type) { + case none: + return None() + case any: + return uc + case Version: + return c2 + case rangeConstraint: + other = append(other, tc2) + case unionConstraint: + other = c2.(unionConstraint) + default: + panic("unknown type") + } + + var newc []Constraint + // TODO there's a smarter way to do this than NxN, but...worth it? + for _, c := range uc { + for _, oc := range other { + i := c.Intersect(oc) + if !IsNone(i) { + newc = append(newc, i) + } + } + } + + return Union(newc...) +} + +func (uc unionConstraint) MatchesAny(c Constraint) bool { + for _, ic := range uc { + if ic.MatchesAny(c) { + return true + } + } + return false +} + +func (uc unionConstraint) Union(c Constraint) Constraint { + return Union(uc, c) +} + +func (uc unionConstraint) String() string { + var pieces []string + for _, c := range uc { + pieces = append(pieces, c.String()) + } + + return strings.Join(pieces, " || ") +} + +func (uc unionConstraint) ImpliedCaretString() string { + var pieces []string + for _, c := range uc { + pieces = append(pieces, c.ImpliedCaretString()) + } + + return strings.Join(pieces, " || ") +} + +func (unionConstraint) _private() {} + +type constraintList []realConstraint + +func (cl constraintList) Len() int { + return len(cl) +} + +func (cl constraintList) Swap(i, j int) { + cl[i], cl[j] = cl[j], cl[i] +} + +func (cl constraintList) Less(i, j int) bool { + ic, jc := cl[i], cl[j] + + switch tic := ic.(type) { + case Version: + switch tjc := jc.(type) { + case Version: + return tic.LessThan(tjc) + case rangeConstraint: + if tjc.minIsZero() { + return false + } + + // Because we don't assume stable sort, always put versions ahead of + // range mins if they're equal and includeMin is on + if tjc.includeMin && tic.Equal(tjc.min) { + return false + } + return tic.LessThan(tjc.min) + } + case rangeConstraint: + switch tjc := jc.(type) { + case Version: + if tic.minIsZero() { + return true + } + + // Because we don't assume stable sort, always put versions ahead of + // range mins if they're equal and includeMin is on + if tic.includeMin && tjc.Equal(tic.min) { + return false + } + return tic.min.LessThan(tjc) + case rangeConstraint: + if tic.minIsZero() { + return true + } + if tjc.minIsZero() { + return false + } + return tic.min.LessThan(tjc.min) + } + } + + panic("unreachable") +} + +func (cl *constraintList) Push(x interface{}) { + *cl = append(*cl, x.(realConstraint)) +} + +func (cl *constraintList) Pop() interface{} { + o := *cl + c := o[len(o)-1] + *cl = o[:len(o)-1] + return c +} diff --git a/vendor/github.com/Masterminds/semver/version.go b/vendor/github.com/Masterminds/semver/version.go index 28b26ac2..449b7540 100644 --- a/vendor/github.com/Masterminds/semver/version.go +++ b/vendor/github.com/Masterminds/semver/version.go @@ -7,72 +7,118 @@ import ( "regexp" "strconv" "strings" + "sync" ) // The compiled version of the regex created at init() is cached here so it // only needs to be created once. var versionRegex *regexp.Regexp -var validPrereleaseRegex *regexp.Regexp var ( // ErrInvalidSemVer is returned a version is found to be invalid when // being parsed. ErrInvalidSemVer = errors.New("Invalid Semantic Version") +) - // ErrInvalidMetadata is returned when the metadata is an invalid format - ErrInvalidMetadata = errors.New("Invalid Metadata string") +// Error type; lets us defer string interpolation +type badVersionSegment struct { + e error +} - // ErrInvalidPrerelease is returned when the pre-release is an invalid format - ErrInvalidPrerelease = errors.New("Invalid Prerelease string") -) +func (b badVersionSegment) Error() string { + return fmt.Sprintf("Error parsing version segment: %s", b.e) +} + +// CacheVersions controls whether or not parsed constraints are cached. Defaults +// to true. +var CacheVersions = true +var versionCache = make(map[string]vcache) +var versionCacheLock sync.RWMutex -// SemVerRegex is the regular expression used to parse a semantic version. +type vcache struct { + v Version + err error +} + +// SemVerRegex id the regular expression used to parse a semantic version. const SemVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` + `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` -// ValidPrerelease is the regular expression which validates -// both prerelease and metadata values. -const ValidPrerelease string = `^([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*)` +type specialVersion uint8 + +const ( + notSpecial specialVersion = iota + zeroVersion + infiniteVersion +) // Version represents a single semantic version. type Version struct { - major, minor, patch int64 + major, minor, patch uint64 pre string metadata string original string + special specialVersion } func init() { versionRegex = regexp.MustCompile("^" + SemVerRegex + "$") - validPrereleaseRegex = regexp.MustCompile(ValidPrerelease) } // NewVersion parses a given version and returns an instance of Version or // an error if unable to parse the version. -func NewVersion(v string) (*Version, error) { +func NewVersion(v string) (Version, error) { + if CacheVersions { + versionCacheLock.RLock() + if sv, exists := versionCache[v]; exists { + versionCacheLock.RUnlock() + return sv.v, sv.err + } + versionCacheLock.RUnlock() + } + m := versionRegex.FindStringSubmatch(v) if m == nil { - return nil, ErrInvalidSemVer + if CacheVersions { + versionCacheLock.Lock() + versionCache[v] = vcache{err: ErrInvalidSemVer} + versionCacheLock.Unlock() + } + return Version{}, ErrInvalidSemVer } - sv := &Version{ + sv := Version{ metadata: m[8], pre: m[5], original: v, } - var temp int64 - temp, err := strconv.ParseInt(m[1], 10, 32) + var temp uint64 + temp, err := strconv.ParseUint(m[1], 10, 32) if err != nil { - return nil, fmt.Errorf("Error parsing version segment: %s", err) + bvs := badVersionSegment{e: err} + if CacheVersions { + versionCacheLock.Lock() + versionCache[v] = vcache{err: bvs} + versionCacheLock.Unlock() + } + + return Version{}, bvs } sv.major = temp if m[2] != "" { - temp, err = strconv.ParseInt(strings.TrimPrefix(m[2], "."), 10, 32) + temp, err = strconv.ParseUint(strings.TrimPrefix(m[2], "."), 10, 32) if err != nil { - return nil, fmt.Errorf("Error parsing version segment: %s", err) + bvs := badVersionSegment{e: err} + if CacheVersions { + versionCacheLock.Lock() + versionCache[v] = vcache{err: bvs} + versionCacheLock.Unlock() + } + + return Version{}, bvs } sv.minor = temp } else { @@ -80,25 +126,29 @@ func NewVersion(v string) (*Version, error) { } if m[3] != "" { - temp, err = strconv.ParseInt(strings.TrimPrefix(m[3], "."), 10, 32) + temp, err = strconv.ParseUint(strings.TrimPrefix(m[3], "."), 10, 32) if err != nil { - return nil, fmt.Errorf("Error parsing version segment: %s", err) + bvs := badVersionSegment{e: err} + if CacheVersions { + versionCacheLock.Lock() + versionCache[v] = vcache{err: bvs} + versionCacheLock.Unlock() + } + + return Version{}, bvs } sv.patch = temp } else { sv.patch = 0 } - return sv, nil -} - -// MustParse parses a given version and panics on error. -func MustParse(v string) *Version { - sv, err := NewVersion(v) - if err != nil { - panic(err) + if CacheVersions { + versionCacheLock.Lock() + versionCache[v] = vcache{v: sv} + versionCacheLock.Unlock() } - return sv + + return sv, nil } // String converts a Version object to a string. @@ -106,10 +156,28 @@ func MustParse(v string) *Version { // See the Original() method to retrieve the original value. Semantic Versions // don't contain a leading v per the spec. Instead it's optional on // impelementation. -func (v *Version) String() string { +func (v Version) String() string { + return v.toString(false) +} + +// ImpliedCaretString follows the same rules as String(), but in accordance with +// the Constraint interface will always print a leading "=", as all Versions, +// when acting as a Constraint, act as exact matches. +func (v Version) ImpliedCaretString() string { + return v.toString(true) +} + +func (v Version) toString(ic bool) string { var buf bytes.Buffer - fmt.Fprintf(&buf, "%d.%d.%d", v.major, v.minor, v.patch) + var base string + if ic { + base = "=%d.%d.%d" + } else { + base = "%d.%d.%d" + } + + fmt.Fprintf(&buf, base, v.major, v.minor, v.patch) if v.pre != "" { fmt.Fprintf(&buf, "-%s", v.pre) } @@ -121,138 +189,49 @@ func (v *Version) String() string { } // Original returns the original value passed in to be parsed. -func (v *Version) Original() string { +func (v Version) Original() string { return v.original } // Major returns the major version. -func (v *Version) Major() int64 { +func (v *Version) Major() uint64 { return v.major } // Minor returns the minor version. -func (v *Version) Minor() int64 { +func (v *Version) Minor() uint64 { return v.minor } // Patch returns the patch version. -func (v *Version) Patch() int64 { +func (v *Version) Patch() uint64 { return v.patch } // Prerelease returns the pre-release version. -func (v *Version) Prerelease() string { +func (v Version) Prerelease() string { return v.pre } // Metadata returns the metadata on the version. -func (v *Version) Metadata() string { +func (v Version) Metadata() string { return v.metadata } -// originalVPrefix returns the original 'v' prefix if any. -func (v *Version) originalVPrefix() string { - - // Note, only lowercase v is supported as a prefix by the parser. - if v.original != "" && v.original[:1] == "v" { - return v.original[:1] - } - return "" -} - -// IncPatch produces the next patch version. -// If the current version does not have prerelease/metadata information, -// it unsets metadata and prerelease values, increments patch number. -// If the current version has any of prerelease or metadata information, -// it unsets both values and keeps curent patch value -func (v Version) IncPatch() Version { - vNext := v - // according to http://semver.org/#spec-item-9 - // Pre-release versions have a lower precedence than the associated normal version. - // according to http://semver.org/#spec-item-10 - // Build metadata SHOULD be ignored when determining version precedence. - if v.pre != "" { - vNext.metadata = "" - vNext.pre = "" - } else { - vNext.metadata = "" - vNext.pre = "" - vNext.patch = v.patch + 1 - } - vNext.original = v.originalVPrefix() + "" + vNext.String() - return vNext -} - -// IncMinor produces the next minor version. -// Sets patch to 0. -// Increments minor number. -// Unsets metadata. -// Unsets prerelease status. -func (v Version) IncMinor() Version { - vNext := v - vNext.metadata = "" - vNext.pre = "" - vNext.patch = 0 - vNext.minor = v.minor + 1 - vNext.original = v.originalVPrefix() + "" + vNext.String() - return vNext -} - -// IncMajor produces the next major version. -// Sets patch to 0. -// Sets minor to 0. -// Increments major number. -// Unsets metadata. -// Unsets prerelease status. -func (v Version) IncMajor() Version { - vNext := v - vNext.metadata = "" - vNext.pre = "" - vNext.patch = 0 - vNext.minor = 0 - vNext.major = v.major + 1 - vNext.original = v.originalVPrefix() + "" + vNext.String() - return vNext -} - -// SetPrerelease defines the prerelease value. -// Value must not include the required 'hypen' prefix. -func (v Version) SetPrerelease(prerelease string) (Version, error) { - vNext := v - if len(prerelease) > 0 && !validPrereleaseRegex.MatchString(prerelease) { - return vNext, ErrInvalidPrerelease - } - vNext.pre = prerelease - vNext.original = v.originalVPrefix() + "" + vNext.String() - return vNext, nil -} - -// SetMetadata defines metadata value. -// Value must not include the required 'plus' prefix. -func (v Version) SetMetadata(metadata string) (Version, error) { - vNext := v - if len(metadata) > 0 && !validPrereleaseRegex.MatchString(metadata) { - return vNext, ErrInvalidMetadata - } - vNext.metadata = metadata - vNext.original = v.originalVPrefix() + "" + vNext.String() - return vNext, nil -} - // LessThan tests if one version is less than another one. -func (v *Version) LessThan(o *Version) bool { +func (v Version) LessThan(o Version) bool { return v.Compare(o) < 0 } // GreaterThan tests if one version is greater than another one. -func (v *Version) GreaterThan(o *Version) bool { +func (v Version) GreaterThan(o Version) bool { return v.Compare(o) > 0 } // Equal tests if two versions are equal to each other. // Note, versions can be equal with different metadata since metadata // is not considered part of the comparable version. -func (v *Version) Equal(o *Version) bool { +func (v Version) Equal(o Version) bool { return v.Compare(o) == 0 } @@ -261,7 +240,27 @@ func (v *Version) Equal(o *Version) bool { // // Versions are compared by X.Y.Z. Build metadata is ignored. Prerelease is // lower than the version without a prerelease. -func (v *Version) Compare(o *Version) int { +func (v Version) Compare(o Version) int { + // The special field supercedes all the other information. If it's not + // equal, we can skip out early + if v.special != o.special { + switch v.special { + case zeroVersion: + return -1 + case notSpecial: + if o.special == zeroVersion { + return 1 + } + return -1 + case infiniteVersion: + return 1 + } + } else if v.special != notSpecial { + // If special fields are equal and not notSpecial, then they're + // necessarily equal + return 0 + } + // Compare the major, minor, and patch version for differences. If a // difference is found return the comparison. if d := compareSegment(v.Major(), o.Major()); d != 0 { @@ -291,7 +290,58 @@ func (v *Version) Compare(o *Version) int { return comparePrerelease(ps, po) } -func compareSegment(v, o int64) int { +// Matches checks that a verstions match. If they do not, +// an error is returned indcating the problem; if it does, the error is nil. +// This is part of the Constraint interface. +func (v Version) Matches(v2 Version) error { + if v.Equal(v2) { + return nil + } + + return VersionMatchFailure{v: v, other: v2} +} + +// MatchesAny checks if an instance of a version matches a constraint which can +// include anything matching the Constraint interface. +func (v Version) MatchesAny(c Constraint) bool { + if v2, ok := c.(Version); ok { + return v.Equal(v2) + } + + // The other implementations all have specific handling for this; fall + // back on theirs. + return c.MatchesAny(v) +} + +// Intersect computes the intersection between the receiving Constraint and +// passed Constraint, and returns a new Constraint representing the result. +// This is part of the Constraint interface. +func (v Version) Intersect(c Constraint) Constraint { + if v2, ok := c.(Version); ok { + if v.Equal(v2) { + return v + } + return none{} + } + + return c.Intersect(v) +} + +// Union computes the union between the receiving Constraint and the passed +// Constraint, and returns a new Constraint representing the result. +// This is part of the Constraint interface. +func (v Version) Union(c Constraint) Constraint { + if v2, ok := c.(Version); ok && v.Equal(v2) { + return v + } + + return Union(v, c) +} + +func (Version) _private() {} +func (Version) _real() {} + +func compareSegment(v, o uint64) int { if v < o { return -1 } @@ -321,7 +371,7 @@ func comparePrerelease(v, o string) int { // Iterate over each part of the prereleases to compare the differences. for i := 0; i < l; i++ { - // Since the lentgh of the parts can be different we need to create + // Since the length of the parts can be different we need to create // a placeholder. This is to avoid out of bounds issues. stemp := "" if i < slen { @@ -354,14 +404,14 @@ func comparePrePart(s, o string) int { // When s or o are empty we can use the other in an attempt to determine // the response. if o == "" { - _, n := strconv.ParseInt(s, 10, 64) + _, n := strconv.ParseUint(s, 10, 64) if n != nil { return -1 } return 1 } if s == "" { - _, n := strconv.ParseInt(o, 10, 64) + _, n := strconv.ParseUint(o, 10, 64) if n != nil { return 1 } @@ -373,3 +423,25 @@ func comparePrePart(s, o string) int { } return -1 } + +func numPartsEq(v1, v2 Version) bool { + if v1.special != v2.special { + return false + } + if v1.special != notSpecial { + // If special fields are equal and not notSpecial, then the versions are + // necessarily equal, so their numeric parts are too. + return true + } + + if v1.major != v2.major { + return false + } + if v1.minor != v2.minor { + return false + } + if v1.patch != v2.patch { + return false + } + return true +} diff --git a/vendor/github.com/Masterminds/semver/version_test.go b/vendor/github.com/Masterminds/semver/version_test.go index 0e02a6ac..1fae87f5 100644 --- a/vendor/github.com/Masterminds/semver/version_test.go +++ b/vendor/github.com/Masterminds/semver/version_test.go @@ -180,6 +180,33 @@ func TestCompare(t *testing.T) { ) } } + + // One-off tests for special version comparisons + zero := Version{special: zeroVersion} + inf := Version{special: infiniteVersion} + + if zero.Compare(inf) != -1 { + t.Error("Zero version should always be less than infinite version") + } + if zero.Compare(zero) != 0 { + t.Error("Zero version should equal itself") + } + if inf.Compare(zero) != 1 { + t.Error("Infinite version should always be greater than zero version") + } + if inf.Compare(inf) != 0 { + t.Error("Infinite version should equal itself") + } + + // Need to work vs. a normal version, too. + v := Version{} + + if zero.Compare(v) != -1 { + t.Error("Zero version should always be less than any normal version") + } + if inf.Compare(v) != 1 { + t.Error("Infinite version should always be greater than any normal version") + } } func TestLessThan(t *testing.T) { @@ -281,170 +308,3 @@ func TestEqual(t *testing.T) { } } } - -func TestInc(t *testing.T) { - tests := []struct { - v1 string - expected string - how string - expectedOriginal string - }{ - {"1.2.3", "1.2.4", "patch", "1.2.4"}, - {"v1.2.4", "1.2.5", "patch", "v1.2.5"}, - {"1.2.3", "1.3.0", "minor", "1.3.0"}, - {"v1.2.4", "1.3.0", "minor", "v1.3.0"}, - {"1.2.3", "2.0.0", "major", "2.0.0"}, - {"v1.2.4", "2.0.0", "major", "v2.0.0"}, - {"1.2.3+meta", "1.2.4", "patch", "1.2.4"}, - {"1.2.3-beta+meta", "1.2.3", "patch", "1.2.3"}, - {"v1.2.4-beta+meta", "1.2.4", "patch", "v1.2.4"}, - {"1.2.3-beta+meta", "1.3.0", "minor", "1.3.0"}, - {"v1.2.4-beta+meta", "1.3.0", "minor", "v1.3.0"}, - {"1.2.3-beta+meta", "2.0.0", "major", "2.0.0"}, - {"v1.2.4-beta+meta", "2.0.0", "major", "v2.0.0"}, - } - - for _, tc := range tests { - v1, err := NewVersion(tc.v1) - if err != nil { - t.Errorf("Error parsing version: %s", err) - } - - var v2 Version - switch tc.how { - case "patch": - v2 = v1.IncPatch() - case "minor": - v2 = v1.IncMinor() - case "major": - v2 = v1.IncMajor() - } - - a := v2.String() - e := tc.expected - if a != e { - t.Errorf( - "Inc %q failed. Expected %q got %q", - tc.how, e, a, - ) - } - - a = v2.Original() - e = tc.expectedOriginal - if a != e { - t.Errorf( - "Inc %q failed. Expected original %q got %q", - tc.how, e, a, - ) - } - } -} - -func TestSetPrerelease(t *testing.T) { - tests := []struct { - v1 string - prerelease string - expectedVersion string - expectedPrerelease string - expectedOriginal string - expectedErr error - }{ - {"1.2.3", "**", "1.2.3", "", "1.2.3", ErrInvalidPrerelease}, - {"1.2.3", "beta", "1.2.3-beta", "beta", "1.2.3-beta", nil}, - {"v1.2.4", "beta", "1.2.4-beta", "beta", "v1.2.4-beta", nil}, - } - - for _, tc := range tests { - v1, err := NewVersion(tc.v1) - if err != nil { - t.Errorf("Error parsing version: %s", err) - } - - v2, err := v1.SetPrerelease(tc.prerelease) - if err != tc.expectedErr { - t.Errorf("Expected to get err=%s, but got err=%s", tc.expectedErr, err) - } - - a := v2.Prerelease() - e := tc.expectedPrerelease - if a != e { - t.Errorf("Expected prerelease value=%q, but got %q", e, a) - } - - a = v2.String() - e = tc.expectedVersion - if a != e { - t.Errorf("Expected version string=%q, but got %q", e, a) - } - - a = v2.Original() - e = tc.expectedOriginal - if a != e { - t.Errorf("Expected version original=%q, but got %q", e, a) - } - } -} - -func TestSetMetadata(t *testing.T) { - tests := []struct { - v1 string - metadata string - expectedVersion string - expectedMetadata string - expectedOriginal string - expectedErr error - }{ - {"1.2.3", "**", "1.2.3", "", "1.2.3", ErrInvalidMetadata}, - {"1.2.3", "meta", "1.2.3+meta", "meta", "1.2.3+meta", nil}, - {"v1.2.4", "meta", "1.2.4+meta", "meta", "v1.2.4+meta", nil}, - } - - for _, tc := range tests { - v1, err := NewVersion(tc.v1) - if err != nil { - t.Errorf("Error parsing version: %s", err) - } - - v2, err := v1.SetMetadata(tc.metadata) - if err != tc.expectedErr { - t.Errorf("Expected to get err=%s, but got err=%s", tc.expectedErr, err) - } - - a := v2.Metadata() - e := tc.expectedMetadata - if a != e { - t.Errorf("Expected metadata value=%q, but got %q", e, a) - } - - a = v2.String() - e = tc.expectedVersion - if e != a { - t.Errorf("Expected version string=%q, but got %q", e, a) - } - - a = v2.Original() - e = tc.expectedOriginal - if a != e { - t.Errorf("Expected version original=%q, but got %q", e, a) - } - } -} - -func TestOriginalVPrefix(t *testing.T) { - tests := []struct { - version string - vprefix string - }{ - {"1.2.3", ""}, - {"v1.2.4", "v"}, - } - - for _, tc := range tests { - v1, _ := NewVersion(tc.version) - a := v1.originalVPrefix() - e := tc.vprefix - if a != e { - t.Errorf("Expected vprefix=%q, but got %q", e, a) - } - } -}