Skip to content

Commit

Permalink
NewVersion now validates and CoerceNewVersion handles semver-ish
Browse files Browse the repository at this point in the history
* NewVersion only handles spec compliant SemVer.
* NewVersion has a new method to parse the string that is faster
  than regex and uses fewer B/op. For stable versions is uses
  fewer allocs and when pre-release or metadata are on the version
  it uses 1 more alloc. All around performance is significantly
  faster
* CoerceNewVersion handles a semver-ish string. It tries to take
  a string that is similar to semver (e.g., 1.2) and turn it into
  a valid SemVer instance. This is similar to the previous handling

Closes #75
  • Loading branch information
mattfarina committed Aug 29, 2019
1 parent 220fba6 commit 23f51de
Show file tree
Hide file tree
Showing 7 changed files with 374 additions and 72 deletions.
94 changes: 93 additions & 1 deletion benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,54 @@ import (
/* Constraint creation benchmarks */

func benchNewConstraint(c string, b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = semver.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, _ := semver.NewVersion(v)
constraint, _ := semver.NewConstraint(c)

Expand All @@ -50,30 +66,44 @@ func benchCheckVersion(c, v string, b *testing.B) {
}

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, _ := semver.NewVersion(v)
constraint, _ := semver.NewConstraint(c)

Expand All @@ -85,50 +115,74 @@ func benchValidateVersion(c, v string, b *testing.B) {
/* 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)
}

Expand All @@ -140,18 +194,56 @@ func benchNewVersion(v string, b *testing.B) {
}
}

func benchCoerceNewVersion(v string, b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = semver.CoerceNewVersion(v)
}
}

func BenchmarkNewVersionSimple(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
benchNewVersion("1.0.0", b)
}

func BenchmarkCoerceNewVersionSimple(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
benchCoerceNewVersion("1.0.0", b)
}

func BenchmarkNewVersionPre(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
benchNewVersion("1.0.0-alpha", b)
}

func BenchmarkCoerceNewVersionPre(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
benchCoerceNewVersion("1.0.0-alpha", b)
}

func BenchmarkNewVersionMeta(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
benchNewVersion("1.0.0+metadata", b)
}

func BenchmarkCoerceNewVersionMeta(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
benchCoerceNewVersion("1.0.0+metadata", b)
}

func BenchmarkNewVersionMetaDash(b *testing.B) {
benchNewVersion("1.0.0+metadata-dash", b)
b.ReportAllocs()
b.ResetTimer()
benchNewVersion("1.0.0-alpha.1+meta.data", b)
}

func BenchmarkCoerceNewVersionMetaDash(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
benchCoerceNewVersion("1.0.0-alpha.1+meta.data", b)
}
2 changes: 1 addition & 1 deletion collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package semver
// Collection is a collection of Version instances and implements the sort
// interface. See the sort package for more details.
// https://golang.org/pkg/sort/
type Collection []*Version
type Collection []Version

// Len returns the length of a collection. The number of Version instances
// on the slice.
Expand Down
4 changes: 2 additions & 2 deletions collection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ func TestCollection(t *testing.T) {
"0.4.2",
}

vs := make([]*Version, len(raw))
vs := make([]Version, len(raw))
for i, r := range raw {
v, err := NewVersion(r)
v, err := CoerceNewVersion(r)
if err != nil {
t.Errorf("Error parsing version: %s", err)
}
Expand Down
28 changes: 14 additions & 14 deletions constraints.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func NewConstraint(c string) (*Constraints, error) {
}

// Check tests if a version satisfies the constraints.
func (cs Constraints) Check(v *Version) bool {
func (cs Constraints) Check(v Version) bool {
// loop over the ORs and check the inner ANDs
for _, o := range cs.constraints {
joy := true
Expand All @@ -62,7 +62,7 @@ func (cs Constraints) Check(v *Version) bool {

// 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) {
func (cs Constraints) Validate(v Version) (bool, []error) {
// loop over the ORs and check the inner ANDs
var e []error
for _, o := range cs.constraints {
Expand Down Expand Up @@ -143,7 +143,7 @@ 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
con Version

// The original parsed version (e.g., 4.x from != 4.x)
orig string
Expand All @@ -155,11 +155,11 @@ type constraint struct {
}

// Check if a version meets the constraint
func (c *constraint) check(v *Version) bool {
func (c *constraint) check(v Version) bool {
return c.function(v, c)
}

type cfunc func(v *Version, c *constraint) bool
type cfunc func(v Version, c *constraint) bool

func parseConstraint(c string) (*constraint, error) {
m := constraintRegex.FindStringSubmatch(c)
Expand All @@ -185,7 +185,7 @@ func parseConstraint(c string) (*constraint, error) {
ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6])
}

con, err := NewVersion(ver)
con, err := CoerceNewVersion(ver)
if err != nil {

// The constraintRegex should catch any regex parsing errors. So,
Expand All @@ -206,7 +206,7 @@ func parseConstraint(c string) (*constraint, error) {
}

// Constraint functions
func constraintNotEqual(v *Version, c *constraint) bool {
func constraintNotEqual(v Version, c *constraint) bool {
if c.dirty {

// If there is a pre-release on the version but the constraint isn't looking
Expand All @@ -231,7 +231,7 @@ func constraintNotEqual(v *Version, c *constraint) bool {
return !v.Equal(c.con)
}

func constraintGreaterThan(v *Version, c *constraint) bool {
func constraintGreaterThan(v Version, c *constraint) bool {

// If there is a pre-release on the version but the constraint isn't looking
// for them assume that pre-releases are not compatible. See issue 21 for
Expand All @@ -243,7 +243,7 @@ func constraintGreaterThan(v *Version, c *constraint) bool {
return v.Compare(c.con) == 1
}

func constraintLessThan(v *Version, c *constraint) bool {
func constraintLessThan(v Version, c *constraint) bool {
// If there is a pre-release on the version but the constraint isn't looking
// for them assume that pre-releases are not compatible. See issue 21 for
// more details.
Expand All @@ -264,7 +264,7 @@ func constraintLessThan(v *Version, c *constraint) bool {
return true
}

func constraintGreaterThanEqual(v *Version, c *constraint) bool {
func constraintGreaterThanEqual(v Version, c *constraint) bool {

// If there is a pre-release on the version but the constraint isn't looking
// for them assume that pre-releases are not compatible. See issue 21 for
Expand All @@ -276,7 +276,7 @@ func constraintGreaterThanEqual(v *Version, c *constraint) bool {
return v.Compare(c.con) >= 0
}

func constraintLessThanEqual(v *Version, c *constraint) bool {
func constraintLessThanEqual(v Version, c *constraint) bool {
// If there is a pre-release on the version but the constraint isn't looking
// for them assume that pre-releases are not compatible. See issue 21 for
// more details.
Expand All @@ -303,7 +303,7 @@ func constraintLessThanEqual(v *Version, c *constraint) bool {
// ~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 {
func constraintTilde(v Version, c *constraint) bool {
// If there is a pre-release on the version but the constraint isn't looking
// for them assume that pre-releases are not compatible. See issue 21 for
// more details.
Expand Down Expand Up @@ -335,7 +335,7 @@ func constraintTilde(v *Version, c *constraint) bool {

// When there is a .x (dirty) status it automatically opts in to ~. Otherwise
// it's a straight =
func constraintTildeOrEqual(v *Version, c *constraint) bool {
func constraintTildeOrEqual(v Version, c *constraint) bool {
// If there is a pre-release on the version but the constraint isn't looking
// for them assume that pre-releases are not compatible. See issue 21 for
// more details.
Expand All @@ -357,7 +357,7 @@ func constraintTildeOrEqual(v *Version, c *constraint) bool {
// ^1.2, ^1.2.x --> >=1.2.0, <2.0.0
// ^1.2.3 --> >=1.2.3, <2.0.0
// ^1.2.0 --> >=1.2.0, <2.0.0
func constraintCaret(v *Version, c *constraint) bool {
func constraintCaret(v Version, c *constraint) bool {
// If there is a pre-release on the version but the constraint isn't looking
// for them assume that pre-releases are not compatible. See issue 21 for
// more details.
Expand Down
Loading

0 comments on commit 23f51de

Please sign in to comment.