Skip to content

Commit 657c48b

Browse files
feature: add semantic version types and test (#333)
* add ge and le for number and the semver types * add semantic version. however, i compare something to a target not target to something. So, this already needs a refactor * fix version * add a test with beta and add clenup per ali's comments * add better support for build and pre-release tags * cleanup * add invalid tests and make sure they don't crash the sdk * add checks for invalid sem ver * fix string compare * bring up code coverage * try and remove some of the flakieness in travis unit tests
1 parent 16bf801 commit 657c48b

File tree

11 files changed

+569
-15
lines changed

11 files changed

+569
-15
lines changed

OptimizelySwiftSDK.xcodeproj/project.pbxproj

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,21 @@
88

99
/* Begin PBXBuildFile section */
1010
0B11272E2242D817002A9C20 /* Optimizely.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EBAEB6C21E3FEF800D13AA9 /* Optimizely.framework */; };
11+
0B97DD94249D327F003DE606 /* SemanticVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B97DD93249D327F003DE606 /* SemanticVersion.swift */; };
12+
0B97DD95249D327F003DE606 /* SemanticVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B97DD93249D327F003DE606 /* SemanticVersion.swift */; };
13+
0B97DD99249D332C003DE606 /* SemanticVersionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B97DD96249D332C003DE606 /* SemanticVersionTests.swift */; };
14+
0B97DD9A249D332C003DE606 /* SemanticVersionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B97DD96249D332C003DE606 /* SemanticVersionTests.swift */; };
15+
0B97DD9B249D3733003DE606 /* SemanticVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B97DD93249D327F003DE606 /* SemanticVersion.swift */; };
16+
0B97DD9C249D3735003DE606 /* SemanticVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B97DD93249D327F003DE606 /* SemanticVersion.swift */; };
17+
0B97DD9D249D4A22003DE606 /* SemanticVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B97DD93249D327F003DE606 /* SemanticVersion.swift */; };
18+
0B97DD9E249D4A22003DE606 /* SemanticVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B97DD93249D327F003DE606 /* SemanticVersion.swift */; };
19+
0B97DD9F249D4A23003DE606 /* SemanticVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B97DD93249D327F003DE606 /* SemanticVersion.swift */; };
20+
0B97DDA0249D4A24003DE606 /* SemanticVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B97DD93249D327F003DE606 /* SemanticVersion.swift */; };
21+
0B97DDA1249D4A24003DE606 /* SemanticVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B97DD93249D327F003DE606 /* SemanticVersion.swift */; };
22+
0B97DDA2249D4A25003DE606 /* SemanticVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B97DD93249D327F003DE606 /* SemanticVersion.swift */; };
23+
0B97DDA3249D4A26003DE606 /* SemanticVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B97DD93249D327F003DE606 /* SemanticVersion.swift */; };
24+
0B97DDA4249D4A27003DE606 /* SemanticVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B97DD93249D327F003DE606 /* SemanticVersion.swift */; };
25+
0B97DDA5249D4A28003DE606 /* SemanticVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B97DD93249D327F003DE606 /* SemanticVersion.swift */; };
1126
0BAB9B0122567E34000DC388 /* (null) in Sources */ = {isa = PBXBuildFile; };
1227
6E12B1CA22C55A250005E9E6 /* optimizely_6372300739_v4.json in Resources */ = {isa = PBXBuildFile; fileRef = 6E75196222C5211100B2B157 /* optimizely_6372300739_v4.json */; };
1328
6E12B1CB22C55A250005E9E6 /* feature_rollout_toggle_on.json in Resources */ = {isa = PBXBuildFile; fileRef = 6E75196722C5211100B2B157 /* feature_rollout_toggle_on.json */; };
@@ -1450,6 +1465,8 @@
14501465
/* End PBXContainerItemProxy section */
14511466

