diff --git a/OptimizelySwiftSDK.xcodeproj/project.pbxproj b/OptimizelySwiftSDK.xcodeproj/project.pbxproj index 3df0de83..9770d72b 100644 --- a/OptimizelySwiftSDK.xcodeproj/project.pbxproj +++ b/OptimizelySwiftSDK.xcodeproj/project.pbxproj @@ -1978,8 +1978,42 @@ 84F6BAB427FCC5CF004BE62A /* OptimizelyUserContextTests_ODP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F6BAB227FCC5CF004BE62A /* OptimizelyUserContextTests_ODP.swift */; }; 84F6BADD27FD011B004BE62A /* OptimizelyUserContextTests_ODP_Decide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F6BADC27FD011B004BE62A /* OptimizelyUserContextTests_ODP_Decide.swift */; }; 84F6BADE27FD011B004BE62A /* OptimizelyUserContextTests_ODP_Decide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F6BADC27FD011B004BE62A /* OptimizelyUserContextTests_ODP_Decide.swift */; }; + 980CC8F72D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC8F82D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC8F92D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC8FA2D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC8FB2D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC8FC2D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC8FD2D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC8FE2D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC8FF2D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC9002D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC9012D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC9022D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC9032D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC9042D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC9052D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC9062D833F0D00E07D24 /* Holdout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC8F62D833F0D00E07D24 /* Holdout.swift */; }; + 980CC9082D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC9092D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC90A2D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC90B2D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC90C2D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC90D2D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC90E2D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC90F2D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC9102D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC9112D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC9122D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC9132D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC9142D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC9152D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC9162D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; + 980CC9172D833F2800E07D24 /* ExperimentCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980CC9072D833F2800E07D24 /* ExperimentCore.swift */; }; 98137C552A41E86F004896EB /* OptimizelyClientTests_Init_Async_Await.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98137C542A41E86F004896EB /* OptimizelyClientTests_Init_Async_Await.swift */; }; 98137C572A42BA0F004896EB /* OptimizelyUserContextTests_ODP_Aync_Await.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98137C562A42BA0F004896EB /* OptimizelyUserContextTests_ODP_Aync_Await.swift */; }; + 982C071F2D8C82AE0068B1FF /* HoldoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 982C071E2D8C82AE0068B1FF /* HoldoutTests.swift */; }; + 982C07202D8C82AE0068B1FF /* HoldoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 982C071E2D8C82AE0068B1FF /* HoldoutTests.swift */; }; 984E2FDC2B27199B001F477A /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 987F11D92AF3F56F0083D3F9 /* PrivacyInfo.xcprivacy */; }; 984E2FDD2B27199C001F477A /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 987F11D92AF3F56F0083D3F9 /* PrivacyInfo.xcprivacy */; }; 984E2FDE2B27199D001F477A /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 987F11D92AF3F56F0083D3F9 /* PrivacyInfo.xcprivacy */; }; @@ -2436,8 +2470,11 @@ 84E7ABBA27D2A1F100447CAE /* ThreadSafeLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreadSafeLogger.swift; sourceTree = ""; }; 84F6BAB227FCC5CF004BE62A /* OptimizelyUserContextTests_ODP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptimizelyUserContextTests_ODP.swift; sourceTree = ""; }; 84F6BADC27FD011B004BE62A /* OptimizelyUserContextTests_ODP_Decide.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptimizelyUserContextTests_ODP_Decide.swift; sourceTree = ""; }; + 980CC8F62D833F0D00E07D24 /* Holdout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Holdout.swift; sourceTree = ""; }; + 980CC9072D833F2800E07D24 /* ExperimentCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentCore.swift; sourceTree = ""; }; 98137C542A41E86F004896EB /* OptimizelyClientTests_Init_Async_Await.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptimizelyClientTests_Init_Async_Await.swift; sourceTree = ""; }; 98137C562A42BA0F004896EB /* OptimizelyUserContextTests_ODP_Aync_Await.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptimizelyUserContextTests_ODP_Aync_Await.swift; sourceTree = ""; }; + 982C071E2D8C82AE0068B1FF /* HoldoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HoldoutTests.swift; sourceTree = ""; }; 984FE5102CC8AA88004F6F41 /* UserProfileTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileTracker.swift; sourceTree = ""; }; 987F11D92AF3F56F0083D3F9 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; BD6485812491474500F30986 /* Optimizely.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Optimizely.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -2821,6 +2858,8 @@ 6E75169122C520D400B2B157 /* TrafficAllocation.swift */, 6E75169222C520D400B2B157 /* Project.swift */, 6E75169322C520D400B2B157 /* Experiment.swift */, + 980CC9072D833F2800E07D24 /* ExperimentCore.swift */, + 980CC8F62D833F0D00E07D24 /* Holdout.swift */, 6E75169422C520D400B2B157 /* FeatureFlag.swift */, 6E75169522C520D400B2B157 /* Group.swift */, 6E75169622C520D400B2B157 /* Variable.swift */, @@ -3012,6 +3051,7 @@ 6E7519A922C5211100B2B157 /* UserAttributeTests.swift */, 6E7519AA22C5211100B2B157 /* GroupTests.swift */, 6E7519AB22C5211100B2B157 /* VariationTests.swift */, + 982C071E2D8C82AE0068B1FF /* HoldoutTests.swift */, 6E7519AC22C5211100B2B157 /* ExperimentTests.swift */, 6E7519AD22C5211100B2B157 /* EventTests.swift */, 6E7519AE22C5211100B2B157 /* ConditionHolderTests.swift */, @@ -4209,7 +4249,9 @@ 6E14CD722423F96B00010234 /* OptimizelyClient+ObjC.swift in Sources */, 6E14CDA62423F9C300010234 /* HandlerRegistryService.swift in Sources */, 6E14CD7B2423F98D00010234 /* OPTEventDispatcher.swift in Sources */, + 980CC9022D833F0D00E07D24 /* Holdout.swift in Sources */, 6E14CD902423F9A700010234 /* Variation.swift in Sources */, + 980CC9162D833F2800E07D24 /* ExperimentCore.swift in Sources */, 6E14CD8E2423F9A700010234 /* FeatureVariable.swift in Sources */, 8464087728130D3200CCF97D /* Integration.swift in Sources */, 6E14CD8D2423F9A700010234 /* ProjectConfig.swift in Sources */, @@ -4272,6 +4314,7 @@ 6E424D0326324B620081004A /* AttributeValue.swift in Sources */, 84E2E9482852A378001114AB /* VuidManager.swift in Sources */, 6E424D0426324B620081004A /* ConditionLeaf.swift in Sources */, + 980CC90E2D833F2800E07D24 /* ExperimentCore.swift in Sources */, 6E424D0526324B620081004A /* ConditionHolder.swift in Sources */, 6E424D5226324C4D0081004A /* OptimizelyClient+Decide.swift in Sources */, 6E424D0626324B620081004A /* UserAttribute.swift in Sources */, @@ -4300,6 +4343,7 @@ 6E424D1526324B620081004A /* DataStoreQueueStack.swift in Sources */, 6E424D1626324B620081004A /* OPTDataStore.swift in Sources */, 6E424D2F26324BBA0081004A /* OTUtils.swift in Sources */, + 980CC8F72D833F0D00E07D24 /* Holdout.swift in Sources */, 6E424D1726324B620081004A /* OPTDecisionService.swift in Sources */, 6E424D1826324B620081004A /* OPTBucketer.swift in Sources */, 6E424D1926324B620081004A /* ArrayEventForDispatch+Extension.swift in Sources */, @@ -4358,6 +4402,7 @@ 6EF8DE3224BF7D69008B9488 /* DecisionReasons.swift in Sources */, 6E75192522C520D500B2B157 /* DataStoreQueueStack.swift in Sources */, 6E7516FB22C520D400B2B157 /* OptimizelyLogLevel.swift in Sources */, + 980CC9042D833F0D00E07D24 /* Holdout.swift in Sources */, 84E7ABBC27D2A1F100447CAE /* ThreadSafeLogger.swift in Sources */, 6E75184D22C520D400B2B157 /* ProjectConfig.swift in Sources */, 8464087128130D3200CCF97D /* Integration.swift in Sources */, @@ -4404,6 +4449,7 @@ 6E75177F22C520D400B2B157 /* ArrayEventForDispatch+Extension.swift in Sources */, 6E75181122C520D400B2B157 /* DataStoreQueueStackImpl.swift in Sources */, 6EF8DE1B24BD1BB2008B9488 /* OptimizelyDecideOption.swift in Sources */, + 980CC90D2D833F2800E07D24 /* ExperimentCore.swift in Sources */, 6E75173722C520D400B2B157 /* MurmurHash3.swift in Sources */, 6E7517F922C520D400B2B157 /* DataStoreUserDefaults.swift in Sources */, C78CAF592445AD8D009FE876 /* OptimizelyJSON.swift in Sources */, @@ -4516,7 +4562,9 @@ 6E75174A22C520D400B2B157 /* HandlerRegistryService.swift in Sources */, 6E7516F622C520D400B2B157 /* OptimizelyError.swift in Sources */, 6E75188422C520D400B2B157 /* TrafficAllocation.swift in Sources */, + 980CC8F92D833F0D00E07D24 /* Holdout.swift in Sources */, 6EA2CC2C2345618E001E7531 /* OptimizelyConfig.swift in Sources */, + 980CC9152D833F2800E07D24 /* ExperimentCore.swift in Sources */, 6E7517D022C520D400B2B157 /* DefaultBucketer.swift in Sources */, 8464087C28130D3200CCF97D /* Integration.swift in Sources */, 6E75180022C520D400B2B157 /* DataStoreUserDefaults.swift in Sources */, @@ -4565,6 +4613,7 @@ 6E75181422C520D400B2B157 /* DataStoreQueueStackImpl.swift in Sources */, 6E593FB625BB9C5500EC72BC /* OptimizelyClientTests_Decide.swift in Sources */, 6E7516C222C520D400B2B157 /* DefaultEventDispatcher.swift in Sources */, + 980CC8FC2D833F0D00E07D24 /* Holdout.swift in Sources */, 848617F22863E21200B7F41B /* OdpEventApiManager.swift in Sources */, 6E75188022C520D400B2B157 /* TrafficAllocation.swift in Sources */, 6E9B11DD22C548A200C22D81 /* OptimizelyClientTests_Valid.swift in Sources */, @@ -4600,6 +4649,7 @@ 6E75194C22C520D500B2B157 /* OPTDatafileHandler.swift in Sources */, 6E652308278E688B00954EA1 /* LruCache.swift in Sources */, 6E7517C022C520D400B2B157 /* DefaultDatafileHandler.swift in Sources */, + 980CC90F2D833F2800E07D24 /* ExperimentCore.swift in Sources */, 6E75183822C520D400B2B157 /* EventForDispatch.swift in Sources */, 6E75175222C520D400B2B157 /* LogMessage.swift in Sources */, 6E9B11DB22C548A200C22D81 /* OptimizelyClientTests_Variables.swift in Sources */, @@ -4702,6 +4752,7 @@ 6E75181722C520D400B2B157 /* DataStoreQueueStackImpl.swift in Sources */, 6E75185322C520D400B2B157 /* ProjectConfig.swift in Sources */, 84E2E94D2852A378001114AB /* VuidManager.swift in Sources */, + 980CC8F82D833F0D00E07D24 /* Holdout.swift in Sources */, 6E75173D22C520D400B2B157 /* MurmurHash3.swift in Sources */, 6E7516E922C520D400B2B157 /* OPTEventDispatcher.swift in Sources */, 6E7518A722C520D400B2B157 /* FeatureFlag.swift in Sources */, @@ -4718,6 +4769,7 @@ 84B4D75B27E2A7550078CDA4 /* OptimizelySegmentOption.swift in Sources */, 6E7517E722C520D400B2B157 /* DefaultDecisionService.swift in Sources */, 6E424BE7263228E90081004A /* AtomicArray.swift in Sources */, + 980CC9102D833F2800E07D24 /* ExperimentCore.swift in Sources */, 6E7516DD22C520D400B2B157 /* OPTUserProfileService.swift in Sources */, 6E75188F22C520D400B2B157 /* Project.swift in Sources */, 6E75195B22C520D500B2B157 /* OPTBucketer.swift in Sources */, @@ -4807,6 +4859,7 @@ 84F6BAB427FCC5CF004BE62A /* OptimizelyUserContextTests_ODP.swift in Sources */, 6E7516C722C520D400B2B157 /* DefaultEventDispatcher.swift in Sources */, 6E9B11B522C5489600C22D81 /* MockUrlSession.swift in Sources */, + 980CC9012D833F0D00E07D24 /* Holdout.swift in Sources */, 6EF8DE1724BD1BB2008B9488 /* OptimizelyDecision.swift in Sources */, 8428D3D12807337400D0FB0C /* LruCacheTests.swift in Sources */, 84861816286D0B8900B7F41B /* VuidManagerTests.swift in Sources */, @@ -4847,6 +4900,7 @@ 845945C8287758A500D13E11 /* OdpConfig.swift in Sources */, 6E9B116422C5487100C22D81 /* BucketTests_Others.swift in Sources */, 6E7518CD22C520D400B2B157 /* Audience.swift in Sources */, + 980CC90C2D833F2800E07D24 /* ExperimentCore.swift in Sources */, 84E2E96E28540B5E001114AB /* OptimizelySdkSettings.swift in Sources */, 6E9B117322C5487100C22D81 /* BatchEventBuilderTests_Attributes.swift in Sources */, 6E9B11B622C5489600C22D81 /* OTUtils.swift in Sources */, @@ -4924,6 +4978,7 @@ 0B97DD9A249D332C003DE606 /* SemanticVersionTests.swift in Sources */, 6E65230E278E688B00954EA1 /* LruCache.swift in Sources */, 6E9B119C22C5488300C22D81 /* ProjectConfigTests.swift in Sources */, + 980CC8FF2D833F0D00E07D24 /* Holdout.swift in Sources */, 6E7518FE22C520D500B2B157 /* UserAttribute.swift in Sources */, 6E7517F622C520D400B2B157 /* DataStoreMemory.swift in Sources */, 6E9B119322C5488300C22D81 /* AttributeTests.swift in Sources */, @@ -4936,6 +4991,8 @@ 84861809286CF33700B7F41B /* OdpEvent.swift in Sources */, 6E75171022C520D400B2B157 /* OptimizelyClient.swift in Sources */, 6E7516C822C520D400B2B157 /* DefaultEventDispatcher.swift in Sources */, + 982C071F2D8C82AE0068B1FF /* HoldoutTests.swift in Sources */, + 980CC9112D833F2800E07D24 /* ExperimentCore.swift in Sources */, 84E2E9502852A378001114AB /* VuidManager.swift in Sources */, 6E75194622C520D500B2B157 /* OPTDecisionService.swift in Sources */, 6E75185622C520D400B2B157 /* ProjectConfig.swift in Sources */, @@ -5095,7 +5152,7 @@ 6E75171522C520D400B2B157 /* OptimizelyClient+ObjC.swift in Sources */, 6E6522E3278E4F3800954EA1 /* OdpManager.swift in Sources */, 6EA2CC272345618E001E7531 /* OptimizelyConfig.swift in Sources */, - 84861815286D0B8900B7F41B /* OdpVuidManagerTests.swift in Sources */, + 84861815286D0B8900B7F41B /* VuidManagerTests.swift in Sources */, 984FE51E2CC8AA88004F6F41 /* UserProfileTracker.swift in Sources */, C78CAFA724486E0A009FE876 /* OptimizelyJSON+ObjC.swift in Sources */, 6E75185B22C520D400B2B157 /* FeatureVariable.swift in Sources */, @@ -5122,6 +5179,7 @@ 6E0A72D426C5B9AE00FF92B5 /* OptimizelyUserContextTests_ForcedDecisions.swift in Sources */, 6EF41A332522BE1900EAADF1 /* OptimizelyUserContextTests_Decide.swift in Sources */, 848617EF2863E21200B7F41B /* OdpEventApiManager.swift in Sources */, + 980CC9002D833F0D00E07D24 /* Holdout.swift in Sources */, 6E27ECBE266FD78600B4A6D4 /* DecisionReasonsTests.swift in Sources */, 6E9B115E22C5486E00C22D81 /* DataStoreTests.swift in Sources */, 6E4544AF270E67C800F2CEBC /* NetworkReachability.swift in Sources */, @@ -5153,6 +5211,7 @@ 6E7517E322C520D400B2B157 /* DefaultDecisionService.swift in Sources */, 6E75179922C520D400B2B157 /* DataStoreQueueStackImpl+Extension.swift in Sources */, 6E9B115C22C5486E00C22D81 /* DatafileHandlerTests.swift in Sources */, + 980CC9082D833F2800E07D24 /* ExperimentCore.swift in Sources */, 6E9B114D22C5486E00C22D81 /* BatchEventBuilderTests_Events.swift in Sources */, 6E75188B22C520D400B2B157 /* Project.swift in Sources */, 6E75187F22C520D400B2B157 /* TrafficAllocation.swift in Sources */, @@ -5193,6 +5252,7 @@ 0B97DD99249D332C003DE606 /* SemanticVersionTests.swift in Sources */, 6E652309278E688B00954EA1 /* LruCache.swift in Sources */, 6E9B118622C5488100C22D81 /* ProjectConfigTests.swift in Sources */, + 980CC9032D833F0D00E07D24 /* Holdout.swift in Sources */, 6E7518F922C520D500B2B157 /* UserAttribute.swift in Sources */, 6E7517F122C520D400B2B157 /* DataStoreMemory.swift in Sources */, 6E9B117D22C5488100C22D81 /* AttributeTests.swift in Sources */, @@ -5205,6 +5265,8 @@ 84861804286CF33700B7F41B /* OdpEvent.swift in Sources */, 6E75170B22C520D400B2B157 /* OptimizelyClient.swift in Sources */, 6E7516C322C520D400B2B157 /* DefaultEventDispatcher.swift in Sources */, + 982C07202D8C82AE0068B1FF /* HoldoutTests.swift in Sources */, + 980CC9172D833F2800E07D24 /* ExperimentCore.swift in Sources */, 84E2E94B2852A378001114AB /* VuidManager.swift in Sources */, 6E75194122C520D500B2B157 /* OPTDecisionService.swift in Sources */, 6E75185122C520D400B2B157 /* ProjectConfig.swift in Sources */, @@ -5327,6 +5389,7 @@ 6EC6DD4924ABF89B0017D296 /* OptimizelyUserContext.swift in Sources */, 6E75188222C520D400B2B157 /* TrafficAllocation.swift in Sources */, 6E5D12252638DDF4000ABFC3 /* MockEventDispatcher.swift in Sources */, + 980CC9142D833F2800E07D24 /* ExperimentCore.swift in Sources */, 848617F42863E21200B7F41B /* OdpEventApiManager.swift in Sources */, 6ECB60D0234D5D9C00016D41 /* OptimizelyConfig+ObjC.swift in Sources */, 6E9B11E322C548AF00C22D81 /* ThrowableConditionListTest.swift in Sources */, @@ -5343,6 +5406,7 @@ 6E7516E822C520D400B2B157 /* OPTEventDispatcher.swift in Sources */, 6E75191222C520D500B2B157 /* BackgroundingCallbacks.swift in Sources */, 6E7518E222C520D400B2B157 /* ConditionLeaf.swift in Sources */, + 980CC9062D833F0D00E07D24 /* Holdout.swift in Sources */, 6E75182E22C520D400B2B157 /* BatchEvent.swift in Sources */, 6E7516DC22C520D400B2B157 /* OPTUserProfileService.swift in Sources */, 6E75182222C520D400B2B157 /* BatchEventBuilder.swift in Sources */, @@ -5429,6 +5493,7 @@ 6EC6DD4E24ABF89B0017D296 /* OptimizelyUserContext.swift in Sources */, 6E75188722C520D400B2B157 /* TrafficAllocation.swift in Sources */, 6E5D122A2638DDF4000ABFC3 /* MockEventDispatcher.swift in Sources */, + 980CC9122D833F2800E07D24 /* ExperimentCore.swift in Sources */, 848617F92863E21200B7F41B /* OdpEventApiManager.swift in Sources */, 6ECB60D5234D5D9C00016D41 /* OptimizelyConfig+ObjC.swift in Sources */, 6E9B11E522C548B100C22D81 /* ThrowableConditionListTest.swift in Sources */, @@ -5445,6 +5510,7 @@ 6E7516ED22C520D400B2B157 /* OPTEventDispatcher.swift in Sources */, 6E75191722C520D500B2B157 /* BackgroundingCallbacks.swift in Sources */, 6E7518E722C520D400B2B157 /* ConditionLeaf.swift in Sources */, + 980CC8FD2D833F0D00E07D24 /* Holdout.swift in Sources */, 6E75183322C520D400B2B157 /* BatchEvent.swift in Sources */, 6E7516E122C520D400B2B157 /* OPTUserProfileService.swift in Sources */, 6E75182722C520D400B2B157 /* BatchEventBuilder.swift in Sources */, @@ -5518,6 +5584,7 @@ 6EF8DE3124BF7D69008B9488 /* DecisionReasons.swift in Sources */, 6E7517BC22C520D400B2B157 /* DefaultDatafileHandler.swift in Sources */, 6E7516CA22C520D400B2B157 /* OPTLogger.swift in Sources */, + 980CC9052D833F0D00E07D24 /* Holdout.swift in Sources */, 84E7ABBB27D2A1F100447CAE /* ThreadSafeLogger.swift in Sources */, 6E75182822C520D400B2B157 /* BatchEvent.swift in Sources */, 8464087028130D3200CCF97D /* Integration.swift in Sources */, @@ -5564,6 +5631,7 @@ 6E75173622C520D400B2B157 /* MurmurHash3.swift in Sources */, 6E75185822C520D400B2B157 /* FeatureVariable.swift in Sources */, 6EF8DE1A24BD1BB2008B9488 /* OptimizelyDecideOption.swift in Sources */, + 980CC90B2D833F2800E07D24 /* ExperimentCore.swift in Sources */, 6E75186422C520D400B2B157 /* Rollout.swift in Sources */, 6E75179622C520D400B2B157 /* DataStoreQueueStackImpl+Extension.swift in Sources */, C78CAF582445AD8D009FE876 /* OptimizelyJSON.swift in Sources */, @@ -5676,7 +5744,9 @@ 6E75174422C520D400B2B157 /* HandlerRegistryService.swift in Sources */, 6E7516F022C520D400B2B157 /* OptimizelyError.swift in Sources */, 6E75187E22C520D400B2B157 /* TrafficAllocation.swift in Sources */, + 980CC8FA2D833F0D00E07D24 /* Holdout.swift in Sources */, 6EA2CC262345618E001E7531 /* OptimizelyConfig.swift in Sources */, + 980CC90A2D833F2800E07D24 /* ExperimentCore.swift in Sources */, 6E7517CA22C520D400B2B157 /* DefaultBucketer.swift in Sources */, 8464087428130D3200CCF97D /* Integration.swift in Sources */, 6E7517FA22C520D400B2B157 /* DataStoreUserDefaults.swift in Sources */, @@ -5707,6 +5777,7 @@ 8464087328130D3200CCF97D /* Integration.swift in Sources */, 75C71A0025E454460084187E /* OptimizelyError.swift in Sources */, 75C71A0125E454460084187E /* OptimizelyLogLevel.swift in Sources */, + 980CC8FB2D833F0D00E07D24 /* Holdout.swift in Sources */, 75C71A0225E454460084187E /* OptimizelyClient.swift in Sources */, 75C71A0325E454460084187E /* OptimizelyClient+ObjC.swift in Sources */, 75C71A0425E454460084187E /* OptimizelyResult.swift in Sources */, @@ -5748,6 +5819,7 @@ 75C71A2025E454460084187E /* BatchEvent.swift in Sources */, 75C71A2125E454460084187E /* EventForDispatch.swift in Sources */, 75C71A2225E454460084187E /* SemanticVersion.swift in Sources */, + 980CC9092D833F2800E07D24 /* ExperimentCore.swift in Sources */, 75C71A2325E454460084187E /* Audience.swift in Sources */, 84E2E9452852A378001114AB /* VuidManager.swift in Sources */, 6E6522E1278E4F3800954EA1 /* OdpManager.swift in Sources */, @@ -5810,6 +5882,7 @@ 6EF8DE3324BF7D69008B9488 /* DecisionReasons.swift in Sources */, BD6485432491474500F30986 /* DefaultDatafileHandler.swift in Sources */, BD6485442491474500F30986 /* OPTLogger.swift in Sources */, + 980CC8FE2D833F0D00E07D24 /* Holdout.swift in Sources */, 84E7ABBD27D2A1F100447CAE /* ThreadSafeLogger.swift in Sources */, BD6485452491474500F30986 /* BatchEvent.swift in Sources */, 8464087228130D3200CCF97D /* Integration.swift in Sources */, @@ -5856,6 +5929,7 @@ BD6485622491474500F30986 /* MurmurHash3.swift in Sources */, BD6485632491474500F30986 /* FeatureVariable.swift in Sources */, 6EF8DE1C24BD1BB2008B9488 /* OptimizelyDecideOption.swift in Sources */, + 980CC9132D833F2800E07D24 /* ExperimentCore.swift in Sources */, BD6485642491474500F30986 /* Rollout.swift in Sources */, BD6485652491474500F30986 /* DataStoreQueueStackImpl+Extension.swift in Sources */, BD6485662491474500F30986 /* OptimizelyJSON.swift in Sources */, diff --git a/Sources/Data Model/Experiment.swift b/Sources/Data Model/Experiment.swift index 415bcb98..ab75730e 100644 --- a/Sources/Data Model/Experiment.swift +++ b/Sources/Data Model/Experiment.swift @@ -16,7 +16,7 @@ import Foundation -struct Experiment: Codable, OptimizelyExperiment { +struct Experiment: Codable, ExperimentCore { enum Status: String, Codable { case running = "Running" case launched = "Launched" @@ -64,74 +64,9 @@ extension Experiment: Equatable { // MARK: - Utils extension Experiment { - func getVariation(id: String) -> Variation? { - return variations.filter { $0.id == id }.first - } - - func getVariation(key: String) -> Variation? { - return variations.filter { $0.key == key }.first - } var isActivated: Bool { return status == .running } - mutating func serializeAudiences(with audiencesMap: [String: String]) { - guard let conditions = audienceConditions else { return } - - let serialized = conditions.serialized - audiences = replaceAudienceIdsWithNames(string: serialized, audiencesMap: audiencesMap) - } - - /// Replace audience ids with audience names - /// - /// example: - /// - string: "(AUDIENCE(1) OR AUDIENCE(2)) AND AUDIENCE(3)" - /// - replaced: "(\"us\" OR \"female\") AND \"adult\"" - /// - /// - Parameter string: before replacement - /// - Returns: string after replacement - func replaceAudienceIdsWithNames(string: String, audiencesMap: [String: String]) -> String { - let beginWord = "AUDIENCE(" - let endWord = ")" - var keyIdx = 0 - var audienceId = "" - var collect = false - - var replaced = "" - for ch in string { - // extract audience id in parenthesis (example: AUDIENCE("35") => "35") - if collect { - if String(ch) == endWord { - // output the extracted audienceId - replaced += "\"\(audiencesMap[audienceId] ?? audienceId)\"" - collect = false - audienceId = "" - } else { - audienceId += String(ch) - } - continue - } - - // walk-through until finding a matching keyword "AUDIENCE(" - if ch == Array(beginWord)[keyIdx] { - keyIdx += 1 - if keyIdx == beginWord.count { - keyIdx = 0 - collect = true - } - continue - } else { - if keyIdx > 0 { - replaced += Array(beginWord)[.. Variation? { + return variations.filter { $0.id == id }.first + } + + func getVariation(key: String) -> Variation? { + return variations.filter { $0.key == key }.first + } + + func replaceAudienceIdsWithNames(string: String, audiencesMap: [String: String]) -> String { + let beginWord = "AUDIENCE(" + let endWord = ")" + var keyIdx = 0 + var audienceId = "" + var collect = false + + var replaced = "" + for ch in string { + if collect { + if String(ch) == endWord { + replaced += "\"\(audiencesMap[audienceId] ?? audienceId)\"" + collect = false + audienceId = "" + } else { + audienceId += String(ch) + } + continue + } + + if ch == Array(beginWord)[keyIdx] { + keyIdx += 1 + if keyIdx == beginWord.count { + keyIdx = 0 + collect = true + } + continue + } else { + if keyIdx > 0 { + replaced += Array(beginWord)[.. Bool { + return lhs.id == rhs.id && + lhs.key == rhs.key && + lhs.status == rhs.status && + lhs.layerId == rhs.layerId && + lhs.variations == rhs.variations && + lhs.trafficAllocation == rhs.trafficAllocation && + lhs.audienceIds == rhs.audienceIds && + lhs.audienceConditions == rhs.audienceConditions && + lhs.includedFlags == rhs.includedFlags && + lhs.excludedFlags == rhs.excludedFlags + } +} + + +extension Holdout { + var isActivated: Bool { + return status == .running + } +} diff --git a/Tests/OptimizelyTests-DataModel/HoldoutTests.swift b/Tests/OptimizelyTests-DataModel/HoldoutTests.swift new file mode 100644 index 00000000..b065165b --- /dev/null +++ b/Tests/OptimizelyTests-DataModel/HoldoutTests.swift @@ -0,0 +1,253 @@ +// +// Copyright 2022, Optimizely, Inc. and contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +import XCTest + +// MARK: - Sample Data + +class HoldoutTests: XCTestCase { + /// Global holoout without included and excluded key + static var sampleData: [String: Any] = ["id": "11111", + "key": "background", + "status": "Running", + "layerId": "22222", + "variations": [VariationTests.sampleData], + "trafficAllocation": [TrafficAllocationTests.sampleData], + "audienceIds": ["33333"], + "audienceConditions": ConditionHolderTests.sampleData] + +} + +// MARK: - Decode + +extension HoldoutTests { + + func testDecodeSuccessWithJSONValid() { + let data: [String: Any] = HoldoutTests.sampleData + let model: Holdout = try! OTUtils.model(from: data) + + XCTAssert(model.id == "11111") + XCTAssert(model.key == "background") + XCTAssert(model.status == .running) + XCTAssert(model.layerId == "22222") + XCTAssert(model.variations == [try! OTUtils.model(from: VariationTests.sampleData)]) + XCTAssert(model.trafficAllocation == [try! OTUtils.model(from: TrafficAllocationTests.sampleData)]) + XCTAssert(model.audienceIds == ["33333"]) + XCTAssert(model.audienceConditions == (try! OTUtils.model(from: ConditionHolderTests.sampleData))) + } + + func testDecodeSuccessWithIncludedFlags() { + var data: [String: Any] = HoldoutTests.sampleData + data["includedFlags"] = ["4444", "5555"] + + let model: Holdout = try! OTUtils.model(from: data) + + XCTAssert(model.id == "11111") + XCTAssert(model.key == "background") + XCTAssert(model.status == .running) + XCTAssert(model.layerId == "22222") + XCTAssert(model.variations == [try! OTUtils.model(from: VariationTests.sampleData)]) + XCTAssert(model.trafficAllocation == [try! OTUtils.model(from: TrafficAllocationTests.sampleData)]) + XCTAssert(model.audienceIds == ["33333"]) + XCTAssert(model.audienceConditions == (try! OTUtils.model(from: ConditionHolderTests.sampleData))) + XCTAssertEqual(model.includedFlags, ["4444", "5555"]) + } + + func testDecodeSuccessWithExcludedFlags() { + var data: [String: Any] = HoldoutTests.sampleData + data["excludedFlags"] = ["4444", "5555"] + + let model: Holdout = try! OTUtils.model(from: data) + + XCTAssert(model.id == "11111") + XCTAssert(model.key == "background") + XCTAssert(model.status == .running) + XCTAssert(model.layerId == "22222") + XCTAssert(model.variations == [try! OTUtils.model(from: VariationTests.sampleData)]) + XCTAssert(model.trafficAllocation == [try! OTUtils.model(from: TrafficAllocationTests.sampleData)]) + XCTAssert(model.audienceIds == ["33333"]) + XCTAssert(model.audienceConditions == (try! OTUtils.model(from: ConditionHolderTests.sampleData))) + XCTAssertEqual(model.excludedFlags, ["4444", "5555"]) + } + + + func testDecodeSuccessWithMissingAudienceConditions() { + var data: [String: Any] = HoldoutTests.sampleData + data["audienceConditions"] = nil + + let model: Holdout = try! OTUtils.model(from: data) + + XCTAssert(model.id == "11111") + XCTAssert(model.key == "background") + XCTAssert(model.status == .running) + XCTAssert(model.layerId == "22222") + XCTAssert(model.variations == [try! OTUtils.model(from: VariationTests.sampleData)]) + XCTAssert(model.trafficAllocation == [try! OTUtils.model(from: TrafficAllocationTests.sampleData)]) + XCTAssert(model.audienceIds == ["33333"]) + } + + func testDecodeFailWithMissingId() { + var data: [String: Any] = HoldoutTests.sampleData + data["id"] = nil + + let model: Holdout? = try? OTUtils.model(from: data) + XCTAssertNil(model) + } + + func testDecodeFailWithMissingKey() { + var data: [String: Any] = HoldoutTests.sampleData + data["key"] = nil + + let model: Holdout? = try? OTUtils.model(from: data) + XCTAssertNil(model) + } + + func testDecodeFailWithMissingStatus() { + var data: [String: Any] = HoldoutTests.sampleData + data["status"] = nil + + let model: Holdout? = try? OTUtils.model(from: data) + XCTAssertNil(model) + } + + func testDecodeFailWithMissingLayerId() { + var data: [String: Any] = HoldoutTests.sampleData + data["layerId"] = nil + + let model: Holdout? = try? OTUtils.model(from: data) + XCTAssertNil(model) + } + + func testDecodeFailWithMissingVariations() { + var data: [String: Any] = HoldoutTests.sampleData + data["variations"] = nil + + let model: Holdout? = try? OTUtils.model(from: data) + XCTAssertNil(model) + } + + func testDecodeFailWithMissingTrafficAllocation() { + var data: [String: Any] = HoldoutTests.sampleData + data["trafficAllocation"] = nil + + let model: Holdout? = try? OTUtils.model(from: data) + XCTAssertNil(model) + } + +} + +// MARK: - Encode + +extension HoldoutTests { + + func testEncodeJSON() { + let data: [String: Any] = HoldoutTests.sampleData + let modelGiven: Holdout = try! OTUtils.model(from: data) + + XCTAssert(OTUtils.isEqualWithEncodeThenDecode(modelGiven)) + } + +} + +// MARK: - audiences serialization + +extension HoldoutTests { + + func testAudiencesSerialization() { + let commonData: [String: Any] = ["id": "11111", + "key": "background", + "status": "Running", + "layerId": "22222", + "variations": [VariationTests.sampleData], + "trafficAllocation": [TrafficAllocationTests.sampleData], + "audienceIds": [], + "audienceConditions": [], + "forcedVariations": ["12345": "1234567890"]] + + let audiencesMap = [ + "1": "us", + "11": "fr", + "2": "female", + "12": "male", + "3": "adult", + "13": "kid" + ] + + let audiencesInput: [Any] = [ + [], + ["or", "1", "2"], + ["and", "1", "2", "3"], + ["not", "1"], + ["or", "1"], + ["and", "1"], + ["1"], + ["1", "2"], + ["and", ["or", "1", "2"], "3"], + ["and", ["or", "1", ["and", "2", "3"]], ["and", "11", ["or", "12", "13"]]], + ["not", ["and", "1", "2"]], + ["or", "1", "100000"], + ["and", "and"] + ] + + let audiencesOutput: [String] = [ + "", + "\"us\" OR \"female\"", + "\"us\" AND \"female\" AND \"adult\"", + "NOT \"us\"", + "\"us\"", + "\"us\"", + "\"us\"", + "\"us\" OR \"female\"", + "(\"us\" OR \"female\") AND \"adult\"", + "(\"us\" OR (\"female\" AND \"adult\")) AND (\"fr\" AND (\"male\" OR \"kid\"))", + "NOT (\"us\" AND \"female\")", + "\"us\" OR \"100000\"", + "" + ] + + for (idx, audience) in audiencesInput.enumerated() { + var data = commonData + data["audienceConditions"] = audience + var model: Holdout = try! OTUtils.model(from: data) + model.serializeAudiences(with: audiencesMap) + XCTAssertEqual(model.audiences, audiencesOutput[idx]) + } + } + +} + +// MARK: - Test Utils + +extension HoldoutTests { + + func testIsActivated() { + let data: [String: Any] = HoldoutTests.sampleData + var model: Holdout = try! OTUtils.model(from: data) + + XCTAssertTrue(model.isActivated) + + let allNotActiveStates: [Holdout.Status] = [.draft, .concluded, .archived] + for status in allNotActiveStates { + model.status = status + XCTAssertFalse(model.isActivated) + } + + model.status = .running + XCTAssertTrue(model.isActivated) + } +} +