Skip to content

Commit

Permalink
Add tests for compound version range comparisons. Fix behaviour. Remo…
Browse files Browse the repository at this point in the history
…ve deprecated calls.

Update upgrade functionality to accept compound ranges.
  • Loading branch information
rtibbles committed Nov 20, 2024
1 parent 7d5f3e9 commit 4dd33e2
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 21 deletions.
51 changes: 51 additions & 0 deletions kolibri/core/test/test_upgrade.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@ def test_filter_old_version():
unfiltered_mock.assert_called_once()


def test_filter_compound_old_version():
filtered_mock = Mock()
unfiltered_mock = Mock()

filtered = VersionUpgrade(old_version=">1.0.1,<1.1.1", upgrade=filtered_mock)
not_filtered = VersionUpgrade(upgrade=unfiltered_mock)
with patch(
"kolibri.core.upgrade.get_upgrades", return_value=[not_filtered, filtered]
):
run_upgrades("1.0.0", "1.1.2")
filtered_mock.assert_not_called()
unfiltered_mock.assert_called_once()


def test_not_filter_alpha_version():
unfiltered_mock = Mock()

Expand Down Expand Up @@ -59,6 +73,17 @@ def test_order_old_version():
function.assert_has_calls([call(0), call(1)])


def test_order_compound_old_version():
function = Mock()

first = VersionUpgrade(old_version=">0.9.1,<0.10.1", upgrade=lambda: function(0))
second = VersionUpgrade(upgrade=lambda: function(1))

with patch("kolibri.core.upgrade.get_upgrades", return_value=[second, first]):
run_upgrades("0.10.0", "1.1.2")
function.assert_has_calls([call(0), call(1)])


def test_order_new_version():
function = Mock()

Expand All @@ -70,6 +95,17 @@ def test_order_new_version():
function.assert_has_calls([call(0), call(1)])


def test_order_compound_new_version():
function = Mock()

first = VersionUpgrade(new_version=">0.10.1,<1.1.3", upgrade=lambda: function(0))
second = VersionUpgrade(upgrade=lambda: function(1))

with patch("kolibri.core.upgrade.get_upgrades", return_value=[second, first]):
run_upgrades("0.10.0", "1.1.2")
function.assert_has_calls([call(0), call(1)])


def test_order_old_and_new_version():
function = Mock()

Expand Down Expand Up @@ -100,6 +136,21 @@ def test_filter_new_version():
unfiltered_mock.assert_called_once()


def test_filter_compound_new_version():
filtered_mock = Mock()
unfiltered_mock = Mock()

filtered = VersionUpgrade(new_version=">1.1.1,<1.1.3", upgrade=filtered_mock)
not_filtered = VersionUpgrade(upgrade=unfiltered_mock)

with patch(
"kolibri.core.upgrade.get_upgrades", return_value=[not_filtered, filtered]
):
run_upgrades("1.0.1", "1.1.0")
filtered_mock.assert_not_called()
unfiltered_mock.assert_called_once()


def test_blank_old_version():
function = Mock()

Expand Down
7 changes: 2 additions & 5 deletions kolibri/core/upgrade.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from importlib import import_module

from django.apps import apps
from semver import match
from semver import VersionInfo

import kolibri
from kolibri.utils.version import get_version_and_operator_from_range
from kolibri.utils.version import normalize_version_to_semver
from kolibri.utils.version import version_matches_range


CURRENT_VERSION = VersionInfo.parse(normalize_version_to_semver(kolibri.__version__))
Expand Down Expand Up @@ -110,10 +110,7 @@ def wrapper(upgrade):
def matches_version(version, version_range):
if version_range is None or not version:
return True
# For the purposes of upgrade comparison, treat dev versions as alphas
version = normalize_version_to_semver(version).replace("dev", "a")
version_range = "".join(get_version_and_operator_from_range(version_range))
return match(version, version_range)
return version_matches_range(version, version_range)


def get_upgrades(app_configs=None):
Expand Down
108 changes: 104 additions & 4 deletions kolibri/utils/tests/test_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import unittest

