diff --git a/.github/workflows/instrumented.yml b/.github/workflows/instrumented.yml index ad07f29d9..765778384 100644 --- a/.github/workflows/instrumented.yml +++ b/.github/workflows/instrumented.yml @@ -9,7 +9,7 @@ on: jobs: test: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest strategy: fail-fast: false @@ -18,23 +18,17 @@ jobs: shard: [ 0, 1, 2, 3 ] steps: - - name: Enable KVM group perms - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - - name: checkout uses: actions/checkout@v4 - name: Gradle cache uses: gradle/gradle-build-action@v3 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: 11 + java-version: 17 cache: 'gradle' - name: AVD cache @@ -46,6 +40,12 @@ jobs: ~/.android/adb* key: avd-${{ matrix.api-level }} + - name: Enable KVM group perms + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + - name: create AVD and generate snapshot for caching if: steps.avd-cache.outputs.cache-hit != 'true' uses: reactivecircus/android-emulator-runner@v2 diff --git a/.gitignore b/.gitignore index b54870c45..f09c2a403 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ gradle.properties *.iml .DS_Store .settings/org.eclipse.buildship.core.prefs -.gradle \ No newline at end of file +.gradle +.vscode/ \ No newline at end of file diff --git a/src/androidTest/java/fake/SplitClientStub.java b/src/androidTest/java/fake/SplitClientStub.java index d500ef366..4acebddbc 100644 --- a/src/androidTest/java/fake/SplitClientStub.java +++ b/src/androidTest/java/fake/SplitClientStub.java @@ -3,10 +3,12 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import io.split.android.client.EvaluationOptions; import io.split.android.client.SplitClient; import io.split.android.client.SplitResult; import io.split.android.client.events.SplitEvent; @@ -23,39 +25,79 @@ public String getTreatment(String featureFlagName, Map attribute return "control"; } + @Override + public String getTreatment(String featureFlagName, Map attributes, EvaluationOptions evaluationOptions) { + return getTreatment(featureFlagName, attributes); + } + @Override public SplitResult getTreatmentWithConfig(String featureFlagName, Map attributes) { + return getTreatmentWithConfig(featureFlagName, attributes, null); + } + + @Override + public SplitResult getTreatmentWithConfig(String featureFlagName, Map attributes, EvaluationOptions evaluationOptions) { return null; } @Override public Map getTreatments(List featureFlagNames, Map attributes) { - return null; + return getTreatments(featureFlagNames, attributes, null); + } + + @Override + public Map getTreatments(List featureFlagNames, Map attributes, EvaluationOptions evaluationOptions) { + return Collections.emptyMap(); } @Override public Map getTreatmentsWithConfig(List featureFlagNames, Map attributes) { - return null; + return getTreatmentsWithConfig(featureFlagNames, attributes, null); + } + + @Override + public Map getTreatmentsWithConfig(List featureFlagNames, Map attributes, EvaluationOptions evaluationOptions) { + return Collections.emptyMap(); } @Override public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { - return null; + return getTreatmentsByFlagSet(flagSet, attributes, null); + } + + @Override + public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes, EvaluationOptions evaluationOptions) { + return Collections.emptyMap(); } @Override public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { - return null; + return getTreatmentsByFlagSets(flagSets, attributes, null); + } + + @Override + public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes, EvaluationOptions evaluationOptions) { + return Collections.emptyMap(); } @Override public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { - return null; + return getTreatmentsWithConfigByFlagSet(flagSet, attributes, null); + } + + @Override + public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes, EvaluationOptions evaluationOptions) { + return Collections.emptyMap(); } @Override public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { - return null; + return getTreatmentsWithConfigByFlagSets(flagSets, attributes, null); + } + + @Override + public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes, EvaluationOptions evaluationOptions) { + return Collections.emptyMap(); } @Override diff --git a/src/main/java/io/split/android/client/AlwaysReturnControlSplitClient.java b/src/main/java/io/split/android/client/AlwaysReturnControlSplitClient.java index 2275622fb..19d7017ba 100644 --- a/src/main/java/io/split/android/client/AlwaysReturnControlSplitClient.java +++ b/src/main/java/io/split/android/client/AlwaysReturnControlSplitClient.java @@ -37,6 +37,11 @@ public Map getTreatments(List featureFlagNames, Map getTreatments(List featureFlagNames, Map attributes, EvaluationOptions evaluationOptions) { + return Collections.emptyMap(); + } + @Override public Map getTreatmentsWithConfig(List featureFlagNames, Map attributes) { Map results = new HashMap<>(); @@ -50,36 +55,71 @@ public Map getTreatmentsWithConfig(List featureFlag return results; } + @Override + public Map getTreatmentsWithConfig(List featureFlagNames, Map attributes, EvaluationOptions evaluationOptions) { + return Collections.emptyMap(); + } + @Override public String getTreatment(String featureFlagName, Map attributes) { return Treatments.CONTROL; } + @Override + public String getTreatment(String featureFlagName, Map attributes, EvaluationOptions evaluationOptions) { + return getTreatment(featureFlagName, attributes); + } + @Override public SplitResult getTreatmentWithConfig(String featureFlagName, Map attributes) { return new SplitResult(Treatments.CONTROL); } + @Override + public SplitResult getTreatmentWithConfig(String featureFlagName, Map attributes, EvaluationOptions evaluationOptions) { + return getTreatmentWithConfig(featureFlagName, attributes); + } + @Override public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { return Collections.emptyMap(); } + @Override + public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes, EvaluationOptions evaluationOptions) { + return Collections.emptyMap(); + } + @Override public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { return Collections.emptyMap(); } + @Override + public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes, EvaluationOptions evaluationOptions) { + return getTreatmentsByFlagSets(flagSets, attributes); + } + @Override public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { return Collections.emptyMap(); } + @Override + public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes, EvaluationOptions evaluationOptions) { + return getTreatmentsWithConfigByFlagSet(flagSet, attributes); + } + @Override public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { return Collections.emptyMap(); } + @Override + public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes, EvaluationOptions evaluationOptions) { + return getTreatmentsWithConfigByFlagSets(flagSets, attributes); + } + @Override public boolean setAttribute(String attributeName, Object value) { return true; diff --git a/src/main/java/io/split/android/client/EvaluationOptions.java b/src/main/java/io/split/android/client/EvaluationOptions.java new file mode 100644 index 000000000..c78532e88 --- /dev/null +++ b/src/main/java/io/split/android/client/EvaluationOptions.java @@ -0,0 +1,42 @@ +package io.split.android.client; + +import androidx.annotation.Nullable; + +import java.util.HashMap; +import java.util.Map; + +public class EvaluationOptions { + + private final Map mProperties; + + public EvaluationOptions(Map properties) { + mProperties = properties != null ? new HashMap<>(properties) : null; + } + + public Map getProperties() { + return mProperties != null ? new HashMap<>(mProperties) : null; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (!(obj instanceof EvaluationOptions)) { + return false; + } + EvaluationOptions other = (EvaluationOptions) obj; + if (mProperties == null) { + return other.mProperties == null; + } + return mProperties.equals(other.mProperties); + } + + @Override + public int hashCode() { + return mProperties != null ? mProperties.hashCode() : 0; + } +} diff --git a/src/main/java/io/split/android/client/SplitClient.java b/src/main/java/io/split/android/client/SplitClient.java index 7214ffcc6..63d35f457 100644 --- a/src/main/java/io/split/android/client/SplitClient.java +++ b/src/main/java/io/split/android/client/SplitClient.java @@ -59,6 +59,7 @@ public interface SplitClient extends AttributesManager { */ String getTreatment(String featureFlagName, Map attributes); + String getTreatment(String featureFlagName, Map attributes, EvaluationOptions evaluationOptions); /** * This method is useful when you want to determine the treatment to show @@ -77,6 +78,8 @@ public interface SplitClient extends AttributesManager { */ SplitResult getTreatmentWithConfig(String featureFlagName, Map attributes); + SplitResult getTreatmentWithConfig(String featureFlagName, Map attributes, EvaluationOptions evaluationOptions); + /** * This method is useful when you want to determine the treatment of several feature flags at * the same time. @@ -90,6 +93,7 @@ public interface SplitClient extends AttributesManager { */ Map getTreatments(List featureFlagNames, Map attributes); + Map getTreatments(List featureFlagNames, Map attributes, EvaluationOptions evaluationOptions); /** * This method is useful when you want to determine the treatment of several feature flags at @@ -105,6 +109,8 @@ public interface SplitClient extends AttributesManager { */ Map getTreatmentsWithConfig(List featureFlagNames, Map attributes); + Map getTreatmentsWithConfig(List featureFlagNames, Map attributes, EvaluationOptions evaluationOptions); + /** * This method is useful when you want to determine the treatment of several feature flags * belonging to a specific Flag Set at the same time. @@ -115,6 +121,8 @@ public interface SplitClient extends AttributesManager { */ Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes); + Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes, EvaluationOptions evaluationOptions); + /** * This method is useful when you want to determine the treatment of several feature flags * belonging to a specific list of Flag Sets at the same time. @@ -125,6 +133,8 @@ public interface SplitClient extends AttributesManager { */ Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes); + Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes, EvaluationOptions evaluationOptions); + /** * This method is useful when you want to determine the treatment of several feature flags * belonging to a specific Flag Set @@ -135,6 +145,8 @@ public interface SplitClient extends AttributesManager { */ Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes); + Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes, EvaluationOptions evaluationOptions); + /** * This method is useful when you want to determine the treatment of several feature flags * belonging to a specific list of Flag Sets @@ -145,6 +157,8 @@ public interface SplitClient extends AttributesManager { */ Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes); + Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes, EvaluationOptions evaluationOptions); + /** * Destroys the background processes and clears the cache, releasing the resources used by * any instances of SplitClient or SplitManager generated by the client's parent SplitFactory diff --git a/src/main/java/io/split/android/client/SplitClientImpl.java b/src/main/java/io/split/android/client/SplitClientImpl.java index 7b4c097f6..913bd005e 100644 --- a/src/main/java/io/split/android/client/SplitClientImpl.java +++ b/src/main/java/io/split/android/client/SplitClientImpl.java @@ -34,7 +34,6 @@ public final class SplitClientImpl implements SplitClient { private final TreatmentManager mTreatmentManager; private final ValidationMessageLogger mValidationLogger; private final AttributesManager mAttributesManager; - private final SplitValidator mSplitValidator; private final EventsTracker mEventsTracker; private static final double TRACK_DEFAULT_VALUE = 0.0; @@ -64,7 +63,6 @@ public SplitClientImpl(SplitFactory container, mValidationLogger = new ValidationMessageLoggerImpl(); mTreatmentManager = treatmentManager; mAttributesManager = checkNotNull(attributesManager); - mSplitValidator = checkNotNull(splitValidator); } @Override @@ -110,42 +108,82 @@ public String getTreatment(String featureFlagName) { @Override public String getTreatment(String featureFlagName, Map attributes) { - return mTreatmentManager.getTreatment(featureFlagName, attributes, mIsClientDestroyed); + return getTreatment(featureFlagName, attributes, null); + } + + @Override + public String getTreatment(String featureFlagName, Map attributes, EvaluationOptions evaluationOptions) { + return mTreatmentManager.getTreatment(featureFlagName, attributes, evaluationOptions, mIsClientDestroyed); } @Override public SplitResult getTreatmentWithConfig(String featureFlagName, Map attributes) { - return mTreatmentManager.getTreatmentWithConfig(featureFlagName, attributes, mIsClientDestroyed); + return getTreatmentWithConfig(featureFlagName, attributes, null); + } + + @Override + public SplitResult getTreatmentWithConfig(String featureFlagName, Map attributes, EvaluationOptions evaluationOptions) { + return mTreatmentManager.getTreatmentWithConfig(featureFlagName, attributes, evaluationOptions, mIsClientDestroyed); } @Override public Map getTreatments(List featureFlagNames, Map attributes) { - return mTreatmentManager.getTreatments(featureFlagNames, attributes, mIsClientDestroyed); + return getTreatments(featureFlagNames, attributes, null); + } + + @Override + public Map getTreatments(List featureFlagNames, Map attributes, EvaluationOptions evaluationOptions) { + return mTreatmentManager.getTreatments(featureFlagNames, attributes, evaluationOptions, mIsClientDestroyed); } @Override public Map getTreatmentsWithConfig(List featureFlagNames, Map attributes) { - return mTreatmentManager.getTreatmentsWithConfig(featureFlagNames, attributes, mIsClientDestroyed); + return getTreatmentsWithConfig(featureFlagNames, attributes, null); + } + + @Override + public Map getTreatmentsWithConfig(List featureFlagNames, Map attributes, EvaluationOptions evaluationOptions) { + return mTreatmentManager.getTreatmentsWithConfig(featureFlagNames, attributes, evaluationOptions, mIsClientDestroyed); } @Override public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { - return mTreatmentManager.getTreatmentsByFlagSet(flagSet, attributes, mIsClientDestroyed); + return getTreatmentsByFlagSet(flagSet, attributes, null); + } + + @Override + public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes, EvaluationOptions evaluationOptions) { + return mTreatmentManager.getTreatmentsByFlagSet(flagSet, attributes, evaluationOptions, mIsClientDestroyed); } @Override public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { - return mTreatmentManager.getTreatmentsByFlagSets(flagSets, attributes, mIsClientDestroyed); + return getTreatmentsByFlagSets(flagSets, attributes, null); + } + + @Override + public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes, EvaluationOptions evaluationOptions) { + return mTreatmentManager.getTreatmentsByFlagSets(flagSets, attributes, evaluationOptions, mIsClientDestroyed); } @Override public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { - return mTreatmentManager.getTreatmentsWithConfigByFlagSet(flagSet, attributes, mIsClientDestroyed); + return getTreatmentsWithConfigByFlagSet(flagSet, attributes, null); + } + + @Override + public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes, EvaluationOptions evaluationOptions) { + return mTreatmentManager.getTreatmentsWithConfigByFlagSet(flagSet, attributes, evaluationOptions, mIsClientDestroyed); } @Override public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { - return mTreatmentManager.getTreatmentsWithConfigByFlagSets(flagSets, attributes, mIsClientDestroyed); + return getTreatmentsWithConfigByFlagSets(flagSets, attributes, null); + } + + @Override + public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes, EvaluationOptions evaluationOptions) { + return mTreatmentManager.getTreatmentsWithConfigByFlagSets(flagSets, attributes, evaluationOptions, mIsClientDestroyed); } public void on(SplitEvent event, SplitEventTask task) { diff --git a/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java b/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java index 77c394308..71e766525 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java @@ -12,6 +12,7 @@ import java.util.Map; import java.util.Set; +import io.split.android.client.EvaluationOptions; import io.split.android.client.EvaluatorImpl; import io.split.android.client.FlagSetsFilter; import io.split.android.client.SplitClient; @@ -79,19 +80,18 @@ public LocalhostSplitClient(@NonNull LocalhostSplitFactory container, @Override public String getTreatment(String featureFlagName) { - try { - return mTreatmentManager.getTreatment(featureFlagName, null, mIsClientDestroyed); - } catch (Exception exception) { - Logger.e(exception); - - return Treatments.CONTROL; - } + return getTreatment(featureFlagName, Collections.emptyMap(), null); } @Override public String getTreatment(String featureFlagName, Map attributes) { + return getTreatment(featureFlagName, attributes, null); + } + + @Override + public String getTreatment(String featureFlagName, Map attributes, EvaluationOptions evaluationOptions) { try { - return mTreatmentManager.getTreatment(featureFlagName, attributes, mIsClientDestroyed); + return mTreatmentManager.getTreatment(featureFlagName, attributes, evaluationOptions, mIsClientDestroyed); } catch (Exception exception) { Logger.e(exception); @@ -101,8 +101,13 @@ public String getTreatment(String featureFlagName, Map attribute @Override public SplitResult getTreatmentWithConfig(String featureFlagName, Map attributes) { + return getTreatmentWithConfig(featureFlagName, attributes, null); + } + + @Override + public SplitResult getTreatmentWithConfig(String featureFlagName, Map attributes, EvaluationOptions evaluationOptions) { try { - return mTreatmentManager.getTreatmentWithConfig(featureFlagName, attributes, mIsClientDestroyed); + return mTreatmentManager.getTreatmentWithConfig(featureFlagName, attributes, evaluationOptions, mIsClientDestroyed); } catch (Exception exception) { Logger.e(exception); @@ -112,8 +117,13 @@ public SplitResult getTreatmentWithConfig(String featureFlagName, Map getTreatments(List featureFlagNames, Map attributes) { + return getTreatments(featureFlagNames, attributes, null); + } + + @Override + public Map getTreatments(List featureFlagNames, Map attributes, EvaluationOptions evaluationOptions) { try { - return mTreatmentManager.getTreatments(featureFlagNames, attributes, mIsClientDestroyed); + return mTreatmentManager.getTreatments(featureFlagNames, attributes, evaluationOptions, mIsClientDestroyed); } catch (Exception exception) { Logger.e(exception); @@ -129,8 +139,13 @@ public Map getTreatments(List featureFlagNames, Map getTreatmentsWithConfig(List featureFlagNames, Map attributes) { + return getTreatmentsWithConfig(featureFlagNames, attributes, null); + } + + @Override + public Map getTreatmentsWithConfig(List featureFlagNames, Map attributes, EvaluationOptions evaluationOptions) { try { - return mTreatmentManager.getTreatmentsWithConfig(featureFlagNames, attributes, mIsClientDestroyed); + return mTreatmentManager.getTreatmentsWithConfig(featureFlagNames, attributes, evaluationOptions, mIsClientDestroyed); } catch (Exception exception) { Logger.e(exception); @@ -146,8 +161,13 @@ public Map getTreatmentsWithConfig(List featureFlag @Override public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { + return getTreatmentsByFlagSet(flagSet, attributes, null); + } + + @Override + public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes, EvaluationOptions evaluationOptions) { try { - return mTreatmentManager.getTreatmentsByFlagSet(flagSet, attributes, mIsClientDestroyed); + return mTreatmentManager.getTreatmentsByFlagSet(flagSet, attributes, evaluationOptions, mIsClientDestroyed); } catch (Exception exception) { Logger.e(exception); @@ -157,8 +177,13 @@ public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Null @Override public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { + return getTreatmentsByFlagSets(flagSets, attributes, null); + } + + @Override + public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes, EvaluationOptions evaluationOptions) { try { - return mTreatmentManager.getTreatmentsByFlagSets(flagSets, attributes, mIsClientDestroyed); + return mTreatmentManager.getTreatmentsByFlagSets(flagSets, attributes, evaluationOptions, mIsClientDestroyed); } catch (Exception exception) { Logger.e(exception); @@ -168,8 +193,13 @@ public Map getTreatmentsByFlagSets(@NonNull List flagSet @Override public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { + return getTreatmentsWithConfigByFlagSet(flagSet, attributes, null); + } + + @Override + public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes, EvaluationOptions evaluationOptions) { try { - return mTreatmentManager.getTreatmentsWithConfigByFlagSet(flagSet, attributes, mIsClientDestroyed); + return mTreatmentManager.getTreatmentsWithConfigByFlagSet(flagSet, attributes, evaluationOptions, mIsClientDestroyed); } catch (Exception exception) { Logger.e(exception); @@ -179,8 +209,13 @@ public Map getTreatmentsWithConfigByFlagSet(@NonNull String @Override public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { + return getTreatmentsWithConfigByFlagSets(flagSets, attributes, null); + } + + @Override + public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes, EvaluationOptions evaluationOptions) { try { - return mTreatmentManager.getTreatmentsWithConfigByFlagSets(flagSets, attributes, mIsClientDestroyed); + return mTreatmentManager.getTreatmentsWithConfigByFlagSets(flagSets, attributes, evaluationOptions, mIsClientDestroyed); } catch (Exception exception) { Logger.e(exception); @@ -216,7 +251,7 @@ public void on(SplitEvent event, SplitEventTask task) { checkNotNull(task); if (!event.equals(SplitEvent.SDK_READY_FROM_CACHE) && mEventsManager.eventAlreadyTriggered(event)) { - Logger.w(String.format("A listener was added for %s on the SDK, which has already fired and won’t be emitted again. The callback won’t be executed.", event.toString())); + Logger.w(String.format("A listener was added for %s on the SDK, which has already fired and won’t be emitted again. The callback won’t be executed.", event)); return; } diff --git a/src/main/java/io/split/android/client/validators/TreatmentManager.java b/src/main/java/io/split/android/client/validators/TreatmentManager.java index fbb790052..6929fe8e3 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManager.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManager.java @@ -5,6 +5,8 @@ import java.util.List; import java.util.Map; + +import io.split.android.client.EvaluationOptions; import io.split.android.client.SplitResult; public interface TreatmentManager { @@ -24,4 +26,22 @@ public interface TreatmentManager { Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed); Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed); + + + // temporary methods to reduce changes in this iteration + String getTreatment(String split, Map attributes, EvaluationOptions evaluationOptions, boolean isClientDestroyed); + + SplitResult getTreatmentWithConfig(String split, Map attributes, EvaluationOptions evaluationOptions, boolean isClientDestroyed); + + Map getTreatments(List splits, Map attributes, EvaluationOptions evaluationOptions, boolean isClientDestroyed); + + Map getTreatmentsWithConfig(List splits, Map attributes, EvaluationOptions evaluationOptions, boolean isClientDestroyed); + + Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes, EvaluationOptions evaluationOptions, boolean isClientDestroyed); + + Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes, EvaluationOptions evaluationOptions, boolean isClientDestroyed); + + Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes, EvaluationOptions evaluationOptions, boolean isClientDestroyed); + + Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes, EvaluationOptions evaluationOptions, boolean isClientDestroyed); } diff --git a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java index 307dd310a..81d5ac347 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java @@ -12,6 +12,7 @@ import java.util.Map; import java.util.Set; +import io.split.android.client.EvaluationOptions; import io.split.android.client.EvaluationResult; import io.split.android.client.Evaluator; import io.split.android.client.FlagSetsFilter; @@ -84,11 +85,52 @@ public TreatmentManagerImpl(String matchingKey, @Override public String getTreatment(String split, Map attributes, boolean isClientDestroyed) { + return getTreatment(split, attributes, null, isClientDestroyed); + } + + @Override + public SplitResult getTreatmentWithConfig(String split, Map attributes, boolean isClientDestroyed) { + return getTreatmentWithConfig(split, attributes, null, isClientDestroyed); + } + + @Override + public Map getTreatments(List splits, Map attributes, boolean isClientDestroyed) { + return getTreatments(splits, attributes, null, isClientDestroyed); + } + + @Override + public Map getTreatmentsWithConfig(List splits, Map attributes, boolean isClientDestroyed) { + return getTreatmentsWithConfig(splits, attributes, null, isClientDestroyed); + } + + @Override + public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed) { + return getTreatmentsByFlagSet(flagSet, attributes, null, isClientDestroyed); + } + + @Override + public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed) { + return getTreatmentsByFlagSets(flagSets, attributes, null, isClientDestroyed); + } + + @Override + public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed) { + return getTreatmentsWithConfigByFlagSet(flagSet, attributes, null, isClientDestroyed); + } + + @Override + public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed) { + return getTreatmentsWithConfigByFlagSets(flagSets, attributes, null, isClientDestroyed); + } + + @Override + public String getTreatment(String split, Map attributes, EvaluationOptions evaluationOptions, boolean isClientDestroyed) { try { String treatment = getTreatmentsWithConfigGeneric( Collections.singletonList(split), null, attributes, + evaluationOptions, isClientDestroyed, SplitResult::treatment, Method.TREATMENT @@ -106,12 +148,13 @@ public String getTreatment(String split, Map attributes, boolean } @Override - public SplitResult getTreatmentWithConfig(String split, Map attributes, boolean isClientDestroyed) { + public SplitResult getTreatmentWithConfig(String split, Map attributes, EvaluationOptions evaluationOptions, boolean isClientDestroyed) { try { SplitResult splitResult = getTreatmentsWithConfigGeneric( Collections.singletonList(split), null, attributes, + evaluationOptions, isClientDestroyed, ResultTransformer::identity, Method.TREATMENT_WITH_CONFIG @@ -128,66 +171,72 @@ public SplitResult getTreatmentWithConfig(String split, Map attr } @Override - public Map getTreatments(List splits, Map attributes, boolean isClientDestroyed) { + public Map getTreatments(List splits, Map attributes, EvaluationOptions evaluationOptions, boolean isClientDestroyed) { return getTreatmentsWithConfigGeneric( splits, null, attributes, + evaluationOptions, isClientDestroyed, SplitResult::treatment, Method.TREATMENTS); } @Override - public Map getTreatmentsWithConfig(List splits, Map attributes, boolean isClientDestroyed) { + public Map getTreatmentsWithConfig(List splits, Map attributes, EvaluationOptions evaluationOptions, boolean isClientDestroyed) { return getTreatmentsWithConfigGeneric( splits, null, attributes, + evaluationOptions, isClientDestroyed, ResultTransformer::identity, Method.TREATMENTS_WITH_CONFIG); } @Override - public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed) { + public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes, EvaluationOptions evaluationOptions, boolean isClientDestroyed) { return getTreatmentsWithConfigGeneric( null, Collections.singletonList(flagSet), attributes, + evaluationOptions, isClientDestroyed, SplitResult::treatment, Method.TREATMENTS_BY_FLAG_SET); } @Override - public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed) { + public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes, EvaluationOptions evaluationOptions, boolean isClientDestroyed) { return getTreatmentsWithConfigGeneric( null, flagSets, attributes, + evaluationOptions, isClientDestroyed, SplitResult::treatment, Method.TREATMENTS_BY_FLAG_SETS); } @Override - public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed) { + public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes, EvaluationOptions evaluationOptions, boolean isClientDestroyed) { return getTreatmentsWithConfigGeneric( null, Collections.singletonList(flagSet), attributes, + evaluationOptions, isClientDestroyed, ResultTransformer::identity, Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SET); } @Override - public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed) { + public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes, EvaluationOptions evaluationOptions, boolean isClientDestroyed) { return getTreatmentsWithConfigGeneric( null, flagSets, attributes, + evaluationOptions, isClientDestroyed, ResultTransformer::identity, Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS); @@ -196,6 +245,7 @@ public Map getTreatmentsWithConfigByFlagSets(@NonNull List< private Map getTreatmentsWithConfigGeneric(@Nullable List names, @Nullable List flagSets, @Nullable Map attributes, + EvaluationOptions evaluationOptions, boolean isClientDestroyed, ResultTransformer resultTransformer, Method telemetryMethodName) { diff --git a/src/test/java/io/split/android/client/EvaluationOptionsTest.java b/src/test/java/io/split/android/client/EvaluationOptionsTest.java new file mode 100644 index 000000000..3c57ada2d --- /dev/null +++ b/src/test/java/io/split/android/client/EvaluationOptionsTest.java @@ -0,0 +1,80 @@ +package io.split.android.client; + +import org.junit.Test; +import static org.junit.Assert.*; + +import java.util.HashMap; +import java.util.Map; + +public class EvaluationOptionsTest { + + @Test + public void equalsWithSamePropertiesReturnsTrue() { + Map props = mapOf("key1", "value1", "key2", 2); + EvaluationOptions opt1 = new EvaluationOptions(props); + EvaluationOptions opt2 = new EvaluationOptions(props); + assertEquals(opt1, opt2); + assertEquals(opt1.hashCode(), opt2.hashCode()); + } + + @Test + public void equalsWithNullPropertiesReturnsTrue() { + EvaluationOptions opt1 = optionsWithNullProps(); + EvaluationOptions opt2 = optionsWithNullProps(); + assertEquals(opt1, opt2); + assertEquals(opt1.hashCode(), opt2.hashCode()); + } + + @Test + public void equalsWithDifferentPropertiesReturnsFalse() { + EvaluationOptions opt1 = optionsWithProps("key1", "value1"); + EvaluationOptions opt2 = optionsWithProps("key1", "value2"); + assertNotEquals(opt1, opt2); + } + + @Test + public void equalsWithNullAndNonNullPropertiesReturnsFalse() { + EvaluationOptions opt1 = optionsWithProps("k", "v"); + EvaluationOptions opt2 = optionsWithNullProps(); + assertNotEquals(opt1, opt2); + assertNotEquals(opt2, opt1); + } + + @Test + public void inputMapModificationDoesNotAffectInternalState() { + Map props = mapOf("key", "value"); + EvaluationOptions opt = new EvaluationOptions(props); + props.put("key2", "value2"); + // opt's properties should not include key2 + Map optProps = opt.getProperties(); + assertFalse(optProps.containsKey("key2")); + } + + @Test + public void getPropertiesReturnsDefensiveCopy() { + EvaluationOptions opt = optionsWithProps("key", "value"); + Map first = opt.getProperties(); + Map second = opt.getProperties(); + + assertNotSame(first, second); + // Modifying the returned map does not affect internal state + first.put("another", "thing"); + assertFalse(opt.getProperties().containsKey("another")); + } + + private static Map mapOf(Object... keyValuePairs) { + Map map = new HashMap<>(); + for (int i = 0; i < keyValuePairs.length - 1; i += 2) { + map.put((String) keyValuePairs[i], keyValuePairs[i+1]); + } + return map; + } + + private static EvaluationOptions optionsWithProps(Object... keyValuePairs) { + return new EvaluationOptions(mapOf(keyValuePairs)); + } + + private static EvaluationOptions optionsWithNullProps() { + return new EvaluationOptions(null); + } +} diff --git a/src/test/java/io/split/android/client/SplitClientImplBaseTest.java b/src/test/java/io/split/android/client/SplitClientImplBaseTest.java index 7b8076a18..1b6fe4a0d 100644 --- a/src/test/java/io/split/android/client/SplitClientImplBaseTest.java +++ b/src/test/java/io/split/android/client/SplitClientImplBaseTest.java @@ -13,7 +13,6 @@ import io.split.android.client.storage.mysegments.MySegmentsStorage; import io.split.android.client.storage.mysegments.MySegmentsStorageContainer; import io.split.android.client.storage.splits.SplitsStorage; -import io.split.android.client.telemetry.storage.TelemetryStorageProducer; import io.split.android.client.validators.SplitValidator; import io.split.android.client.validators.TreatmentManager; import io.split.android.engine.experiments.SplitParser; diff --git a/src/test/java/io/split/android/client/SplitClientImplEvaluationOptionsTest.java b/src/test/java/io/split/android/client/SplitClientImplEvaluationOptionsTest.java new file mode 100644 index 000000000..2e1bae71e --- /dev/null +++ b/src/test/java/io/split/android/client/SplitClientImplEvaluationOptionsTest.java @@ -0,0 +1,105 @@ +package io.split.android.client; + +import static org.mockito.Mockito.verify; + +import androidx.annotation.NonNull; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class SplitClientImplEvaluationOptionsTest extends SplitClientImplBaseTest { + + @Test + public void getTreatmentDelegatesToTreatmentManager() { + Map attrs = getAttrs(); + EvaluationOptions evaluationOptions = getEvaluationOptions(); + splitClient.getTreatment("test", attrs, evaluationOptions); + + verify(treatmentManager).getTreatment("test", attrs, evaluationOptions, false); + } + + @Test + public void getTreatmentsDelegatesToTreatmentManager() { + Map attrs = getAttrs(); + EvaluationOptions evaluationOptions = getEvaluationOptions(); + List flags = Arrays.asList("test", "test2"); + splitClient.getTreatments(flags, attrs, evaluationOptions); + + verify(treatmentManager).getTreatments(flags, attrs, evaluationOptions, false); + } + + @Test + public void getTreatmentWithConfigDelegatesToTreatmentManager() { + Map attrs = getAttrs(); + EvaluationOptions evaluationOptions = getEvaluationOptions(); + splitClient.getTreatmentWithConfig("test", attrs, evaluationOptions); + + verify(treatmentManager).getTreatmentWithConfig("test", attrs, evaluationOptions, false); + } + + @Test + public void getTreatmentsWithConfigDelegatesToTreatmentManager() { + Map attrs = getAttrs(); + EvaluationOptions evaluationOptions = getEvaluationOptions(); + List flags = Arrays.asList("test", "test2"); + splitClient.getTreatmentsWithConfig(flags, attrs, evaluationOptions); + + verify(treatmentManager).getTreatmentsWithConfig(flags, attrs, evaluationOptions, false); + } + + @Test + public void getTreatmentsByFlagSetDelegatesToTreatmentManager() { + Map attrs = getAttrs(); + EvaluationOptions evaluationOptions = getEvaluationOptions(); + splitClient.getTreatmentsByFlagSet("test", attrs, evaluationOptions); + + verify(treatmentManager).getTreatmentsByFlagSet("test", attrs, evaluationOptions, false); + } + + @Test + public void getTreatmentsByFlagSetsDelegatesToTreatmentManager() { + Map attrs = getAttrs(); + EvaluationOptions evaluationOptions = getEvaluationOptions(); + List flagSets = Arrays.asList("test", "test2"); + splitClient.getTreatmentsByFlagSets(flagSets, attrs, evaluationOptions); + + verify(treatmentManager).getTreatmentsByFlagSets(flagSets, attrs, evaluationOptions, false); + } + + @Test + public void getTreatmentsWithConfigByFlagSetDelegatesToTreatmentManager() { + Map attrs = getAttrs(); + EvaluationOptions evaluationOptions = getEvaluationOptions(); + splitClient.getTreatmentsWithConfigByFlagSet("test", attrs, evaluationOptions); + + verify(treatmentManager).getTreatmentsWithConfigByFlagSet("test", attrs, evaluationOptions, false); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsDelegatesToTreatmentManager() { + Map attrs = getAttrs(); + EvaluationOptions evaluationOptions = getEvaluationOptions(); + List flagSets = Arrays.asList("test", "test2"); + splitClient.getTreatmentsWithConfigByFlagSets(flagSets, attrs, evaluationOptions); + + verify(treatmentManager).getTreatmentsWithConfigByFlagSets(flagSets, attrs, evaluationOptions, false); + } + + @NonNull + private static EvaluationOptions getEvaluationOptions() { + HashMap properties = new HashMap<>(); + properties.put("key", "value"); + properties.put("key2", 2); + return new EvaluationOptions(properties); + } + + private static Map getAttrs() { + Map attrs = new HashMap<>(); + attrs.put("key", "value"); + return attrs; + } +} diff --git a/src/test/java/io/split/android/client/SplitClientImplFlagSetsTest.java b/src/test/java/io/split/android/client/SplitClientImplFlagSetsTest.java index a3f621553..7fcad6e3d 100644 --- a/src/test/java/io/split/android/client/SplitClientImplFlagSetsTest.java +++ b/src/test/java/io/split/android/client/SplitClientImplFlagSetsTest.java @@ -14,7 +14,7 @@ public void getTreatmentsByFlagSetDelegatesToTreatmentManager() { Map attributes = Collections.singletonMap("key", "value"); splitClient.getTreatmentsByFlagSet("set", attributes); - verify(treatmentManager).getTreatmentsByFlagSet("set", attributes, false); + verify(treatmentManager).getTreatmentsByFlagSet("set", attributes, null, false); } @Test @@ -22,7 +22,7 @@ public void getTreatmentsByFlagSetsDelegatesToTreatmentManager() { Map attributes = Collections.singletonMap("key", "value"); splitClient.getTreatmentsByFlagSets(Collections.singletonList("set"), attributes); - verify(treatmentManager).getTreatmentsByFlagSets(Collections.singletonList("set"), attributes, false); + verify(treatmentManager).getTreatmentsByFlagSets(Collections.singletonList("set"), attributes, null, false); } @Test @@ -30,7 +30,7 @@ public void getTreatmentsWithConfigByFlagSetDelegatesToTreatmentManager() { Map attributes = Collections.singletonMap("key", "value"); splitClient.getTreatmentsWithConfigByFlagSet("set", attributes); - verify(treatmentManager).getTreatmentsWithConfigByFlagSet("set", attributes, false); + verify(treatmentManager).getTreatmentsWithConfigByFlagSet("set", attributes, null, false); } @Test @@ -38,6 +38,6 @@ public void getTreatmentsWithConfigByFlagSetsDelegatesToTreatmentManager() { Map attributes = Collections.singletonMap("key", "value"); splitClient.getTreatmentsWithConfigByFlagSets(Collections.singletonList("set"), attributes); - verify(treatmentManager).getTreatmentsWithConfigByFlagSets(Collections.singletonList("set"), attributes, false); + verify(treatmentManager).getTreatmentsWithConfigByFlagSets(Collections.singletonList("set"), attributes, null, false); } }