pax_global_header00006660000000000000000000000064137567654320014534gustar00rootroot0000000000000052 comment=7bb0c843b53d6ad21a3f619cb22c4b442bb3ef3e semver-3.1.1/000077500000000000000000000000001375676543200130375ustar00rootroot00000000000000semver-3.1.1/.github/000077500000000000000000000000001375676543200143775ustar00rootroot00000000000000semver-3.1.1/.github/workflows/000077500000000000000000000000001375676543200164345ustar00rootroot00000000000000semver-3.1.1/.github/workflows/test.yaml000066400000000000000000000014241375676543200203000ustar00rootroot00000000000000on: [push, pull_request] name: Tests jobs: test: strategy: matrix: go-version: [1.12.x, 1.13.x, 1.14.x, 1.15.x] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} steps: - name: Install Go uses: actions/setup-go@v1 with: go-version: ${{ matrix.go-version }} - name: Checkout code uses: actions/checkout@v1 - name: Install golangci-lint if: runner.os == 'Linux' run: curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1 - name: Lint if: runner.os == 'Linux' run: $(go env GOPATH)/bin/golangci-lint run - name: Test env: GO111MODULE: on run: go test -cover . semver-3.1.1/.gitignore000066400000000000000000000000061375676543200150230ustar00rootroot00000000000000_fuzz/semver-3.1.1/.golangci.yml000066400000000000000000000005211375676543200154210ustar00rootroot00000000000000run: deadline: 2m linters: disable-all: true enable: - deadcode - dupl - errcheck - gofmt - goimports - golint - gosimple - govet - ineffassign - misspell - nakedret - structcheck - unused - varcheck linters-settings: gofmt: simplify: true dupl: threshold: 400 semver-3.1.1/CHANGELOG.md000066400000000000000000000127471375676543200146630ustar00rootroot00000000000000# Changelog ## 3.1.1 (2020-11-23) ### Fixed - #158: Fixed issue with generated regex operation order that could cause problem ## 3.1.0 (2020-04-15) ### Added - #131: Add support for serializing/deserializing SQL (thanks @ryancurrah) ### Changed - #148: More accurate validation messages on constraints ## 3.0.3 (2019-12-13) ### Fixed - #141: Fixed issue with <= comparison ## 3.0.2 (2019-11-14) ### Fixed - #134: Fixed broken constraint checking with ^0.0 (thanks @krmichelos) ## 3.0.1 (2019-09-13) ### Fixed - #125: Fixes issue with module path for v3 ## 3.0.0 (2019-09-12) This is a major release of the semver package which includes API changes. The Go API is compatible with ^1. The Go API was not changed because many people are using `go get` without Go modules for their applications and API breaking changes cause errors which we have or would need to support. The changes in this release are the handling based on the data passed into the functions. These are described in the added and changed sections below. ### Added - StrictNewVersion function. This is similar to NewVersion but will return an error if the version passed in is not a strict semantic version. For example, 1.2.3 would pass but v1.2.3 or 1.2 would fail because they are not strictly speaking semantic versions. This function is faster, performs fewer operations, and uses fewer allocations than NewVersion. - Fuzzing has been performed on NewVersion, StrictNewVersion, and NewConstraint. The Makefile contains the operations used. For more information on you can start on Wikipedia at https://en.wikipedia.org/wiki/Fuzzing - Now using Go modules ### Changed - NewVersion has proper prerelease and metadata validation with error messages to signal an issue with either of them - ^ now operates using a similar set of rules to npm/js and Rust/Cargo. If the version is >=1 the ^ ranges works the same as v1. For major versions of 0 the rules have changed. The minor version is treated as the stable version unless a patch is specified and then it is equivalent to =. One difference from npm/js is that prereleases there are only to a specific version (e.g. 1.2.3). Prereleases here look over multiple versions and follow semantic version ordering rules. This pattern now follows along with the expected and requested handling of this packaged by numerous users. ## 1.5.0 (2019-09-11) ### Added - #103: Add basic fuzzing for `NewVersion()` (thanks @jesse-c) ### Changed - #82: Clarify wildcard meaning in range constraints and update tests for it (thanks @greysteil) - #83: Clarify caret operator range for pre-1.0.0 dependencies (thanks @greysteil) - #72: Adding docs comment pointing to vert for a cli - #71: Update the docs on pre-release comparator handling - #89: Test with new go versions (thanks @thedevsaddam) - #87: Added $ to ValidPrerelease for better validation (thanks @jeremycarroll) ### Fixed - #78: Fix unchecked error in example code (thanks @ravron) - #70: Fix the handling of pre-releases and the 0.0.0 release edge case - #97: Fixed copyright file for proper display on GitHub - #107: Fix handling prerelease when sorting alphanum and num - #109: Fixed where Validate sometimes returns wrong message on error ## 1.4.2 (2018-04-10) ### Changed - #72: Updated the docs to point to vert for a console appliaction - #71: Update the docs on pre-release comparator handling ### Fixed - #70: Fix the handling of pre-releases and the 0.0.0 release edge case ## 1.4.1 (2018-04-02) ### Fixed - Fixed #64: Fix pre-release precedence issue (thanks @uudashr) ## 1.4.0 (2017-10-04) ### Changed - #61: Update NewVersion to parse ints with a 64bit int size (thanks @zknill) ## 1.3.1 (2017-07-10) ### Fixed - Fixed #57: number comparisons in prerelease sometimes inaccurate ## 1.3.0 (2017-05-02) ### Added - #45: Added json (un)marshaling support (thanks @mh-cbon) - Stability marker. See https://masterminds.github.io/stability/ ### Fixed - #51: Fix handling of single digit tilde constraint (thanks @dgodd) ### Changed - #55: The godoc icon moved from png to svg ## 1.2.3 (2017-04-03) ### Fixed - #46: Fixed 0.x.x and 0.0.x in constraints being treated as * ## Release 1.2.2 (2016-12-13) ### 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) - Issue #2: Implemented validation to provide reasons a versions failed a constraint. ## Release 1.0.1 (2015-12-31) - Fixed #1: * constraint failing on valid versions. ## Release 1.0.0 (2015-10-20) - Initial release semver-3.1.1/LICENSE.txt000066400000000000000000000020661375676543200146660ustar00rootroot00000000000000Copyright (C) 2014-2019, Matt Butcher and Matt Farina Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. semver-3.1.1/Makefile000066400000000000000000000017141375676543200145020ustar00rootroot00000000000000GOPATH=$(shell go env GOPATH) GOLANGCI_LINT=$(GOPATH)/bin/golangci-lint GOFUZZBUILD = $(GOPATH)/bin/go-fuzz-build GOFUZZ = $(GOPATH)/bin/go-fuzz .PHONY: lint lint: $(GOLANGCI_LINT) @echo "==> Linting codebase" @$(GOLANGCI_LINT) run .PHONY: test test: @echo "==> Running tests" GO111MODULE=on go test -v .PHONY: test-cover test-cover: @echo "==> Running Tests with coverage" GO111MODULE=on go test -cover . .PHONY: fuzz fuzz: $(GOFUZZBUILD) $(GOFUZZ) @echo "==> Fuzz testing" $(GOFUZZBUILD) $(GOFUZZ) -workdir=_fuzz $(GOLANGCI_LINT): # Install golangci-lint. The configuration for it is in the .golangci.yml # file in the root of the repository echo ${GOPATH} curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.17.1 $(GOFUZZBUILD): cd / && go get -u github.com/dvyukov/go-fuzz/go-fuzz-build $(GOFUZZ): cd / && go get -u github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-depsemver-3.1.1/README.md000066400000000000000000000240321375676543200143170ustar00rootroot00000000000000# SemVer The `semver` package provides the ability to work with [Semantic Versions](http://semver.org) in Go. Specifically it provides the ability to: * Parse semantic versions * Sort semantic versions * Check if a semantic version fits within a set of constraints * Optionally work with a `v` prefix [![Stability: Active](https://masterminds.github.io/stability/active.svg)](https://masterminds.github.io/stability/active.html) [![](https://github.com/Masterminds/semver/workflows/Tests/badge.svg)](https://github.com/Masterminds/semver/actions) [![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/github.com/Masterminds/semver/v3) [![Go Report Card](https://goreportcard.com/badge/github.com/Masterminds/semver)](https://goreportcard.com/report/github.com/Masterminds/semver) If you are looking for a command line tool for version comparisons please see [vert](https://github.com/Masterminds/vert) which uses this library. ## Package Versions There are three major versions fo the `semver` package. * 3.x.x is the new stable and active version. This version is focused on constraint compatibility for range handling in other tools from other languages. It has a similar API to the v1 releases. The development of this version is on the master branch. The documentation for this version is below. * 2.x was developed primarily for [dep](https://github.com/golang/dep). There are no tagged releases and the development was performed by [@sdboyer](https://github.com/sdboyer). There are API breaking changes from v1. This version lives on the [2.x branch](https://github.com/Masterminds/semver/tree/2.x). * 1.x.x is the most widely used version with numerous tagged releases. This is the previous stable and is still maintained for bug fixes. The development, to fix bugs, occurs on the release-1 branch. You can read the documentation [here](https://github.com/Masterminds/semver/blob/release-1/README.md). ## Parsing Semantic Versions There are two functions that can parse semantic versions. The `StrictNewVersion` function only parses valid version 2 semantic versions as outlined in the specification. The `NewVersion` function attempts to coerce a version into a semantic version and parse it. For example, if there is a leading v or a version listed without all 3 parts (e.g. `v1.2`) it will attempt to coerce it into a valid semantic version (e.g., 1.2.0). In both cases a `Version` object is returned that can be sorted, compared, and used in constraints. When parsing a version an error is returned if there is an issue parsing the version. For example, v, err := semver.NewVersion("1.2.3-beta.1+build345") The version object has methods to get the parts of the version, compare it to other versions, convert the version back into a string, and get the original string. Getting the original string is useful if the semantic version was coerced into a valid form. ## Sorting Semantic Versions A set of versions can be sorted using the `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 { v, err := semver.NewVersion(r) if err != nil { t.Errorf("Error parsing version: %s", err) } vs[i] = v } sort.Sort(semver.Collection(vs)) ``` ## Checking Version Constraints There are two methods for comparing versions. One uses comparison methods on `Version` instances and the other uses `Constraints`. There are some important differences to notes between these two methods of comparison. 1. When two versions are compared using functions such as `Compare`, `LessThan`, and others it will follow the specification and always include prereleases within the comparison. It will provide an answer that is valid with the comparison section of the spec at https://semver.org/#spec-item-11 2. When constraint checking is used for checks or validation it will follow a different set of rules that are common for ranges with tools like npm/js and Rust/Cargo. This includes considering prereleases to be invalid if the ranges does not include one. If you want to have it include pre-releases a simple solution is to include `-0` in your range. 3. Constraint ranges can have some complex rules including the shorthand use of ~ and ^. For more details on those see the options below. There are differences between the two methods or checking versions because the comparison methods on `Version` follow the specification while comparison ranges are not part of the specification. Different packages and tools have taken it upon themselves to come up with range rules. This has resulted in differences. For example, npm/js and Cargo/Rust follow similar patterns while PHP has a different pattern for ^. The comparison features in this package follow the npm/js and Cargo/Rust lead because applications using it have followed similar patters with their versions. 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 parsable. } v, err := semver.NewVersion("1.3") if err != nil { // Handle version not being parsable. } // Check if the version meets the constraints. The a variable will be true. a := c.Check(v) ``` ### Basic Comparisons There are two elements to the comparisons. First, a comparison string is a list of space or comma separated AND comparisons. These are then separated by || (OR) comparisons. For example, `">= 1.2 < 3.0.0 || >= 4.2.3"` is looking for a comparison that's greater than or equal to 1.2 and less than 3.0.0 or is greater than or equal to 4.2.3. The basic comparisons are: * `=`: equal (aliased to no operator) * `!=`: not equal * `>`: greater than * `<`: less than * `>=`: greater than or equal to * `<=`: less than or equal to ### Working With Prerelease Versions Pre-releases, for those not familiar with them, are used for software releases prior to stable or generally available releases. Examples of prereleases include development, alpha, beta, and release candidate releases. A prerelease may be a version such as `1.2.3-beta.1` while the stable release would be `1.2.3`. In the order of precedence, prereleases come before their associated releases. In this example `1.2.3-beta.1 < 1.2.3`. According to the Semantic Version specification prereleases 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 using constraints without a prerelease comparator will skip prerelease versions. For example, `>=1.2.3` will skip prereleases when looking at a list of releases while `>=1.2.3-0` will evaluate and find prereleases. The reason for the `0` as a pre-release version in the example comparison is because pre-releases can only contain ASCII alphanumerics and hyphens (along with `.` separators), per the spec. Sorting happens in ASCII sort order, again per the spec. The lowest character is a `0` in ASCII sort order (see an [ASCII Table](http://www.asciitable.com/)) Understanding ASCII sort ordering is important because A-Z comes before a-z. That means `>=1.2.3-BETA` will return `1.2.3-alpha`. What you might expect from case sensitivity doesn't apply here. This is due to ASCII sort ordering which is what the spec specifies. ### Hyphen Range Comparisons There are multiple methods to handle ranges and the first is hyphens ranges. These look like: * `1.2 - 1.4.5` which is equivalent to `>= 1.2 <= 1.4.5` * `2.3.4 - 4.5` which is equivalent to `>= 2.3.4 <= 4.5` ### Wildcards In Comparisons The `x`, `X`, and `*` characters can be used as a wildcard character. This works for all comparison operators. When used on the `=` operator it falls back to the patch level comparison (see tilde below). For example, * `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` * `>= 1.2.x` is equivalent to `>= 1.2.0` * `<= 2.x` is equivalent to `< 3` * `*` is equivalent to `>= 0.0.0` ### Tilde Range Comparisons (Patch) The tilde (`~`) comparison operator is for patch level ranges when a minor version is specified and major level changes when the minor number is missing. For example, * `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0` * `~1` is equivalent to `>= 1, < 2` * `~2.3` is equivalent to `>= 2.3, < 2.4` * `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` * `~1.x` is equivalent to `>= 1, < 2` ### Caret Range Comparisons (Major) The caret (`^`) comparison operator is for major level changes once a stable (1.0.0) release has occurred. Prior to a 1.0.0 release the minor versions acts as the API stability level. This is useful when comparisons of API versions as a major change is API breaking. For example, * `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0` * `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0` * `^2.3` is equivalent to `>= 2.3, < 3` * `^2.x` is equivalent to `>= 2.0.0, < 3` * `^0.2.3` is equivalent to `>=0.2.3 <0.3.0` * `^0.2` is equivalent to `>=0.2.0 <0.3.0` * `^0.0.3` is equivalent to `>=0.0.3 <0.0.4` * `^0.0` is equivalent to `>=0.0.0 <0.1.0` * `^0` is equivalent to `>=0.0.0 <1.0.0` ## Validation In addition to testing a version against a constraint, a version can be validated 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. } v, err := semver.NewVersion("1.3") if err != nil { // Handle version not being parseable. } // Validate a version against a constraint. a, msgs := c.Validate(v) // a is false for _, m := range msgs { fmt.Println(m) // Loops over the errors which would read // "1.3 is greater than 1.2.3" // "1.3 is less than 1.4" } ``` ## Contribute If you find an issue or want to contribute please file an [issue](https://github.com/Masterminds/semver/issues) or [create a pull request](https://github.com/Masterminds/semver/pulls). semver-3.1.1/benchmark_test.go000066400000000000000000000120151375676543200163560ustar00rootroot00000000000000package semver import ( "testing" ) /* Constraint creation benchmarks */ func benchNewConstraint(c string, b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = NewConstraint(c) } } func BenchmarkNewConstraintUnary(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchNewConstraint("=2.0", b) } func BenchmarkNewConstraintTilde(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchNewConstraint("~2.0.0", b) } func BenchmarkNewConstraintCaret(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchNewConstraint("^2.0.0", b) } func BenchmarkNewConstraintWildcard(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchNewConstraint("1.x", b) } func BenchmarkNewConstraintRange(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchNewConstraint(">=2.1.x, <3.1.0", b) } func BenchmarkNewConstraintUnion(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchNewConstraint("~2.0.0 || =3.1.0", b) } /* Check benchmarks */ func benchCheckVersion(c, v string, b *testing.B) { b.ReportAllocs() b.ResetTimer() version, _ := NewVersion(v) constraint, _ := NewConstraint(c) for i := 0; i < b.N; i++ { constraint.Check(version) } } func BenchmarkCheckVersionUnary(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchCheckVersion("=2.0", "2.0.0", b) } func BenchmarkCheckVersionTilde(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchCheckVersion("~2.0.0", "2.0.5", b) } func BenchmarkCheckVersionCaret(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchCheckVersion("^2.0.0", "2.1.0", b) } func BenchmarkCheckVersionWildcard(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchCheckVersion("1.x", "1.4.0", b) } func BenchmarkCheckVersionRange(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchCheckVersion(">=2.1.x, <3.1.0", "2.4.5", b) } func BenchmarkCheckVersionUnion(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchCheckVersion("~2.0.0 || =3.1.0", "3.1.0", b) } func benchValidateVersion(c, v string, b *testing.B) { b.ReportAllocs() b.ResetTimer() version, _ := NewVersion(v) constraint, _ := NewConstraint(c) for i := 0; i < b.N; i++ { constraint.Validate(version) } } /* Validate benchmarks, including fails */ func BenchmarkValidateVersionUnary(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchValidateVersion("=2.0", "2.0.0", b) } func BenchmarkValidateVersionUnaryFail(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchValidateVersion("=2.0", "2.0.1", b) } func BenchmarkValidateVersionTilde(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchValidateVersion("~2.0.0", "2.0.5", b) } func BenchmarkValidateVersionTildeFail(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchValidateVersion("~2.0.0", "1.0.5", b) } func BenchmarkValidateVersionCaret(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchValidateVersion("^2.0.0", "2.1.0", b) } func BenchmarkValidateVersionCaretFail(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchValidateVersion("^2.0.0", "4.1.0", b) } func BenchmarkValidateVersionWildcard(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchValidateVersion("1.x", "1.4.0", b) } func BenchmarkValidateVersionWildcardFail(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchValidateVersion("1.x", "2.4.0", b) } func BenchmarkValidateVersionRange(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchValidateVersion(">=2.1.x, <3.1.0", "2.4.5", b) } func BenchmarkValidateVersionRangeFail(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchValidateVersion(">=2.1.x, <3.1.0", "1.4.5", b) } func BenchmarkValidateVersionUnion(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchValidateVersion("~2.0.0 || =3.1.0", "3.1.0", b) } func BenchmarkValidateVersionUnionFail(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchValidateVersion("~2.0.0 || =3.1.0", "3.1.1", b) } /* Version creation benchmarks */ func benchNewVersion(v string, b *testing.B) { for i := 0; i < b.N; i++ { _, _ = NewVersion(v) } } func benchStrictNewVersion(v string, b *testing.B) { for i := 0; i < b.N; i++ { _, _ = StrictNewVersion(v) } } func BenchmarkNewVersionSimple(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchNewVersion("1.0.0", b) } func BenchmarkCoerceNewVersionSimple(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchStrictNewVersion("1.0.0", b) } func BenchmarkNewVersionPre(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchNewVersion("1.0.0-alpha", b) } func BenchmarkStrictNewVersionPre(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchStrictNewVersion("1.0.0-alpha", b) } func BenchmarkNewVersionMeta(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchNewVersion("1.0.0+metadata", b) } func BenchmarkStrictNewVersionMeta(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchStrictNewVersion("1.0.0+metadata", b) } func BenchmarkNewVersionMetaDash(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchNewVersion("1.0.0-alpha.1+meta.data", b) } func BenchmarkStrictNewVersionMetaDash(b *testing.B) { b.ReportAllocs() b.ResetTimer() benchStrictNewVersion("1.0.0-alpha.1+meta.data", b) } semver-3.1.1/collection.go000066400000000000000000000013311375676543200155170ustar00rootroot00000000000000package 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 // Len returns the length of a collection. The number of Version instances // on the slice. func (c Collection) Len() int { return len(c) } // Less is needed for the sort interface to compare two Version objects on the // slice. If checks if one is less than the other. func (c Collection) Less(i, j int) bool { return c[i].LessThan(c[j]) } // Swap is needed for the sort interface to replace the Version objects // at two different positions in the slice. func (c Collection) Swap(i, j int) { c[i], c[j] = c[j], c[i] } semver-3.1.1/collection_test.go000066400000000000000000000011151375676543200165560ustar00rootroot00000000000000package semver import ( "reflect" "sort" "testing" ) func TestCollection(t *testing.T) { raw := []string{ "1.2.3", "1.0", "1.3", "2", "0.4.2", } vs := make([]*Version, len(raw)) for i, r := range raw { v, err := NewVersion(r) if err != nil { t.Errorf("Error parsing version: %s", err) } vs[i] = v } sort.Sort(Collection(vs)) e := []string{ "0.4.2", "1.0.0", "1.2.3", "1.3.0", "2.0.0", } a := make([]string, len(vs)) for i, v := range vs { a[i] = v.String() } if !reflect.DeepEqual(a, e) { t.Error("Sorting Collection failed") } } semver-3.1.1/constraints.go000066400000000000000000000366621375676543200157520ustar00rootroot00000000000000package semver import ( "bytes" "errors" "fmt" "regexp" "strings" ) // 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 { // TODO: Find a way to validate and fetch all the constraints in a simpler form // Validate the segment if !validConstraintRegex.MatchString(v) { return nil, fmt.Errorf("improper constraint: %s", v) } cs := findConstraintRegex.FindAllString(v, -1) if cs == nil { cs = append(cs, 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 { // TODO(mattfarina): For v4 of this library consolidate the Check and Validate // functions as the underlying functions make that possible now. // loop over the ORs and check the inner ANDs for _, o := range cs.constraints { joy := true for _, c := range o { if check, _ := c.check(v); !check { 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 // Capture the prerelease message only once. When it happens the first time // this var is marked var prerelesase bool for _, o := range cs.constraints { joy := true for _, c := range o { // Before running the check handle the case there the version is // a prerelease and the check is not searching for prereleases. if c.con.pre == "" && v.pre != "" { if !prerelesase { em := fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) e = append(e, em) prerelesase = true } joy = false } else { if _, err := c.check(v); err != nil { e = append(e, err) joy = false } } } if joy { return true, []error{} } } return false, e } func (cs Constraints) String() string { buf := make([]string, len(cs.constraints)) var tmp bytes.Buffer for k, v := range cs.constraints { tmp.Reset() vlen := len(v) for kk, c := range v { tmp.WriteString(c.string()) // Space separate the AND conditions if vlen > 1 && kk < vlen-1 { tmp.WriteString(" ") } } buf[k] = tmp.String() } return strings.Join(buf, " || ") } var constraintOps map[string]cfunc var constraintRegex *regexp.Regexp var constraintRangeRegex *regexp.Regexp // Used to find individual constraints within a multi-constraint string var findConstraintRegex *regexp.Regexp // Used to validate an segment of ANDs is valid var validConstraintRegex *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 init() { constraintOps = map[string]cfunc{ "": constraintTildeOrEqual, "=": constraintTildeOrEqual, "!=": constraintNotEqual, ">": constraintGreaterThan, "<": constraintLessThan, ">=": constraintGreaterThanEqual, "=>": constraintGreaterThanEqual, "<=": constraintLessThanEqual, "=<": constraintLessThanEqual, "~": constraintTilde, "~>": constraintTilde, "^": constraintCaret, } ops := `=||!=|>|<|>=|=>|<=|=<|~|~>|\^` constraintRegex = regexp.MustCompile(fmt.Sprintf( `^\s*(%s)\s*(%s)\s*$`, ops, cvRegex)) constraintRangeRegex = regexp.MustCompile(fmt.Sprintf( `\s*(%s)\s+-\s+(%s)\s*`, cvRegex, cvRegex)) findConstraintRegex = regexp.MustCompile(fmt.Sprintf( `(%s)\s*(%s)`, ops, cvRegex)) validConstraintRegex = regexp.MustCompile(fmt.Sprintf( `^(\s*(%s)\s*(%s)\s*\,?)+$`, ops, cvRegex)) } // An individual constraint type constraint struct { // 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 // The original operator for the constraint origfunc string // When an x is used as part of the version (e.g., 1.x) minorDirty bool dirty bool patchDirty bool } // Check if a version meets the constraint func (c *constraint) check(v *Version) (bool, error) { return constraintOps[c.origfunc](v, c) } // String prints an individual constraint into a string func (c *constraint) string() string { return c.origfunc + c.orig } type cfunc func(v *Version, c *constraint) (bool, error) func parseConstraint(c string) (*constraint, error) { if len(c) > 0 { m := constraintRegex.FindStringSubmatch(c) if m == nil { return nil, fmt.Errorf("improper constraint: %s", c) } cs := &constraint{ orig: m[2], origfunc: m[1], } ver := m[2] minorDirty := false patchDirty := false dirty := false if isX(m[3]) || m[3] == "" { ver = "0.0.0" dirty = true } else if isX(strings.TrimPrefix(m[4], ".")) || m[4] == "" { minorDirty = true dirty = true ver = fmt.Sprintf("%s.0.0%s", m[3], m[6]) } else if isX(strings.TrimPrefix(m[5], ".")) || m[5] == "" { dirty = true patchDirty = 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.con = con cs.minorDirty = minorDirty cs.patchDirty = patchDirty cs.dirty = dirty return cs, nil } // The rest is the special case where an empty string was passed in which // is equivalent to * or >=0.0.0 con, err := StrictNewVersion("0.0.0") 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{ con: con, orig: c, origfunc: "", minorDirty: false, patchDirty: false, dirty: true, } return cs, nil } // Constraint functions func constraintNotEqual(v *Version, c *constraint) (bool, error) { 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, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) } if c.con.Major() != v.Major() { return true, nil } if c.con.Minor() != v.Minor() && !c.minorDirty { return true, nil } else if c.minorDirty { return false, fmt.Errorf("%s is equal to %s", v, c.orig) } else if c.con.Patch() != v.Patch() && !c.patchDirty { return true, nil } else if c.patchDirty { // Need to handle prereleases if present if v.Prerelease() != "" || c.con.Prerelease() != "" { eq := comparePrerelease(v.Prerelease(), c.con.Prerelease()) != 0 if eq { return true, nil } return false, fmt.Errorf("%s is equal to %s", v, c.orig) } return false, fmt.Errorf("%s is equal to %s", v, c.orig) } } eq := v.Equal(c.con) if eq { return false, fmt.Errorf("%s is equal to %s", v, c.orig) } return true, nil } func constraintGreaterThan(v *Version, c *constraint) (bool, error) { // 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, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) } var eq bool if !c.dirty { eq = v.Compare(c.con) == 1 if eq { return true, nil } return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) } if v.Major() > c.con.Major() { return true, nil } else if v.Major() < c.con.Major() { return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) } else if c.minorDirty { // This is a range case such as >11. When the version is something like // 11.1.0 is it not > 11. For that we would need 12 or higher return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) } else if c.patchDirty { // This is for ranges such as >11.1. A version of 11.1.1 is not greater // which one of 11.2.1 is greater eq = v.Minor() > c.con.Minor() if eq { return true, nil } return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) } // If we have gotten here we are not comparing pre-preleases and can use the // Compare function to accomplish that. eq = v.Compare(c.con) == 1 if eq { return true, nil } return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) } func constraintLessThan(v *Version, c *constraint) (bool, error) { // 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, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) } eq := v.Compare(c.con) < 0 if eq { return true, nil } return false, fmt.Errorf("%s is greater than or equal to %s", v, c.orig) } func constraintGreaterThanEqual(v *Version, c *constraint) (bool, error) { // 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, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) } eq := v.Compare(c.con) >= 0 if eq { return true, nil } return false, fmt.Errorf("%s is less than %s", v, c.orig) } func constraintLessThanEqual(v *Version, c *constraint) (bool, error) { // 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, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) } var eq bool if !c.dirty { eq = v.Compare(c.con) <= 0 if eq { return true, nil } return false, fmt.Errorf("%s is greater than %s", v, c.orig) } if v.Major() > c.con.Major() { return false, fmt.Errorf("%s is greater than %s", v, c.orig) } else if v.Major() == c.con.Major() && v.Minor() > c.con.Minor() && !c.minorDirty { return false, fmt.Errorf("%s is greater than %s", v, c.orig) } return true, nil } // ~*, ~>* --> >= 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, error) { // 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, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) } if v.LessThan(c.con) { return false, fmt.Errorf("%s is less than %s", v, c.orig) } // ~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 && !c.minorDirty && !c.patchDirty { return true, nil } if v.Major() != c.con.Major() { return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig) } if v.Minor() != c.con.Minor() && !c.minorDirty { return false, fmt.Errorf("%s does not have same major and minor version as %s", v, c.orig) } return true, 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, error) { // 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, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) } if c.dirty { return constraintTilde(v, c) } eq := v.Equal(c.con) if eq { return true, nil } return false, fmt.Errorf("%s is not equal to %s", v, c.orig) } // ^* --> (any) // ^1.2.3 --> >=1.2.3 <2.0.0 // ^1.2 --> >=1.2.0 <2.0.0 // ^1 --> >=1.0.0 <2.0.0 // ^0.2.3 --> >=0.2.3 <0.3.0 // ^0.2 --> >=0.2.0 <0.3.0 // ^0.0.3 --> >=0.0.3 <0.0.4 // ^0.0 --> >=0.0.0 <0.1.0 // ^0 --> >=0.0.0 <1.0.0 func constraintCaret(v *Version, c *constraint) (bool, error) { // 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, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) } // This less than handles prereleases if v.LessThan(c.con) { return false, fmt.Errorf("%s is less than %s", v, c.orig) } var eq bool // ^ when the major > 0 is >=x.y.z < x+1 if c.con.Major() > 0 || c.minorDirty { // ^ has to be within a major range for > 0. Everything less than was // filtered out with the LessThan call above. This filters out those // that greater but not within the same major range. eq = v.Major() == c.con.Major() if eq { return true, nil } return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig) } // ^ when the major is 0 and minor > 0 is >=0.y.z < 0.y+1 if c.con.Major() == 0 && v.Major() > 0 { return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig) } // If the con Minor is > 0 it is not dirty if c.con.Minor() > 0 || c.patchDirty { eq = v.Minor() == c.con.Minor() if eq { return true, nil } return false, fmt.Errorf("%s does not have same minor version as %s. Expected minor versions to match when constraint major version is 0", v, c.orig) } // At this point the major is 0 and the minor is 0 and not dirty. The patch // is not dirty so we need to check if they are equal. If they are not equal eq = c.con.Patch() == v.Patch() if eq { return true, nil } return false, fmt.Errorf("%s does not equal %s. Expect version and constraint to equal when major and minor versions are 0", v, c.orig) } func isX(x string) bool { switch x { case "x", "*", "X": return true default: return false } } 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 } semver-3.1.1/constraints_test.go000066400000000000000000000460661375676543200170100ustar00rootroot00000000000000package semver import ( "reflect" "testing" ) func TestParseConstraint(t *testing.T) { tests := []struct { in string f cfunc v string 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}, {"< 40.50.10", constraintLessThan, "40.50.10", false}, } for _, tc := range tests { c, err := parseConstraint(tc.in) 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) } // If an error was expected continue the loop and don't try the other // tests as they will cause errors. if tc.err { continue } if tc.v != c.con.String() { t.Errorf("Incorrect version found on %s", tc.in) } f1 := reflect.ValueOf(tc.f) f2 := reflect.ValueOf(constraintOps[c.origfunc]) if f1 != f2 { t.Errorf("Wrong constraint found for %s", tc.in) } } } func TestConstraintCheck(t *testing.T) { tests := []struct { constraint string version string check bool }{ {"=2.0.0", "1.2.3", false}, {"=2.0.0", "2.0.0", true}, {"=2.0", "1.2.3", false}, {"=2.0", "2.0.0", true}, {"=2.0", "2.0.1", true}, {"4.1", "4.1.0", true}, {"!=4.1.0", "4.1.0", false}, {"!=4.1.0", "4.1.1", true}, {"!=4.1", "4.1.0", false}, {"!=4.1", "4.1.1", false}, {"!=4.1", "5.1.0-alpha.1", false}, {"!=4.1-alpha", "4.1.0", true}, {"!=4.1", "5.1.0", true}, {"<11", "0.1.0", true}, {"<11", "11.1.0", false}, {"<1.1", "0.1.0", true}, {"<1.1", "1.1.0", false}, {"<1.1", "1.1.1", false}, {"<=11", "1.2.3", true}, {"<=11", "12.2.3", false}, {"<=11", "11.2.3", true}, {"<=1.1", "1.2.3", false}, {"<=1.1", "0.1.0", true}, {"<=1.1", "1.1.0", true}, {"<=1.1", "1.1.1", true}, {">1.1", "4.1.0", true}, {">1.1", "1.1.0", false}, {">0", "0", false}, {">0", "1", true}, {">0", "0.0.1-alpha", false}, {">0.0", "0.0.1-alpha", false}, {">0-0", "0.0.1-alpha", false}, {">0.0-0", "0.0.1-alpha", false}, {">0", "0.0.0-alpha", false}, {">0-0", "0.0.0-alpha", false}, {">0.0.0-0", "0.0.0-alpha", true}, {">1.2.3-alpha.1", "1.2.3-alpha.2", true}, {">1.2.3-alpha.1", "1.3.3-alpha.2", true}, {">11", "11.1.0", false}, {">11.1", "11.1.0", false}, {">11.1", "11.1.1", false}, {">11.1", "11.2.1", true}, {">=11", "11.1.2", true}, {">=11.1", "11.1.2", true}, {">=11.1", "11.0.2", false}, {">=1.1", "4.1.0", true}, {">=1.1", "1.1.0", true}, {">=1.1", "0.0.9", false}, {">=0", "0.0.1-alpha", false}, {">=0.0", "0.0.1-alpha", false}, {">=0-0", "0.0.1-alpha", true}, {">=0.0-0", "0.0.1-alpha", true}, {">=0", "0.0.0-alpha", false}, {">=0-0", "0.0.0-alpha", true}, {">=0.0.0-0", "0.0.0-alpha", true}, {">=0.0.0-0", "1.2.3", true}, {">=0.0.0-0", "3.4.5-beta.1", true}, {"<0", "0.0.0-alpha", false}, {"<0-z", "0.0.0-alpha", true}, {">=0", "0", true}, {"=0", "1", false}, {"*", "1", true}, {"*", "4.5.6", true}, {"*", "1.2.3-alpha.1", false}, {"2.*", "1", false}, {"2.*", "3.4.5", false}, {"2.*", "2.1.1", true}, {"2.1.*", "2.1.1", true}, {"2.1.*", "2.2.1", false}, {"", "1", true}, // An empty string is treated as * or wild card {"", "4.5.6", true}, {"", "1.2.3-alpha.1", false}, {"2", "1", false}, {"2", "3.4.5", false}, {"2", "2.1.1", true}, {"2.1", "2.1.1", true}, {"2.1", "2.2.1", false}, {"~1.2.3", "1.2.4", true}, {"~1.2.3", "1.3.4", false}, {"~1.2", "1.2.4", true}, {"~1.2", "1.3.4", false}, {"~1", "1.2.4", true}, {"~1", "2.3.4", false}, {"~0.2.3", "0.2.5", true}, {"~0.2.3", "0.3.5", false}, {"~1.2.3-beta.2", "1.2.3-beta.4", true}, // This next test is a case that is different from npm/js semver handling. // Their prereleases are only range scoped to patch releases. This is // technically not following semver as docs note. In our case we are // following semver. {"~1.2.3-beta.2", "1.2.4-beta.2", true}, {"~1.2.3-beta.2", "1.3.4-beta.2", false}, {"^1.2.3", "1.8.9", true}, {"^1.2.3", "2.8.9", false}, {"^1.2.3", "1.2.1", false}, {"^1.1.0", "2.1.0", false}, {"^1.2.0", "2.2.1", false}, {"^1.2.0", "1.2.1-alpha.1", false}, {"^1.2.0-alpha.0", "1.2.1-alpha.1", true}, {"^1.2.0-alpha.0", "1.2.1-alpha.0", true}, {"^1.2.0-alpha.2", "1.2.0-alpha.1", false}, {"^1.2", "1.8.9", true}, {"^1.2", "2.8.9", false}, {"^1", "1.8.9", true}, {"^1", "2.8.9", false}, {"^0.2.3", "0.2.5", true}, {"^0.2.3", "0.5.6", false}, {"^0.2", "0.2.5", true}, {"^0.2", "0.5.6", false}, {"^0.0.3", "0.0.3", true}, {"^0.0.3", "0.0.4", false}, {"^0.0", "0.0.3", true}, {"^0.0", "0.1.4", false}, {"^0.0", "1.0.4", false}, {"^0", "0.2.3", true}, {"^0", "1.1.4", false}, {"^0.2.3-beta.2", "0.2.3-beta.4", true}, // This next test is a case that is different from npm/js semver handling. // Their prereleases are only range scoped to patch releases. This is // technically not following semver as docs note. In our case we are // following semver. {"^0.2.3-beta.2", "0.2.4-beta.2", true}, {"^0.2.3-beta.2", "0.3.4-beta.2", false}, {"^0.2.3-beta.2", "0.2.3-beta.2", true}, } for _, tc := range tests { c, err := parseConstraint(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 } a, _ := c.check(v) if a != tc.check { t.Errorf("Constraint %q failing with %q", tc.constraint, tc.version) } } } func TestNewConstraint(t *testing.T) { tests := []struct { input string ors int count int err bool }{ {">= 1.1", 1, 1, false}, {">40.50.60, < 50.70", 1, 2, false}, {"2.0", 1, 1, false}, {"v2.3.5-20161202202307-sha.e8fc5e5", 1, 1, false}, {">= bar", 0, 0, true}, {"BAR >= 1.2.3", 0, 0, true}, // Test with space separated AND {">= 1.2.3 < 2.0", 1, 2, false}, {">= 1.2.3 < 2.0 || => 3.0 < 4", 2, 2, false}, // Test with commas separating AND {">= 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}, } for _, tc := range tests { v, err := NewConstraint(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 } l := len(v.constraints) if tc.ors != l { t.Errorf("Expected %s to have %d ORs but got %d", tc.input, tc.ors, l) } 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) } } } func TestConstraintsCheck(t *testing.T) { tests := []struct { constraint string version string check bool }{ {"*", "1.2.3", true}, {"~0.0.0", "1.2.3", true}, {"0.x.x", "1.2.3", false}, {"0.0.x", "1.2.3", false}, {"0.0.0", "1.2.3", false}, {"*", "1.2.3", true}, {"^0.0.0", "1.2.3", false}, {"= 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.1-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}, {"!=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", false}, {"<1.x", "0.1.1", true}, {"<1.x", "2.0.0", false}, {"<1.1.x", "1.2.1", false}, {"<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.0.0", false}, {"<=1.1", "1.1.1", true}, {"<=1.1.x", "1.2.500", false}, {"<=4.5", "3.4.0", true}, {"<=4.5", "3.7.0", true}, {"<=4.5", "4.6.3", false}, {">1.1, <2", "1.1.1", false}, {">1.1, <2", "1.2.1", true}, {">1.1, <3", "4.3.2", false}, {">=1.1, <2, !=1.2.3", "1.2.3", false}, {">1.1 <2", "1.1.1", false}, {">1.1 <2", "1.2.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", "4.1.2", true}, {">=1.1, <2, !=1.2.3 || > 3", "3.1.2", false}, {">=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.2.3", "1.2.3", false}, {">=1.1 <2 !=1.2.3 || > 3", "4.1.2", true}, {">=1.1 <2 !=1.2.3 || > 3", "3.1.2", false}, {">=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", false}, {"> 1.1, <2", "1.2.1", true}, {">1.1, < 3", "4.3.2", false}, {">= 1.1, < 2, !=1.2.3", "1.2.3", false}, {"> 1.1 < 2", "1.1.1", false}, {">1.1 < 2", "1.2.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", "4.1.2", true}, {">= 1.1, <2, != 1.2.3 || > 3", "3.1.2", false}, {">= 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.2.3", "1.2.3", false}, {">= 1.1 <2 != 1.2.3 || > 3", "4.1.2", true}, {">= 1.1 <2 != 1.2.3 || > 3", "3.1.2", false}, {">= 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.5.0 - 4.5", "3.7.0", true}, {"1.1-3", "4.3.2", false}, {"^1.1", "1.1.1", true}, {"^1.1", "4.3.2", false}, {"^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", "2.1.1", false}, {"~1", "1.3.5", true}, {"~1", "1.4", 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}, {"~1.1", "1.2.3", false}, {"~1.3", "2.4.5", false}, } for _, tc := range tests { 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 } a := c.Check(v) if a != tc.check { t.Errorf("Constraint '%s' failing with '%s'", tc.constraint, tc.version) } } } func TestRewriteRange(t *testing.T) { tests := []struct { 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"}, } for _, tc := range tests { o := rewriteRange(tc.c) if o != tc.nc { t.Errorf("Range %s rewritten incorrectly as '%s'", tc.c, o) } } } func TestIsX(t *testing.T) { tests := []struct { t string c bool }{ {"A", false}, {"%", false}, {"X", true}, {"x", true}, {"*", true}, } for _, tc := range tests { a := isX(tc.t) if a != tc.c { t.Errorf("Function isX error on %s", tc.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", false}, {"<2.x", "1.1.1", true}, {"<1.x", "2.1.1", false}, {"<1.1.x", "1.2.1", false}, {"<1.1.x", "1.1.500", false}, {"<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", true}, {"<=1.1.x", "1.2.500", false}, {">1.1, <2", "1.1.1", false}, {">1.1, <2", "1.2.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", false}, {">=1.1, <2, !=1.2.3 || > 3", "4.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}, {"^0.0.1", "0.1.3", false}, {"^0.0.1", "0.0.1", true}, {"~*", "2.1.1", true}, {"~1", "2.1.1", false}, {"~1", "1.3.5", true}, {"~1", "1.3.5-beta", false}, {"~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}, } for _, tc := range tests { 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 } 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) } // if a == false { // for _, m := range msgs { // t.Errorf("%s", m) // } // } } v, err := StrictNewVersion("1.2.3") if err != nil { t.Errorf("err: %s", err) } c, err := NewConstraint("!= 1.2.5, ^2, <= 1.1.x") if err != nil { t.Errorf("err: %s", err) } _, msgs := c.Validate(v) if len(msgs) != 2 { t.Error("Invalid number of validations found") } e := msgs[0].Error() if e != "1.2.3 is less than 2" { t.Error("Did not get expected message: 1.2.3 is less than 2") } 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") } tests2 := []struct { constraint, version, msg string }{ {"2.x", "1.2.3", "1.2.3 is less than 2.x"}, {"2", "1.2.3", "1.2.3 is less than 2"}, {"= 2.0", "1.2.3", "1.2.3 is less than 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.2.1", "1.2.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"}, {"^1.12.7", "1.6.6", "1.6.6 is less than 1.12.7"}, {"^2.x", "1.1.1", "1.1.1 is less than 2.x"}, {"^1.x", "2.1.1", "2.1.1 does not have same major version as 1.x"}, {"^0.2", "0.3.0", "0.3.0 does not have same minor version as 0.2. Expected minor versions to match when constraint major version is 0"}, {"^0.2", "0.1.1", "0.1.1 is less than 0.2"}, {"^0.0.3", "0.1.1", "0.1.1 does not equal 0.0.3. Expect version and constraint to equal when major and minor versions are 0"}, {"^0.0.3", "0.0.4", "0.0.4 does not equal 0.0.3. Expect version and constraint to equal when major and minor versions are 0"}, {"^0.0.3", "0.0.2", "0.0.2 is less than 0.0.3"}, {"~1", "2.1.2", "2.1.2 does not have same major version as 1"}, {"~1.x", "2.1.1", "2.1.1 does not have same major version as 1.x"}, {"~1.2.3", "1.2.2", "1.2.2 is less than 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 version as 1.3"}, {"> 1.2.3", "1.2.3-beta.1", "1.2.3-beta.1 is a prerelease version and the constraint is only looking for release versions"}, } for _, tc := range tests2 { c, err := NewConstraint(tc.constraint) if err != nil { t.Errorf("constraint parsing err: %s", err) continue } v, err := StrictNewVersion(tc.version) if err != nil { t.Errorf("version parsing err: %s", err) continue } _, msgs := c.Validate(v) if len(msgs) == 0 { t.Errorf("Did not get error message on constraint %q", tc.constraint) } else { e := msgs[0].Error() if e != tc.msg { t.Errorf("Did not get expected message. Expected %q, got %q", tc.msg, e) } } } } func TestConstraintString(t *testing.T) { tests := []struct { constraint string st string }{ {"*", "*"}, {">=1.2.3", ">=1.2.3"}, {">= 1.2.3", ">=1.2.3"}, {"2.x, >=1.2.3 || >4.5.6, < 5.7", "2.x >=1.2.3 || >4.5.6 <5.7"}, {"2.x, >=1.2.3 || >4.5.6, < 5.7 || >40.50.60, < 50.70", "2.x >=1.2.3 || >4.5.6 <5.7 || >40.50.60 <50.70"}, {"1.2", "1.2"}, } for _, tc := range tests { c, err := NewConstraint(tc.constraint) if err != nil { t.Errorf("cannot create constraint for %q, err: %s", tc.constraint, err) continue } if c.String() != tc.st { t.Errorf("expected constraint from %q to be a string as %q but got %q", tc.constraint, tc.st, c.String()) } if _, err = NewConstraint(c.String()); err != nil { t.Errorf("expected string from constrint %q to parse as valid but got err: %s", tc.constraint, err) } } } semver-3.1.1/doc.go000066400000000000000000000155141375676543200141410ustar00rootroot00000000000000/* Package semver provides the ability to work with Semantic Versions (http://semver.org) in Go. Specifically it provides the ability to: * Parse semantic versions * Sort semantic versions * Check if a semantic version fits within a set of constraints * Optionally work with a `v` prefix Parsing Semantic Versions There are two functions that can parse semantic versions. The `StrictNewVersion` function only parses valid version 2 semantic versions as outlined in the specification. The `NewVersion` function attempts to coerce a version into a semantic version and parse it. For example, if there is a leading v or a version listed without all 3 parts (e.g. 1.2) it will attempt to coerce it into a valid semantic version (e.g., 1.2.0). In both cases a `Version` object is returned that can be sorted, compared, and used in constraints. When parsing a version an optional error can be returned if there is an issue parsing the version. For example, v, err := semver.NewVersion("1.2.3-beta.1+b345") The version object has methods to get the parts of the version, compare it to other versions, convert the version back into a string, and get the original string. For more details please see the documentation at https://godoc.org/github.com/Masterminds/semver. Sorting Semantic Versions A set of versions can be sorted using the `sort` package from the standard library. For example, raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",} vs := make([]*semver.Version, len(raw)) for i, r := range raw { v, err := semver.NewVersion(r) if err != nil { t.Errorf("Error parsing version: %s", err) } vs[i] = v } sort.Sort(semver.Collection(vs)) Checking Version Constraints and Comparing Versions There are two methods for comparing versions. One uses comparison methods on `Version` instances and the other is using Constraints. There are some important differences to notes between these two methods of comparison. 1. When two versions are compared using functions such as `Compare`, `LessThan`, and others it will follow the specification and always include prereleases within the comparison. It will provide an answer valid with the comparison spec section at https://semver.org/#spec-item-11 2. When constraint checking is used for checks or validation it will follow a different set of rules that are common for ranges with tools like npm/js and Rust/Cargo. This includes considering prereleases to be invalid if the ranges does not include on. If you want to have it include pre-releases a simple solution is to include `-0` in your range. 3. Constraint ranges can have some complex rules including the shorthard use of ~ and ^. For more details on those see the options below. There are differences between the two methods or checking versions because the comparison methods on `Version` follow the specification while comparison ranges are not part of the specification. Different packages and tools have taken it upon themselves to come up with range rules. This has resulted in differences. For example, npm/js and Cargo/Rust follow similar patterns which PHP has a different pattern for ^. The comparison features in this package follow the npm/js and Cargo/Rust lead because applications using it have followed similar patters with their versions. Checking a version against version constraints is one of the most featureful parts of the package. c, err := semver.NewConstraint(">= 1.2.3") if err != nil { // Handle constraint not being parsable. } v, err := semver.NewVersion("1.3") if err != nil { // Handle version not being parsable. } // Check if the version meets the constraints. The a variable will be true. a := c.Check(v) Basic Comparisons There are two elements to the comparisons. First, a comparison string is a list of comma or space separated AND comparisons. These are then separated by || (OR) comparisons. For example, `">= 1.2 < 3.0.0 || >= 4.2.3"` is looking for a comparison that's greater than or equal to 1.2 and less than 3.0.0 or is greater than or equal to 4.2.3. This can also be written as `">= 1.2, < 3.0.0 || >= 4.2.3"` The basic comparisons are: * `=`: equal (aliased to no operator) * `!=`: not equal * `>`: greater than * `<`: less than * `>=`: greater than or equal to * `<=`: less than or equal to Hyphen Range Comparisons There are multiple methods to handle ranges and the first is hyphens ranges. These look like: * `1.2 - 1.4.5` which is equivalent to `>= 1.2, <= 1.4.5` * `2.3.4 - 4.5` which is equivalent to `>= 2.3.4 <= 4.5` Wildcards In Comparisons The `x`, `X`, and `*` characters can be used as a wildcard character. This works for all comparison operators. When used on the `=` operator it falls back to the tilde operation. For example, * `1.2.x` is equivalent to `>= 1.2.0 < 1.3.0` * `>= 1.2.x` is equivalent to `>= 1.2.0` * `<= 2.x` is equivalent to `<= 3` * `*` is equivalent to `>= 0.0.0` Tilde Range Comparisons (Patch) The tilde (`~`) comparison operator is for patch level ranges when a minor version is specified and major level changes when the minor number is missing. For example, * `~1.2.3` is equivalent to `>= 1.2.3 < 1.3.0` * `~1` is equivalent to `>= 1, < 2` * `~2.3` is equivalent to `>= 2.3 < 2.4` * `~1.2.x` is equivalent to `>= 1.2.0 < 1.3.0` * `~1.x` is equivalent to `>= 1 < 2` Caret Range Comparisons (Major) The caret (`^`) comparison operator is for major level changes once a stable (1.0.0) release has occurred. Prior to a 1.0.0 release the minor versions acts as the API stability level. This is useful when comparisons of API versions as a major change is API breaking. For example, * `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0` * `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0` * `^2.3` is equivalent to `>= 2.3, < 3` * `^2.x` is equivalent to `>= 2.0.0, < 3` * `^0.2.3` is equivalent to `>=0.2.3 <0.3.0` * `^0.2` is equivalent to `>=0.2.0 <0.3.0` * `^0.0.3` is equivalent to `>=0.0.3 <0.0.4` * `^0.0` is equivalent to `>=0.0.0 <0.1.0` * `^0` is equivalent to `>=0.0.0 <1.0.0` Validation In addition to testing a version against a constraint, a version can be validated against a constraint. When validation fails a slice of errors containing why a version didn't meet the constraint is returned. For example, c, err := semver.NewConstraint("<= 1.2.3, >= 1.4") if err != nil { // Handle constraint not being parseable. } v, _ := semver.NewVersion("1.3") if err != nil { // Handle version not being parseable. } // Validate a version against a constraint. a, msgs := c.Validate(v) // a is false for _, m := range msgs { fmt.Println(m) // Loops over the errors which would read // "1.3 is greater than 1.2.3" // "1.3 is less than 1.4" } */ package semver semver-3.1.1/fuzz.go000066400000000000000000000010031375676543200143560ustar00rootroot00000000000000// +build gofuzz package semver func Fuzz(data []byte) int { d := string(data) // Test NewVersion _, _ = NewVersion(d) // Test StrictNewVersion _, _ = StrictNewVersion(d) // Test NewConstraint _, _ = NewConstraint(d) // The return value should be 0 normally, 1 if the priority in future tests // should be increased, and -1 if future tests should skip passing in that // data. We do not have a reason to change priority so 0 is always returned. // There are example tests that do this. return 0 } semver-3.1.1/go.mod000066400000000000000000000000611375676543200141420ustar00rootroot00000000000000module github.com/Masterminds/semver/v3 go 1.12 semver-3.1.1/version.go000066400000000000000000000360351375676543200150620ustar00rootroot00000000000000package semver import ( "bytes" "database/sql/driver" "encoding/json" "errors" "fmt" "regexp" "strconv" "strings" ) // 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 ( // ErrInvalidSemVer is returned a version is found to be invalid when // being parsed. ErrInvalidSemVer = errors.New("Invalid Semantic Version") // ErrEmptyString is returned when an empty string is passed in for parsing. ErrEmptyString = errors.New("Version string empty") // ErrInvalidCharacters is returned when invalid characters are found as // part of a version ErrInvalidCharacters = errors.New("Invalid characters in version") // ErrSegmentStartsZero is returned when a version segment starts with 0. // This is invalid in SemVer. ErrSegmentStartsZero = errors.New("Version segment starts with 0") // ErrInvalidMetadata is returned when the metadata is an invalid format ErrInvalidMetadata = errors.New("Invalid Metadata string") // ErrInvalidPrerelease is returned when the pre-release is an invalid format ErrInvalidPrerelease = errors.New("Invalid Prerelease string") ) // semVerRegex is 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\-]+)*))?` // Version represents a single semantic version. type Version struct { major, minor, patch uint64 pre string metadata string original string } func init() { versionRegex = regexp.MustCompile("^" + semVerRegex + "$") } const num string = "0123456789" const allowed string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" + num // StrictNewVersion parses a given version and returns an instance of Version or // an error if unable to parse the version. Only parses valid semantic versions. // Performs checking that can find errors within the version. // If you want to coerce a version, such as 1 or 1.2, and perse that as the 1.x // releases of semver provided use the NewSemver() function. func StrictNewVersion(v string) (*Version, error) { // Parsing here does not use RegEx in order to increase performance and reduce // allocations. if len(v) == 0 { return nil, ErrEmptyString } // Split the parts into [0]major, [1]minor, and [2]patch,prerelease,build parts := strings.SplitN(v, ".", 3) if len(parts) != 3 { return nil, ErrInvalidSemVer } sv := &Version{ original: v, } // check for prerelease or build metadata var extra []string if strings.ContainsAny(parts[2], "-+") { // Start with the build metadata first as it needs to be on the right extra = strings.SplitN(parts[2], "+", 2) if len(extra) > 1 { // build metadata found sv.metadata = extra[1] parts[2] = extra[0] } extra = strings.SplitN(parts[2], "-", 2) if len(extra) > 1 { // prerelease found sv.pre = extra[1] parts[2] = extra[0] } } // Validate the number segments are valid. This includes only having positive // numbers and no leading 0's. for _, p := range parts { if !containsOnly(p, num) { return nil, ErrInvalidCharacters } if len(p) > 1 && p[0] == '0' { return nil, ErrSegmentStartsZero } } // Extract the major, minor, and patch elements onto the returned Version var err error sv.major, err = strconv.ParseUint(parts[0], 10, 64) if err != nil { return nil, err } sv.minor, err = strconv.ParseUint(parts[1], 10, 64) if err != nil { return nil, err } sv.patch, err = strconv.ParseUint(parts[2], 10, 64) if err != nil { return nil, err } // No prerelease or build metadata found so returning now as a fastpath. if sv.pre == "" && sv.metadata == "" { return sv, nil } if sv.pre != "" { if err = validatePrerelease(sv.pre); err != nil { return nil, err } } if sv.metadata != "" { if err = validateMetadata(sv.metadata); err != nil { return nil, err } } return sv, nil } // NewVersion parses a given version and returns an instance of Version or // an error if unable to parse the version. If the version is SemVer-ish it // attempts to convert it to SemVer. If you want to validate it was a strict // semantic version at parse time see StrictNewVersion(). func NewVersion(v string) (*Version, error) { m := versionRegex.FindStringSubmatch(v) if m == nil { return nil, ErrInvalidSemVer } sv := &Version{ metadata: m[8], pre: m[5], original: v, } var err error sv.major, err = strconv.ParseUint(m[1], 10, 64) if err != nil { return nil, fmt.Errorf("Error parsing version segment: %s", err) } if m[2] != "" { sv.minor, err = strconv.ParseUint(strings.TrimPrefix(m[2], "."), 10, 64) if err != nil { return nil, fmt.Errorf("Error parsing version segment: %s", err) } } else { sv.minor = 0 } if m[3] != "" { sv.patch, err = strconv.ParseUint(strings.TrimPrefix(m[3], "."), 10, 64) if err != nil { return nil, fmt.Errorf("Error parsing version segment: %s", err) } } else { sv.patch = 0 } // Perform some basic due diligence on the extra parts to ensure they are // valid. if sv.pre != "" { if err = validatePrerelease(sv.pre); err != nil { return nil, err } } if sv.metadata != "" { if err = validateMetadata(sv.metadata); err != nil { return nil, err } } 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) } return sv } // String converts a Version object to a string. // Note, if the original version contained a leading v this version will not. // 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 // implementation. func (v Version) String() string { var buf bytes.Buffer fmt.Fprintf(&buf, "%d.%d.%d", v.major, v.minor, v.patch) if v.pre != "" { fmt.Fprintf(&buf, "-%s", v.pre) } if v.metadata != "" { fmt.Fprintf(&buf, "+%s", v.metadata) } return buf.String() } // Original returns the original value passed in to be parsed. func (v *Version) Original() string { return v.original } // Major returns the major version. func (v Version) Major() uint64 { return v.major } // Minor returns the minor version. func (v Version) Minor() uint64 { return v.minor } // Patch returns the patch version. func (v Version) Patch() uint64 { return v.patch } // Prerelease returns the pre-release version. func (v Version) Prerelease() string { return v.pre } // Metadata returns the metadata on the version. 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 current 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 'hyphen' prefix. func (v Version) SetPrerelease(prerelease string) (Version, error) { vNext := v if len(prerelease) > 0 { if err := validatePrerelease(prerelease); err != nil { return vNext, err } } 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 { if err := validateMetadata(metadata); err != nil { return vNext, err } } 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 { return v.Compare(o) < 0 } // GreaterThan tests if one version is greater than another one. 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 { return v.Compare(o) == 0 } // Compare compares this version to another one. It returns -1, 0, or 1 if // the version smaller, equal, or larger than the other version. // // Versions are compared by X.Y.Z. Build metadata is ignored. Prerelease is // lower than the version without a prerelease. Compare always takes into account // prereleases. If you want to work with ranges using typical range syntaxes that // skip prereleases if the range is not looking for them use constraints. func (v *Version) Compare(o *Version) int { // 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 { return d } if d := compareSegment(v.Minor(), o.Minor()); d != 0 { return d } if d := compareSegment(v.Patch(), o.Patch()); d != 0 { return d } // At this point the major, minor, and patch versions are the same. ps := v.pre po := o.Prerelease() if ps == "" && po == "" { return 0 } if ps == "" { return 1 } if po == "" { return -1 } return comparePrerelease(ps, po) } // UnmarshalJSON implements JSON.Unmarshaler interface. func (v *Version) UnmarshalJSON(b []byte) error { var s string if err := json.Unmarshal(b, &s); err != nil { return err } temp, err := NewVersion(s) if err != nil { return err } v.major = temp.major v.minor = temp.minor v.patch = temp.patch v.pre = temp.pre v.metadata = temp.metadata v.original = temp.original return nil } // MarshalJSON implements JSON.Marshaler interface. func (v Version) MarshalJSON() ([]byte, error) { return json.Marshal(v.String()) } // Scan implements the SQL.Scanner interface. func (v *Version) Scan(value interface{}) error { var s string s, _ = value.(string) temp, err := NewVersion(s) if err != nil { return err } v.major = temp.major v.minor = temp.minor v.patch = temp.patch v.pre = temp.pre v.metadata = temp.metadata v.original = temp.original return nil } // Value implements the Driver.Valuer interface. func (v Version) Value() (driver.Value, error) { return v.String(), nil } func compareSegment(v, o uint64) int { if v < o { return -1 } if v > o { return 1 } return 0 } func comparePrerelease(v, o string) int { // split the prelease versions by their part. The separator, per the spec, // is a . sparts := strings.Split(v, ".") oparts := strings.Split(o, ".") // Find the longer length of the parts to know how many loop iterations to // go through. slen := len(sparts) olen := len(oparts) l := slen if olen > slen { l = olen } // 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 // a placeholder. This is to avoid out of bounds issues. stemp := "" if i < slen { stemp = sparts[i] } otemp := "" if i < olen { otemp = oparts[i] } d := comparePrePart(stemp, otemp) if d != 0 { return d } } // Reaching here means two versions are of equal value but have different // metadata (the part following a +). They are not identical in string form // but the version comparison finds them to be equal. return 0 } func comparePrePart(s, o string) int { // Fastpath if they are equal if s == o { return 0 } // When s or o are empty we can use the other in an attempt to determine // the response. if s == "" { if o != "" { return -1 } return 1 } if o == "" { if s != "" { return 1 } return -1 } // When comparing strings "99" is greater than "103". To handle // cases like this we need to detect numbers and compare them. According // to the semver spec, numbers are always positive. If there is a - at the // start like -99 this is to be evaluated as an alphanum. numbers always // have precedence over alphanum. Parsing as Uints because negative numbers // are ignored. oi, n1 := strconv.ParseUint(o, 10, 64) si, n2 := strconv.ParseUint(s, 10, 64) // The case where both are strings compare the strings if n1 != nil && n2 != nil { if s > o { return 1 } return -1 } else if n1 != nil { // o is a string and s is a number return -1 } else if n2 != nil { // s is a string and o is a number return 1 } // Both are numbers if si > oi { return 1 } return -1 } // Like strings.ContainsAny but does an only instead of any. func containsOnly(s string, comp string) bool { return strings.IndexFunc(s, func(r rune) bool { return !strings.ContainsRune(comp, r) }) == -1 } // From the spec, "Identifiers MUST comprise only // ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. // Numeric identifiers MUST NOT include leading zeroes.". These segments can // be dot separated. func validatePrerelease(p string) error { eparts := strings.Split(p, ".") for _, p := range eparts { if containsOnly(p, num) { if len(p) > 1 && p[0] == '0' { return ErrSegmentStartsZero } } else if !containsOnly(p, allowed) { return ErrInvalidPrerelease } } return nil } // From the spec, "Build metadata MAY be denoted by // appending a plus sign and a series of dot separated identifiers immediately // following the patch or pre-release version. Identifiers MUST comprise only // ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty." func validateMetadata(m string) error { eparts := strings.Split(m, ".") for _, p := range eparts { if !containsOnly(p, allowed) { return ErrInvalidMetadata } } return nil } semver-3.1.1/version_test.go000066400000000000000000000331341375676543200161160ustar00rootroot00000000000000package semver import ( "database/sql" "encoding/json" "fmt" "testing" ) func TestStrictNewVersion(t *testing.T) { tests := []struct { version string err bool }{ {"1.2.3", false}, {"1.2.3-alpha.01", true}, {"1.2.3+test.01", false}, {"1.2.3-alpha.-1", false}, {"v1.2.3", true}, {"1.0", true}, {"v1.0", true}, {"1", true}, {"v1", true}, {"1.2.beta", true}, {"v1.2.beta", true}, {"foo", true}, {"1.2-5", true}, {"v1.2-5", true}, {"1.2-beta.5", true}, {"v1.2-beta.5", true}, {"\n1.2", true}, {"\nv1.2", true}, {"1.2.0-x.Y.0+metadata", false}, {"v1.2.0-x.Y.0+metadata", true}, {"1.2.0-x.Y.0+metadata-width-hypen", false}, {"v1.2.0-x.Y.0+metadata-width-hypen", true}, {"1.2.3-rc1-with-hypen", false}, {"v1.2.3-rc1-with-hypen", true}, {"1.2.3.4", true}, {"v1.2.3.4", true}, {"1.2.2147483648", false}, {"1.2147483648.3", false}, {"2147483648.3.0", false}, } for _, tc := range tests { _, err := StrictNewVersion(tc.version) if tc.err && err == nil { t.Fatalf("expected error for version: %s", tc.version) } else if !tc.err && err != nil { t.Fatalf("error for version %s: %s", tc.version, err) } } } func TestNewVersion(t *testing.T) { tests := []struct { version string err bool }{ {"1.2.3", false}, {"1.2.3-alpha.01", true}, {"1.2.3+test.01", false}, {"1.2.3-alpha.-1", false}, {"v1.2.3", false}, {"1.0", false}, {"v1.0", false}, {"1", false}, {"v1", false}, {"1.2.beta", true}, {"v1.2.beta", true}, {"foo", true}, {"1.2-5", false}, {"v1.2-5", false}, {"1.2-beta.5", false}, {"v1.2-beta.5", false}, {"\n1.2", true}, {"\nv1.2", true}, {"1.2.0-x.Y.0+metadata", false}, {"v1.2.0-x.Y.0+metadata", false}, {"1.2.0-x.Y.0+metadata-width-hypen", false}, {"v1.2.0-x.Y.0+metadata-width-hypen", false}, {"1.2.3-rc1-with-hypen", false}, {"v1.2.3-rc1-with-hypen", false}, {"1.2.3.4", true}, {"v1.2.3.4", true}, {"1.2.2147483648", false}, {"1.2147483648.3", false}, {"2147483648.3.0", false}, } for _, tc := range tests { _, err := NewVersion(tc.version) if tc.err && err == nil { t.Fatalf("expected error for version: %s", tc.version) } else if !tc.err && err != nil { t.Fatalf("error for version %s: %s", tc.version, err) } } } func TestOriginal(t *testing.T) { tests := []string{ "1.2.3", "v1.2.3", "1.0", "v1.0", "1", "v1", "1.2-5", "v1.2-5", "1.2-beta.5", "v1.2-beta.5", "1.2.0-x.Y.0+metadata", "v1.2.0-x.Y.0+metadata", "1.2.0-x.Y.0+metadata-width-hypen", "v1.2.0-x.Y.0+metadata-width-hypen", "1.2.3-rc1-with-hypen", "v1.2.3-rc1-with-hypen", } for _, tc := range tests { v, err := NewVersion(tc) if err != nil { t.Errorf("Error parsing version %s", tc) } o := v.Original() if o != tc { t.Errorf("Error retrieving original. Expected '%s' but got '%v'", tc, v) } } } func TestParts(t *testing.T) { v, err := NewVersion("1.2.3-beta.1+build.123") if err != nil { t.Error("Error parsing version 1.2.3-beta.1+build.123") } if v.Major() != 1 { t.Error("Major() returning wrong value") } if v.Minor() != 2 { t.Error("Minor() returning wrong value") } if v.Patch() != 3 { t.Error("Patch() returning wrong value") } if v.Prerelease() != "beta.1" { t.Error("Prerelease() returning wrong value") } if v.Metadata() != "build.123" { t.Error("Metadata() returning wrong value") } } func TestCoerceString(t *testing.T) { tests := []struct { version string expected string }{ {"1.2.3", "1.2.3"}, {"v1.2.3", "1.2.3"}, {"1.0", "1.0.0"}, {"v1.0", "1.0.0"}, {"1", "1.0.0"}, {"v1", "1.0.0"}, {"1.2-5", "1.2.0-5"}, {"v1.2-5", "1.2.0-5"}, {"1.2-beta.5", "1.2.0-beta.5"}, {"v1.2-beta.5", "1.2.0-beta.5"}, {"1.2.0-x.Y.0+metadata", "1.2.0-x.Y.0+metadata"}, {"v1.2.0-x.Y.0+metadata", "1.2.0-x.Y.0+metadata"}, {"1.2.0-x.Y.0+metadata-width-hypen", "1.2.0-x.Y.0+metadata-width-hypen"}, {"v1.2.0-x.Y.0+metadata-width-hypen", "1.2.0-x.Y.0+metadata-width-hypen"}, {"1.2.3-rc1-with-hypen", "1.2.3-rc1-with-hypen"}, {"v1.2.3-rc1-with-hypen", "1.2.3-rc1-with-hypen"}, } for _, tc := range tests { v, err := NewVersion(tc.version) if err != nil { t.Errorf("Error parsing version %s", tc) } s := v.String() if s != tc.expected { t.Errorf("Error generating string. Expected '%s' but got '%s'", tc.expected, s) } } } func TestCompare(t *testing.T) { tests := []struct { v1 string v2 string expected int }{ {"1.2.3", "1.5.1", -1}, {"2.2.3", "1.5.1", 1}, {"2.2.3", "2.2.2", 1}, {"3.2-beta", "3.2-beta", 0}, {"1.3", "1.1.4", 1}, {"4.2", "4.2-beta", 1}, {"4.2-beta", "4.2", -1}, {"4.2-alpha", "4.2-beta", -1}, {"4.2-alpha", "4.2-alpha", 0}, {"4.2-beta.2", "4.2-beta.1", 1}, {"4.2-beta2", "4.2-beta1", 1}, {"4.2-beta", "4.2-beta.2", -1}, {"4.2-beta", "4.2-beta.foo", -1}, {"4.2-beta.2", "4.2-beta", 1}, {"4.2-beta.foo", "4.2-beta", 1}, {"1.2+bar", "1.2+baz", 0}, {"1.0.0-beta.4", "1.0.0-beta.-2", -1}, {"1.0.0-beta.-2", "1.0.0-beta.-3", -1}, {"1.0.0-beta.-3", "1.0.0-beta.5", 1}, } for _, tc := range tests { v1, err := NewVersion(tc.v1) if err != nil { t.Errorf("Error parsing version: %s", err) } v2, err := NewVersion(tc.v2) if err != nil { t.Errorf("Error parsing version: %s", err) } a := v1.Compare(v2) e := tc.expected if a != e { t.Errorf( "Comparison of '%s' and '%s' failed. Expected '%d', got '%d'", tc.v1, tc.v2, e, a, ) } } } func TestLessThan(t *testing.T) { tests := []struct { v1 string v2 string expected bool }{ {"1.2.3", "1.5.1", true}, {"2.2.3", "1.5.1", false}, {"3.2-beta", "3.2-beta", false}, } for _, tc := range tests { v1, err := NewVersion(tc.v1) if err != nil { t.Errorf("Error parsing version: %s", err) } v2, err := NewVersion(tc.v2) if err != nil { t.Errorf("Error parsing version: %s", err) } a := v1.LessThan(v2) e := tc.expected if a != e { t.Errorf( "Comparison of '%s' and '%s' failed. Expected '%t', got '%t'", tc.v1, tc.v2, e, a, ) } } } func TestGreaterThan(t *testing.T) { tests := []struct { v1 string v2 string expected bool }{ {"1.2.3", "1.5.1", false}, {"2.2.3", "1.5.1", true}, {"3.2-beta", "3.2-beta", false}, {"3.2.0-beta.1", "3.2.0-beta.5", false}, {"3.2-beta.4", "3.2-beta.2", true}, {"7.43.0-SNAPSHOT.99", "7.43.0-SNAPSHOT.103", false}, {"7.43.0-SNAPSHOT.FOO", "7.43.0-SNAPSHOT.103", true}, {"7.43.0-SNAPSHOT.99", "7.43.0-SNAPSHOT.BAR", false}, } for _, tc := range tests { v1, err := NewVersion(tc.v1) if err != nil { t.Errorf("Error parsing version: %s", err) } v2, err := NewVersion(tc.v2) if err != nil { t.Errorf("Error parsing version: %s", err) } a := v1.GreaterThan(v2) e := tc.expected if a != e { t.Errorf( "Comparison of '%s' and '%s' failed. Expected '%t', got '%t'", tc.v1, tc.v2, e, a, ) } } } func TestEqual(t *testing.T) { tests := []struct { v1 string v2 string expected bool }{ {"1.2.3", "1.5.1", false}, {"2.2.3", "1.5.1", false}, {"3.2-beta", "3.2-beta", true}, {"3.2-beta+foo", "3.2-beta+bar", true}, } for _, tc := range tests { v1, err := NewVersion(tc.v1) if err != nil { t.Errorf("Error parsing version: %s", err) } v2, err := NewVersion(tc.v2) if err != nil { t.Errorf("Error parsing version: %s", err) } a := v1.Equal(v2) e := tc.expected if a != e { t.Errorf( "Comparison of '%s' and '%s' failed. Expected '%t', got '%t'", tc.v1, tc.v2, e, a, ) } } } 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) } } } func TestJsonMarshal(t *testing.T) { sVer := "1.1.1" x, err := StrictNewVersion(sVer) if err != nil { t.Errorf("Error creating version: %s", err) } out, err2 := json.Marshal(x) if err2 != nil { t.Errorf("Error marshaling version: %s", err2) } got := string(out) want := fmt.Sprintf("%q", sVer) if got != want { t.Errorf("Error marshaling unexpected marshaled content: got=%q want=%q", got, want) } } func TestJsonUnmarshal(t *testing.T) { sVer := "1.1.1" ver := &Version{} err := json.Unmarshal([]byte(fmt.Sprintf("%q", sVer)), ver) if err != nil { t.Errorf("Error unmarshaling version: %s", err) } got := ver.String() want := sVer if got != want { t.Errorf("Error unmarshaling unexpected object content: got=%q want=%q", got, want) } } func TestSQLScanner(t *testing.T) { sVer := "1.1.1" x, err := StrictNewVersion(sVer) if err != nil { t.Errorf("Error creating version: %s", err) } var s sql.Scanner = x var out *Version var ok bool if out, ok = s.(*Version); !ok { t.Errorf("Error expected Version type, got=%T want=%T", s, Version{}) } got := out.String() want := sVer if got != want { t.Errorf("Error sql scanner unexpected scan content: got=%q want=%q", got, want) } } func TestDriverValuer(t *testing.T) { sVer := "1.1.1" x, err := StrictNewVersion(sVer) if err != nil { t.Errorf("Error creating version: %s", err) } got, err := x.Value() if err != nil { t.Fatalf("Error getting value, got %v", err) } want := sVer if got != want { t.Errorf("Error driver valuer unexpected value content: got=%q want=%q", got, want) } } func TestValidatePrerelease(t *testing.T) { tests := []struct { pre string expected error }{ {"foo", nil}, {"alpha.1", nil}, {"alpha.01", ErrSegmentStartsZero}, {"foo☃︎", ErrInvalidPrerelease}, {"alpha.0-1", nil}, } for _, tc := range tests { if err := validatePrerelease(tc.pre); err != tc.expected { t.Errorf("Unexpected error %q for prerelease %q", err, tc.pre) } } } func TestValidateMetadata(t *testing.T) { tests := []struct { meta string expected error }{ {"foo", nil}, {"alpha.1", nil}, {"alpha.01", nil}, {"foo☃︎", ErrInvalidMetadata}, {"alpha.0-1", nil}, {"al-pha.1Phe70CgWe050H9K1mJwRUqTNQXZRERwLOEg37wpXUb4JgzgaD5YkL52ABnoyiE", nil}, } for _, tc := range tests { if err := validateMetadata(tc.meta); err != tc.expected { t.Errorf("Unexpected error %q for metadata %q", err, tc.meta) } } }