From c24f4e2157438be40b44e6ba02d90c6bfa934454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 23 Aug 2019 19:26:24 +0200 Subject: Fix inconsistent matching behaviour. According to the stated goal of "intuitive" behaviour, we want: ``Version('0.1.1-a1') not in Spec('<0.1.1')``. Tests, code and docs have been fixed. --- ChangeLog | 4 ++++ docs/reference.rst | 4 ++-- semantic_version/base.py | 6 +++++- tests/test_base.py | 48 ++++++++++++++++++++++++++---------------------- tests/test_match.py | 13 ++----------- 5 files changed, 39 insertions(+), 36 deletions(-) diff --git a/ChangeLog b/ChangeLog index 8ad05dc..687d899 100644 --- a/ChangeLog +++ b/ChangeLog @@ -10,6 +10,10 @@ ChangeLog (``Version(major=1, minor=2, patch=3)``) * Add ``Version.truncate()`` to build a truncated copy of a ``Version`` +*Bugfix:* + + * Fix inconsistent behaviour regarding versions with a prerelease specification. + *Removed:* * Remove support for Python2 (End of life 4 months after this release) diff --git a/docs/reference.rst b/docs/reference.rst index f9b4aed..a7fce0b 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -330,8 +330,8 @@ does not map well onto `SemVer`_ precedence rules: In order to have version specification behave naturally, the rules are the following: -* If no pre-release number was included in the specification, pre-release numbers - are ignored when deciding whether a version satisfies a specification. +* If no pre-release number was included in the specification, versions with a pre-release + numbers are excluded from matching that specification. * If no build metadata was included in the specification, build metadata is ignored when deciding whether a version satisfies a specification. diff --git a/semantic_version/base.py b/semantic_version/base.py index 8251efa..1caffa5 100644 --- a/semantic_version/base.py +++ b/semantic_version/base.py @@ -441,7 +441,7 @@ class Version: base_cmp, # Major is still mandatory make_optional(base_cmp), make_optional(base_cmp), - make_optional(prerelease_cmp), + prerelease_cmp, make_optional(build_cmp), ] else: @@ -568,6 +568,8 @@ class SpecItem: if self.kind == self.KIND_ANY: return True elif self.kind == self.KIND_LT: + if version.prerelease and self.spec.prerelease is None: + version = Version(major=version.major, minor=version.minor, patch=version.patch) return version < self.spec elif self.kind == self.KIND_LTE: return version <= self.spec @@ -578,6 +580,8 @@ class SpecItem: elif self.kind == self.KIND_GT: return version > self.spec elif self.kind == self.KIND_NEQ: + if version.prerelease and version.truncate() == self.spec.truncate() and self.spec.prerelease is None: + return False return version != self.spec elif self.kind == self.KIND_CARET: if self.spec.major != 0: diff --git a/tests/test_base.py b/tests/test_base.py index 76d120b..d5794b3 100755 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -83,7 +83,7 @@ class TopLevelTestCase(unittest.TestCase): matches = ( ('>=0.1.1', '0.1.2'), ('>=0.1.1', '0.1.1'), - ('>=0.1.1', '0.1.1-alpha'), + ('>=0.1.1', '0.1.2-alpha'), ('>=0.1.1,!=0.2.0', '0.2.1'), ) @@ -463,16 +463,16 @@ class SpecItemTestCase(unittest.TestCase): matches = { '==0.1.0': ( - ['0.1.0', '0.1.0-rc1', '0.1.0+build1', '0.1.0-rc1+build2'], - ['0.0.1', '0.2.0', '0.1.1'], + ['0.1.0', '0.1.0+build1'], + ['0.0.1', '0.1.0-rc1', '0.2.0', '0.1.1', '0.1.0-rc1+build2'], ), '=0.1.0': ( - ['0.1.0', '0.1.0-rc1', '0.1.0+build1', '0.1.0-rc1+build2'], - ['0.0.1', '0.2.0', '0.1.1'], + ['0.1.0', '0.1.0+build1'], + ['0.0.1', '0.1.0-rc1', '0.2.0', '0.1.1', '0.1.0-rc1+build2'], ), '0.1.0': ( - ['0.1.0', '0.1.0-rc1', '0.1.0+build1', '0.1.0-rc1+build2'], - ['0.0.1', '0.2.0', '0.1.1'], + ['0.1.0', '0.1.0+build1'], + ['0.0.1', '0.1.0-rc1', '0.2.0', '0.1.1', '0.1.0-rc1+build2'], ), '==0.1.2-rc3': ( ['0.1.2-rc3+build1', '0.1.2-rc3+build4.5'], @@ -502,6 +502,10 @@ class SpecItemTestCase(unittest.TestCase): ['0.2.3-rc3', '0.2.3', '0.2.3+1', '0.2.3-rc2', '0.2.3-rc2+1'], ['0.2.3-rc1', '0.2.2'], ), + '>=0.2.3': ( + ['0.2.3', '0.2.3+1'], + ['0.2.3-rc3', '0.2.3-rc2', '0.2.3-rc2+1', '0.2.3-rc1', '0.2.2'], + ), '==0.2.3+': ( ['0.2.3'], ['0.2.3+rc1', '0.2.4', '0.2.3-rc2'], @@ -523,32 +527,32 @@ class SpecItemTestCase(unittest.TestCase): ['0.3.4', '0.3.4+b1'], ), '~1.1.2': ( - ['1.1.3', '1.1.2-alpha', '1.1.2-alpha+b1'], - ['1.1.1', '1.2.1', '2.1.0'], + ['1.1.3', '1.1.2+b1'], + ['1.1.1', '1.1.2-alpha', '1.1.2-alpha+b1', '1.2.1', '2.1.0'], ), '^1.1.2': ( - ['1.1.3', '1.2.1', '1.1.2-alpha', '1.1.2-alpha+b1'], - ['1.1.1', '2.1.0'], + ['1.1.3', '1.1.2+b1', '1.2.1'], + ['1.1.1', '1.1.2-alpha', '1.1.2-alpha+b1', '2.1.0'], ), '^0.1.2': ( - ['0.1.2', '0.1.2-alpha', '0.1.3'], - ['0.2.0', '1.1.2', '0.1.1'], + ['0.1.2', '0.1.2+b1', '0.1.3'], + ['0.1.2-alpha', '0.2.0', '1.1.2', '0.1.1'], ), '^0.0.2': ( - ['0.0.2', '0.0.2-alpha', '0.0.2+abb'], - ['0.1.0', '0.0.3', '1.0.0'], + ['0.0.2', '0.0.2+abb'], + ['0.0.2-alpha', '0.1.0', '0.0.3', '1.0.0'], ), '~=1.4.5': ( ['1.4.5', '1.4.10-alpha', '1.4.10'], - ['1.3.6', '1.4.4', '1.5.0'], + ['1.3.6', '1.4.4', '1.4.5-alpha', '1.5.0'], ), '~=1.4.0': ( ['1.4.0', '1.4.10-alpha', '1.4.10'], - ['1.3.6', '1.3.9', '1.5.0'], + ['1.3.6', '1.3.9', '1.4.0-alpha', '1.5.0'], ), '~=1.4': ( ['1.4.0', '1.6.10-alpha', '1.6.10'], - ['1.3.0', '2.0.0'], + ['1.3.0', '1.4.0-alpha', '2.0.0'], ), } @@ -646,12 +650,12 @@ class SpecTestCase(unittest.TestCase): self.assertIn(str(base.SpecItem(spec_text)), repr(spec_list)) matches = { - # At least 0.1.1 including pre-releases, less than 0.1.2 excluding pre-releases + # At least 0.1.1 excluding pre-releases, less than 0.1.2 excluding pre-releases '>=0.1.1,<0.1.2': ( - ['0.1.1', '0.1.1+4', '0.1.1-alpha'], - ['0.1.2-alpha', '0.1.2', '1.3.4'], + ['0.1.1', '0.1.1+4'], + ['0.1.1-alpha', '0.1.2-alpha', '0.1.2', '1.3.4'], ), - # At least 0.1.0 without pre-releases, less than 0.1.4 excluding pre-releases, + # At least 0.1.0 with pre-releases, less than 0.1.4 excluding pre-releases, # neither 0.1.3-rc1 nor any build of that version, # not 0.1.0+b3 precisely '>=0.1.0-,!=0.1.3-rc1,!=0.1.0+b3,<0.1.4': ( diff --git a/tests/test_match.py b/tests/test_match.py index 16d7e5a..4bf7162 100755 --- a/tests/test_match.py +++ b/tests/test_match.py @@ -47,22 +47,13 @@ class MatchTestCase(unittest.TestCase): '1.0.0', ], '==0.1.2': [ - '0.1.2-rc1', - '0.1.2-rc1.3.4', '0.1.2+build42-12.2012-01-01.12h23', - '0.1.2-rc1.3-14.15+build.2012-01-01.11h34', ], '=0.1.2': [ - '0.1.2-rc1', - '0.1.2-rc1.3.4', '0.1.2+build42-12.2012-01-01.12h23', - '0.1.2-rc1.3-14.15+build.2012-01-01.11h34', ], '0.1.2': [ - '0.1.2-rc1', - '0.1.2-rc1.3.4', '0.1.2+build42-12.2012-01-01.12h23', - '0.1.2-rc1.3-14.15+build.2012-01-01.11h34', ], '<=0.1.2': [ '0.1.1', @@ -146,9 +137,9 @@ class MatchTestCase(unittest.TestCase): spec = semantic_version.Spec(spec_text) self.assertNotEqual(spec, spec_text) version = semantic_version.Version(version_text) + self.assertIn(version, spec) self.assertTrue(spec.match(version), "%r does not match %r" % (version, spec)) self.assertTrue(semantic_version.match(spec_text, version_text)) - self.assertTrue(version in spec, "%r not in %r" % (version, spec)) def test_contains(self): spec = semantic_version.Spec('<=0.1.1') @@ -164,7 +155,7 @@ class MatchTestCase(unittest.TestCase): strict_spec = semantic_version.Spec('>=0.1.1-') lax_spec = semantic_version.Spec('>=0.1.1') version = semantic_version.Version('0.1.1-rc1+4.2') - self.assertTrue(version in lax_spec, "%r should be in %r" % (version, lax_spec)) + self.assertFalse(version in lax_spec, "%r should not be in %r" % (version, lax_spec)) self.assertFalse(version in strict_spec, "%r should not be in %r" % (version, strict_spec)) def test_build_check(self): -- cgit v1.2.1