import mock
from parameterized import parameterized

import kolibri
from kolibri.utils import version
Expand All @@ -14,6 +15,25 @@
get_version = version.get_version.__wrapped__ # @UndefinedVariable


def _sanitize(name):
name = name.replace(" ", "_")
name = name.replace(">=", "gte")
name = name.replace(">", "gt")
name = name.replace("<=", "lte")
name = name.replace("<", "lt")
name = name.replace("==", "eq")
name = name.replace("!=", "ne")
name = name.replace(".", "_")
name = name.replace("+", "_")
name = name.replace("-", "_")
name = name.replace(",", "_")
return name


def _name_func(test_func, param_num, params):
return f"{test_func.__name__}_{param_num}_{_sanitize(params.args[0])}_{_sanitize(params.args[1])}_{params.args[2]}"


class TestKolibriVersion(unittest.TestCase):
def test_version(self):
"""
Expand Down Expand Up @@ -342,31 +362,31 @@ def test_normalize_version_to_semver_tripartite(self):
version.normalize_version_to_semver(
"0.15.0",
),
"0.15.0-c",
"0.15.0",
)

def test_normalize_version_to_semver_bipartite(self):
self.assertEqual(
version.normalize_version_to_semver(
"1.10",
),
"1.10-c",
"1.10",
)

def test_normalize_version_to_semver_alpa(self):
self.assertEqual(
version.normalize_version_to_semver(
"0.14a1",
),
"0.14-a.1.c",
"0.14-a.1",
)

def test_normalize_version_to_semver_beta(self):
self.assertEqual(
version.normalize_version_to_semver(
"0.16b1",
),
"0.16-b.1.c",
"0.16-b.1",
)

@mock.patch("kolibri.utils.version.get_git_describe", return_value="v0.15.8")
Expand All @@ -385,3 +405,83 @@ def test_get_version_from_file(self, describe_mock):
"0.15.8",
)
assert describe_mock.call_count == 1

@parameterized.expand(
[
("0.15.8", ">=0.15.8", True),
("0.15.7", ">=0.15.8", False),
("0.15.8a5", ">=0.15.8", False),
("0.15.9a5", ">=0.15.8", True),
("0.15.8a5.dev0+git.682.g0be46de2", ">=0.15.8", False),
("0.15.9a5.dev0+git.682.g0be46de2", ">=0.15.8", True),
("0.15.9", ">0.15.8", True),
("0.15.8", ">0.15.8", False),
("0.15.9a5.dev0+git.682.g0be46de2", ">0.15.8", True),
("0.15.8a5.dev0+git.682.g0be46de2", ">0.15.8", False),
("0.15.9a5", ">0.15.8", True),
("0.15.8a5", ">0.15.8", False),
("0.15.9b5", ">0.15.8", True),
("0.15.8b5", ">0.15.8", False),
("0.15.9rc5", ">0.15.8", True),
("0.15.8rc5", ">0.15.8", False),
("0.15.8", "<=0.15.8", True),
("0.15.9", "<=0.15.8", False),
("0.15.9a5.dev0+git.682.g0be46de2", "<=0.15.8", False),
("0.15.8a5.dev0+git.682.g0be46de2", "<=0.15.8", True),
("0.15.9a5", "<=0.15.8", False),
("0.15.8a5", "<=0.15.8", True),
("0.15.9b5", "<=0.15.8", False),
("0.15.8b5", "<=0.15.8", True),
("0.15.9rc5", "<=0.15.8", False),
("0.15.8rc5", "<=0.15.8", True),
("0.15.7", "<0.15.8", True),
("0.15.8", "<0.15.8", False),
("0.15.8a5.dev0+git.682.g0be46de2", "<0.15.8", True),
("0.15.9a5.dev0+git.682.g0be46de2", "<0.15.8", False),
("0.15.8a5", "<0.15.8", True),
("0.15.9a5", "<0.15.8", False),
("0.15.8b5", "<0.15.8", True),
("0.15.9b5", "<0.15.8", False),
("0.15.8rc5", "<0.15.8", True),
("0.15.9rc5", "<0.15.8", False),
("0.15.8", "==0.15.8", True),
("0.15.9", "==0.15.8", False),
("0.15.7", "!=0.15.8", True),
("0.15.8", "!=0.15.8", False),
],
name_func=_name_func,
)
def test_version_matches_simple_range(self, version_string, version_range, matches):
self.assertEqual(
version.version_matches_range(version_string, version_range), matches
)

@parameterized.expand(
[
("0.15.8", ">0.15.8,<0.16.0", False),
("0.15.7", ">0.15.8,<0.16.0", False),
("0.15.8a5.dev+git.682.g0be46de2", ">0.15.8,<0.16.0", False),
("0.15.9a5.dev+git.682.g0be46de2", ">0.15.8,<0.16.0", True),
("0.15.8a5", ">0.15.8,<0.16.0", False),
("0.15.9a5", ">0.15.8,<0.16.0", True),
("0.15.8b5", ">0.15.8,<0.16.0", False),
("0.15.9b5", ">0.15.8,<0.16.0", True),
("0.15.8rc5", ">0.15.8,<0.16.0", False),
("0.15.9rc5", ">0.15.8,<0.16.0", True),
("0.15.9", ">0.15.8,<0.16.0", True),
("0.16.0", ">0.15.8,<0.16.0", False),
("0.15.9", ">0.15.8,<0.16.0", True),
("0.15.8", ">=0.15.8,<0.16.0", True),
("0.15.7", ">=0.15.8,<0.16.0", False),
("0.16.0", ">=0.15.8,<0.16.0", False),
("0.16.0", ">=0.15.8,<=0.16.0", True),
("0.16.1", ">=0.15.8,<=0.16.0", False),
],
name_func=_name_func,
)
def test_version_matches_compound_range(
self, version_string, compound_range, matches
):
self.assertEqual(
version.version_matches_range(version_string, compound_range), matches
)
22 changes: 10 additions & 12 deletions kolibri/utils/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,10 +379,10 @@ def version_matches_range(version, version_range):

# extract and normalize version strings
operator, range_version = get_version_and_operator_from_range(version_range)
version = normalize_version_to_semver(version)
version = semver.VersionInfo.parse(normalize_version_to_semver(version))

# check whether the version is in the range
return semver.match(version, operator + range_version)
return version.match(operator + range_version)


def normalize_version_to_semver(version):
Expand All @@ -405,10 +405,6 @@ def normalize_version_to_semver(version):
if after_pieces:
after = ".".join([piece for piece in after_pieces.group() if piece])

# position final releases between alphas, betas, and further dev
if not dev:
after = (after + ".c").strip(".")

# make sure dev versions are sorted nicely relative to one another
dev = (dev or "").replace("+", ".").replace("-", ".")

Expand All @@ -430,19 +426,21 @@ def truncate_version(version, truncation_level=PATCH_VERSION):
"""
import semver

v = semver.parse_version_info(
v = semver.VersionInfo.parse(
normalize_version_to_semver(version).replace(".dev", "+dev")
)

if truncation_level == MAJOR_VERSION:
return semver.format_version(v.major, 0, 0)
return str(semver.VersionInfo(major=v.major, minor=0, patch=0))
if truncation_level == MINOR_VERSION:
return semver.format_version(v.major, v.minor, 0)
return str(semver.VersionInfo(major=v.major, minor=v.minor, patch=0))
if truncation_level == PATCH_VERSION:
return semver.format_version(v.major, v.minor, v.patch)
return str(semver.VersionInfo(major=v.major, minor=v.minor, patch=v.patch))
if truncation_level == PRERELEASE_VERSION:
truncated_version = semver.format_version(
v.major, v.minor, v.patch, prerelease=v.prerelease
truncated_version = str(
semver.VersionInfo(
major=v.major, minor=v.minor, patch=v.patch, prerelease=v.prerelease
)
)
# ensure prerelease formatting matches our convention
truncated_version, prerelease_version = truncated_version.split("-")
Expand Down

0 comments on commit 4dd33e2

Please sign in to comment.