Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Semver class #514

Merged
merged 4 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/splitclient-rb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
require 'splitclient-rb/engine/matchers/equal_to_boolean_matcher'
require 'splitclient-rb/engine/matchers/equal_to_matcher'
require 'splitclient-rb/engine/matchers/matches_string_matcher'
require 'splitclient-rb/engine/matchers/semver'
require 'splitclient-rb/engine/evaluator/splitter'
require 'splitclient-rb/engine/impressions/noop_unique_keys_tracker'
require 'splitclient-rb/engine/impressions/unique_keys_tracker'
Expand Down
180 changes: 180 additions & 0 deletions lib/splitclient-rb/engine/matchers/semver.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# frozen_string_literal: true

module SplitIoClient
class Semver
METADATA_DELIMITER = '+'
PRE_RELEASE_DELIMITER = '-'
VALUE_DELIMITER = '.'

attr_reader :major, :minor, :patch, :pre_release, :is_stable, :old_version

def initialize(version)
@major = 0
@minor = 0
@patch = 0
@pre_release = []
@is_stable = false
@old_version = version
parse
chillaq marked this conversation as resolved.
Show resolved Hide resolved
end

#
# Class builder
#
# @param version [String] raw version as read from splitChanges response.
#
# @return [type] Semver instance
def self.build(version, logger)
new(version)
rescue RuntimeError => e
logger.warn("Failed to parse Semver data: #{e}")
nil
end

#
# Check if there is any metadata characters in self._old_version.
#
# @return [type] String semver without the metadata
#
def remove_metadata_if_exists
index = @old_version.index(METADATA_DELIMITER)
return @old_version if index.nil?

@old_version[0, index]
end

# Compare the current Semver object to a given Semver object, return:
# 0: if self == passed
# 1: if self > passed
# -1: if self < passed
#
# @param to_compare [trype] splitio.models.grammar.matchers.semver.Semver object
#
# @returns [Integer] based on comparison
def compare(to_compare)
return 0 if @old_version == to_compare.old_version

# Compare major, minor, and patch versions numerically
return compare_attributes(to_compare) if compare_attributes(to_compare) != 0

# Compare pre-release versions lexically
compare_pre_release(to_compare)
end

private

def integer?(value)
value.to_i.to_s == value
end

#
# Parse the string in self._old_version to update the other internal variables
#
def parse
without_metadata = remove_metadata_if_exists

index = without_metadata.index(PRE_RELEASE_DELIMITER)
if index.nil?
@is_stable = true
else
pre_release_data = without_metadata[index + 1..-1]
without_metadata = without_metadata[0, index]
@pre_release = pre_release_data.split(VALUE_DELIMITER)
end
assign_major_minor_and_patch(without_metadata)
end

#
# Set the major, minor and patch internal variables based on string passed.
#
# @param version [String] raw version containing major.minor.patch numbers.
def assign_major_minor_and_patch(version)
parts = version.split(VALUE_DELIMITER)
if parts.length != 3 ||
!(integer?(parts[0]) &&
integer?(parts[1]) &&
integer?(parts[2]))
raise "Unable to convert to Semver, incorrect format: #{version}"
end

@major = parts[0].to_i
@minor = parts[1].to_i
@patch = parts[2].to_i
end

#
# Compare 2 variables and return int as follows:
# 0: if var1 == var2
# 1: if var1 > var2
# -1: if var1 < var2
#
# @param var1 [type] String/Integer object that accept ==, < or > operators
# @param var2 [type] String/Integer object that accept ==, < or > operators
#
# @returns [Integer] based on comparison
def compare_vars(var1, var2)
return 0 if var1 == var2

return 1 if var1 > var2

-1
end

# Compare the current Semver object's major, minor, patch and is_stable attributes to a given Semver object, return:
# 0: if self == passed
# 1: if self > passed
# -1: if self < passed
#
# @param to_compare [trype] splitio.models.grammar.matchers.semver.Semver object
#
# @returns [Integer] based on comparison
def compare_attributes(to_compare)
result = compare_vars(@major, to_compare.major)
return result if result != 0

result = compare_vars(@minor, to_compare.minor)
return result if result != 0

result = compare_vars(@patch, to_compare.patch)
return result if result != 0

return -1 if !@is_stable && to_compare.is_stable

return 1 if @is_stable && !to_compare.is_stable

0
end

# Compare the current Semver object's pre_release attribute to a given Semver object, return:
# 0: if self == passed
# 1: if self > passed
# -1: if self < passed
#
# @param to_compare [trype] splitio.models.grammar.matchers.semver.Semver object
#
# @returns [Integer] based on comparison
def compare_pre_release(to_compare)
min_length = get_pre_min_length(to_compare)
0.upto(min_length - 1) do |i|
next if @pre_release[i] == to_compare.pre_release[i]

if integer?(@pre_release[i]) && integer?(to_compare.pre_release[i])
return compare_vars(@pre_release[i].to_i, to_compare.pre_release[i].to_i)
end

return compare_vars(@pre_release[i], to_compare.pre_release[i])
end
# Compare lengths of pre-release versions
compare_vars(@pre_release.length, to_compare.pre_release.length)
end

# Get minimum of current Semver object's pre_release attributes length to a given Semver object
#
# @param to_compare [trype] splitio.models.grammar.matchers.semver.Semver object
#
# @returns [Integer]
def get_pre_min_length(to_compare)
[@pre_release.length, to_compare.pre_release.length].min
end
end
end
59 changes: 59 additions & 0 deletions spec/engine/matchers/semver_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
require 'spec_helper'
require 'csv'

