From fbe76cfa08cd0fa2695a1e8feea4687221ed2b32 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 14 Mar 2018 11:18:12 -0400 Subject: [PATCH] fix pre-release checks in constraints Pre-releases should not be automatically accepted as candidate versions for constraints without a pre-release component. This follows the logic documented for RubyGems which appears to be the inspiration for the constraint operators, and matches the behavior of other version packages. Adding a pre-release component to a pessimistic constraint will limit accepted versions to pre-releases only matching the base segments. Adding a pre-release to other constraint types will accept any comparable version, but pre-releases must still match the base segments. --- constraint.go | 34 ++++++++++++++++++++++++++++++---- constraint_test.go | 13 +++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/constraint.go b/constraint.go index 8c73df0..d055759 100644 --- a/constraint.go +++ b/constraint.go @@ -2,6 +2,7 @@ package version import ( "fmt" + "reflect" "regexp" "strings" ) @@ -113,6 +114,26 @@ func parseSingle(v string) (*Constraint, error) { }, nil } +func prereleaseCheck(v, c *Version) bool { + switch vPre, cPre := v.Prerelease() != "", c.Prerelease() != ""; { + case cPre && vPre: + // A constraint with a pre-release can only match a pre-release version + // with the same base segments. + return reflect.DeepEqual(c.Segments64(), v.Segments64()) + + case !cPre && vPre: + // A constraint without a pre-release can only match a version without a + // pre-release. + return false + + case cPre && !vPre: + // OK, except with the pessimistic operator + case !cPre && !vPre: + // OK + } + return true +} + //------------------------------------------------------------------- // Constraint functions //------------------------------------------------------------------- @@ -126,22 +147,27 @@ func constraintNotEqual(v, c *Version) bool { } func constraintGreaterThan(v, c *Version) bool { - return v.Compare(c) == 1 + return prereleaseCheck(v, c) && v.Compare(c) == 1 } func constraintLessThan(v, c *Version) bool { - return v.Compare(c) == -1 + return prereleaseCheck(v, c) && v.Compare(c) == -1 } func constraintGreaterThanEqual(v, c *Version) bool { - return v.Compare(c) >= 0 + return prereleaseCheck(v, c) && v.Compare(c) >= 0 } func constraintLessThanEqual(v, c *Version) bool { - return v.Compare(c) <= 0 + return prereleaseCheck(v, c) && v.Compare(c) <= 0 } func constraintPessimistic(v, c *Version) bool { + // Using a pessimistic constraint with a pre-release, restricts versions to pre-releases + if !prereleaseCheck(v, c) || (c.Prerelease() != "" && v.Prerelease() == "") { + return false + } + // If the version being checked is naturally less than the constraint, then there // is no way for the version to be valid against the constraint if v.LessThan(c) { diff --git a/constraint_test.go b/constraint_test.go index 2e733a4..9c5bee3 100644 --- a/constraint_test.go +++ b/constraint_test.go @@ -62,6 +62,19 @@ func TestConstraintCheck(t *testing.T) { {"~> 1.0.9.5", "1.0.9.6", true}, {"~> 1.0.9.5", "1.0.9.5.0", true}, {"~> 1.0.9.5", "1.0.9.5.1", true}, + {"~> 2.0", "2.1.0-beta", false}, + {"~> 2.1.0-a", "2.2.0", false}, + {"~> 2.1.0-a", "2.1.0", false}, + {"~> 2.1.0-a", "2.1.0-beta", true}, + {"~> 2.1.0-a", "2.2.0-alpha", false}, + {"> 2.0", "2.1.0-beta", false}, + {">= 2.1.0-a", "2.1.0-beta", true}, + {">= 2.1.0-a", "2.1.1-beta", false}, + {">= 2.0.0", "2.1.0-beta", false}, + {">= 2.1.0-a", "2.1.1", true}, + {">= 2.1.0-a", "2.1.1-beta", false}, + {">= 2.1.0-a", "2.1.0", true}, + {"<= 2.1.0-a", "2.0.0", true}, } for _, tc := range cases {