14521467
/* Begin PBXFileReference section */
1468+
0B97DD93249D327F003DE606 /* SemanticVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SemanticVersion.swift; sourceTree = "<group>"; };
1469+
0B97DD96249D332C003DE606 /* SemanticVersionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SemanticVersionTests.swift; sourceTree = "<group>"; };
14531470
6E14CD632423F80B00010234 /* OptimizelyTests-Batch-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "OptimizelyTests-Batch-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
14541471
6E34A6162319EBB700BAE302 /* Notifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = "<group>"; };
14551472
6E34A623231ED04900BAE302 /* empty_datafile_new_project_id.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = empty_datafile_new_project_id.json; sourceTree = "<group>"; };
@@ -1948,6 +1965,7 @@
19481965
6E75169722C520D400B2B157 /* Audience */ = {
19491966
isa = PBXGroup;
19501967
children = (
1968+
0B97DD93249D327F003DE606 /* SemanticVersion.swift */,
19511969
6E75169822C520D400B2B157 /* Audience.swift */,
19521970
6E75169922C520D400B2B157 /* AttributeValue.swift */,
19531971
6E75169A22C520D400B2B157 /* ConditionLeaf.swift */,
@@ -2101,6 +2119,7 @@
21012119
6E7519AF22C5211100B2B157 /* TrafficAllocationTests.swift */,
21022120
6E7519B022C5211100B2B157 /* ProjectTests.swift */,
21032121
6E7519B122C5211100B2B157 /* ConditionHolderTests_Evaluate.swift */,
2122+
0B97DD96249D332C003DE606 /* SemanticVersionTests.swift */,
21042123
);
21052124
path = "OptimizelyTests-DataModel";
21062125
sourceTree = "<group>";
@@ -3020,6 +3039,7 @@
30203039
6E14CD902423F9A700010234 /* Variation.swift in Sources */,
30213040
6E14CD8E2423F9A700010234 /* FeatureVariable.swift in Sources */,
30223041
6E14CD8D2423F9A700010234 /* ProjectConfig.swift in Sources */,
3042+
0B97DD9F249D4A23003DE606 /* SemanticVersion.swift in Sources */,
30233043
6E14CD8F2423F9A700010234 /* Rollout.swift in Sources */,
30243044
6E14CD892423F9A100010234 /* ConditionLeaf.swift in Sources */,
30253045
6E14CD9F2423F9C300010234 /* ArrayEventForDispatch+Extension.swift in Sources */,
@@ -3098,6 +3118,7 @@
30983118
6E75170722C520D400B2B157 /* OptimizelyClient.swift in Sources */,
30993119
6E75174322C520D400B2B157 /* HandlerRegistryService.swift in Sources */,
31003120
6E75188922C520D400B2B157 /* Project.swift in Sources */,
3121+
0B97DD95249D327F003DE606 /* SemanticVersion.swift in Sources */,
31013122
6E7518B922C520D400B2B157 /* Variable.swift in Sources */,
31023123
6E34A6182319EBB800BAE302 /* Notifications.swift in Sources */,
31033124
);
@@ -3161,6 +3182,7 @@
31613182
6EA2CC2C2345618E001E7531 /* OptimizelyConfig.swift in Sources */,
31623183
6E7517D022C520D400B2B157 /* DefaultBucketer.swift in Sources */,
31633184
6E75180022C520D400B2B157 /* DataStoreUserDefaults.swift in Sources */,
3185+
0B97DDA3249D4A26003DE606 /* SemanticVersion.swift in Sources */,
31643186
6E9B11B322C5489500C22D81 /* MockUrlSession.swift in Sources */,
31653187
6E7517DC22C520D400B2B157 /* DefaultNotificationCenter.swift in Sources */,
31663188
6E75178622C520D400B2B157 /* ArrayEventForDispatch+Extension.swift in Sources */,
@@ -3197,6 +3219,7 @@
31973219
6E7516C222C520D400B2B157 /* DefaultEventDispatcher.swift in Sources */,
31983220
6E75188022C520D400B2B157 /* TrafficAllocation.swift in Sources */,
31993221
6E9B11DD22C548A200C22D81 /* OptimizelyClientTests_Valid.swift in Sources */,
3222+
0B97DDA0249D4A24003DE606 /* SemanticVersion.swift in Sources */,
32003223
6E7518D422C520D400B2B157 /* AttributeValue.swift in Sources */,
32013224
6E7518BC22C520D400B2B157 /* Variable.swift in Sources */,
32023225
6E75192822C520D500B2B157 /* DataStoreQueueStack.swift in Sources */,
@@ -3280,6 +3303,7 @@
32803303
6E75184722C520D400B2B157 /* Event.swift in Sources */,
32813304
6E75170D22C520D400B2B157 /* OptimizelyClient.swift in Sources */,
32823305
6E75177922C520D400B2B157 /* SDKVersion.swift in Sources */,
3306+
0B97DDA2249D4A25003DE606 /* SemanticVersion.swift in Sources */,
32833307
6E7516C522C520D400B2B157 /* DefaultEventDispatcher.swift in Sources */,
32843308
6E75189B22C520D400B2B157 /* Experiment.swift in Sources */,
32853309
6E75176122C520D400B2B157 /* AtomicProperty.swift in Sources */,
@@ -3351,6 +3375,7 @@
33513375
6E75179F22C520D400B2B157 /* DataStoreQueueStackImpl+Extension.swift in Sources */,
33523376
6E7516BB22C520D400B2B157 /* DefaultUserProfileService.swift in Sources */,
33533377
6E75184922C520D400B2B157 /* Event.swift in Sources */,
3378+
0B97DDA4249D4A27003DE606 /* SemanticVersion.swift in Sources */,
33543379
6ECB60D3234D5D9C00016D41 /* OptimizelyConfig+ObjC.swift in Sources */,
33553380
6E9B116C22C5487100C22D81 /* BucketTests_ExpToVariation.swift in Sources */,
33563381
6E75193922C520D500B2B157 /* OPTDataStore.swift in Sources */,
@@ -3447,6 +3472,7 @@
34473472
6E75172822C520D400B2B157 /* OptimizelyResult.swift in Sources */,
34483473
6E75170422C520D400B2B157 /* OptimizelyLogLevel.swift in Sources */,
34493474
6E75187A22C520D400B2B157 /* Variation.swift in Sources */,
3475+
0B97DD9A249D332C003DE606 /* SemanticVersionTests.swift in Sources */,
34503476
6E9B119C22C5488300C22D81 /* ProjectConfigTests.swift in Sources */,
34513477
6E7518FE22C520D500B2B157 /* UserAttribute.swift in Sources */,
34523478
6E7517F622C520D400B2B157 /* DataStoreMemory.swift in Sources */,
@@ -3489,6 +3515,7 @@
34893515
6E7517EA22C520D400B2B157 /* DefaultDecisionService.swift in Sources */,
34903516
6E75171C22C520D400B2B157 /* OptimizelyClient+ObjC.swift in Sources */,
34913517
6E7516B022C520D400B2B157 /* DefaultLogger.swift in Sources */,
3518+
0B97DD9C249D3735003DE606 /* SemanticVersion.swift in Sources */,
34923519
6E7517DE22C520D400B2B157 /* DefaultNotificationCenter.swift in Sources */,
34933520
6E75195E22C520D500B2B157 /* OPTBucketer.swift in Sources */,
34943521
6E75186E22C520D400B2B157 /* Rollout.swift in Sources */,
@@ -3542,6 +3569,7 @@
35423569
6E75172122C520D400B2B157 /* OptimizelyResult.swift in Sources */,
35433570
6E75186722C520D400B2B157 /* Rollout.swift in Sources */,
35443571
6E7518A322C520D400B2B157 /* FeatureFlag.swift in Sources */,
3572+
0B97DD9E249D4A22003DE606 /* SemanticVersion.swift in Sources */,
35453573
6ECB60CD234D5D9C00016D41 /* OptimizelyConfig+ObjC.swift in Sources */,
35463574
6E9B115222C5486E00C22D81 /* BucketTests_ExpToVariation.swift in Sources */,
35473575
6E75189722C520D400B2B157 /* Experiment.swift in Sources */,
@@ -3638,6 +3666,7 @@
36383666
6E75172322C520D400B2B157 /* OptimizelyResult.swift in Sources */,
36393667
6E7516FF22C520D400B2B157 /* OptimizelyLogLevel.swift in Sources */,
36403668
6E75187522C520D400B2B157 /* Variation.swift in Sources */,
3669+
0B97DD99249D332C003DE606 /* SemanticVersionTests.swift in Sources */,
36413670
6E9B118622C5488100C22D81 /* ProjectConfigTests.swift in Sources */,
36423671
6E7518F922C520D500B2B157 /* UserAttribute.swift in Sources */,
36433672
6E7517F122C520D400B2B157 /* DataStoreMemory.swift in Sources */,
@@ -3680,6 +3709,7 @@
36803709
6E7517E522C520D400B2B157 /* DefaultDecisionService.swift in Sources */,
36813710
6E75171722C520D400B2B157 /* OptimizelyClient+ObjC.swift in Sources */,
36823711
6E7516AB22C520D400B2B157 /* DefaultLogger.swift in Sources */,
3712+
0B97DD9B249D3733003DE606 /* SemanticVersion.swift in Sources */,
36833713
6E7517D922C520D400B2B157 /* DefaultNotificationCenter.swift in Sources */,
36843714
6E75195922C520D500B2B157 /* OPTBucketer.swift in Sources */,
36853715
6E75186922C520D400B2B157 /* Rollout.swift in Sources */,
@@ -3722,6 +3752,7 @@
37223752
isa = PBXSourcesBuildPhase;
37233753
buildActionMask = 2147483647;
37243754
files = (
3755+
0B97DDA1249D4A24003DE606 /* SemanticVersion.swift in Sources */,
37253756
6E7516AC22C520D400B2B157 /* DefaultLogger.swift in Sources */,
37263757
6E75176C22C520D400B2B157 /* Utils.swift in Sources */,
37273758
6E7516C422C520D400B2B157 /* DefaultEventDispatcher.swift in Sources */,
@@ -3795,6 +3826,7 @@
37953826
isa = PBXSourcesBuildPhase;
37963827
buildActionMask = 2147483647;
37973828
files = (
3829+
0B97DDA5249D4A28003DE606 /* SemanticVersion.swift in Sources */,
37983830
6E7516B122C520D400B2B157 /* DefaultLogger.swift in Sources */,
37993831
6E75177122C520D400B2B157 /* Utils.swift in Sources */,
38003832
6E7516C922C520D400B2B157 /* DefaultEventDispatcher.swift in Sources */,
@@ -3928,6 +3960,7 @@
39283960
6E75187C22C520D400B2B157 /* TrafficAllocation.swift in Sources */,
39293961
6E7517EC22C520D400B2B157 /* DataStoreMemory.swift in Sources */,
39303962
6E75174E22C520D400B2B157 /* LogMessage.swift in Sources */,
3963+
0B97DD94249D327F003DE606 /* SemanticVersion.swift in Sources */,
39313964
6E75175A22C520D400B2B157 /* AtomicProperty.swift in Sources */,
39323965
6E34A6172319EBB800BAE302 /* Notifications.swift in Sources */,
39333966
);
@@ -3991,6 +4024,7 @@
39914024
6EA2CC262345618E001E7531 /* OptimizelyConfig.swift in Sources */,
39924025
6E7517CA22C520D400B2B157 /* DefaultBucketer.swift in Sources */,
39934026
6E7517FA22C520D400B2B157 /* DataStoreUserDefaults.swift in Sources */,
4027+
0B97DD9D249D4A22003DE606 /* SemanticVersion.swift in Sources */,
39944028
6E9B11A722C5489200C22D81 /* MockUrlSession.swift in Sources */,
39954029
6E7517D622C520D400B2B157 /* DefaultNotificationCenter.swift in Sources */,
39964030
6E75178022C520D400B2B157 /* ArrayEventForDispatch+Extension.swift in Sources */,

Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ SPEC CHECKSUMS:
1313

1414
PODFILE CHECKSUM: 159491f53ea704c30be049e048d854a49f9b7433
1515

16-
COCOAPODS: 1.9.1
16+
COCOAPODS: 1.9.2

Sources/Data Model/Audience/AttributeValue.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ extension AttributeValue {
166166

167167
return currentDouble > targetDouble
168168
}
169+
170+
func isGreaterOrEqual(than target: Any, condition: String = "", name: String = "") throws -> Bool {
171+
return try isGreater(than: target, condition: condition, name: name) || isExactMatch(with: target, condition: condition, name: name)
172+
}
169173

170174
func isLess(than target: Any, condition: String = "", name: String = "") throws -> Bool {
171175

@@ -182,6 +186,29 @@ extension AttributeValue {
182186
return currentDouble < targetDouble
183187
}
184188

189+
func isLessOrEqual(than target: Any, condition: String = "", name: String = "") throws -> Bool {
190+
return try isLess(than: target, condition: condition, name: name) || isExactMatch(with: target, condition: condition, name: name)
191+
}
192+
193+
func isSemanticVersionEqual(than target: SemanticVersion) throws -> Bool {
194+
return try (self.stringValue as SemanticVersion).compareVersion(targetedVersion: target) == 0
195+
}
196+
197+
func isSemanticVersionGreater(than target: SemanticVersion) throws -> Bool {
198+
return try (self.stringValue as SemanticVersion).compareVersion(targetedVersion: target) > 0
199+
}
200+
201+
func isSemanticVersionLess(than target: SemanticVersion) throws -> Bool {
202+
return try (self.stringValue as SemanticVersion).compareVersion(targetedVersion: target) < 0
203+
}
204+
205+
func isSemanticVersionGreaterOrEqual(than target: SemanticVersion) throws -> Bool {
206+
return try (self.stringValue as SemanticVersion).compareVersion(targetedVersion: target) >= 0 }
207+
208+
func isSemanticVersionLessOrEqual(than target: SemanticVersion) throws -> Bool {
209+
return try (self.stringValue as SemanticVersion).compareVersion(targetedVersion: target) <= 0
210+
}
211+
185212
var doubleValue: Double? {
186213
switch self {
187214
case .double(let value): return value
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/****************************************************************************
2+
* Copyright 2020, Optimizely, Inc. and contributors *
3+
* *
4+
* Licensed under the Apache License, Version 2.0 (the "License"); *
5+
* you may not use this file except in compliance with the License. *
6+
* You may obtain a copy of the License at *
7+
* *
8+
* http://www.apache.org/licenses/LICENSE-2.0 *
9+
* *
10+
* Unless required by applicable law or agreed to in writing, software *
11+
* distributed under the License is distributed on an "AS IS" BASIS, *
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13+
* See the License for the specific language governing permissions and *
14+
* limitations under the License. *
15+
***************************************************************************/
16+
17+
import Foundation
18+
19+
typealias SemanticVersion = String
20+
/*
21+
This comparison is ported from the Optimizely web version of semantic version compare.
22+
Full testing in SemanticVersionTests.
23+
*/
24+
extension SemanticVersion {
25+
func compareVersion(targetedVersion: SemanticVersion?) throws -> Int {
26+
guard let targetedVersion = targetedVersion else {
27+
// Any version.
28+
return 0
29+
}
30+
31+
32+
let targetedVersionParts = try targetedVersion.splitSemanticVersion()
33+
let versionParts = try self.splitSemanticVersion()
34+
35+
// Up to the precision of targetedVersion, expect version to match exactly.
36+
for (idx, _) in targetedVersionParts.enumerated() {
37+
if versionParts.count <= idx {
38+
return -1;
39+
} else if !versionParts[idx].isNumber {
40+
//Compare strings
41+
if versionParts[idx] < targetedVersionParts[idx] {
42+
return -1;
43+
}
44+
else if versionParts[idx] > targetedVersionParts[idx] {
45+
return 1;
46+
}
47+
48+
} else if let part = Int(versionParts[idx]), let target = Int(targetedVersionParts[idx]){
49+
if (part < target) {
50+
return -1;
51+
} else if part > target {
52+
return 1;
53+
}
54+
} else {
55+
return -1;
56+
}
57+
}
58+
59+
return 0;
60+
}
61+
62+
func splitSemanticVersion() throws -> [Substring] {
63+
var targetParts:[Substring]?
64+
var targetPrefix = self
65+
var targetSuffix:ArraySlice<Substring>?
66+
67+
if isPreRelease || isBuild {
68+
targetParts = split(separator: isPreRelease ? preReleaseSeperator : buildSeperator)
69+
guard let targetParts = targetParts, targetParts.count > 1 else {
70+
throw OptimizelyError.attributeFormatInvalid
71+
}
72+
73+
targetPrefix = String(targetParts[0])
74+
targetSuffix = targetParts[1...]
75+
}
76+
// Expect a version string of the form x.y.z
77+
var targetedVersionParts = targetPrefix.split(separator: ".")
78+
guard targetedVersionParts.count > 0 else {
79+
throw OptimizelyError.attributeFormatInvalid
80+
}
81+
if let targetSuffix = targetSuffix {
82+
targetedVersionParts.append(contentsOf: targetSuffix)
83+
}
84+
return targetedVersionParts
85+
}
86+
87+
var isNumber: Bool {
88+
return !isEmpty && rangeOfCharacter(from: CharacterSet.decimalDigits.inverted) == nil
89+
}
90+
91+
var isPreRelease: Bool {
92+
return contains("-")
93+
}
94+
95+
var isBuild: Bool {
96+
return contains("+")
97+
}
98+
99+
var buildSeperator:Character {
100+
return "+"
101+
}
102+
var preReleaseSeperator:Character {
103+
return "-"
104+
}
105+
}
106+
107+
extension Substring {
108+
var isNumber: Bool {
109+
return !isEmpty && rangeOfCharacter(from: CharacterSet.decimalDigits.inverted) == nil
110+
}
111+
112+
var isPreRelease: Bool {
113+
return contains("-")
114+
}
115+
116+
var isBuild: Bool {
117+
return contains("+")
118+
}
119+
120+
}

0 commit comments

Comments
 (0)