Skip to content

Commit ccdb728

Browse files
author
Bilal Al
committed
added Semver class
1 parent 35011e4 commit ccdb728

File tree

2 files changed

+229
-0
lines changed

2 files changed

+229
-0
lines changed
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
"""Semver matcher classes."""
2+
import abc
3+
4+
class Semver(object, metaclass=abc.ABCMeta):
5+
"""Semver class."""
6+
7+
_METADATA_DELIMITER = "+"
8+
_PRE_RELEASE_DELIMITER = "-"
9+
_VALUE_DELIMITER = "."
10+
11+
def __init__(self, version):
12+
"""
13+
Class Initializer
14+
15+
:param version: raw version as read from splitChanges response.
16+
:type version: str
17+
"""
18+
self._major = 0
19+
self._minor = 0
20+
self._patch = 0
21+
self._pre_release = []
22+
self._is_stable = False
23+
self._old_version = version
24+
self.parse()
25+
26+
def parse(self):
27+
"""
28+
Parse the string in self._old_version to update the other internal variables
29+
"""
30+
without_metadata = self.remove_metadata_if_exists()
31+
32+
index = without_metadata.find(self._PRE_RELEASE_DELIMITER)
33+
if index == -1:
34+
self._is_stable = True
35+
else:
36+
pre_release_data = without_metadata[index+1:]
37+
without_metadata = without_metadata[:index]
38+
self._pre_release = pre_release_data.split(self._VALUE_DELIMITER)
39+
40+
self.set_major_minor_and_patch(without_metadata)
41+
42+
def remove_metadata_if_exists(self):
43+
"""
44+
Check if there is any metadata characters in self._old_version.
45+
46+
:returns: The semver string without the metadata
47+
:rtype: str
48+
"""
49+
index = self._old_version.find(self._METADATA_DELIMITER)
50+
if index == -1:
51+
return self._old_version
52+
53+
return self._old_version[:index]
54+
55+
def set_major_minor_and_patch(self, version):
56+
"""
57+
Set the major, minor and patch internal variables based on string passed.
58+
59+
:param version: raw version containing major.minor.patch numbers.
60+
:type version: str
61+
"""
62+
63+
parts = version.split(self._VALUE_DELIMITER)
64+
if len(parts) != 3 or not ( parts[0].isnumeric() and parts[1].isnumeric() and parts[2].isnumeric()):
65+
raise RuntimeError("Unable to convert to Semver, incorrect format: " + version)
66+
67+
self._major = int(parts[0])
68+
self._minor = int(parts[1])
69+
self._patch = int(parts[2])
70+
71+
def compare(self, to_compare):
72+
"""
73+
Compare the current Semver object to a given Semver object, return:
74+
0: if self == passed
75+
1: if self > passed
76+
2: if self < passed
77+
78+
:param to_compare: a Semver object
79+
:type to_compare: splitio.models.grammar.matchers.semver.Semver
80+
81+
:returns: integer based on comparison
82+
:rtype: int
83+
"""
84+
if self._old_version == to_compare._old_version:
85+
return 0
86+
87+
# Compare major, minor, and patch versions numerically
88+
result = self._compare_vars(self._major, to_compare._major)
89+
if result != 0:
90+
return result
91+
92+
result = self._compare_vars(self._minor, to_compare._minor)
93+
if result != 0:
94+
return result
95+
96+
result = self._compare_vars(self._patch, to_compare._patch)
97+
if result != 0:
98+
return result
99+
100+
if not self._is_stable and to_compare._is_stable:
101+
return -1
102+
elif self._is_stable and not to_compare._is_stable:
103+
return 1
104+
105+
# Compare pre-release versions lexically
106+
min_length = min(len(self._pre_release), len(to_compare._pre_release))
107+
for i in range(min_length):
108+
if self._pre_release[i] == to_compare._pre_release[i]:
109+
continue
110+
111+
if self._pre_release[i].isnumeric() and to_compare._pre_release[i].isnumeric():
112+
return self._compare_vars(int(self._pre_release[i]), int(to_compare._pre_release[i]))
113+
114+
return self._compare_vars(self._pre_release[i], to_compare._pre_release[i])
115+
116+
# Compare lengths of pre-release versions
117+
return self._compare_vars(len(self._pre_release), len(to_compare._pre_release))
118+
119+
def _compare_vars(self, var1, var2):
120+
"""
121+
Compare 2 variables and return int as follows:
122+
0: if var1 == var2
123+
1: if var1 > var2
124+
2: if var1 < var2
125+
126+
:param var1: any object accept ==, < or > operators
127+
:type var1: str/int
128+
:param var2: any object accept ==, < or > operators
129+
:type var2: str/int
130+
131+
:returns: integer based on comparison
132+
:rtype: int
133+
"""
134+
if var1 == var2:
135+
return 0
136+
if var1 > var2:
137+
return 1
138+
return -1
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
"""Condition model tests module."""
2+
import pytest
3+
4+
from splitio.models.grammar.matchers.semver import Semver
5+
6+
class SemverTests(object):
7+
"""Test the semver object model."""
8+
9+
valid_versions = ["1.1.2", "1.1.1", "1.0.0", "1.0.0-rc.1", "1.0.0-beta.11", "1.0.0-beta.2",
10+
"1.0.0-beta", "1.0.0-alpha.beta", "1.0.0-alpha.1", "1.0.0-alpha", "2.2.2-rc.2+metadata-lalala", "2.2.2-rc.1.2",
11+
"1.2.3", "0.0.4", "1.1.2+meta", "1.1.2-prerelease+meta", "1.0.0-beta", "1.0.0-alpha", "1.0.0-alpha0.valid",
12+
"1.0.0-alpha.0valid", "1.0.0-rc.1+build.1", "1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay",
13+
"10.2.3-DEV-SNAPSHOT", "1.2.3-SNAPSHOT-123", "1.1.1-rc2", "1.0.0-0A.is.legal", "1.2.3----RC-SNAPSHOT.12.9.1--.12+788",
14+
"1.2.3----R-S.12.9.1--.12+meta", "1.2.3----RC-SNAPSHOT.12.9.1--.12.88", "1.2.3----RC-SNAPSHOT.12.9.1--.12",
15+
"9223372036854775807.9223372036854775807.9223372036854775807", "9223372036854775807.9223372036854775807.9223372036854775806",
16+
"1.1.1-alpha.beta.rc.build.java.pr.support.10", "1.1.1-alpha.beta.rc.build.java.pr.support"]
17+
18+
def test_valid_versions(self):
19+
major = [1, 1, 1, 1, 1, 1,
20+
1, 1, 1, 1, 2, 2,
21+
1, 0, 1, 1, 1, 1, 1,
22+
1, 1, 1,
23+
10, 1, 1, 1, 1,
24+
1, 1, 1,
25+
9223372036854775807, 9223372036854775807,
26+
1,1]
27+
minor = [1, 1, 0, 0, 0, 0,
28+
0, 0, 0, 0, 2, 2,
29+
2, 0, 1, 1, 0, 0, 0,
30+
0, 0, 0,
31+
2, 2, 1, 0, 2,
32+
2, 2, 2,
33+
9223372036854775807, 9223372036854775807,
34+
1, 1]
35+
patch = [2, 1, 0, 0, 0, 0,
36+
0, 0, 0, 0, 2, 2,
37+
3, 4, 2, 2, 0, 0, 0,
38+
0, 0, 0,
39+
3, 3, 1, 0, 3,
40+
3, 3, 3,
41+
9223372036854775807, 9223372036854775806,
42+
1, 1]
43+
pre_release = [[], [], [], ["rc","1"], ["beta","11"],["beta","2"],
44+
["beta"], ["alpha","beta"], ["alpha","1"], ["alpha"], ["rc","2"], ["rc","1","2"],
45+
[], [], [], ["prerelease"], ["beta"], ["alpha"], ["alpha0","valid"],
46+
["alpha","0valid"], ["rc","1"], ["alpha-a","b-c-somethinglong"],
47+
["DEV-SNAPSHOT"], ["SNAPSHOT-123"], ["rc2"], ["0A","is","legal"], ["---RC-SNAPSHOT","12","9","1--","12"],
48+
["---R-S","12","9","1--","12"], ["---RC-SNAPSHOT","12","9","1--","12","88"], ["---RC-SNAPSHOT","12","9","1--","12"],
49+
[], [],
50+
["alpha","beta","rc","build","java","pr","support","10"], ["alpha","beta","rc","build","java","pr","support"]]
51+
52+
for i in range(len(major)-1):
53+
semver = Semver(self.valid_versions[i])
54+
self._verify_version(semver, major[i], minor[i], patch[i], pre_release[i], pre_release[i]==[])
55+
56+
def test_invalid_versions(self):
57+
"""Test parsing invalid versions."""
58+
invalid_versions = [
59+
"1", "1.2", "1.alpha.2", "+invalid", "-invalid", "-invalid+invalid", "+justmeta",
60+
"-invalid.01", "alpha", "alpha.beta", "alpha.beta.1", "alpha.1", "alpha+beta",
61+
"alpha_beta", "alpha.", "alpha..", "beta", "-alpha.", "1.2", "1.2.3.DEV", "-1.0.3-gamma+b7718",
62+
"1.2-SNAPSHOT", "1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788", "1.2-RC-SNAPSHOT"]
63+
# "99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12"]
64+
65+
for version in invalid_versions:
66+
with pytest.raises(RuntimeError):
67+
semver = Semver(version)
68+
pass
69+
70+
def test_compare(self):
71+
cnt = 0
72+
for i in range(int(len(self.valid_versions)/2)):
73+
assert Semver(self.valid_versions[cnt]).compare(Semver(self.valid_versions[cnt+1])) == 1
74+
assert Semver(self.valid_versions[cnt+1]).compare(Semver(self.valid_versions[cnt])) == -1
75+
assert Semver(self.valid_versions[cnt]).compare(Semver(self.valid_versions[cnt])) == 0
76+
assert Semver(self.valid_versions[cnt+1]).compare(Semver(self.valid_versions[cnt+1])) == 0
77+
cnt = cnt + 2
78+
79+
assert Semver("1.1.1").compare(Semver("1.1.1")) == 0
80+
assert Semver("1.1.1").compare(Semver("1.1.1+metadata")) == 0
81+
assert Semver("1.1.1").compare(Semver("1.1.1-rc.1")) == 1
82+
assert Semver("88.88.88").compare(Semver("88.88.88")) == 0
83+
assert Semver("1.2.3----RC-SNAPSHOT.12.9.1--.12").compare(Semver("1.2.3----RC-SNAPSHOT.12.9.1--.12")) == 0
84+
assert Semver("10.2.3-DEV-SNAPSHOT").compare(Semver("10.2.3-SNAPSHOT-123")) == -1
85+
86+
def _verify_version(self, semver, major, minor, patch, pre_release="", is_stable=True):
87+
assert semver._major == major
88+
assert semver._minor == minor
89+
assert semver._patch == patch
90+
assert semver._pre_release == pre_release
91+
assert semver._is_stable == is_stable

0 commit comments

Comments
 (0)