describe SplitIoClient::Semver do
let(:valid_versions) do
CSV.parse(File.read(File.expand_path(File.join(File.dirname(__FILE__),
'../../test_data/splits/semver/valid-semantic-versions.csv'))))
end
let(:invalid_versions) do
CSV.parse(File.read(File.expand_path(File.join(File.dirname(__FILE__),
'../../test_data/splits/semver/invalid-semantic-versions.csv'))))
end
let(:equal_to_versions) do
CSV.parse(File.read(File.expand_path(File.join(File.dirname(__FILE__),
'../../test_data/splits/semver/equal-to-semver.csv'))))
end

let(:logger) { Logger.new('/dev/null') }

context 'check versions' do
it 'accept valid versions' do
for i in (0..valid_versions.length-1)
expect(described_class.build(valid_versions[i][0], logger)).should_not be_nil
end
end
it 'reject invalid versions' do
for version in invalid_versions
expect(described_class.build(version[0], logger)).to eq(nil)
end
end
end

context 'compare versions' do
it 'equal and not equal' do
for i in (1..valid_versions.length-1)
expect(described_class.build(valid_versions[i][0], logger).compare(described_class.build(valid_versions[i][1], logger))).to eq(1)
expect(described_class.build(valid_versions[i][1], logger).compare(described_class.build(valid_versions[i][0], logger))).to eq(-1)
expect(described_class.build(valid_versions[i][0], logger).compare(described_class.build(valid_versions[i][0], logger))).to eq(0)
expect(described_class.build(valid_versions[i][1], logger).compare(described_class.build(valid_versions[i][1], logger))).to eq(0)
end
for i in (1..equal_to_versions.length-1)
if valid_versions[i][2]
expect(described_class.build(valid_versions[i][0], logger).compare(described_class.build(valid_versions[i][1], logger))).to eq(0)
else
expect(described_class.build(valid_versions[i][0], logger).compare(described_class.build(valid_versions[i][1], logger))).not_to eq(0)
end
end

end
end

def verify_version(semver, major, minor, patch, pre_release="", is_stable=True)
if semver.major == major && semver.minor == minor && semver.patch == patch &&
semver.pre_release == pre_release && semver.is_stable == is_stable
return true
end
return false
end
end
18 changes: 18 additions & 0 deletions spec/test_data/splits/semver/between-semver.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# version1, version2, version3, expected
1.1.1,2.2.2,3.3.3,true
1.1.1-rc.1,1.1.1-rc.2,1.1.1-rc.3,true
1.0.0-alpha,1.0.0-alpha.1,1.0.0-alpha.beta,true
1.0.0-alpha.1,1.0.0-alpha.beta,1.0.0-beta,true
1.0.0-alpha.beta,1.0.0-beta,1.0.0-beta.2,true
1.0.0-beta,1.0.0-beta.2,1.0.0-beta.11,true
1.0.0-beta.2,1.0.0-beta.11,1.0.0-rc.1,true
1.0.0-beta.11,1.0.0-rc.1,1.0.0,true
1.1.2,1.1.3,1.1.4,true
1.2.1,1.3.1,1.4.1,true
2.0.0,3.0.0,4.0.0,true
2.2.2,2.2.3-rc1,2.2.3,true
2.2.2,2.3.2-rc100,2.3.3,true
1.0.0-rc.1+build.1,1.2.3-beta,1.2.3-rc.1+build.123,true
3.3.3,3.3.3-alpha,3.3.4,false
2.2.2-rc.1,2.2.2+metadata,2.2.2-rc.10,false
1.1.1-rc.1,1.1.1-rc.3,1.1.1-rc.2,false
7 changes: 7 additions & 0 deletions spec/test_data/splits/semver/equal-to-semver.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# version1, version2, equals
1.1.1,1.1.1,true
1.1.1,1.1.1+metadata,false
1.1.1,1.1.1-rc.1,false
88.88.88,88.88.88,true
1.2.3----RC-SNAPSHOT.12.9.1--.12,1.2.3----RC-SNAPSHOT.12.9.1--.12,true
10.2.3-DEV-SNAPSHOT,10.2.3-SNAPSHOT-123,false
26 changes: 26 additions & 0 deletions spec/test_data/splits/semver/invalid-semantic-versions.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# invalid
1
1.2
1.alpha.2
+invalid
-invalid
-invalid+invalid
-invalid.01
alpha
alpha.beta
alpha.beta.1
alpha.1
alpha+beta
alpha_beta
alpha.
alpha..
beta
-alpha.
1.2
1.2.3.DEV
1.2-SNAPSHOT
1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788
1.2-RC-SNAPSHOT
-1.0.3-gamma+b7718
+justmeta
#99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12
25 changes: 25 additions & 0 deletions spec/test_data/splits/semver/valid-semantic-versions.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# higher, lower
1.1.2,1.1.1
1.0.0,1.0.0-rc.1
1.1.0-rc.1,1.0.0-beta.11
1.0.0-beta.11,1.0.0-beta.2
1.0.0-beta.2,1.0.0-beta
1.0.0-beta,1.0.0-alpha.beta
1.0.0-alpha.beta,1.0.0-alpha.1
1.0.0-alpha.1,1.0.0-alpha
2.2.2-rc.2+metadata-lalala,2.2.2-rc.1.2
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,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
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,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
9223372036854775807.9223372036854775807.9223372036854775807,9223372036854775807.9223372036854775807.9223372036854775806
1.1.1-alpha.beta.rc.build.java.pr.support.10,1.1.1-alpha.beta.rc.build.java.pr.support
1.1.2,1.1.1
1.2.1,1.1.1
2.1.1,1.1.1
1.1.1-rc.1,1.1.1-rc.0