From a896f6d8658d00f302a4adc85611f95f5388560e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Thea?= Date: Wed, 23 Aug 2023 14:03:03 -0300 Subject: [PATCH 01/89] Modify flags DTO & manager object (#522) --- .github/workflows/deploy.yml | 1 + build.gradle | 4 +- .../android/client/SplitManagerImpl.java | 1 + .../split/android/client/api/SplitView.java | 1 + .../io/split/android/client/dtos/Split.java | 33 +++++ .../engine/experiments/ParsedSplit.java | 138 +++++++++--------- .../engine/experiments/SplitParser.java | 15 +- .../android/client/SplitManagerImplTest.java | 23 ++- .../engine/experiments/SplitParserTest.java | 2 + .../io/split/android/helpers/SplitHelper.java | 31 +++- 10 files changed, 174 insertions(+), 75 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index fa1639912..9ffc78efc 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -4,6 +4,7 @@ on: push: branches: - development + - SDKS-7440 jobs: build-app: diff --git a/build.gradle b/build.gradle index aab058bc6..a4a2c5adc 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ apply plugin: 'signing' apply plugin: 'kotlin-android' ext { - splitVersion = '3.3.1-alpha-1' + splitVersion = '3.3.1-alpha-2' } android { @@ -225,6 +225,7 @@ afterEvaluate { release(MavenPublication) { from components.release + artifactId = 'android-client' groupId = 'io.split.client' version = splitVersion artifact sourcesJar @@ -241,6 +242,7 @@ afterEvaluate { development(MavenPublication) { from components.release + artifactId = 'android-client' groupId = 'io.split.client' version = splitVersion artifact sourcesJar diff --git a/src/main/java/io/split/android/client/SplitManagerImpl.java b/src/main/java/io/split/android/client/SplitManagerImpl.java index 533142730..b26ab60bd 100644 --- a/src/main/java/io/split/android/client/SplitManagerImpl.java +++ b/src/main/java/io/split/android/client/SplitManagerImpl.java @@ -142,6 +142,7 @@ private SplitView toSplitView(ParsedSplit parsedSplit) { splitView.killed = parsedSplit.killed(); splitView.changeNumber = parsedSplit.changeNumber(); splitView.configs = parsedSplit.configurations(); + splitView.sets = new ArrayList<>(parsedSplit.sets() == null ? new HashSet<>() : parsedSplit.sets()); Set treatments = new HashSet<>(); for (ParsedCondition condition : parsedSplit.parsedConditions()) { diff --git a/src/main/java/io/split/android/client/api/SplitView.java b/src/main/java/io/split/android/client/api/SplitView.java index dd13b3463..abaa0e91b 100644 --- a/src/main/java/io/split/android/client/api/SplitView.java +++ b/src/main/java/io/split/android/client/api/SplitView.java @@ -16,4 +16,5 @@ public class SplitView { public List treatments; public long changeNumber; public Map configs; + public List sets; } diff --git a/src/main/java/io/split/android/client/dtos/Split.java b/src/main/java/io/split/android/client/dtos/Split.java index d74669262..395a4c3a3 100644 --- a/src/main/java/io/split/android/client/dtos/Split.java +++ b/src/main/java/io/split/android/client/dtos/Split.java @@ -1,19 +1,52 @@ package io.split.android.client.dtos; +import androidx.annotation.Nullable; + +import com.google.gson.annotations.SerializedName; + import java.util.List; import java.util.Map; +import java.util.Set; public class Split { + + @SerializedName("name") public String name; + + @SerializedName("seed") public int seed; + + @SerializedName("status") public Status status; + + @SerializedName("killed") public boolean killed; + + @SerializedName("defaultTreatment") public String defaultTreatment; + + @SerializedName("conditions") public List conditions; + + @SerializedName("trafficTypeName") public String trafficTypeName; + + @SerializedName("changeNumber") public long changeNumber; + + @SerializedName("trafficAllocation") public Integer trafficAllocation; + + @SerializedName("trafficAllocationSeed") public Integer trafficAllocationSeed; + + @SerializedName("algo") public int algo; + + @SerializedName("configurations") public Map configurations; + + @Nullable + @SerializedName("sets") + public Set sets; } diff --git a/src/main/java/io/split/android/engine/experiments/ParsedSplit.java b/src/main/java/io/split/android/engine/experiments/ParsedSplit.java index db800870a..1661d3cdf 100644 --- a/src/main/java/io/split/android/engine/experiments/ParsedSplit.java +++ b/src/main/java/io/split/android/engine/experiments/ParsedSplit.java @@ -1,30 +1,28 @@ package io.split.android.engine.experiments; +import androidx.annotation.NonNull; + import com.google.common.collect.ImmutableList; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Set; -/** - * a value class representing an io.codigo.dtos.Experiment. Why are we not using - * that class? Because it does not have the logic of matching. ParsedExperiment - * has the matchers that also encapsulate the logic of matching. We - * can easily cache this object. - */ -@SuppressWarnings("RedundantCast") public class ParsedSplit { - private final String _split; - private final int _seed; - private final boolean _killed; - private final String _defaultTreatment; - private final ImmutableList _parsedCondition; - private final String _trafficTypeName; - private final long _changeNumber; - private final int _trafficAllocation; - private final int _trafficAllocationSeed; - private final int _algo; - private final Map _configurations; + private final String mSplit; + private final int mSeed; + private final boolean mKilled; + private final String mDefaultTreatment; + private final ImmutableList mParsedCondition; + private final String mTrafficTypeName; + private final long mChangeNumber; + private final int mTrafficAllocation; + private final int mTrafficAllocationSeed; + private final int mAlgo; + private final Map mConfigurations; + private final Set mSets; public ParsedSplit( String feature, @@ -37,81 +35,87 @@ public ParsedSplit( int trafficAllocation, int trafficAllocationSeed, int algo, - Map configurations + Map configurations, + Set sets ) { - _split = feature; - _seed = seed; - _killed = killed; - _defaultTreatment = defaultTreatment; - _parsedCondition = ImmutableList.copyOf(matcherAndSplits); - _trafficTypeName = trafficTypeName; - _changeNumber = changeNumber; - _algo = algo; - _configurations = configurations; - - if (_defaultTreatment == null) { + mSplit = feature; + mSeed = seed; + mKilled = killed; + mDefaultTreatment = defaultTreatment; + mParsedCondition = ImmutableList.copyOf(matcherAndSplits); + mTrafficTypeName = trafficTypeName; + mChangeNumber = changeNumber; + mAlgo = algo; + mConfigurations = configurations; + + if (mDefaultTreatment == null) { throw new IllegalArgumentException("DefaultTreatment is null"); } - this._trafficAllocation = trafficAllocation; - this._trafficAllocationSeed = trafficAllocationSeed; + mTrafficAllocation = trafficAllocation; + mTrafficAllocationSeed = trafficAllocationSeed; + mSets = sets; } - public String feature() { - return _split; + return mSplit; } public int trafficAllocation() { - return _trafficAllocation; + return mTrafficAllocation; } public int trafficAllocationSeed() { - return _trafficAllocationSeed; + return mTrafficAllocationSeed; } public int seed() { - return _seed; + return mSeed; } public boolean killed() { - return _killed; + return mKilled; } public String defaultTreatment() { - return _defaultTreatment; + return mDefaultTreatment; } public List parsedConditions() { - return _parsedCondition; + return mParsedCondition; } public String trafficTypeName() { - return _trafficTypeName; + return mTrafficTypeName; } public long changeNumber() { - return _changeNumber; + return mChangeNumber; } public int algo() { - return _algo; + return mAlgo; } public Map configurations() { - return _configurations; + return mConfigurations; + } + + public Set sets() { + return mSets; } @Override public int hashCode() { int result = 17; - result = 31 * result + _split.hashCode(); - result = 31 * result + (int) (_seed ^ (_seed >>> 32)); - result = 31 * result + (_killed ? 1 : 0); - result = 31 * result + _defaultTreatment.hashCode(); - result = 31 * result + _parsedCondition.hashCode(); - result = 31 * result + (_trafficTypeName == null ? 0 : _trafficTypeName.hashCode()); - result = 31 * result + (int) (_changeNumber ^ (_changeNumber >>> 32)); - result = 31 * result + (_algo ^ (_algo >>> 32)); + result = 31 * result + mSplit.hashCode(); + result = 31 * result + (int) (mSeed ^ (mSeed >>> 32)); + result = 31 * result + (mKilled ? 1 : 0); + result = 31 * result + mDefaultTreatment.hashCode(); + result = 31 * result + mParsedCondition.hashCode(); + result = 31 * result + (mTrafficTypeName == null ? 0 : mTrafficTypeName.hashCode()); + result = 31 * result + (int) (mChangeNumber ^ (mChangeNumber >>> 32)); + result = 31 * result + (mAlgo ^ (mAlgo >>> 32)); + result = 31 * result + ((mSets != null) ? mSets.hashCode() : 0); return result; } @@ -122,25 +126,27 @@ public boolean equals(Object obj) { if (!(obj instanceof ParsedSplit)) return false; ParsedSplit other = (ParsedSplit) obj; - return _split.equals(other._split) - && _seed == other._seed - && _killed == other._killed - && _defaultTreatment.equals(other._defaultTreatment) - && _parsedCondition.equals(other._parsedCondition) - && (_trafficTypeName == null ? other._trafficTypeName == null : _trafficTypeName.equals(other._trafficTypeName)) - && _changeNumber == other._changeNumber - && _algo == other._algo - && (_configurations == null ? other._configurations == null : _configurations.equals(other._configurations)); + return mSplit.equals(other.mSplit) + && mSeed == other.mSeed + && mKilled == other.mKilled + && mDefaultTreatment.equals(other.mDefaultTreatment) + && mParsedCondition.equals(other.mParsedCondition) + && (Objects.equals(mTrafficTypeName, other.mTrafficTypeName)) + && mChangeNumber == other.mChangeNumber + && mAlgo == other.mAlgo + && (Objects.equals(mConfigurations, other.mConfigurations)) + && (Objects.equals(mSets, other.mSets)); } + @NonNull @Override public String toString() { - return "name:" + _split + ", seed:" + _seed + ", killed:" + _killed + - ", default treatment:" + _defaultTreatment + - ", parsedConditions:" + _parsedCondition + - ", trafficTypeName:" + _trafficTypeName + ", changeNumber:" + _changeNumber + - ", algo:" + _algo + ", config:" + _configurations; + return "name:" + mSplit + ", seed:" + mSeed + ", killed:" + mKilled + + ", default treatment:" + mDefaultTreatment + + ", parsedConditions:" + mParsedCondition + + ", trafficTypeName:" + mTrafficTypeName + ", changeNumber:" + mChangeNumber + + ", algo:" + mAlgo + ", config:" + mConfigurations + ", sets:" + mSets; } } diff --git a/src/main/java/io/split/android/engine/experiments/SplitParser.java b/src/main/java/io/split/android/engine/experiments/SplitParser.java index f07087e0f..274afbe0d 100644 --- a/src/main/java/io/split/android/engine/experiments/SplitParser.java +++ b/src/main/java/io/split/android/engine/experiments/SplitParser.java @@ -62,7 +62,7 @@ public ParsedSplit parse(@Nullable Split split, @Nullable String matchingKey) { try { return parseWithoutExceptionHandling(split, matchingKey); } catch (Throwable t) { - Logger.e(t, "Could not parse feature flag: %s", split); + Logger.e(t, "Could not parse feature flag: %s", (split != null) ? split.name : "null"); return null; } } @@ -90,7 +90,18 @@ private ParsedSplit parseWithoutExceptionHandling(Split split, String matchingKe parsedConditionList.add(new ParsedCondition(condition.conditionType, matcher, partitions, condition.label)); } - return new ParsedSplit(split.name, split.seed, split.killed, split.defaultTreatment, parsedConditionList, split.trafficTypeName, split.changeNumber, split.trafficAllocation, split.trafficAllocationSeed, split.algo, split.configurations); + return new ParsedSplit(split.name, + split.seed, + split.killed, + split.defaultTreatment, + parsedConditionList, + split.trafficTypeName, + split.changeNumber, + split.trafficAllocation, + split.trafficAllocationSeed, + split.algo, + split.configurations, + split.sets); } private CombiningMatcher toMatcher(MatcherGroup matcherGroup, String matchingKey) { diff --git a/src/test/java/io/split/android/client/SplitManagerImplTest.java b/src/test/java/io/split/android/client/SplitManagerImplTest.java index 06dee96fa..2097ba00f 100644 --- a/src/test/java/io/split/android/client/SplitManagerImplTest.java +++ b/src/test/java/io/split/android/client/SplitManagerImplTest.java @@ -23,6 +23,7 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -32,6 +33,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.core.IsNull.notNullValue; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.when; @@ -65,7 +67,6 @@ public void splitCallWithNonExistentSplit() { @Test public void splitCallWithExistentSplit() { String existent = "existent"; - SplitFetcher splitFetcher = Mockito.mock(SplitFetcher.class); Map configs = new HashMap<>(); configs.put("off", "{\"f\":\"v\"}"); @@ -101,7 +102,6 @@ public void splitsCallWithNoSplit() { @Test public void splitsCallWithSplit() { - SplitFetcher splitFetcher = Mockito.mock(SplitFetcher.class); Map splitsMap = new HashMap<>(); Split split = SplitHelper.createSplit("FeatureName", 123, true, "off", Lists.newArrayList(getTestCondition()), "traffic", 456L, 1, null); splitsMap.put(split.name, split); @@ -128,7 +128,6 @@ public void splitNamesCallWithNoSplit() { @Test public void splitNamesCallWithSplit() { - SplitFetcher splitFetcher = Mockito.mock(SplitFetcher.class); Map splitsMap = new HashMap<>(); Split split = SplitHelper.createSplit("FeatureName", 123, true, "off", Lists.newArrayList(getTestCondition()), @@ -142,8 +141,24 @@ public void splitNamesCallWithSplit() { assertThat(splitNames.get(0), is(equalTo(split.name))); } + @Test + public void flagSets() { + Map splitsMap = new HashMap<>(); + Split split = SplitHelper.createSplit("FeatureName", 123, true, + "off", Lists.newArrayList(getTestCondition()), + "traffic", 456L, 1, null); + splitsMap.put(split.name, split); + when(mSplitsStorage.getAll()).thenReturn(splitsMap); + + SplitManager splitManager = mSplitManager; + + List splitNames = splitManager.splits(); + assertEquals(1, splitNames.size()); + assertEquals(split.name, splitNames.get(0).name); + assertEquals(new ArrayList<>(split.sets), splitNames.get(0).sets); + } + private Condition getTestCondition() { return SplitHelper.createCondition(CombiningMatcher.of(new AllKeysMatcher()), Lists.newArrayList(ConditionsTestUtil.partition("off", 10))); } - } diff --git a/src/test/java/io/split/android/engine/experiments/SplitParserTest.java b/src/test/java/io/split/android/engine/experiments/SplitParserTest.java index eb4f90da2..18fdf9fe8 100644 --- a/src/test/java/io/split/android/engine/experiments/SplitParserTest.java +++ b/src/test/java/io/split/android/engine/experiments/SplitParserTest.java @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -351,6 +352,7 @@ private Split makeSplit(String name, List conditions, long changeNumb split.changeNumber = changeNumber; split.algo = 1; split.configurations = configurations; + split.sets = Collections.emptySet(); return split; } diff --git a/src/test/java/io/split/android/helpers/SplitHelper.java b/src/test/java/io/split/android/helpers/SplitHelper.java index f7baf468d..0aec872e8 100644 --- a/src/test/java/io/split/android/helpers/SplitHelper.java +++ b/src/test/java/io/split/android/helpers/SplitHelper.java @@ -1,9 +1,11 @@ package io.split.android.helpers; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import io.split.android.client.dtos.Condition; import io.split.android.client.dtos.ConditionType; @@ -42,6 +44,30 @@ public static Split createSplit( long changeNumber, int algo, Map configurations + ) { + return createSplit(feature, + seed, + killed, + defaultTreatment, + conditions, + trafficTypeName, + changeNumber, + algo, + configurations, + Collections.emptySet()); + } + + public static Split createSplit( + String feature, + int seed, + boolean killed, + String defaultTreatment, + List conditions, + String trafficTypeName, + long changeNumber, + int algo, + Map configurations, + Set sets ) { Split split = new Split(); split.name = feature; @@ -52,11 +78,11 @@ public static Split createSplit( split.trafficTypeName = trafficTypeName; split.changeNumber = changeNumber; split.trafficAllocation = 100; - split.seed = seed; split.trafficAllocationSeed = seed; split.algo = algo; split.status = Status.ACTIVE; split.configurations = configurations; + split.sets = sets; return split; } @@ -95,7 +121,8 @@ public static ParsedSplit createParsedSplit( 100, seed, algo, - configurations + configurations, + Collections.emptySet() ); } From b18bbb848e8fb8687b5807ac907aa91cfd40eaf6 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 23 Aug 2023 14:05:50 -0300 Subject: [PATCH 02/89] Sets validator; BY_SET filter --- .../io/split/android/client/SplitFilter.java | 28 +++++-- .../validators/FlagSetsValidatorImpl.java | 46 ++++++++++ .../validators/SplitFilterValidator.java | 8 ++ .../validators/FlagSetsValidatorImplTest.java | 84 +++++++++++++++++++ 4 files changed, 159 insertions(+), 7 deletions(-) create mode 100644 src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java create mode 100644 src/main/java/io/split/android/client/validators/SplitFilterValidator.java create mode 100644 src/test/java/io/split/android/client/validators/FlagSetsValidatorImplTest.java diff --git a/src/main/java/io/split/android/client/SplitFilter.java b/src/main/java/io/split/android/client/SplitFilter.java index 7aaa4457a..3136dd771 100644 --- a/src/main/java/io/split/android/client/SplitFilter.java +++ b/src/main/java/io/split/android/client/SplitFilter.java @@ -3,16 +3,20 @@ import androidx.annotation.NonNull; import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import io.split.android.client.validators.FlagSetsValidatorImpl; +import io.split.android.client.validators.SplitFilterValidator; + public class SplitFilter { public enum Type { // Filters here has to be defined in the order // it will be in querystring BY_NAME, + BY_SET, BY_PREFIX; + @NonNull @Override public String toString() { switch (this) { @@ -20,6 +24,8 @@ public String toString() { return "by split name"; case BY_PREFIX: return "by split prefix"; + case BY_SET: + return "by flag set"; default: return "Invalid type"; } @@ -31,6 +37,8 @@ public String queryStringField() { return "names"; case BY_PREFIX: return "prefixes"; + case BY_SET: + return "sets"; default: return "unknown"; } @@ -42,6 +50,8 @@ public int maxValuesCount() { return 400; case BY_PREFIX: return 50; + case BY_SET: + return 1000; default: return 0; } @@ -59,16 +69,25 @@ static public SplitFilter byPrefix(@NonNull List values) { return new SplitFilter(Type.BY_PREFIX, values); } + static public SplitFilter bySet(@NonNull List values) { + return new SplitFilter(Type.BY_SET, values, new FlagSetsValidatorImpl()); + } + // This constructor is not private (but default) to allow Split Sync Config builder be agnostic when creating filters // Also is not public to force SDK users to use static functions "byName" and "byPrefix" SplitFilter(Type type, List values) { - if(values == null) { + if (values == null) { throw new IllegalArgumentException("Values can't be null for " + type.toString() + " filter"); } mType = type; mValues = new ArrayList<>(values); } + SplitFilter(Type type, List values, SplitFilterValidator validator) { + mType = type; + mValues = validator.cleanup(values); + } + public Type getType() { return mType; } @@ -76,9 +95,4 @@ public Type getType() { public List getValues() { return mValues; } - - public void updateValues(List values) { - mValues.clear(); - mValues.addAll(values); - } } diff --git a/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java b/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java new file mode 100644 index 000000000..af71836bf --- /dev/null +++ b/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java @@ -0,0 +1,46 @@ +package io.split.android.client.validators; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.TreeSet; + +import io.split.android.client.utils.logger.Logger; + +public class FlagSetsValidatorImpl implements SplitFilterValidator { + + private static final String FLAG_SET_REGEX = "^[a-z][_a-z0-9]{1,49}$"; + + + /** + * Validates the flag sets and returns a list of + * de-duplicated and alphanumerically ordered valid flag sets. + * + * @param sets list of flag sets + * @return list of unique alphanumerically ordered valid flag sets + */ + @Override + public List cleanup(List sets) { + if (sets == null || sets.isEmpty()) { + return Collections.emptyList(); + } + + TreeSet cleanedUpSets = new TreeSet<>(); + for (String set : sets) { + if (set == null || set.isEmpty()) { + continue; + } + + if (set.startsWith(" ") || set.endsWith(" ")) { + Logger.w("SDK config: Flag Set name " + set + " has extra whitespace, trimming"); + set = set.trim(); + } + + if (set.matches(FLAG_SET_REGEX)) { + cleanedUpSets.add(set); + } + } + + return new ArrayList<>(cleanedUpSets); + } +} diff --git a/src/main/java/io/split/android/client/validators/SplitFilterValidator.java b/src/main/java/io/split/android/client/validators/SplitFilterValidator.java new file mode 100644 index 000000000..89c04def2 --- /dev/null +++ b/src/main/java/io/split/android/client/validators/SplitFilterValidator.java @@ -0,0 +1,8 @@ +package io.split.android.client.validators; + +import java.util.List; + +public interface SplitFilterValidator { + + List cleanup(List sets); +} diff --git a/src/test/java/io/split/android/client/validators/FlagSetsValidatorImplTest.java b/src/test/java/io/split/android/client/validators/FlagSetsValidatorImplTest.java new file mode 100644 index 000000000..5dce643d1 --- /dev/null +++ b/src/test/java/io/split/android/client/validators/FlagSetsValidatorImplTest.java @@ -0,0 +1,84 @@ +package io.split.android.client.validators; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class FlagSetsValidatorImplTest { + + private final FlagSetsValidatorImpl mValidator = new FlagSetsValidatorImpl(); + + @Test + public void nullInputReturnsEmptyList() { + List result = mValidator.cleanup(null); + assertTrue(result.isEmpty()); + } + + @Test + public void emptyInputReturnsEmptyList() { + List result = mValidator.cleanup(Collections.emptyList()); + assertTrue(result.isEmpty()); + } + + @Test + public void duplicatedInputValuesAreRemoved() { + List result = mValidator.cleanup(Arrays.asList("set1", "set1")); + assertEquals(1, result.size()); + assertTrue(result.contains("set1")); + } + + @Test + public void valuesAreSortedAlphanumerically() { + List result = mValidator.cleanup(Arrays.asList("set2", "set1", "set_1")); + assertEquals(3, result.size()); + assertEquals("set1", result.get(0)); + assertEquals("set2", result.get(1)); + assertEquals("set_1", result.get(2)); + } + + @Test + public void invalidValuesAreRemoved() { + List result = mValidator.cleanup(Arrays.asList("set1", "set2", "set_1", "set-1", "set 1", "set 2")); + assertEquals(3, result.size()); + assertEquals("set1", result.get(0)); + assertEquals("set2", result.get(1)); + assertEquals("set_1", result.get(2)); + } + + @Test + public void setWithMoreThan50CharsIsRemoved() { + String longSet = "abcdfghijklmnopqrstuvwxyz1234567890abcdfghijklmnopq"; + List result = mValidator.cleanup(Arrays.asList("set1", longSet)); + assertEquals(51, longSet.length()); + assertEquals(1, result.size()); + assertEquals("set1", result.get(0)); + } + + @Test + public void setWithLessThanOneCharIsOrEmptyRemoved() { + List result = mValidator.cleanup(Arrays.asList("set1", "", " ")); + assertEquals(1, result.size()); + assertEquals("set1", result.get(0)); + } + + @Test + public void nullSetIsRemoved() { + List result = mValidator.cleanup(Arrays.asList("set1", null)); + assertEquals(1, result.size()); + assertEquals("set1", result.get(0)); + } + + @Test + public void setWithExtraWhitespaceIsTrimmed() { + List result = mValidator.cleanup(Arrays.asList("set1 ", " set2", "set3", "set 4")); + assertEquals(3, result.size()); + assertEquals("set1", result.get(0)); + assertEquals("set2", result.get(1)); + assertEquals("set3", result.get(2)); + } +} From 5561a3b4329590a0d9ccee999942a3ab1e49b5cb Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 23 Aug 2023 14:26:24 -0300 Subject: [PATCH 03/89] Add validator log --- .../split/android/client/validators/FlagSetsValidatorImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java b/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java index af71836bf..ac8e79f2b 100644 --- a/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java +++ b/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java @@ -38,6 +38,8 @@ public List cleanup(List sets) { if (set.matches(FLAG_SET_REGEX)) { cleanedUpSets.add(set); + } else { + Logger.w("SDK config: you passed "+ set +", Flag Set must adhere to the regular expressions "+ FLAG_SET_REGEX +". This means a Flag Set must be start with a letter, be in lowercase, alphanumeric and have a max length of 50 characters. "+ set +" was discarded."); } } From 4845a6c68a6832347ded50fba25e224aa90887c0 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 23 Aug 2023 14:44:45 -0300 Subject: [PATCH 04/89] Fix regex --- .../split/android/client/validators/FlagSetsValidatorImpl.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java b/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java index ac8e79f2b..aa59a33bd 100644 --- a/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java +++ b/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java @@ -9,8 +9,7 @@ public class FlagSetsValidatorImpl implements SplitFilterValidator { - private static final String FLAG_SET_REGEX = "^[a-z][_a-z0-9]{1,49}$"; - + private static final String FLAG_SET_REGEX = "^[a-z][_a-z0-9]{0,49}$"; /** * Validates the flag sets and returns a list of From 35f6e991c71ecf325b651c50aa03dfe0e272937a Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 23 Aug 2023 15:06:37 -0300 Subject: [PATCH 05/89] Fix name --- .../android/client/validators/FlagSetsValidatorImpl.java | 8 ++++---- .../android/client/validators/SplitFilterValidator.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java b/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java index aa59a33bd..550342bc3 100644 --- a/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java +++ b/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java @@ -15,17 +15,17 @@ public class FlagSetsValidatorImpl implements SplitFilterValidator { * Validates the flag sets and returns a list of * de-duplicated and alphanumerically ordered valid flag sets. * - * @param sets list of flag sets + * @param values list of flag sets * @return list of unique alphanumerically ordered valid flag sets */ @Override - public List cleanup(List sets) { - if (sets == null || sets.isEmpty()) { + public List cleanup(List values) { + if (values == null || values.isEmpty()) { return Collections.emptyList(); } TreeSet cleanedUpSets = new TreeSet<>(); - for (String set : sets) { + for (String set : values) { if (set == null || set.isEmpty()) { continue; } diff --git a/src/main/java/io/split/android/client/validators/SplitFilterValidator.java b/src/main/java/io/split/android/client/validators/SplitFilterValidator.java index 89c04def2..95a521972 100644 --- a/src/main/java/io/split/android/client/validators/SplitFilterValidator.java +++ b/src/main/java/io/split/android/client/validators/SplitFilterValidator.java @@ -4,5 +4,5 @@ public interface SplitFilterValidator { - List cleanup(List sets); + List cleanup(List values); } From 421a4aa9b7b9d62096928aec0d317aef6489a666 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 24 Aug 2023 10:25:45 -0300 Subject: [PATCH 06/89] Sets in filter builder --- .../split/android/client/FilterBuilder.java | 35 +++++++-- .../android/client/SplitFactoryHelper.java | 2 +- .../io/split/android/client/SplitFilter.java | 10 +-- .../splits/FilterSplitsInCacheTask.java | 2 +- .../android/client/FilterBuilderTest.java | 78 ++++++++++++++----- 5 files changed, 92 insertions(+), 35 deletions(-) diff --git a/src/main/java/io/split/android/client/FilterBuilder.java b/src/main/java/io/split/android/client/FilterBuilder.java index d794ae7e1..748c09c3f 100644 --- a/src/main/java/io/split/android/client/FilterBuilder.java +++ b/src/main/java/io/split/android/client/FilterBuilder.java @@ -12,9 +12,6 @@ public class FilterBuilder { - private final static int MAX_BY_NAME_VALUES = 400; - private final static int MAX_BY_PREFIX_VALUES = 50; - private final List mFilters = new ArrayList<>(); private final FilterGrouper mFilterGrouper = new FilterGrouper(); @@ -26,19 +23,42 @@ public int compare(SplitFilter o1, SplitFilter o2) { } public FilterBuilder addFilters(List filters) { - mFilters.addAll(filters); + if (filters == null) { + return this; + } + + boolean containsSetsFilter = false; + for (SplitFilter filter : filters) { + if (filter == null) { + continue; + } + + if (filter.getType() == SplitFilter.Type.BY_SET) { + if (!containsSetsFilter) { + mFilters.clear(); + containsSetsFilter = true; + } + mFilters.add(filter); + } + + if (!containsSetsFilter) { + mFilters.add(filter); + } + } + return this; } - public String build() { + public String buildQueryString() { if (mFilters.size() == 0) { return ""; } StringHelper stringHelper = new StringHelper(); - StringBuilder queryString = new StringBuilder(""); - List sortedFilters = new ArrayList(mFilterGrouper.group(mFilters)); + StringBuilder queryString = new StringBuilder(); + + List sortedFilters = new ArrayList<>(mFilterGrouper.group(mFilters)); Collections.sort(sortedFilters, new SplitFilterComparator()); for (SplitFilter splitFilter : sortedFilters) { @@ -58,6 +78,7 @@ public String build() { queryString.append("="); queryString.append(stringHelper.join(",", deduptedValues)); } + return queryString.toString(); } diff --git a/src/main/java/io/split/android/client/SplitFactoryHelper.java b/src/main/java/io/split/android/client/SplitFactoryHelper.java index 91489591d..9b33708ae 100644 --- a/src/main/java/io/split/android/client/SplitFactoryHelper.java +++ b/src/main/java/io/split/android/client/SplitFactoryHelper.java @@ -161,7 +161,7 @@ SplitStorageContainer buildStorageContainer(UserConsent userConsentStatus, String buildSplitsFilterQueryString(SplitClientConfig config) { SyncConfig syncConfig = config.syncConfig(); if (syncConfig != null) { - return new FilterBuilder().addFilters(syncConfig.getFilters()).build(); + return new FilterBuilder().addFilters(syncConfig.getFilters()).buildQueryString(); } return null; } diff --git a/src/main/java/io/split/android/client/SplitFilter.java b/src/main/java/io/split/android/client/SplitFilter.java index 3136dd771..cdd7ce283 100644 --- a/src/main/java/io/split/android/client/SplitFilter.java +++ b/src/main/java/io/split/android/client/SplitFilter.java @@ -13,8 +13,8 @@ public enum Type { // Filters here has to be defined in the order // it will be in querystring BY_NAME, - BY_SET, - BY_PREFIX; + BY_PREFIX, + BY_SET; @NonNull @Override @@ -61,15 +61,15 @@ public int maxValuesCount() { private final SplitFilter.Type mType; private final List mValues; - static public SplitFilter byName(@NonNull List values) { + public static SplitFilter byName(@NonNull List values) { return new SplitFilter(Type.BY_NAME, values); } - static public SplitFilter byPrefix(@NonNull List values) { + public static SplitFilter byPrefix(@NonNull List values) { return new SplitFilter(Type.BY_PREFIX, values); } - static public SplitFilter bySet(@NonNull List values) { + public static SplitFilter bySet(@NonNull List values) { return new SplitFilter(Type.BY_SET, values, new FlagSetsValidatorImpl()); } diff --git a/src/main/java/io/split/android/client/service/splits/FilterSplitsInCacheTask.java b/src/main/java/io/split/android/client/service/splits/FilterSplitsInCacheTask.java index b81b0dbf0..a612b819e 100644 --- a/src/main/java/io/split/android/client/service/splits/FilterSplitsInCacheTask.java +++ b/src/main/java/io/split/android/client/service/splits/FilterSplitsInCacheTask.java @@ -52,7 +52,7 @@ public SplitTaskExecutionInfo execute() { prefixesToKeep.addAll(filter.getValues()); break; default: - Logger.e("Unknown filter type" + filter.getType().toString()); + Logger.e("Unknown filter type: " + filter.getType().toString()); } } diff --git a/src/test/java/io/split/android/client/FilterBuilderTest.java b/src/test/java/io/split/android/client/FilterBuilderTest.java index 48104e658..f8473d0e7 100644 --- a/src/test/java/io/split/android/client/FilterBuilderTest.java +++ b/src/test/java/io/split/android/client/FilterBuilderTest.java @@ -1,10 +1,13 @@ package io.split.android.client; +import static org.junit.Assert.assertEquals; + import org.junit.Assert; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; public class FilterBuilderTest { @@ -16,9 +19,9 @@ public void testBasicQueryString() { SplitFilter byNameFilter = SplitFilter.byName(Arrays.asList("nf_a", "nf_c", "nf_b")); SplitFilter byPrefixFilter = SplitFilter.byPrefix(Arrays.asList("pf_c", "pf_b", "pf_a")); - String queryString = new FilterBuilder().addFilters(Arrays.asList(byNameFilter, byPrefixFilter)).build(); + String queryString = new FilterBuilder().addFilters(Arrays.asList(byNameFilter, byPrefixFilter)).buildQueryString(); - Assert.assertEquals("&names=nf_a,nf_b,nf_c&prefixes=pf_a,pf_b,pf_c", queryString); + assertEquals("&names=nf_a,nf_b,nf_c&prefixes=pf_a,pf_b,pf_c", queryString); } @Test @@ -28,16 +31,16 @@ public void testOnlyOneTypeQueryString() { SplitFilter byNameFilter = SplitFilter.byName(Arrays.asList("nf_a", "nf_c", "nf_b")); SplitFilter byPrefixFilter = SplitFilter.byPrefix(Arrays.asList("pf_c", "pf_b", "pf_a")); - String onlyByNameQs = new FilterBuilder().addFilters(Arrays.asList(byNameFilter)).build(); - String onlyByPrefixQs = new FilterBuilder().addFilters(Arrays.asList(byPrefixFilter)).build(); + String onlyByNameQs = new FilterBuilder().addFilters(Arrays.asList(byNameFilter)).buildQueryString(); + String onlyByPrefixQs = new FilterBuilder().addFilters(Arrays.asList(byPrefixFilter)).buildQueryString(); - Assert.assertEquals("&names=nf_a,nf_b,nf_c", onlyByNameQs); - Assert.assertEquals("&prefixes=pf_a,pf_b,pf_c", onlyByPrefixQs); + assertEquals("&names=nf_a,nf_b,nf_c", onlyByNameQs); + assertEquals("&prefixes=pf_a,pf_b,pf_c", onlyByPrefixQs); } @Test public void filterValuesDeduptedAndGrouped() { - // Duplicated filter values should be removed on builing + // Duplicated filter values should be removed on building List filters = Arrays.asList( SplitFilter.byName(Arrays.asList("nf_a", "nf_c", "nf_b")), @@ -47,9 +50,9 @@ public void filterValuesDeduptedAndGrouped() { String queryString = new FilterBuilder() .addFilters(filters) - .build(); + .buildQueryString(); - Assert.assertEquals("&names=nf_a,nf_b,nf_c,nf_d&prefixes=pf_a,pf_b,pf_c,pf_d", queryString); + assertEquals("&names=nf_a,nf_b,nf_c,nf_d&prefixes=pf_a,pf_b,pf_c,pf_d", queryString); } @Test @@ -65,7 +68,7 @@ public void maxByNameFilterExceded() { try { String queryString = new FilterBuilder() .addFilters(Arrays.asList(SplitFilter.byName(values))) - .build(); + .buildQueryString(); } catch (Exception e) { exceptionThrown = true; } @@ -86,7 +89,7 @@ public void maxByPrefixFilterExceded() { try { String queryString = new FilterBuilder() .addFilters(Arrays.asList(SplitFilter.byPrefix(values))) - .build(); + .buildQueryString(); } catch (Exception e) { exceptionThrown = true; } @@ -98,9 +101,9 @@ public void maxByPrefixFilterExceded() { public void testNoFilters() { // When no filter added, query string has to be empty - String queryString = new FilterBuilder().build(); + String queryString = new FilterBuilder().buildQueryString(); - Assert.assertEquals("", queryString); + assertEquals("", queryString); } @Test @@ -110,8 +113,8 @@ public void testQueryStringWithSpecialChars1() { .addSplitFilter(SplitFilter.byName(Arrays.asList("ausgefüllt"))) .addSplitFilter(SplitFilter.byPrefix(Arrays.asList())) .build(); - String queryString = new FilterBuilder().addFilters(config.getFilters()).build(); - Assert.assertEquals("&names=abc\u0223,abc\u0223asd,ausgefüllt,\u0223abc", queryString); + String queryString = new FilterBuilder().addFilters(config.getFilters()).buildQueryString(); + assertEquals("&names=abc\u0223,abc\u0223asd,ausgefüllt,\u0223abc", queryString); } @Test @@ -121,8 +124,8 @@ public void testQueryStringWithSpecialChars2() { .addSplitFilter(SplitFilter.byPrefix(Arrays.asList("ausgefüllt"))) .addSplitFilter(SplitFilter.byName(Arrays.asList())) .build(); - String queryString = new FilterBuilder().addFilters(config.getFilters()).build(); - Assert.assertEquals("&prefixes=abc\u0223,abc\u0223asd,ausgefüllt,\u0223abc", queryString); + String queryString = new FilterBuilder().addFilters(config.getFilters()).buildQueryString(); + assertEquals("&prefixes=abc\u0223,abc\u0223asd,ausgefüllt,\u0223abc", queryString); } @Test @@ -133,8 +136,8 @@ public void testQueryStringWithSpecialChars3() { .addSplitFilter(SplitFilter.byPrefix(Arrays.asList("\u0223abc", "abc\u0223asd", "abc\u0223"))) .addSplitFilter(SplitFilter.byPrefix(Arrays.asList("ausgefüllt"))) .build(); - String queryString = new FilterBuilder().addFilters(config.getFilters()).build(); - Assert.assertEquals("&names=abc\u0223,abc\u0223asd,ausgefüllt,\u0223abc&prefixes=abc\u0223,abc\u0223asd,ausgefüllt,\u0223abc", queryString); + String queryString = new FilterBuilder().addFilters(config.getFilters()).buildQueryString(); + assertEquals("&names=abc\u0223,abc\u0223asd,ausgefüllt,\u0223abc&prefixes=abc\u0223,abc\u0223asd,ausgefüllt,\u0223abc", queryString); } @Test @@ -142,7 +145,40 @@ public void testQueryStringWithSpecialChars4() { SyncConfig config = SyncConfig.builder() .addSplitFilter(SplitFilter.byName(Arrays.asList("__ш", "__a", "%", "%25", " __ш ", "% "))) .build(); - String queryString = new FilterBuilder().addFilters(config.getFilters()).build(); - Assert.assertEquals("&names=%,%25,__a,__ш", queryString); + String queryString = new FilterBuilder().addFilters(config.getFilters()).buildQueryString(); + assertEquals("&names=%,%25,__a,__ш", queryString); + } + + @Test + public void addingBySetFilterAlongsideOtherTypesLeavesOnlyBySet() { + List filters = Arrays.asList( + SplitFilter.byName(Arrays.asList("nf_a", "nf_c", "nf_b")), + SplitFilter.byName(Arrays.asList("nf_b", "nf_d")), + SplitFilter.bySet(Collections.singletonList("zz")), + SplitFilter.byPrefix(Arrays.asList("pf_a", "pf_c", "pf_b")), + SplitFilter.bySet(Arrays.asList("pf_d", "pf_a", "_invalid"))); + + String queryString = new FilterBuilder().addFilters(filters).buildQueryString(); + + assertEquals("&sets=pf_a,pf_d,zz", queryString); + } + + @Test + public void bySetQueryStringIsBuiltCorrectly() { + String queryString = new FilterBuilder().addFilters(Arrays.asList(SplitFilter.bySet(Arrays.asList("pf_d", "pf_a", "_invalid")))).buildQueryString(); + + assertEquals("&sets=pf_a,pf_d", queryString); + } + + @Test + public void addingMultipleBySetFiltersCombinesTheValues() { + List filters = Arrays.asList( + SplitFilter.bySet(Arrays.asList("pf_d", "pf_a", "_invalid")), + SplitFilter.bySet(Arrays.asList("pf_d", "pf_c", "_invalid")), + SplitFilter.bySet(Arrays.asList("zz", "zzz"))); + + String queryString = new FilterBuilder().addFilters(filters).buildQueryString(); + + assertEquals("&sets=pf_a,pf_c,pf_d,zz,zzz", queryString); } } From ece6c422df7ea82d5400d239bc6354d9a8c6c43f Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 24 Aug 2023 10:33:27 -0300 Subject: [PATCH 07/89] Add comment --- src/main/java/io/split/android/client/FilterBuilder.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/io/split/android/client/FilterBuilder.java b/src/main/java/io/split/android/client/FilterBuilder.java index 748c09c3f..d15243dc3 100644 --- a/src/main/java/io/split/android/client/FilterBuilder.java +++ b/src/main/java/io/split/android/client/FilterBuilder.java @@ -34,6 +34,8 @@ public FilterBuilder addFilters(List filters) { } if (filter.getType() == SplitFilter.Type.BY_SET) { + // BY_SET filter has precedence over other filters, so we remove all other filters + // and only add BY_SET filters if (!containsSetsFilter) { mFilters.clear(); containsSetsFilter = true; From be355893590d3b721bfe3bbf364bae35c4b4b75c Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 28 Aug 2023 09:55:01 -0300 Subject: [PATCH 08/89] Pre work --- .../split/android/client/FilterBuilder.java | 31 ++++++++++++++----- .../split/android/client/FilterGrouper.java | 5 +-- .../android/client/SplitFactoryHelper.java | 8 ----- .../android/client/SplitFactoryImpl.java | 11 +++++-- .../executor/SplitTaskFactoryImpl.java | 9 ++++-- 5 files changed, 42 insertions(+), 22 deletions(-) diff --git a/src/main/java/io/split/android/client/FilterBuilder.java b/src/main/java/io/split/android/client/FilterBuilder.java index d15243dc3..8572d8f18 100644 --- a/src/main/java/io/split/android/client/FilterBuilder.java +++ b/src/main/java/io/split/android/client/FilterBuilder.java @@ -1,5 +1,9 @@ package io.split.android.client; +import static com.google.common.base.Preconditions.checkNotNull; + +import androidx.annotation.NonNull; + import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -13,13 +17,14 @@ public class FilterBuilder { private final List mFilters = new ArrayList<>(); - private final FilterGrouper mFilterGrouper = new FilterGrouper(); + private final FilterGrouper mFilterGrouper; - static private class SplitFilterComparator implements Comparator { - @Override - public int compare(SplitFilter o1, SplitFilter o2) { - return o1.getType().compareTo(o2.getType()); - } + public FilterBuilder() { + this(new FilterGrouper()); + } + + FilterBuilder(@NonNull FilterGrouper filterGrouper) { + mFilterGrouper = checkNotNull(filterGrouper); } public FilterBuilder addFilters(List filters) { @@ -60,7 +65,7 @@ public String buildQueryString() { StringHelper stringHelper = new StringHelper(); StringBuilder queryString = new StringBuilder(); - List sortedFilters = new ArrayList<>(mFilterGrouper.group(mFilters)); + List sortedFilters = getGroupedFilter(); Collections.sort(sortedFilters, new SplitFilterComparator()); for (SplitFilter splitFilter : sortedFilters) { @@ -84,6 +89,11 @@ public String buildQueryString() { return queryString.toString(); } + @NonNull + public ArrayList getGroupedFilter() { + return new ArrayList<>(mFilterGrouper.group(mFilters)); + } + private void validateFilterSize(SplitFilter.Type type, int size) { if (size > type.maxValuesCount()) { String message = "Error: " + type.maxValuesCount() + " different split " + type.queryStringField() + @@ -92,4 +102,11 @@ private void validateFilterSize(SplitFilter.Type type, int size) { throw new IllegalArgumentException(message); } } + + private static class SplitFilterComparator implements Comparator { + @Override + public int compare(SplitFilter o1, SplitFilter o2) { + return o1.getType().compareTo(o2.getType()); + } + } } diff --git a/src/main/java/io/split/android/client/FilterGrouper.java b/src/main/java/io/split/android/client/FilterGrouper.java index 139a6ff77..6e5e39fbb 100644 --- a/src/main/java/io/split/android/client/FilterGrouper.java +++ b/src/main/java/io/split/android/client/FilterGrouper.java @@ -5,8 +5,9 @@ import java.util.List; import java.util.Map; -public class FilterGrouper { - public List group(List filters) { +class FilterGrouper { + + List group(List filters) { Map> groupedValues = new HashMap<>(); for (SplitFilter filter : filters) { List groupValues = groupedValues.get(filter.getType()); diff --git a/src/main/java/io/split/android/client/SplitFactoryHelper.java b/src/main/java/io/split/android/client/SplitFactoryHelper.java index 9b33708ae..042393cb8 100644 --- a/src/main/java/io/split/android/client/SplitFactoryHelper.java +++ b/src/main/java/io/split/android/client/SplitFactoryHelper.java @@ -158,14 +158,6 @@ SplitStorageContainer buildStorageContainer(UserConsent userConsentStatus, getTelemetryStorage(shouldRecordTelemetry, telemetryStorage)); } - String buildSplitsFilterQueryString(SplitClientConfig config) { - SyncConfig syncConfig = config.syncConfig(); - if (syncConfig != null) { - return new FilterBuilder().addFilters(syncConfig.getFilters()).buildQueryString(); - } - return null; - } - SplitApiFacade buildApiFacade(SplitClientConfig splitClientConfig, HttpClient httpClient, String splitsFilterQueryString) throws URISyntaxException { diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index 215e596ba..4eceefb3b 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -170,14 +170,21 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { mStorageContainer = factoryHelper.buildStorageContainer(config.userConsent(), splitDatabase, config.shouldRecordTelemetry(), splitCipher, telemetryStorage); - String splitsFilterQueryString = factoryHelper.buildSplitsFilterQueryString(config); + SyncConfig syncConfig = config.syncConfig(); + String splitsFilterQueryString = null; + List filters = null; + if (syncConfig != null) { + FilterBuilder filterBuilder = new FilterBuilder().addFilters(syncConfig.getFilters()); + filters = filterBuilder.getGroupedFilter(); + splitsFilterQueryString = filterBuilder.buildQueryString(); + } SplitApiFacade splitApiFacade = factoryHelper.buildApiFacade( config, defaultHttpClient, splitsFilterQueryString); SplitTaskFactory splitTaskFactory = new SplitTaskFactoryImpl( config, splitApiFacade, mStorageContainer, splitsFilterQueryString, mEventsManagerCoordinator, - testingConfig); + filters, testingConfig); cleanUpDabase(splitTaskExecutor, splitTaskFactory); WorkManagerWrapper workManagerWrapper = factoryHelper.buildWorkManagerWrapper(context, config, apiToken, databaseName); diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java index 0bee62e7c..855329525 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java @@ -11,7 +11,6 @@ import java.util.Map; import java.util.Set; -import io.split.android.client.FilterGrouper; import io.split.android.client.SplitClientConfig; import io.split.android.client.SplitFilter; import io.split.android.client.TestingConfig; @@ -58,6 +57,8 @@ public class SplitTaskFactoryImpl implements SplitTaskFactory { private final TelemetryTaskFactory mTelemetryTaskFactory; private final SplitChangeProcessor mSplitChangeProcessor; private final TelemetryRuntimeProducer mTelemetryRuntimeProducer; + @Nullable + private final List mFilters; @SuppressLint("VisibleForTests") public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig, @@ -65,6 +66,7 @@ public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig, @NonNull SplitStorageContainer splitStorageContainer, @Nullable String splitsFilterQueryString, ISplitEventsManager eventsManager, + @Nullable List filters, @Nullable TestingConfig testingConfig) { mSplitClientConfig = checkNotNull(splitClientConfig); @@ -95,6 +97,8 @@ public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig, splitClientConfig, mSplitsStorageContainer.getSplitsStorage(), mSplitsStorageContainer.getMySegmentsStorageContainer()); + + mFilters = filters; } @Override @@ -141,9 +145,8 @@ public SplitsUpdateTask createSplitsUpdateTask(long since) { @Override public FilterSplitsInCacheTask createFilterSplitsInCacheTask() { - List filters = new FilterGrouper().group(mSplitClientConfig.syncConfig().getFilters()); return new FilterSplitsInCacheTask(mSplitsStorageContainer.getPersistentSplitsStorage(), - filters, mSplitsFilterQueryString); + mFilters, mSplitsFilterQueryString); } @Override From 60b57816f96ebe52bdd6855bf833a5877ab71722 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 28 Aug 2023 11:03:55 -0300 Subject: [PATCH 09/89] Modified version --- .../splits/FilterSplitsInCacheTask.java | 31 +++++- .../android/client/FilterBuilderTest.java | 12 +++ .../service/FilterSplitsInCacheTaskTest.java | 94 +++++++++++++++---- 3 files changed, 116 insertions(+), 21 deletions(-) diff --git a/src/main/java/io/split/android/client/service/splits/FilterSplitsInCacheTask.java b/src/main/java/io/split/android/client/service/splits/FilterSplitsInCacheTask.java index a612b819e..1680ef00a 100644 --- a/src/main/java/io/split/android/client/service/splits/FilterSplitsInCacheTask.java +++ b/src/main/java/io/split/android/client/service/splits/FilterSplitsInCacheTask.java @@ -37,14 +37,18 @@ public FilterSplitsInCacheTask(@NonNull PersistentSplitsStorage splitsStorage, @NonNull public SplitTaskExecutionInfo execute() { - if(!queryStringHasChanged()) { + if (!queryStringHasChanged()) { return SplitTaskExecutionInfo.success(SplitTaskType.FILTER_SPLITS_CACHE); } Set namesToKeep = new HashSet<>(); Set prefixesToKeep = new HashSet<>(); + Set setsToKeep = new HashSet<>(); for (SplitFilter filter : mSplitsFilter) { switch (filter.getType()) { + case BY_SET: + setsToKeep.addAll(filter.getValues()); + break; case BY_NAME: namesToKeep.addAll(filter.getValues()); break; @@ -60,14 +64,39 @@ public SplitTaskExecutionInfo execute() { List splitsInCache = mSplitsStorage.getAll(); for (Split split : splitsInCache) { String splitName = split.name; + + // Since sets filter takes precedence, + // if setsToKeep is not empty, we only keep splits that belong to the sets in setsToKeep + if (setsToKeep.size() > 0) { + boolean keepSplit = false; + if (split.sets != null) { + for (String set : split.sets) { + if (setsToKeep.contains(set)) { + keepSplit = true; + break; + } + } + } + + if (!keepSplit) { + splitsToDelete.add(splitName); + continue; + } + + continue; + } + + // legacy behaviour for names and prefix filters String splitPrefix = getPrefix(splitName); if (!namesToKeep.contains(split.name) && (splitPrefix == null || !prefixesToKeep.contains(splitPrefix))) { splitsToDelete.add(splitName); } } + if (splitsToDelete.size() > 0) { mSplitsStorage.delete(splitsToDelete); } + return SplitTaskExecutionInfo.success(SplitTaskType.FILTER_SPLITS_CACHE); } diff --git a/src/test/java/io/split/android/client/FilterBuilderTest.java b/src/test/java/io/split/android/client/FilterBuilderTest.java index f8473d0e7..9353cbcde 100644 --- a/src/test/java/io/split/android/client/FilterBuilderTest.java +++ b/src/test/java/io/split/android/client/FilterBuilderTest.java @@ -1,6 +1,8 @@ package io.split.android.client; import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import org.junit.Assert; import org.junit.Test; @@ -181,4 +183,14 @@ public void addingMultipleBySetFiltersCombinesTheValues() { assertEquals("&sets=pf_a,pf_c,pf_d,zz,zzz", queryString); } + + @Test + public void getGroupedFiltersUsesFilterGrouper() { + FilterGrouper filterGrouper = mock(FilterGrouper.class); + FilterBuilder filterBuilder = new FilterBuilder(filterGrouper).addFilters(Collections.emptyList()); + + filterBuilder.getGroupedFilter(); + + verify(filterGrouper).group(Collections.emptyList()); + } } diff --git a/src/test/java/io/split/android/client/service/FilterSplitsInCacheTaskTest.java b/src/test/java/io/split/android/client/service/FilterSplitsInCacheTaskTest.java index ae9b990ac..cb357f672 100644 --- a/src/test/java/io/split/android/client/service/FilterSplitsInCacheTaskTest.java +++ b/src/test/java/io/split/android/client/service/FilterSplitsInCacheTaskTest.java @@ -1,31 +1,28 @@ package io.split.android.client.service; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; +import java.util.Collections; +import java.util.HashSet; import java.util.List; -import java.util.Map; import io.split.android.client.SplitFilter; import io.split.android.client.dtos.Split; -import io.split.android.client.dtos.SplitChange; import io.split.android.client.service.splits.FilterSplitsInCacheTask; -import io.split.android.client.service.splits.SplitsSyncHelper; -import io.split.android.client.service.splits.SplitsSyncTask; import io.split.android.client.storage.splits.PersistentSplitsStorage; -import io.split.android.client.storage.splits.SplitsStorage; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; public class FilterSplitsInCacheTaskTest { @@ -35,16 +32,23 @@ public class FilterSplitsInCacheTaskTest { FilterSplitsInCacheTask mTask; List mFilters; + private AutoCloseable closeable; + @Before public void setup() { - MockitoAnnotations.initMocks(this); + closeable = MockitoAnnotations.openMocks(this); mFilters = new ArrayList<>(); } + @After + public void tearDown() throws Exception { + closeable.close(); + } + @Test public void changedQueryStringAndKeepNames() { List splits = new ArrayList<>(); - for(int i=0; i<5; i++) { + for (int i = 0; i < 5; i++) { Split split = new Split(); split.name = "sp" + i; splits.add(split); @@ -62,7 +66,7 @@ public void changedQueryStringAndKeepNames() { @Test public void changedQueryStringAndKeepPrefixes() { List splits = new ArrayList<>(); - for(int i=0; i<5; i++) { + for (int i = 0; i < 5; i++) { Split split = new Split(); split.name = "sp" + i + "__split"; splits.add(split); @@ -80,7 +84,7 @@ public void changedQueryStringAndKeepPrefixes() { @Test public void changedQueryStringAndKeepBoth() { List splits = new ArrayList<>(); - for(int i=0; i<5; i++) { + for (int i = 0; i < 5; i++) { Split split = new Split(); split.name = "sp" + i + "__split"; splits.add(split); @@ -102,7 +106,7 @@ public void changedQueryStringAndKeepBoth() { @Test public void noChangedQueryString() { List splits = new ArrayList<>(); - for(int i=0; i<5; i++) { + for (int i = 0; i < 5; i++) { Split split = new Split(); split.name = "sp" + i + "__split"; splits.add(split); @@ -121,7 +125,7 @@ public void noChangedQueryString() { @Test public void changedQueryStringNoSplitsToDelete() { List splits = new ArrayList<>(); - for(int i=1; i<4; i++) { + for (int i = 1; i < 4; i++) { Split split = new Split(); split.name = "sp" + i; splits.add(split); @@ -135,4 +139,54 @@ public void changedQueryStringNoSplitsToDelete() { verify(mSplitsStorage, never()).delete(any()); } -} \ No newline at end of file + + @Test + public void deleteSplitsNotInSet() { + List splits = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + Split split = new Split(); + split.name = "sp" + i; + split.sets = new HashSet<>(); + + if (i % 3 == 0) { + split.sets.add("set1"); + } + splits.add(split); + } + + mFilters.add(SplitFilter.bySet(Collections.singletonList("set1"))); + when(mSplitsStorage.getFilterQueryString()).thenReturn("sets=set2"); + when(mSplitsStorage.getAll()).thenReturn(splits); + mTask = new FilterSplitsInCacheTask(mSplitsStorage, mFilters, "sets=set1"); + mTask.execute(); + + assertEquals(5, splits.size()); + verify(mSplitsStorage).delete(Arrays.asList("sp1", "sp2", "sp4")); + } + + @Test + public void changedSetsQueryNoSplitsToDelete() { + List splits = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + Split split = new Split(); + split.name = "sp" + i; + split.sets = new HashSet<>(); + + if (i % 3 == 0) { + split.sets.add("set1"); + } else { + split.sets.add("set2"); + } + splits.add(split); + } + + mFilters.add(SplitFilter.bySet(Arrays.asList("set1", "set2"))); + when(mSplitsStorage.getFilterQueryString()).thenReturn("sets=set1,set2"); + when(mSplitsStorage.getAll()).thenReturn(splits); + mTask = new FilterSplitsInCacheTask(mSplitsStorage, mFilters, "sets=set1"); + mTask.execute(); + + assertEquals(5, splits.size()); + verify(mSplitsStorage, never()).delete(any()); + } +} From 22c4e70e5b777978fe5a4043bfedd7be1b3ab6bc Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 28 Aug 2023 11:06:10 -0300 Subject: [PATCH 10/89] Fixes --- .../client/service/splits/FilterSplitsInCacheTask.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/split/android/client/service/splits/FilterSplitsInCacheTask.java b/src/main/java/io/split/android/client/service/splits/FilterSplitsInCacheTask.java index 1680ef00a..009187bfe 100644 --- a/src/main/java/io/split/android/client/service/splits/FilterSplitsInCacheTask.java +++ b/src/main/java/io/split/android/client/service/splits/FilterSplitsInCacheTask.java @@ -67,7 +67,7 @@ public SplitTaskExecutionInfo execute() { // Since sets filter takes precedence, // if setsToKeep is not empty, we only keep splits that belong to the sets in setsToKeep - if (setsToKeep.size() > 0) { + if (!setsToKeep.isEmpty()) { boolean keepSplit = false; if (split.sets != null) { for (String set : split.sets) { @@ -80,7 +80,6 @@ public SplitTaskExecutionInfo execute() { if (!keepSplit) { splitsToDelete.add(splitName); - continue; } continue; @@ -93,7 +92,7 @@ public SplitTaskExecutionInfo execute() { } } - if (splitsToDelete.size() > 0) { + if (!splitsToDelete.isEmpty()) { mSplitsStorage.delete(splitsToDelete); } From 63870bf91b2ce0869d2a8808e6af5fb46aadc5d8 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 28 Aug 2023 11:19:45 -0300 Subject: [PATCH 11/89] Remove unnecesary builder structure --- .../split/android/client/FilterBuilder.java | 65 ++++++++++--------- .../android/client/SplitFactoryImpl.java | 2 +- .../android/client/FilterBuilderTest.java | 33 +++++----- 3 files changed, 49 insertions(+), 51 deletions(-) diff --git a/src/main/java/io/split/android/client/FilterBuilder.java b/src/main/java/io/split/android/client/FilterBuilder.java index 8572d8f18..67b2eb13a 100644 --- a/src/main/java/io/split/android/client/FilterBuilder.java +++ b/src/main/java/io/split/android/client/FilterBuilder.java @@ -3,6 +3,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; @@ -19,41 +20,13 @@ public class FilterBuilder { private final List mFilters = new ArrayList<>(); private final FilterGrouper mFilterGrouper; - public FilterBuilder() { - this(new FilterGrouper()); + public FilterBuilder(List filters) { + this(new FilterGrouper(), filters); } - FilterBuilder(@NonNull FilterGrouper filterGrouper) { + FilterBuilder(@NonNull FilterGrouper filterGrouper, @Nullable List filters) { mFilterGrouper = checkNotNull(filterGrouper); - } - - public FilterBuilder addFilters(List filters) { - if (filters == null) { - return this; - } - - boolean containsSetsFilter = false; - for (SplitFilter filter : filters) { - if (filter == null) { - continue; - } - - if (filter.getType() == SplitFilter.Type.BY_SET) { - // BY_SET filter has precedence over other filters, so we remove all other filters - // and only add BY_SET filters - if (!containsSetsFilter) { - mFilters.clear(); - containsSetsFilter = true; - } - mFilters.add(filter); - } - - if (!containsSetsFilter) { - mFilters.add(filter); - } - } - - return this; + addFilters(filters); } public String buildQueryString() { @@ -94,6 +67,34 @@ public ArrayList getGroupedFilter() { return new ArrayList<>(mFilterGrouper.group(mFilters)); } + private void addFilters(List filters) { + if (filters == null) { + return; + } + + boolean containsSetsFilter = false; + for (SplitFilter filter : filters) { + if (filter == null) { + continue; + } + + if (filter.getType() == SplitFilter.Type.BY_SET) { + // BY_SET filter has precedence over other filters, so we remove all other filters + // and only add BY_SET filters + if (!containsSetsFilter) { + mFilters.clear(); + containsSetsFilter = true; + } + mFilters.add(filter); + } + + if (!containsSetsFilter) { + mFilters.add(filter); + } + } + + } + private void validateFilterSize(SplitFilter.Type type, int size) { if (size > type.maxValuesCount()) { String message = "Error: " + type.maxValuesCount() + " different split " + type.queryStringField() + diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index 4eceefb3b..d138dee97 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -174,7 +174,7 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { String splitsFilterQueryString = null; List filters = null; if (syncConfig != null) { - FilterBuilder filterBuilder = new FilterBuilder().addFilters(syncConfig.getFilters()); + FilterBuilder filterBuilder = new FilterBuilder(syncConfig.getFilters()); filters = filterBuilder.getGroupedFilter(); splitsFilterQueryString = filterBuilder.buildQueryString(); } diff --git a/src/test/java/io/split/android/client/FilterBuilderTest.java b/src/test/java/io/split/android/client/FilterBuilderTest.java index 9353cbcde..898999535 100644 --- a/src/test/java/io/split/android/client/FilterBuilderTest.java +++ b/src/test/java/io/split/android/client/FilterBuilderTest.java @@ -21,7 +21,7 @@ public void testBasicQueryString() { SplitFilter byNameFilter = SplitFilter.byName(Arrays.asList("nf_a", "nf_c", "nf_b")); SplitFilter byPrefixFilter = SplitFilter.byPrefix(Arrays.asList("pf_c", "pf_b", "pf_a")); - String queryString = new FilterBuilder().addFilters(Arrays.asList(byNameFilter, byPrefixFilter)).buildQueryString(); + String queryString = new FilterBuilder(Arrays.asList(byNameFilter, byPrefixFilter)).buildQueryString(); assertEquals("&names=nf_a,nf_b,nf_c&prefixes=pf_a,pf_b,pf_c", queryString); } @@ -33,8 +33,8 @@ public void testOnlyOneTypeQueryString() { SplitFilter byNameFilter = SplitFilter.byName(Arrays.asList("nf_a", "nf_c", "nf_b")); SplitFilter byPrefixFilter = SplitFilter.byPrefix(Arrays.asList("pf_c", "pf_b", "pf_a")); - String onlyByNameQs = new FilterBuilder().addFilters(Arrays.asList(byNameFilter)).buildQueryString(); - String onlyByPrefixQs = new FilterBuilder().addFilters(Arrays.asList(byPrefixFilter)).buildQueryString(); + String onlyByNameQs = new FilterBuilder(Arrays.asList(byNameFilter)).buildQueryString(); + String onlyByPrefixQs = new FilterBuilder(Arrays.asList(byPrefixFilter)).buildQueryString(); assertEquals("&names=nf_a,nf_b,nf_c", onlyByNameQs); assertEquals("&prefixes=pf_a,pf_b,pf_c", onlyByPrefixQs); @@ -50,8 +50,7 @@ public void filterValuesDeduptedAndGrouped() { SplitFilter.byPrefix(Arrays.asList("pf_a", "pf_c", "pf_b")), SplitFilter.byPrefix(Arrays.asList("pf_d", "pf_a"))); - String queryString = new FilterBuilder() - .addFilters(filters) + String queryString = new FilterBuilder(filters) .buildQueryString(); assertEquals("&names=nf_a,nf_b,nf_c,nf_d&prefixes=pf_a,pf_b,pf_c,pf_d", queryString); @@ -68,8 +67,7 @@ public void maxByNameFilterExceded() { } try { - String queryString = new FilterBuilder() - .addFilters(Arrays.asList(SplitFilter.byName(values))) + String queryString = new FilterBuilder(Arrays.asList(SplitFilter.byName(values))) .buildQueryString(); } catch (Exception e) { exceptionThrown = true; @@ -89,8 +87,7 @@ public void maxByPrefixFilterExceded() { } try { - String queryString = new FilterBuilder() - .addFilters(Arrays.asList(SplitFilter.byPrefix(values))) + String queryString = new FilterBuilder(Arrays.asList(SplitFilter.byPrefix(values))) .buildQueryString(); } catch (Exception e) { exceptionThrown = true; @@ -103,7 +100,7 @@ public void maxByPrefixFilterExceded() { public void testNoFilters() { // When no filter added, query string has to be empty - String queryString = new FilterBuilder().buildQueryString(); + String queryString = new FilterBuilder(Collections.emptyList()).buildQueryString(); assertEquals("", queryString); } @@ -115,7 +112,7 @@ public void testQueryStringWithSpecialChars1() { .addSplitFilter(SplitFilter.byName(Arrays.asList("ausgefüllt"))) .addSplitFilter(SplitFilter.byPrefix(Arrays.asList())) .build(); - String queryString = new FilterBuilder().addFilters(config.getFilters()).buildQueryString(); + String queryString = new FilterBuilder(config.getFilters()).buildQueryString(); assertEquals("&names=abc\u0223,abc\u0223asd,ausgefüllt,\u0223abc", queryString); } @@ -126,7 +123,7 @@ public void testQueryStringWithSpecialChars2() { .addSplitFilter(SplitFilter.byPrefix(Arrays.asList("ausgefüllt"))) .addSplitFilter(SplitFilter.byName(Arrays.asList())) .build(); - String queryString = new FilterBuilder().addFilters(config.getFilters()).buildQueryString(); + String queryString = new FilterBuilder(config.getFilters()).buildQueryString(); assertEquals("&prefixes=abc\u0223,abc\u0223asd,ausgefüllt,\u0223abc", queryString); } @@ -138,7 +135,7 @@ public void testQueryStringWithSpecialChars3() { .addSplitFilter(SplitFilter.byPrefix(Arrays.asList("\u0223abc", "abc\u0223asd", "abc\u0223"))) .addSplitFilter(SplitFilter.byPrefix(Arrays.asList("ausgefüllt"))) .build(); - String queryString = new FilterBuilder().addFilters(config.getFilters()).buildQueryString(); + String queryString = new FilterBuilder(config.getFilters()).buildQueryString(); assertEquals("&names=abc\u0223,abc\u0223asd,ausgefüllt,\u0223abc&prefixes=abc\u0223,abc\u0223asd,ausgefüllt,\u0223abc", queryString); } @@ -147,7 +144,7 @@ public void testQueryStringWithSpecialChars4() { SyncConfig config = SyncConfig.builder() .addSplitFilter(SplitFilter.byName(Arrays.asList("__ш", "__a", "%", "%25", " __ш ", "% "))) .build(); - String queryString = new FilterBuilder().addFilters(config.getFilters()).buildQueryString(); + String queryString = new FilterBuilder(config.getFilters()).buildQueryString(); assertEquals("&names=%,%25,__a,__ш", queryString); } @@ -160,14 +157,14 @@ public void addingBySetFilterAlongsideOtherTypesLeavesOnlyBySet() { SplitFilter.byPrefix(Arrays.asList("pf_a", "pf_c", "pf_b")), SplitFilter.bySet(Arrays.asList("pf_d", "pf_a", "_invalid"))); - String queryString = new FilterBuilder().addFilters(filters).buildQueryString(); + String queryString = new FilterBuilder(filters).buildQueryString(); assertEquals("&sets=pf_a,pf_d,zz", queryString); } @Test public void bySetQueryStringIsBuiltCorrectly() { - String queryString = new FilterBuilder().addFilters(Arrays.asList(SplitFilter.bySet(Arrays.asList("pf_d", "pf_a", "_invalid")))).buildQueryString(); + String queryString = new FilterBuilder(Arrays.asList(SplitFilter.bySet(Arrays.asList("pf_d", "pf_a", "_invalid")))).buildQueryString(); assertEquals("&sets=pf_a,pf_d", queryString); } @@ -179,7 +176,7 @@ public void addingMultipleBySetFiltersCombinesTheValues() { SplitFilter.bySet(Arrays.asList("pf_d", "pf_c", "_invalid")), SplitFilter.bySet(Arrays.asList("zz", "zzz"))); - String queryString = new FilterBuilder().addFilters(filters).buildQueryString(); + String queryString = new FilterBuilder(filters).buildQueryString(); assertEquals("&sets=pf_a,pf_c,pf_d,zz,zzz", queryString); } @@ -187,7 +184,7 @@ public void addingMultipleBySetFiltersCombinesTheValues() { @Test public void getGroupedFiltersUsesFilterGrouper() { FilterGrouper filterGrouper = mock(FilterGrouper.class); - FilterBuilder filterBuilder = new FilterBuilder(filterGrouper).addFilters(Collections.emptyList()); + FilterBuilder filterBuilder = new FilterBuilder(filterGrouper, Collections.emptyList()); filterBuilder.getGroupedFilter(); From 4f22c8f6b763af84e3d913961fdb8eeda6357743 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 28 Aug 2023 11:21:34 -0300 Subject: [PATCH 12/89] Remove line --- src/main/java/io/split/android/client/FilterBuilder.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/io/split/android/client/FilterBuilder.java b/src/main/java/io/split/android/client/FilterBuilder.java index 67b2eb13a..2892d0a42 100644 --- a/src/main/java/io/split/android/client/FilterBuilder.java +++ b/src/main/java/io/split/android/client/FilterBuilder.java @@ -92,7 +92,6 @@ private void addFilters(List filters) { mFilters.add(filter); } } - } private void validateFilterSize(SplitFilter.Type type, int size) { From 6f1dc649931d0b47b6517dd074ef79c49c488feb Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 28 Aug 2023 14:42:27 -0300 Subject: [PATCH 13/89] Sets in storage --- .../java/tests/storage/SplitsStorageTest.java | 27 ++++++++++--- .../split/android/client/api/SplitView.java | 7 +++- .../localhost/LocalhostSplitsStorage.java | 34 +++++++++++++++++ .../client/storage/splits/SplitsStorage.java | 4 ++ .../storage/splits/SplitsStorageImpl.java | 38 +++++++++++++++++++ 5 files changed, 103 insertions(+), 7 deletions(-) diff --git a/src/androidTest/java/tests/storage/SplitsStorageTest.java b/src/androidTest/java/tests/storage/SplitsStorageTest.java index 7fc36f8d9..eab15e2cf 100644 --- a/src/androidTest/java/tests/storage/SplitsStorageTest.java +++ b/src/androidTest/java/tests/storage/SplitsStorageTest.java @@ -13,6 +13,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -32,15 +33,15 @@ public class SplitsStorageTest { private static final Long INITIAL_CHANGE_NUMBER = 9999L; private static final String JSON_SPLIT_TEMPLATE = "{\"name\":\"%s\", \"changeNumber\": %d}"; - private static final String JSON_SPLIT_WITH_TRAFFIC_TYPE_TEMPLATE = "{\"name\":\"%s\", \"changeNumber\": %d, \"trafficTypeName\":\"%s\"}"; + private static final String JSON_SPLIT_WITH_TRAFFIC_TYPE_TEMPLATE = "{\"name\":\"%s\", \"changeNumber\": %d, \"trafficTypeName\":\"%s\", \"sets\":[\"%s\"]}"; + private SplitRoomDatabase mRoomDb; - private Context mContext; private SplitsStorage mSplitsStorage; @Before public void setUp() { - mContext = InstrumentationRegistry.getInstrumentation().getContext(); - mRoomDb = DatabaseHelper.getTestDatabase(mContext); + Context context = InstrumentationRegistry.getInstrumentation().getContext(); + mRoomDb = DatabaseHelper.getTestDatabase(context); mRoomDb.clearAllTables(); List entities = new ArrayList<>(); for (int i = 0; i < 4; i++) { @@ -349,6 +350,17 @@ public void loadedFromStorageTrafficTypesAreCorrectlyUpdated() { Assert.assertTrue(mSplitsStorage.isValidTrafficType("test_type_2")); } + @Test + public void flagSetsAreUpdatedWhenCallingLoadLocal() throws InterruptedException { + mRoomDb.clearAllTables(); + mRoomDb.splitDao().insert(Arrays.asList(newSplitEntity("split_test", "test_type", Collections.singleton("set_1")), newSplitEntity("split_test_2", "test_type_2", Collections.singleton("set_2")))); + + mSplitsStorage.loadLocal(); + + Assert.assertEquals(Collections.singleton("split_test"), mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))); + Assert.assertEquals(Collections.singleton("split_test_2"), mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_2"))); + } + private Split newSplit(String name, Status status, String trafficType) { Split split = new Split(); split.name = name; @@ -362,9 +374,14 @@ private Split newSplit(String name, Status status, String trafficType) { } private static SplitEntity newSplitEntity(String name, String trafficType) { + return newSplitEntity(name, trafficType, Collections.emptySet()); + } + + private static SplitEntity newSplitEntity(String name, String trafficType, Set sets) { SplitEntity entity = new SplitEntity(); + String setsString = String.join(",", sets); entity.setName(name); - entity.setBody(String.format(JSON_SPLIT_WITH_TRAFFIC_TYPE_TEMPLATE, name, INITIAL_CHANGE_NUMBER, trafficType)); + entity.setBody(String.format(JSON_SPLIT_WITH_TRAFFIC_TYPE_TEMPLATE, name, INITIAL_CHANGE_NUMBER, trafficType, setsString)); return entity; } diff --git a/src/main/java/io/split/android/client/api/SplitView.java b/src/main/java/io/split/android/client/api/SplitView.java index abaa0e91b..4825eb33d 100644 --- a/src/main/java/io/split/android/client/api/SplitView.java +++ b/src/main/java/io/split/android/client/api/SplitView.java @@ -1,5 +1,8 @@ package io.split.android.client.api; +import androidx.annotation.NonNull; + +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -7,7 +10,6 @@ /** * A view of a feature flag, meant for consumption through {@link SplitManager} interface. - * */ public class SplitView { public String name; @@ -16,5 +18,6 @@ public class SplitView { public List treatments; public long changeNumber; public Map configs; - public List sets; + @NonNull + public List sets = new ArrayList<>(); } diff --git a/src/main/java/io/split/android/client/localhost/LocalhostSplitsStorage.java b/src/main/java/io/split/android/client/localhost/LocalhostSplitsStorage.java index 1771ca6b3..76bc60b3d 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostSplitsStorage.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostSplitsStorage.java @@ -10,8 +10,10 @@ import java.io.IOException; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import io.split.android.client.dtos.Split; import io.split.android.client.events.EventsManagerCoordinator; @@ -28,6 +30,7 @@ public class LocalhostSplitsStorage implements SplitsStorage { private String mLocalhostFileName; private final Context mContext; private final Map mInMemorySplits = Maps.newConcurrentMap(); + private final Map> mFlagSets = Maps.newConcurrentMap(); private final FileStorage mFileStorage; private LocalhostFileParser mParser; private final EventsManagerCoordinator mEventsManager; @@ -121,6 +124,23 @@ public void clear() { mInMemorySplits.clear(); } + @NonNull + @Override + public Set getNamesByFlagSets(List sets) { + Set namesToReturn = new HashSet<>(); + if (sets == null || sets.isEmpty()) { + return namesToReturn; + } + + for (String set : sets) { + Set splits = mFlagSets.get(set); + if (splits != null) { + namesToReturn.addAll(splits); + } + } + return namesToReturn; + } + private void setup() { String fileName = mLocalhostFileName; @@ -164,6 +184,20 @@ private void loadSplits() { Map values = mParser.parse(content); if (values != null) { mInMemorySplits.putAll(values); + + for (Split split : values.values()) { + Set sets = split.sets; + if (sets != null) { + for (String set : sets) { + Set splitsForSet = mFlagSets.get(set); + if (splitsForSet == null) { + splitsForSet = new HashSet<>(); + mFlagSets.put(set, splitsForSet); + } + splitsForSet.add(split.name); + } + } + } } if (!content.equals(mLastContentLoaded)) { mEventsManager.notifyInternalEvent(SplitInternalEvent.SPLITS_LOADED_FROM_STORAGE); diff --git a/src/main/java/io/split/android/client/storage/splits/SplitsStorage.java b/src/main/java/io/split/android/client/storage/splits/SplitsStorage.java index 038ad185d..d152243b5 100644 --- a/src/main/java/io/split/android/client/storage/splits/SplitsStorage.java +++ b/src/main/java/io/split/android/client/storage/splits/SplitsStorage.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Map; +import java.util.Set; import io.split.android.client.dtos.Split; @@ -32,4 +33,7 @@ public interface SplitsStorage { void updateSplitsFilterQueryString(String queryString); void clear(); + + @NonNull + Set getNamesByFlagSets(List flagSets); } diff --git a/src/main/java/io/split/android/client/storage/splits/SplitsStorageImpl.java b/src/main/java/io/split/android/client/storage/splits/SplitsStorageImpl.java index 5f4374bbd..e9fdfad52 100644 --- a/src/main/java/io/split/android/client/storage/splits/SplitsStorageImpl.java +++ b/src/main/java/io/split/android/client/storage/splits/SplitsStorageImpl.java @@ -7,8 +7,10 @@ import androidx.annotation.WorkerThread; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import io.split.android.client.dtos.Split; @@ -17,6 +19,7 @@ public class SplitsStorageImpl implements SplitsStorage { private final PersistentSplitsStorage mPersistentStorage; private final Map mInMemorySplits; + private final Map> mFlagSets; private long mChangeNumber; private long mUpdateTimestamp; private String mSplitsFilterQueryString; @@ -26,6 +29,7 @@ public SplitsStorageImpl(@NonNull PersistentSplitsStorage persistentStorage) { mPersistentStorage = checkNotNull(persistentStorage); mInMemorySplits = new ConcurrentHashMap<>(); mTrafficTypes = new ConcurrentHashMap<>(); + mFlagSets = new ConcurrentHashMap<>(); } @Override @@ -38,6 +42,7 @@ public void loadLocal() { mSplitsFilterQueryString = snapshot.getSplitsFilterQueryString(); for (Split split : splits) { mInMemorySplits.put(split.name, split); + cacheFlags(split); increaseTrafficTypeCount(split.trafficTypeName); } } @@ -135,6 +140,25 @@ public void clear() { mInMemorySplits.clear(); mChangeNumber = -1; mPersistentStorage.clear(); + mFlagSets.clear(); + mTrafficTypes.clear(); + } + + @NonNull + @Override + public Set getNamesByFlagSets(List sets) { + Set namesToReturn = new HashSet<>(); + if (sets == null || sets.isEmpty()) { + return namesToReturn; + } + + for (String set : sets) { + Set splits = mFlagSets.get(set); + if (splits != null) { + namesToReturn.addAll(splits); + } + } + return namesToReturn; } @Override @@ -177,4 +201,18 @@ private int countForTrafficType(@NonNull String name) { } return count; } + + private void cacheFlags(Split split) { + Set sets = split.sets; + if (sets != null) { + for (String set : sets) { + Set splitsForSet = mFlagSets.get(set); + if (splitsForSet == null) { + splitsForSet = new HashSet<>(); + mFlagSets.put(set, splitsForSet); + } + splitsForSet.add(split.name); + } + } + } } From 8d2e1a98fd6bb285de1c7c47a40c3e66450004e9 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 28 Aug 2023 16:10:20 -0300 Subject: [PATCH 14/89] WIP --- .../java/tests/storage/SplitsStorageTest.java | 42 ++++++++++++++- .../storage/splits/SplitsStorageImpl.java | 54 +++++++++++++++---- 2 files changed, 84 insertions(+), 12 deletions(-) diff --git a/src/androidTest/java/tests/storage/SplitsStorageTest.java b/src/androidTest/java/tests/storage/SplitsStorageTest.java index eab15e2cf..2575c4bea 100644 --- a/src/androidTest/java/tests/storage/SplitsStorageTest.java +++ b/src/androidTest/java/tests/storage/SplitsStorageTest.java @@ -351,7 +351,7 @@ public void loadedFromStorageTrafficTypesAreCorrectlyUpdated() { } @Test - public void flagSetsAreUpdatedWhenCallingLoadLocal() throws InterruptedException { + public void flagSetsAreUpdatedWhenCallingLoadLocal() { mRoomDb.clearAllTables(); mRoomDb.splitDao().insert(Arrays.asList(newSplitEntity("split_test", "test_type", Collections.singleton("set_1")), newSplitEntity("split_test_2", "test_type_2", Collections.singleton("set_2")))); @@ -361,7 +361,45 @@ public void flagSetsAreUpdatedWhenCallingLoadLocal() throws InterruptedException Assert.assertEquals(Collections.singleton("split_test_2"), mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_2"))); } + @Test + public void flagSetsAreRemovedWhenUpdating() { + mRoomDb.clearAllTables(); + mRoomDb.splitDao().insert(Arrays.asList(newSplitEntity("split_test", "test_type", Collections.singleton("set_1")), newSplitEntity("split_test_2", "test_type_2", Collections.singleton("set_2")))); + mSplitsStorage.loadLocal(); + + Set initialSet1 = mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1")); + Set initialSet2 = mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_2")); + + mSplitsStorage.update(new ProcessedSplitChange( + Collections.singletonList(newSplit("split_test", Status.ACTIVE, "test_type")), Collections.emptyList(), + 1L, 0L)); + + Assert.assertFalse(initialSet1.isEmpty()); + Assert.assertEquals(Collections.emptySet(), mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))); + Assert.assertEquals(initialSet2, mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_2"))); + } + + @Test + public void updateWithoutChecksRemovesFromFlagSet() { + mRoomDb.clearAllTables(); + mRoomDb.splitDao().insert(Arrays.asList(newSplitEntity("split_test", "test_type", Collections.singleton("set_1")), newSplitEntity("split_test_2", "test_type_2", Collections.singleton("set_2")))); + mSplitsStorage.loadLocal(); + + Set initialSet1 = mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1")); + Set initialSet2 = mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_2")); + + mSplitsStorage.updateWithoutChecks(newSplit("split_test", Status.ACTIVE, "test_type")); + + Assert.assertFalse(initialSet1.isEmpty()); + Assert.assertEquals(Collections.emptySet(), mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))); + Assert.assertEquals(initialSet2, mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_2"))); + } + private Split newSplit(String name, Status status, String trafficType) { + return newSplit(name, status, trafficType, Collections.emptySet()); + } + + private Split newSplit(String name, Status status, String trafficType, Set sets) { Split split = new Split(); split.name = name; split.status = status; @@ -370,6 +408,8 @@ private Split newSplit(String name, Status status, String trafficType) { } else { split.trafficTypeName = "custom"; } + split.sets = sets; + return split; } diff --git a/src/main/java/io/split/android/client/storage/splits/SplitsStorageImpl.java b/src/main/java/io/split/android/client/storage/splits/SplitsStorageImpl.java index e9fdfad52..9dcb73200 100644 --- a/src/main/java/io/split/android/client/storage/splits/SplitsStorageImpl.java +++ b/src/main/java/io/split/android/client/storage/splits/SplitsStorageImpl.java @@ -42,7 +42,7 @@ public void loadLocal() { mSplitsFilterQueryString = snapshot.getSplitsFilterQueryString(); for (Split split : splits) { mInMemorySplits.put(split.name, split); - cacheFlags(split); + addOrUpdateFlagSets(split); increaseTrafficTypeCount(split.trafficTypeName); } } @@ -91,6 +91,7 @@ public void update(ProcessedSplitChange splitChange) { } increaseTrafficTypeCount(split.trafficTypeName); mInMemorySplits.put(split.name, split); + addOrUpdateFlagSets(split); } } @@ -98,6 +99,7 @@ public void update(ProcessedSplitChange splitChange) { for (Split split : archivedSplits) { if (mInMemorySplits.remove(split.name) != null) { decreaseTrafficTypeCount(split.trafficTypeName); + deleteFromFlagSetsIfNecessary(split); } } } @@ -112,6 +114,7 @@ public void update(ProcessedSplitChange splitChange) { public void updateWithoutChecks(Split split) { mInMemorySplits.put(split.name, split); mPersistentStorage.update(split); + deleteFromFlagSets(split); } @Override @@ -202,16 +205,45 @@ private int countForTrafficType(@NonNull String name) { return count; } - private void cacheFlags(Split split) { - Set sets = split.sets; - if (sets != null) { - for (String set : sets) { - Set splitsForSet = mFlagSets.get(set); - if (splitsForSet == null) { - splitsForSet = new HashSet<>(); - mFlagSets.put(set, splitsForSet); - } - splitsForSet.add(split.name); + private void addOrUpdateFlagSets(Split split) { + if (split.sets == null) { + return; + } + + for (String set : split.sets) { + Set splitsForSet = mFlagSets.get(set); + if (splitsForSet == null) { + splitsForSet = new HashSet<>(); + mFlagSets.put(set, splitsForSet); + } + splitsForSet.add(split.name); + } + + deleteFromFlagSetsIfNecessary(split); + } + + private void deleteFromFlagSetsIfNecessary(Split featureFlag) { + if (featureFlag.sets == null) { + return; + } + + for (String set : mFlagSets.keySet()) { + if (featureFlag.sets.contains(set)) { + continue; + } + + Set flagsForSet = mFlagSets.get(set); + if (flagsForSet != null) { + flagsForSet.remove(featureFlag.name); + } + } + } + + private void deleteFromFlagSets(Split featureFlag) { + for (String set : mFlagSets.keySet()) { + Set flagsForSet = mFlagSets.get(set); + if (flagsForSet != null) { + flagsForSet.remove(featureFlag.name); } } } From ea248ea25b61f1b26933a5ac4c70faba76161919 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 28 Aug 2023 16:49:32 -0300 Subject: [PATCH 15/89] Improve tests --- .../java/tests/storage/SplitsStorageTest.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/androidTest/java/tests/storage/SplitsStorageTest.java b/src/androidTest/java/tests/storage/SplitsStorageTest.java index 2575c4bea..1669c1660 100644 --- a/src/androidTest/java/tests/storage/SplitsStorageTest.java +++ b/src/androidTest/java/tests/storage/SplitsStorageTest.java @@ -11,6 +11,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -353,18 +354,25 @@ public void loadedFromStorageTrafficTypesAreCorrectlyUpdated() { @Test public void flagSetsAreUpdatedWhenCallingLoadLocal() { mRoomDb.clearAllTables(); - mRoomDb.splitDao().insert(Arrays.asList(newSplitEntity("split_test", "test_type", Collections.singleton("set_1")), newSplitEntity("split_test_2", "test_type_2", Collections.singleton("set_2")))); + mRoomDb.splitDao().insert(Arrays.asList( + newSplitEntity("split_test", "test_type", Collections.singleton("set_1")), + newSplitEntity("split_test_2", "test_type_2", Collections.singleton("set_2")), + newSplitEntity("split_test_3", "test_type_2", Collections.singleton("set_2")), + newSplitEntity("split_test_4", "test_type_2", Collections.singleton("set_1")))); mSplitsStorage.loadLocal(); - Assert.assertEquals(Collections.singleton("split_test"), mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))); - Assert.assertEquals(Collections.singleton("split_test_2"), mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_2"))); + Assert.assertEquals(new HashSet<>(Arrays.asList("split_test", "split_test_4")), mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))); + Assert.assertEquals(new HashSet<>(Arrays.asList("split_test_2", "split_test_3")), mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_2"))); } @Test public void flagSetsAreRemovedWhenUpdating() { mRoomDb.clearAllTables(); - mRoomDb.splitDao().insert(Arrays.asList(newSplitEntity("split_test", "test_type", Collections.singleton("set_1")), newSplitEntity("split_test_2", "test_type_2", Collections.singleton("set_2")))); + mRoomDb.splitDao().insert(Arrays.asList( + newSplitEntity("split_test", "test_type", Collections.singleton("set_1")), + newSplitEntity("split_test_2", "test_type_2", Collections.singleton("set_2")), + newSplitEntity("split_test_3", "test_type_2", Collections.singleton("set_2")))); mSplitsStorage.loadLocal(); Set initialSet1 = mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1")); From 04fb6c2043a82c0577d73decf13b7fed29e8bb25 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 28 Aug 2023 17:53:50 -0300 Subject: [PATCH 16/89] FIx --- .../split/android/client/storage/splits/SplitsStorageImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/split/android/client/storage/splits/SplitsStorageImpl.java b/src/main/java/io/split/android/client/storage/splits/SplitsStorageImpl.java index 9dcb73200..a35e7b0c6 100644 --- a/src/main/java/io/split/android/client/storage/splits/SplitsStorageImpl.java +++ b/src/main/java/io/split/android/client/storage/splits/SplitsStorageImpl.java @@ -161,6 +161,7 @@ public Set getNamesByFlagSets(List sets) { namesToReturn.addAll(splits); } } + return namesToReturn; } From 97c61d4db1138878ebecc9da41259798204a81ef Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 29 Aug 2023 10:01:05 -0300 Subject: [PATCH 17/89] Sets validator fixes (#523) --- .../validators/FlagSetsValidatorImpl.java | 9 ++++++-- .../validators/FlagSetsValidatorImplTest.java | 22 ++++++++++++++----- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java b/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java index 550342bc3..72daeb055 100644 --- a/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java +++ b/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java @@ -9,7 +9,7 @@ public class FlagSetsValidatorImpl implements SplitFilterValidator { - private static final String FLAG_SET_REGEX = "^[a-z][_a-z0-9]{0,49}$"; + private static final String FLAG_SET_REGEX = "^[a-z0-9][_a-z0-9]{0,49}$"; /** * Validates the flag sets and returns a list of @@ -30,11 +30,16 @@ public List cleanup(List values) { continue; } - if (set.startsWith(" ") || set.endsWith(" ")) { + if (set.trim().length() != set.length()) { Logger.w("SDK config: Flag Set name " + set + " has extra whitespace, trimming"); set = set.trim(); } + if (!set.toLowerCase().equals(set)) { + Logger.w("SDK config: Flag Set name "+set+" should be all lowercase - converting string to lowercase"); + set = set.toLowerCase(); + } + if (set.matches(FLAG_SET_REGEX)) { cleanedUpSets.add(set); } else { diff --git a/src/test/java/io/split/android/client/validators/FlagSetsValidatorImplTest.java b/src/test/java/io/split/android/client/validators/FlagSetsValidatorImplTest.java index 5dce643d1..cd8bc9604 100644 --- a/src/test/java/io/split/android/client/validators/FlagSetsValidatorImplTest.java +++ b/src/test/java/io/split/android/client/validators/FlagSetsValidatorImplTest.java @@ -34,11 +34,12 @@ public void duplicatedInputValuesAreRemoved() { @Test public void valuesAreSortedAlphanumerically() { - List result = mValidator.cleanup(Arrays.asList("set2", "set1", "set_1")); - assertEquals(3, result.size()); - assertEquals("set1", result.get(0)); - assertEquals("set2", result.get(1)); - assertEquals("set_1", result.get(2)); + List result = mValidator.cleanup(Arrays.asList("set2", "set1", "set_1", "1set")); + assertEquals(4, result.size()); + assertEquals("1set", result.get(0)); + assertEquals("set1", result.get(1)); + assertEquals("set2", result.get(2)); + assertEquals("set_1", result.get(3)); } @Test @@ -75,10 +76,19 @@ public void nullSetIsRemoved() { @Test public void setWithExtraWhitespaceIsTrimmed() { - List result = mValidator.cleanup(Arrays.asList("set1 ", " set2", "set3", "set 4")); + List result = mValidator.cleanup(Arrays.asList("set1 ", " set2\r", "set3 ", "set 4\n")); assertEquals(3, result.size()); assertEquals("set1", result.get(0)); assertEquals("set2", result.get(1)); assertEquals("set3", result.get(2)); } + + @Test + public void setsAreLowercase() { + List result = mValidator.cleanup(Arrays.asList("SET1", "Set2", "SET_3")); + assertEquals(3, result.size()); + assertEquals("set1", result.get(0)); + assertEquals("set2", result.get(1)); + assertEquals("set_3", result.get(2)); + } } From 25dad002be645d16ec1c2acc04fbf893c2f2d846 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 29 Aug 2023 17:27:43 -0300 Subject: [PATCH 18/89] Sets in processor --- .../android/client/SplitFactoryHelper.java | 5 +- .../android/client/SplitFactoryImpl.java | 10 ++- .../client/service/ServiceConstants.java | 1 + .../executor/SplitTaskFactoryImpl.java | 8 ++- .../service/splits/SplitChangeProcessor.java | 65 +++++++++++++++---- .../synchronizer/WorkManagerWrapper.java | 8 ++- .../service/workmanager/SplitsSyncWorker.java | 12 +++- .../synchronizer/WorkManagerWrapperTest.java | 5 +- .../splits/SplitChangeProcessorTest.java | 53 ++++++++++++++- 9 files changed, 142 insertions(+), 25 deletions(-) diff --git a/src/main/java/io/split/android/client/SplitFactoryHelper.java b/src/main/java/io/split/android/client/SplitFactoryHelper.java index 042393cb8..0233ade4f 100644 --- a/src/main/java/io/split/android/client/SplitFactoryHelper.java +++ b/src/main/java/io/split/android/client/SplitFactoryHelper.java @@ -10,6 +10,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.Map; +import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; @@ -184,9 +185,9 @@ SplitApiFacade buildApiFacade(SplitClientConfig splitClientConfig, } WorkManagerWrapper buildWorkManagerWrapper(Context context, SplitClientConfig splitClientConfig, - String apiKey, String databaseName) { + String apiKey, String databaseName, Set configuredFlagSets) { return new WorkManagerWrapper( - WorkManager.getInstance(context), splitClientConfig, apiKey, databaseName); + WorkManager.getInstance(context), splitClientConfig, apiKey, databaseName, configuredFlagSets); } diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index d138dee97..8453b1187 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -6,7 +6,9 @@ import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import io.split.android.client.api.Key; import io.split.android.client.common.CompressionUtilProvider; @@ -31,7 +33,6 @@ import io.split.android.client.service.impressions.ImpressionManager; import io.split.android.client.service.impressions.StrategyImpressionManager; import io.split.android.client.service.sseclient.sseclient.StreamingComponents; -import io.split.android.client.service.synchronizer.FeatureFlagsSynchronizerImpl; import io.split.android.client.service.synchronizer.SyncManager; import io.split.android.client.service.synchronizer.Synchronizer; import io.split.android.client.service.synchronizer.SynchronizerImpl; @@ -182,12 +183,15 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { SplitApiFacade splitApiFacade = factoryHelper.buildApiFacade( config, defaultHttpClient, splitsFilterQueryString); + Set configuredFlagSets = (filters != null && !filters.isEmpty() && filters.get(0).getType() == SplitFilter.Type.BY_SET) ? + new HashSet<>(filters.get(0).getValues()) : new HashSet<>(); + SplitTaskFactory splitTaskFactory = new SplitTaskFactoryImpl( config, splitApiFacade, mStorageContainer, splitsFilterQueryString, mEventsManagerCoordinator, - filters, testingConfig); + filters, configuredFlagSets, testingConfig); cleanUpDabase(splitTaskExecutor, splitTaskFactory); - WorkManagerWrapper workManagerWrapper = factoryHelper.buildWorkManagerWrapper(context, config, apiToken, databaseName); + WorkManagerWrapper workManagerWrapper = factoryHelper.buildWorkManagerWrapper(context, config, apiToken, databaseName, configuredFlagSets); SplitSingleThreadTaskExecutor splitSingleThreadTaskExecutor = new SplitSingleThreadTaskExecutor(); ImpressionManager impressionManager = new StrategyImpressionManager(factoryHelper.getImpressionStrategy(splitTaskExecutor, splitTaskFactory, mStorageContainer, config)); diff --git a/src/main/java/io/split/android/client/service/ServiceConstants.java b/src/main/java/io/split/android/client/service/ServiceConstants.java index 3c25221c9..f68dd503a 100644 --- a/src/main/java/io/split/android/client/service/ServiceConstants.java +++ b/src/main/java/io/split/android/client/service/ServiceConstants.java @@ -29,6 +29,7 @@ public class ServiceConstants { public static final String WORKER_PARAM_UNIQUE_KEYS_PER_PUSH = "unique_keys_per_push"; public static final String WORKER_PARAM_UNIQUE_KEYS_ESTIMATED_SIZE_IN_BYTES = "unique_keys_estimated_size_in_bytes"; public static final String WORKER_PARAM_ENCRYPTION_ENABLED = "encryptionEnabled"; + public static final String WORKER_PARAM_CONFIGURED_SETS = "configuredSets"; public static final long LAST_SEEN_IMPRESSION_CACHE_SIZE = 500; public static final int MY_SEGMENT_V2_DATA_SIZE = 1024 * 10;// bytes diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java index 855329525..afcd4105b 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java @@ -7,6 +7,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -67,6 +68,7 @@ public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig, @Nullable String splitsFilterQueryString, ISplitEventsManager eventsManager, @Nullable List filters, + @NonNull Set configuredFlagSets, @Nullable TestingConfig testingConfig) { mSplitClientConfig = checkNotNull(splitClientConfig); @@ -74,7 +76,7 @@ public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig, mSplitsStorageContainer = checkNotNull(splitStorageContainer); mSplitsFilterQueryString = splitsFilterQueryString; mEventsManager = eventsManager; - mSplitChangeProcessor = new SplitChangeProcessor(); + mSplitChangeProcessor = new SplitChangeProcessor(configuredFlagSets); TelemetryStorage telemetryStorage = mSplitsStorageContainer.getTelemetryStorage(); mTelemetryRuntimeProducer = telemetryStorage; @@ -87,7 +89,7 @@ public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig, } else { mSplitsSyncHelper = new SplitsSyncHelper(mSplitApiFacade.getSplitFetcher(), mSplitsStorageContainer.getSplitsStorage(), - new SplitChangeProcessor(), + mSplitChangeProcessor, mTelemetryRuntimeProducer); } @@ -119,7 +121,7 @@ public ImpressionsRecorderTask createImpressionsRecorderTask() { mSplitClientConfig.impressionsPerPush(), ServiceConstants.ESTIMATED_IMPRESSION_SIZE_IN_BYTES, mSplitClientConfig.shouldRecordTelemetry()), - mSplitsStorageContainer.getTelemetryStorage()); + mSplitsStorageContainer.getTelemetryStorage()); } @Override diff --git a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java index 38e643af9..92b05f7d3 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java +++ b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java @@ -1,10 +1,12 @@ package io.split.android.client.service.splits; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; import io.split.android.client.dtos.Split; import io.split.android.client.dtos.SplitChange; @@ -12,6 +14,18 @@ import io.split.android.client.storage.splits.ProcessedSplitChange; public class SplitChangeProcessor { + + private final Set mConfiguredSets; + + @VisibleForTesting + SplitChangeProcessor() { + this(Collections.emptySet()); + } + + public SplitChangeProcessor(Set configuredSets) { + mConfiguredSets = configuredSets; + } + public ProcessedSplitChange process(SplitChange splitChange) { if (splitChange == null || splitChange.splits == null) { return new ProcessedSplitChange(new ArrayList<>(), new ArrayList<>(), -1L, 0); @@ -20,25 +34,54 @@ public ProcessedSplitChange process(SplitChange splitChange) { return buildProcessedSplitChange(splitChange.splits, splitChange.till); } - public ProcessedSplitChange process(Split split, long changeNumber) { - return buildProcessedSplitChange(Collections.singletonList(split), changeNumber); + public ProcessedSplitChange process(Split featureFlag, long changeNumber) { + return buildProcessedSplitChange(Collections.singletonList(featureFlag), changeNumber); } @NonNull - private static ProcessedSplitChange buildProcessedSplitChange(List featureFlags, long changeNumber) { - List activeSplits = new ArrayList<>(); - List archivedSplits = new ArrayList<>(); - for (Split split : featureFlags) { - if (split.name == null) { + private ProcessedSplitChange buildProcessedSplitChange(List featureFlags, long changeNumber) { + List activeFeatureFlags = new ArrayList<>(); + List archivedFeatureFlags = new ArrayList<>(); + for (Split featureFlag : featureFlags) { + if (featureFlag.name == null) { continue; } - if (split.status == Status.ACTIVE) { - activeSplits.add(split); + + if (mConfiguredSets.isEmpty()) { + processWithoutSets(activeFeatureFlags, archivedFeatureFlags, featureFlag); } else { - archivedSplits.add(split); + processWithSets(activeFeatureFlags, archivedFeatureFlags, featureFlag); + } + } + + return new ProcessedSplitChange(activeFeatureFlags, archivedFeatureFlags, changeNumber, System.currentTimeMillis() / 100); + } + + private void processWithoutSets(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag) { + if (featureFlag.status == Status.ACTIVE) { + activeFeatureFlags.add(featureFlag); + } else { + archivedFeatureFlags.add(featureFlag); + } + } + + private void processWithSets(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag) { + if (featureFlag.sets == null || featureFlag.sets.isEmpty()) { + archivedFeatureFlags.add(featureFlag); + return; + } + + boolean shouldArchive = true; + for (String set : featureFlag.sets) { + if (mConfiguredSets.contains(set)) { + processWithoutSets(activeFeatureFlags, archivedFeatureFlags, featureFlag); + shouldArchive = false; + break; } } - return new ProcessedSplitChange(activeSplits, archivedSplits, changeNumber, System.currentTimeMillis() / 100); + if (shouldArchive) { + archivedFeatureFlags.add(featureFlag); + } } } diff --git a/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java b/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java index e80480315..31c9cad96 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java +++ b/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java @@ -45,18 +45,21 @@ public class WorkManagerWrapper implements MySegmentsWorkManagerWrapper { private WeakReference mFetcherExecutionListener; // This variable is used to avoid loading data first time // we receive enqueued event - final private Set mShouldLoadFromLocal; + private final Set mShouldLoadFromLocal; + private final Set mConfiguredFlagSets; public WorkManagerWrapper(@NonNull WorkManager workManager, @NonNull SplitClientConfig splitClientConfig, @NonNull String apiKey, - @NonNull String databaseName) { + @NonNull String databaseName, + @NonNull Set configuredFlagSets) { mWorkManager = checkNotNull(workManager); mDatabaseName = checkNotNull(databaseName); mSplitClientConfig = checkNotNull(splitClientConfig); mApiKey = checkNotNull(apiKey); mShouldLoadFromLocal = new HashSet<>(); mConstraints = buildConstraints(); + mConfiguredFlagSets = checkNotNull(configuredFlagSets); } public void setFetcherExecutionListener(SplitTaskExecutionListener fetcherExecutionListener) { @@ -184,6 +187,7 @@ private Data buildSplitSyncInputData() { dataBuilder.putLong(ServiceConstants.WORKER_PARAM_SPLIT_CACHE_EXPIRATION, mSplitClientConfig.cacheExpirationInSeconds()); dataBuilder.putString(ServiceConstants.WORKER_PARAM_ENDPOINT, mSplitClientConfig.endpoint()); dataBuilder.putBoolean(ServiceConstants.SHOULD_RECORD_TELEMETRY, mSplitClientConfig.shouldRecordTelemetry()); + dataBuilder.putStringArray(ServiceConstants.WORKER_PARAM_CONFIGURED_SETS, mConfiguredFlagSets.toArray(new String[0])); return buildInputData(dataBuilder.build()); } diff --git a/src/main/java/io/split/android/client/service/workmanager/SplitsSyncWorker.java b/src/main/java/io/split/android/client/service/workmanager/SplitsSyncWorker.java index f851908a1..f1dab760e 100644 --- a/src/main/java/io/split/android/client/service/workmanager/SplitsSyncWorker.java +++ b/src/main/java/io/split/android/client/service/workmanager/SplitsSyncWorker.java @@ -7,6 +7,9 @@ import androidx.work.WorkerParameters; import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import io.split.android.client.dtos.SplitChange; import io.split.android.client.service.ServiceConstants; @@ -32,6 +35,12 @@ public SplitsSyncWorker(@NonNull Context context, String apiKey = workerParams.getInputData().getString(ServiceConstants.WORKER_PARAM_API_KEY); boolean encryptionEnabled = workerParams.getInputData().getBoolean(ServiceConstants.WORKER_PARAM_ENCRYPTION_ENABLED, false); + String[] configuredFlagSetsArray = workerParams.getInputData().getStringArray(ServiceConstants.WORKER_PARAM_CONFIGURED_SETS); + Set configuredFlagSets = new HashSet<>(); + if (configuredFlagSetsArray != null) { + configuredFlagSets = new HashSet<>(Arrays.asList(configuredFlagSetsArray)); + } + SplitsStorage splitsStorage = StorageFactory.getSplitsStorageForWorker(getDatabase(), apiKey, encryptionEnabled); // StorageFactory.getSplitsStorageForWorker creates a new storage instance, so it needs // to be populated by calling loadLocal @@ -41,7 +50,8 @@ public SplitsSyncWorker(@NonNull Context context, TelemetryStorage telemetryStorage = StorageFactory.getTelemetryStorage(shouldRecordTelemetry); - SplitsSyncHelper splitsSyncHelper = new SplitsSyncHelper(splitsFetcher, splitsStorage, new SplitChangeProcessor(), telemetryStorage); + SplitsSyncHelper splitsSyncHelper = new SplitsSyncHelper(splitsFetcher, splitsStorage, + new SplitChangeProcessor(configuredFlagSets), telemetryStorage); mSplitTask = buildSplitSyncTask(splitsStorage, telemetryStorage, splitsSyncHelper); } catch (URISyntaxException e) { diff --git a/src/test/java/io/split/android/client/service/synchronizer/WorkManagerWrapperTest.java b/src/test/java/io/split/android/client/service/synchronizer/WorkManagerWrapperTest.java index 67d262f33..13b9ce2dd 100644 --- a/src/test/java/io/split/android/client/service/synchronizer/WorkManagerWrapperTest.java +++ b/src/test/java/io/split/android/client/service/synchronizer/WorkManagerWrapperTest.java @@ -19,6 +19,7 @@ import org.mockito.MockitoAnnotations; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.HashSet; import java.util.concurrent.TimeUnit; @@ -68,7 +69,8 @@ public void setUp() throws Exception { mWorkManager, splitClientConfig, "api_key", - "test_database_name" + "test_database_name", + new HashSet<>(Arrays.asList("set_1", "set_2")) ); } @@ -90,6 +92,7 @@ public void scheduleWorkSchedulesSplitsJob() { .putLong("splitCacheExpiration", 864000) .putString("endpoint", "https://test.split.io/api") .putBoolean("shouldRecordTelemetry", true) + .putStringArray("configuredSets", new String[]{"set_1", "set_2"}) .build(); PeriodicWorkRequest expectedRequest = new PeriodicWorkRequest diff --git a/src/test/java/io/split/android/engine/splits/SplitChangeProcessorTest.java b/src/test/java/io/split/android/engine/splits/SplitChangeProcessorTest.java index 624cf214b..f727fded3 100644 --- a/src/test/java/io/split/android/engine/splits/SplitChangeProcessorTest.java +++ b/src/test/java/io/split/android/engine/splits/SplitChangeProcessorTest.java @@ -1,11 +1,17 @@ package io.split.android.engine.splits; +import androidx.annotation.Nullable; + import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import io.split.android.client.dtos.Split; import io.split.android.client.dtos.SplitChange; @@ -19,7 +25,7 @@ public class SplitChangeProcessorTest { @Before public void setup() { - mProcessor = new SplitChangeProcessor(); + mProcessor = new SplitChangeProcessor(Collections.emptySet()); } @Test @@ -137,9 +143,47 @@ public void processSingleArchivedSplit() { Assert.assertEquals(archivedSplit.killed, split.killed); } + @Test + public void processAddingWithFlagSets() { + Set configuredSets = new HashSet<>(); + configuredSets.add("set_1"); + configuredSets.add("set_2"); + mProcessor = new SplitChangeProcessor(configuredSets); + + Split split1 = newSplit("split_1", Status.ACTIVE, new HashSet<>(Arrays.asList("set_3", "set_1"))); + Split split2 = newSplit("split_2", Status.ACTIVE, Collections.singleton("set_2")); + Split split3 = newSplit("split_3", Status.ACTIVE, new HashSet<>(Collections.singletonList("set_3"))); + + SplitChange splitChange = new SplitChange(); + splitChange.splits = Arrays.asList(split1, split2, split3); + + ProcessedSplitChange result = mProcessor.process(splitChange); + + Assert.assertEquals(2, result.getActiveSplits().size()); + Assert.assertEquals(1, result.getArchivedSplits().size()); + } + + @Test + public void featureFlagWithNoSetsIsArchivedWhenProcessingWithFlagSets() { + Set configuredSets = new HashSet<>(); + configuredSets.add("set_1"); + configuredSets.add("set_2"); + mProcessor = new SplitChangeProcessor(configuredSets); + + Split split1 = newSplit("split_1", Status.ACTIVE, null); + + SplitChange splitChange = new SplitChange(); + splitChange.splits = Collections.singletonList(split1); + + ProcessedSplitChange result = mProcessor.process(splitChange); + + Assert.assertEquals(0, result.getActiveSplits().size()); + Assert.assertEquals(1, result.getArchivedSplits().size()); + } + private List createSplits(int from, int count, Status status) { List splits = new ArrayList<>(); - for(int i=from; i createSplits(int from, int count, Status status) { } private Split newSplit(String name, Status status) { + return newSplit(name, status, null); + } + + private Split newSplit(String name, Status status, @Nullable Set sets) { Split split = new Split(); split.name = name; split.status = status; + split.sets = sets; return split; } } From 27caf6fa9a7e91398c5db8dff7826a2819dfd591 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 30 Aug 2023 09:47:42 -0300 Subject: [PATCH 19/89] Rename properties for readability --- .../service/executor/SplitTaskFactoryImpl.java | 13 ++++++------- .../service/splits/FilterSplitsInCacheTask.java | 6 +++--- .../client/service/splits/SplitsSyncTask.java | 8 ++++---- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java index afcd4105b..92689164b 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java @@ -7,7 +7,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import java.util.HashSet; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; @@ -53,12 +53,11 @@ public class SplitTaskFactoryImpl implements SplitTaskFactory { private final SplitStorageContainer mSplitsStorageContainer; private final SplitClientConfig mSplitClientConfig; private final SplitsSyncHelper mSplitsSyncHelper; - private final String mSplitsFilterQueryString; + private final String mSplitsFilterQueryStringFromConfig; private final ISplitEventsManager mEventsManager; private final TelemetryTaskFactory mTelemetryTaskFactory; private final SplitChangeProcessor mSplitChangeProcessor; private final TelemetryRuntimeProducer mTelemetryRuntimeProducer; - @Nullable private final List mFilters; @SuppressLint("VisibleForTests") @@ -74,7 +73,7 @@ public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig, mSplitClientConfig = checkNotNull(splitClientConfig); mSplitApiFacade = checkNotNull(splitApiFacade); mSplitsStorageContainer = checkNotNull(splitStorageContainer); - mSplitsFilterQueryString = splitsFilterQueryString; + mSplitsFilterQueryStringFromConfig = splitsFilterQueryString; mEventsManager = eventsManager; mSplitChangeProcessor = new SplitChangeProcessor(configuredFlagSets); @@ -100,7 +99,7 @@ public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig, mSplitsStorageContainer.getSplitsStorage(), mSplitsStorageContainer.getMySegmentsStorageContainer()); - mFilters = filters; + mFilters = (filters == null) ? new ArrayList<>() : filters; } @Override @@ -127,7 +126,7 @@ public ImpressionsRecorderTask createImpressionsRecorderTask() { @Override public SplitsSyncTask createSplitsSyncTask(boolean checkCacheExpiration) { return SplitsSyncTask.build(mSplitsSyncHelper, mSplitsStorageContainer.getSplitsStorage(), checkCacheExpiration, - mSplitClientConfig.cacheExpirationInSeconds(), mSplitsFilterQueryString, mEventsManager, mSplitsStorageContainer.getTelemetryStorage()); + mSplitClientConfig.cacheExpirationInSeconds(), mSplitsFilterQueryStringFromConfig, mEventsManager, mSplitsStorageContainer.getTelemetryStorage()); } @Override @@ -148,7 +147,7 @@ public SplitsUpdateTask createSplitsUpdateTask(long since) { @Override public FilterSplitsInCacheTask createFilterSplitsInCacheTask() { return new FilterSplitsInCacheTask(mSplitsStorageContainer.getPersistentSplitsStorage(), - mFilters, mSplitsFilterQueryString); + mFilters, mSplitsFilterQueryStringFromConfig); } @Override diff --git a/src/main/java/io/split/android/client/service/splits/FilterSplitsInCacheTask.java b/src/main/java/io/split/android/client/service/splits/FilterSplitsInCacheTask.java index 009187bfe..98a2b02ef 100644 --- a/src/main/java/io/split/android/client/service/splits/FilterSplitsInCacheTask.java +++ b/src/main/java/io/split/android/client/service/splits/FilterSplitsInCacheTask.java @@ -23,14 +23,14 @@ public class FilterSplitsInCacheTask implements SplitTask { private final static String PREFIX_SEPARATOR = "__"; private final PersistentSplitsStorage mSplitsStorage; private final List mSplitsFilter; - private final String mSplitsFilterQueryString; + private final String mSplitsFilterQueryStringFromConfig; public FilterSplitsInCacheTask(@NonNull PersistentSplitsStorage splitsStorage, @NonNull List splitsFilter, @Nullable String splitsFilterQueryString) { mSplitsStorage = checkNotNull(splitsStorage); mSplitsFilter = checkNotNull(splitsFilter); - mSplitsFilterQueryString = splitsFilterQueryString; + mSplitsFilterQueryStringFromConfig = splitsFilterQueryString; } @Override @@ -108,7 +108,7 @@ private String getPrefix(String splitName) { } private boolean queryStringHasChanged() { - return !sanitizeString(mSplitsStorage.getFilterQueryString()).equals(sanitizeString(mSplitsFilterQueryString)); + return !sanitizeString(mSplitsStorage.getFilterQueryString()).equals(sanitizeString(mSplitsFilterQueryStringFromConfig)); } private String sanitizeString(String string) { diff --git a/src/main/java/io/split/android/client/service/splits/SplitsSyncTask.java b/src/main/java/io/split/android/client/service/splits/SplitsSyncTask.java index 9e256fd46..d3c3458de 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitsSyncTask.java +++ b/src/main/java/io/split/android/client/service/splits/SplitsSyncTask.java @@ -17,7 +17,7 @@ public class SplitsSyncTask implements SplitTask { - private final String mSplitsFilterQueryString; + private final String mSplitsFilterQueryStringFromConfig; private final SplitsStorage mSplitsStorage; private final boolean mCheckCacheExpiration; @@ -59,7 +59,7 @@ private SplitsSyncTask(@NonNull SplitsSyncHelper splitsSyncHelper, mSplitsSyncHelper = checkNotNull(splitsSyncHelper); mCacheExpirationInSeconds = cacheExpirationInSeconds; mCheckCacheExpiration = checkCacheExpiration; - mSplitsFilterQueryString = splitsFilterQueryString; + mSplitsFilterQueryStringFromConfig = splitsFilterQueryString; mEventsManager = eventsManager; mChangeChecker = new SplitsChangeChecker(); mTelemetryRuntimeProducer = checkNotNull(telemetryRuntimeProducer); @@ -77,7 +77,7 @@ public SplitTaskExecutionInfo execute() { boolean splitsFilterHasChanged = splitsFilterHasChanged(mSplitsStorage.getSplitsFilterQueryString()); if (splitsFilterHasChanged) { - mSplitsStorage.updateSplitsFilterQueryString(mSplitsFilterQueryString); + mSplitsStorage.updateSplitsFilterQueryString(mSplitsFilterQueryStringFromConfig); storedChangeNumber = -1; } @@ -107,7 +107,7 @@ private void notifyInternalEvent(long storedChangeNumber) { } private boolean splitsFilterHasChanged(String storedSplitsFilterQueryString) { - return !sanitizeString(mSplitsFilterQueryString).equals(sanitizeString(storedSplitsFilterQueryString)); + return !sanitizeString(mSplitsFilterQueryStringFromConfig).equals(sanitizeString(storedSplitsFilterQueryString)); } private String sanitizeString(String string) { From e0c34c349306df1384507aabe713110df9363e05 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 30 Aug 2023 10:36:49 -0300 Subject: [PATCH 20/89] Rename methods --- .../service/splits/SplitChangeProcessor.java | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java index 92b05f7d3..58e19963b 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java +++ b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java @@ -1,5 +1,7 @@ package io.split.android.client.service.splits; +import static com.google.common.base.Preconditions.checkNotNull; + import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; @@ -22,8 +24,8 @@ public class SplitChangeProcessor { this(Collections.emptySet()); } - public SplitChangeProcessor(Set configuredSets) { - mConfiguredSets = configuredSets; + public SplitChangeProcessor(@NonNull Set configuredSets) { + mConfiguredSets = checkNotNull(configuredSets); } public ProcessedSplitChange process(SplitChange splitChange) { @@ -48,16 +50,22 @@ private ProcessedSplitChange buildProcessedSplitChange(List featureFlags, } if (mConfiguredSets.isEmpty()) { - processWithoutSets(activeFeatureFlags, archivedFeatureFlags, featureFlag); + processAccordingToStatus(activeFeatureFlags, archivedFeatureFlags, featureFlag); } else { - processWithSets(activeFeatureFlags, archivedFeatureFlags, featureFlag); + processAccordingToSets(activeFeatureFlags, archivedFeatureFlags, featureFlag); } } return new ProcessedSplitChange(activeFeatureFlags, archivedFeatureFlags, changeNumber, System.currentTimeMillis() / 100); } - private void processWithoutSets(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag) { + /** + * Process the feature flag according to its status + * @param activeFeatureFlags List of feature flags with status {@link Status#ACTIVE} + * @param archivedFeatureFlags List of feature flags with status different than {@link Status#ACTIVE} + * @param featureFlag Feature flag to process + */ + private void processAccordingToStatus(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag) { if (featureFlag.status == Status.ACTIVE) { activeFeatureFlags.add(featureFlag); } else { @@ -65,7 +73,13 @@ private void processWithoutSets(List activeFeatureFlags, List arch } } - private void processWithSets(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag) { + /** + * Process the feature flag according to its sets. + * @param activeFeatureFlags List of feature flags with sets that match the configured sets + * @param archivedFeatureFlags List of feature flags with sets that don't match the configured sets + * @param featureFlag Feature flag to process + */ + private void processAccordingToSets(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag) { if (featureFlag.sets == null || featureFlag.sets.isEmpty()) { archivedFeatureFlags.add(featureFlag); return; @@ -74,7 +88,9 @@ private void processWithSets(List activeFeatureFlags, List archive boolean shouldArchive = true; for (String set : featureFlag.sets) { if (mConfiguredSets.contains(set)) { - processWithoutSets(activeFeatureFlags, archivedFeatureFlags, featureFlag); + // If the feature flag has at least one set that matches the configured sets, + // we process it according to its status + processAccordingToStatus(activeFeatureFlags, archivedFeatureFlags, featureFlag); shouldArchive = false; break; } From f16ec4a7e6bdf3cb1fd2718d4cc3df395fddf8a2 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 30 Aug 2023 10:46:17 -0300 Subject: [PATCH 21/89] Simplify comment --- .../client/service/splits/SplitChangeProcessor.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java index 58e19963b..6f004b77b 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java +++ b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java @@ -61,9 +61,6 @@ private ProcessedSplitChange buildProcessedSplitChange(List featureFlags, /** * Process the feature flag according to its status - * @param activeFeatureFlags List of feature flags with status {@link Status#ACTIVE} - * @param archivedFeatureFlags List of feature flags with status different than {@link Status#ACTIVE} - * @param featureFlag Feature flag to process */ private void processAccordingToStatus(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag) { if (featureFlag.status == Status.ACTIVE) { @@ -74,10 +71,7 @@ private void processAccordingToStatus(List activeFeatureFlags, List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag) { if (featureFlag.sets == null || featureFlag.sets.isEmpty()) { From 6cd4a445f78b1b7d28e17407d29b5acf1e1bbfad Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 30 Aug 2023 11:16:34 -0300 Subject: [PATCH 22/89] Move some logicto helper --- .../android/client/SplitFactoryHelper.java | 29 +++++++++++++++++++ .../android/client/SplitFactoryImpl.java | 17 ++++------- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/main/java/io/split/android/client/SplitFactoryHelper.java b/src/main/java/io/split/android/client/SplitFactoryHelper.java index 0233ade4f..1eed1a54f 100644 --- a/src/main/java/io/split/android/client/SplitFactoryHelper.java +++ b/src/main/java/io/split/android/client/SplitFactoryHelper.java @@ -4,11 +4,15 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.util.Pair; import androidx.work.WorkManager; import java.io.File; import java.net.URI; import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.BlockingQueue; @@ -410,6 +414,31 @@ SplitUpdatesWorker getSplitUpdatesWorker(SplitClientConfig config, return null; } + Pair, String>, Set> getFilterConfiguration(SyncConfig syncConfig) { + String splitsFilterQueryString = null; + List groupedFilters = new ArrayList<>(); + Set configuredFlagSets = new HashSet<>(); + + if (syncConfig != null) { + FilterBuilder filterBuilder = new FilterBuilder(syncConfig.getFilters()); + groupedFilters = filterBuilder.getGroupedFilter(); + splitsFilterQueryString = filterBuilder.buildQueryString(); + + if (!groupedFilters.isEmpty()) { + SplitFilter splitFilter = groupedFilters.get(0); + + // In the case of BY_SET, all filters will be grouped into one with the {@link SplitFilter.Type#BY_SET} type + if (splitFilter.getType() == SplitFilter.Type.BY_SET) { + splitsFilterQueryString = splitFilter.getValues().get(0); + } + + configuredFlagSets.addAll(splitFilter.getValues()); + } + } + + return new Pair<>(new Pair<>(groupedFilters, splitsFilterQueryString), configuredFlagSets); + } + private TelemetryStorage getTelemetryStorage(boolean shouldRecordTelemetry, TelemetryStorage telemetryStorage) { if (telemetryStorage != null) { return telemetryStorage; diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index 8453b1187..0bd464088 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -3,10 +3,10 @@ import android.content.Context; import androidx.annotation.NonNull; +import androidx.core.util.Pair; import java.net.URISyntaxException; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Set; @@ -171,21 +171,14 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { mStorageContainer = factoryHelper.buildStorageContainer(config.userConsent(), splitDatabase, config.shouldRecordTelemetry(), splitCipher, telemetryStorage); - SyncConfig syncConfig = config.syncConfig(); - String splitsFilterQueryString = null; - List filters = null; - if (syncConfig != null) { - FilterBuilder filterBuilder = new FilterBuilder(syncConfig.getFilters()); - filters = filterBuilder.getGroupedFilter(); - splitsFilterQueryString = filterBuilder.buildQueryString(); - } + Pair, String>, Set> filtersConfig = factoryHelper.getFilterConfiguration(config.syncConfig()); + List filters = filtersConfig.first.first; + String splitsFilterQueryString = filtersConfig.first.second; + Set configuredFlagSets = filtersConfig.second; SplitApiFacade splitApiFacade = factoryHelper.buildApiFacade( config, defaultHttpClient, splitsFilterQueryString); - Set configuredFlagSets = (filters != null && !filters.isEmpty() && filters.get(0).getType() == SplitFilter.Type.BY_SET) ? - new HashSet<>(filters.get(0).getValues()) : new HashSet<>(); - SplitTaskFactory splitTaskFactory = new SplitTaskFactoryImpl( config, splitApiFacade, mStorageContainer, splitsFilterQueryString, mEventsManagerCoordinator, filters, configuredFlagSets, testingConfig); From c0293049800b2c76df93a24c3e7478d7ba398969 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 30 Aug 2023 17:20:02 -0300 Subject: [PATCH 23/89] Fix --- src/main/java/io/split/android/client/SplitFactoryHelper.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/io/split/android/client/SplitFactoryHelper.java b/src/main/java/io/split/android/client/SplitFactoryHelper.java index 1eed1a54f..8e224773e 100644 --- a/src/main/java/io/split/android/client/SplitFactoryHelper.java +++ b/src/main/java/io/split/android/client/SplitFactoryHelper.java @@ -429,10 +429,8 @@ Pair, String>, Set> getFilterConfiguration(SyncCo // In the case of BY_SET, all filters will be grouped into one with the {@link SplitFilter.Type#BY_SET} type if (splitFilter.getType() == SplitFilter.Type.BY_SET) { - splitsFilterQueryString = splitFilter.getValues().get(0); + configuredFlagSets.addAll(splitFilter.getValues()); } - - configuredFlagSets.addAll(splitFilter.getValues()); } } From 34b83e663663098cf709db1434f5cfe82f5a5559 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 30 Aug 2023 17:43:41 -0300 Subject: [PATCH 24/89] Clear cache on filter change --- .../java/fake/SynchronizerSpyImpl.java | 5 - .../java/tests/storage/LoadSplitTaskTest.java | 25 +++- .../android/client/SplitFactoryImpl.java | 9 +- .../localhost/LocalhostSplitFactory.java | 13 +- .../localhost/LocalhostSynchronizer.java | 8 +- .../service/executor/SplitTaskFactory.java | 2 +- .../executor/SplitTaskFactoryImpl.java | 4 +- .../client/service/splits/LoadSplitsTask.java | 17 ++- .../FeatureFlagsSynchronizer.java | 2 - .../FeatureFlagsSynchronizerImpl.java | 14 +- .../service/synchronizer/Synchronizer.java | 2 - .../synchronizer/SynchronizerImpl.java | 11 +- .../storage/splits/SplitsStorageImpl.java | 1 + .../client/service/LoadSplitsTaskTest.java | 123 ++++++++++++++++++ .../client/service/SynchronizerTest.java | 8 +- .../FeatureFlagsSynchronizerImplTest.java | 20 +-- .../SynchronizerImplTelemetryTest.java | 3 +- 17 files changed, 207 insertions(+), 60 deletions(-) create mode 100644 src/test/java/io/split/android/client/service/LoadSplitsTaskTest.java diff --git a/src/androidTest/java/fake/SynchronizerSpyImpl.java b/src/androidTest/java/fake/SynchronizerSpyImpl.java index b073f066b..88a22b8c3 100644 --- a/src/androidTest/java/fake/SynchronizerSpyImpl.java +++ b/src/androidTest/java/fake/SynchronizerSpyImpl.java @@ -30,11 +30,6 @@ public void loadAndSynchronizeSplits() { mSynchronizer.loadAndSynchronizeSplits(); } - @Override - public void loadSplitsFromCache() { - mSynchronizer.loadSplitsFromCache(); - } - @Override public void loadMySegmentsFromCache() { mSynchronizer.loadMySegmentsFromCache(); diff --git a/src/androidTest/java/tests/storage/LoadSplitTaskTest.java b/src/androidTest/java/tests/storage/LoadSplitTaskTest.java index 745586ca9..e919e7c36 100644 --- a/src/androidTest/java/tests/storage/LoadSplitTaskTest.java +++ b/src/androidTest/java/tests/storage/LoadSplitTaskTest.java @@ -53,9 +53,9 @@ public void setUp() { } @Test - public void execute() { + public void executeWithoutQueryString() { - SplitTask task = new LoadSplitsTask(mSplitsStorage); + SplitTask task = new LoadSplitsTask(mSplitsStorage, null); SplitTaskExecutionInfo result = task.execute(); Split split0 = mSplitsStorage.get("split-0"); @@ -68,5 +68,26 @@ public void execute() { Assert.assertNotNull(split2); Assert.assertNotNull(split3); Assert.assertEquals(SplitTaskExecutionStatus.SUCCESS, result.getStatus()); + Assert.assertEquals(9999L, mSplitsStorage.getTill()); + Assert.assertEquals("", mSplitsStorage.getSplitsFilterQueryString()); + } + + @Test + public void executeWithQueryString() { + + SplitTask task = new LoadSplitsTask(mSplitsStorage, "sets=set1"); + SplitTaskExecutionInfo result = task.execute(); + + Split split0 = mSplitsStorage.get("split-0"); + Split split1 = mSplitsStorage.get("split-1"); + Split split2 = mSplitsStorage.get("split-2"); + Split split3 = mSplitsStorage.get("split-3"); + Assert.assertNull(split0); + Assert.assertNull(split1); + Assert.assertNull(split2); + Assert.assertNull(split3); + Assert.assertEquals(SplitTaskExecutionStatus.ERROR, result.getStatus()); + Assert.assertEquals(-1L, mSplitsStorage.getTill()); + Assert.assertEquals("sets=set1", mSplitsStorage.getSplitsFilterQueryString()); } } diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index 0bd464088..e693aca8d 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -173,14 +173,14 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { Pair, String>, Set> filtersConfig = factoryHelper.getFilterConfiguration(config.syncConfig()); List filters = filtersConfig.first.first; - String splitsFilterQueryString = filtersConfig.first.second; + String splitsFilterQueryStringFromConfig = filtersConfig.first.second; Set configuredFlagSets = filtersConfig.second; SplitApiFacade splitApiFacade = factoryHelper.buildApiFacade( - config, defaultHttpClient, splitsFilterQueryString); + config, defaultHttpClient, splitsFilterQueryStringFromConfig); SplitTaskFactory splitTaskFactory = new SplitTaskFactoryImpl( - config, splitApiFacade, mStorageContainer, splitsFilterQueryString, mEventsManagerCoordinator, + config, splitApiFacade, mStorageContainer, splitsFilterQueryStringFromConfig, mEventsManagerCoordinator, filters, configuredFlagSets, testingConfig); cleanUpDabase(splitTaskExecutor, splitTaskFactory); @@ -205,7 +205,8 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { impressionManager, mStorageContainer.getEventsStorage(), mEventsManagerCoordinator, - streamingComponents.getPushManagerEventBroadcaster()); + streamingComponents.getPushManagerEventBroadcaster(), + splitsFilterQueryStringFromConfig); // Only available for integration tests if (synchronizerSpy != null) { synchronizerSpy.setSynchronizer(mSynchronizer); diff --git a/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java b/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java index 364014ae7..ba942771c 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java @@ -7,11 +7,13 @@ import java.io.IOException; +import io.split.android.client.FilterBuilder; import io.split.android.client.SplitClient; import io.split.android.client.SplitClientConfig; import io.split.android.client.SplitFactory; import io.split.android.client.SplitManager; import io.split.android.client.SplitManagerImpl; +import io.split.android.client.SyncConfig; import io.split.android.client.api.Key; import io.split.android.client.attributes.AttributesManagerFactory; import io.split.android.client.attributes.AttributesManagerFactoryImpl; @@ -77,7 +79,7 @@ public LocalhostSplitFactory(String key, Context context, eventsManagerCoordinator, taskExecutor); - mSynchronizer = new LocalhostSynchronizer(taskExecutor, config, splitsStorage); + mSynchronizer = new LocalhostSynchronizer(taskExecutor, config, splitsStorage, buildQueryString(config.syncConfig())); mSynchronizer.start(); Logger.i("Android SDK initialized!"); @@ -141,4 +143,13 @@ public void setUserConsent(boolean enabled) { public UserConsent getUserConsent() { return UserConsent.GRANTED; } + + private static String buildQueryString(SyncConfig syncConfig) { + if (syncConfig != null) { + FilterBuilder filterBuilder = new FilterBuilder(syncConfig.getFilters()); + return filterBuilder.buildQueryString(); + } + + return ""; + } } diff --git a/src/main/java/io/split/android/client/localhost/LocalhostSynchronizer.java b/src/main/java/io/split/android/client/localhost/LocalhostSynchronizer.java index 884c02540..6d8d73570 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostSynchronizer.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostSynchronizer.java @@ -3,6 +3,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import javax.security.auth.Destroyable; @@ -18,17 +19,20 @@ public class LocalhostSynchronizer implements SplitLifecycleAware, Destroyable { private final SplitTaskExecutor mTaskExecutor; private final int mRefreshRate; private final SplitsStorage mSplitsStorage; + private final String mSplitsFilterQueryStringFromConfig; public LocalhostSynchronizer(@NonNull SplitTaskExecutor taskExecutor, @NonNull SplitClientConfig splitClientConfig, - @NonNull SplitsStorage splitsStorage) { + @NonNull SplitsStorage splitsStorage, + @Nullable String splitsFilterQueryStringFromConfig) { mTaskExecutor = checkNotNull(taskExecutor); mRefreshRate = checkNotNull(splitClientConfig).offlineRefreshRate(); mSplitsStorage = checkNotNull(splitsStorage); + mSplitsFilterQueryStringFromConfig = splitsFilterQueryStringFromConfig; } public void start() { - SplitTask loadTask = new LoadSplitsTask(mSplitsStorage); + SplitTask loadTask = new LoadSplitsTask(mSplitsStorage, mSplitsFilterQueryStringFromConfig); if (mRefreshRate > 0) { mTaskExecutor.schedule( loadTask, 0, diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactory.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactory.java index e23407f55..b6e04b8a7 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactory.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactory.java @@ -18,7 +18,7 @@ public interface SplitTaskFactory extends TelemetryTaskFactory, ImpressionsTaskF SplitsSyncTask createSplitsSyncTask(boolean checkCacheExpiration); - LoadSplitsTask createLoadSplitsTask(); + LoadSplitsTask createLoadSplitsTask(String createLoadSplitsTask); SplitKillTask createSplitKillTask(Split split); diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java index 92689164b..d3f191424 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java @@ -130,8 +130,8 @@ public SplitsSyncTask createSplitsSyncTask(boolean checkCacheExpiration) { } @Override - public LoadSplitsTask createLoadSplitsTask() { - return new LoadSplitsTask(mSplitsStorageContainer.getSplitsStorage()); + public LoadSplitsTask createLoadSplitsTask(String splitsFilterQueryStringFromConfig) { + return new LoadSplitsTask(mSplitsStorageContainer.getSplitsStorage(), splitsFilterQueryStringFromConfig); } @Override diff --git a/src/main/java/io/split/android/client/service/splits/LoadSplitsTask.java b/src/main/java/io/split/android/client/service/splits/LoadSplitsTask.java index be0015060..1c093a803 100644 --- a/src/main/java/io/split/android/client/service/splits/LoadSplitsTask.java +++ b/src/main/java/io/split/android/client/service/splits/LoadSplitsTask.java @@ -12,18 +12,31 @@ public class LoadSplitsTask implements SplitTask { private final SplitsStorage mSplitsStorage; + private final String mSplitsFilterQueryStringFromConfig; - public LoadSplitsTask(SplitsStorage splitsStorage) { + public LoadSplitsTask(SplitsStorage splitsStorage, String splitsFilterQueryStringFromConfig) { mSplitsStorage = checkNotNull(splitsStorage); + mSplitsFilterQueryStringFromConfig = (splitsFilterQueryStringFromConfig == null) ? "" : splitsFilterQueryStringFromConfig; } @Override @NonNull public SplitTaskExecutionInfo execute() { mSplitsStorage.loadLocal(); - if(mSplitsStorage.getTill() > -1) { + String queryStringFromStorage = mSplitsStorage.getSplitsFilterQueryString(); + if (queryStringFromStorage == null) { + queryStringFromStorage = ""; + } + + if (mSplitsStorage.getTill() > -1 && mSplitsFilterQueryStringFromConfig.equals(queryStringFromStorage)) { return SplitTaskExecutionInfo.success(SplitTaskType.LOAD_LOCAL_SPLITS); } + + if (!mSplitsFilterQueryStringFromConfig.equals(queryStringFromStorage)) { + mSplitsStorage.clear(); + mSplitsStorage.updateSplitsFilterQueryString(mSplitsFilterQueryStringFromConfig); + } + return SplitTaskExecutionInfo.error(SplitTaskType.LOAD_LOCAL_SPLITS); } } diff --git a/src/main/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizer.java b/src/main/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizer.java index 04b87e777..6b48eeccd 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizer.java +++ b/src/main/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizer.java @@ -6,8 +6,6 @@ public interface FeatureFlagsSynchronizer { void loadAndSynchronize(); - void loadFromCache(); - void synchronize(long since); void synchronize(); diff --git a/src/main/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizerImpl.java b/src/main/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizerImpl.java index 2e8a66dc1..a60f9bad1 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizerImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizerImpl.java @@ -37,6 +37,7 @@ public class FeatureFlagsSynchronizerImpl implements FeatureFlagsSynchronizer { private final RetryBackoffCounterTimer mSplitsUpdateRetryTimer; @Nullable private final SplitTaskExecutionListener mSplitsSyncListener; + private final String mSplitsFilterQueryStringFromConfig; public FeatureFlagsSynchronizerImpl(@NonNull SplitClientConfig splitClientConfig, @NonNull SplitTaskExecutor taskExecutor, @@ -44,7 +45,8 @@ public FeatureFlagsSynchronizerImpl(@NonNull SplitClientConfig splitClientConfig @NonNull SplitTaskFactory splitTaskFactory, @NonNull ISplitEventsManager splitEventsManager, @NonNull RetryBackoffCounterTimerFactory retryBackoffCounterTimerFactory, - @Nullable PushManagerEventBroadcaster pushManagerEventBroadcaster) { + @Nullable PushManagerEventBroadcaster pushManagerEventBroadcaster, + @NonNull String splitsFilterQueryStringFromConfig) { mTaskExecutor = checkNotNull(taskExecutor); mSplitsTaskExecutor = splitSingleThreadTaskExecutor; @@ -69,18 +71,14 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { mSplitsSyncRetryTimer.setTask(mSplitTaskFactory.createSplitsSyncTask(true), mSplitsSyncListener); mLoadLocalSplitsListener = new LoadLocalDataListener( splitEventsManager, SplitInternalEvent.SPLITS_LOADED_FROM_STORAGE); - } - - @Override - public void loadFromCache() { - submitLoadingTask(mLoadLocalSplitsListener); + mSplitsFilterQueryStringFromConfig = splitsFilterQueryStringFromConfig; } @Override public void loadAndSynchronize() { List enqueued = new ArrayList<>(); enqueued.add(new SplitTaskBatchItem(mSplitTaskFactory.createFilterSplitsInCacheTask(), null)); - enqueued.add(new SplitTaskBatchItem(mSplitTaskFactory.createLoadSplitsTask(), mLoadLocalSplitsListener)); + enqueued.add(new SplitTaskBatchItem(mSplitTaskFactory.createLoadSplitsTask(mSplitsFilterQueryStringFromConfig), mLoadLocalSplitsListener)); enqueued.add(new SplitTaskBatchItem(() -> { synchronize(); return SplitTaskExecutionInfo.success(SplitTaskType.GENERIC_TASK); @@ -117,7 +115,7 @@ public void stopSynchronization() { @Override public void submitLoadingTask(SplitTaskExecutionListener listener) { - mTaskExecutor.submit(mSplitTaskFactory.createLoadSplitsTask(), + mTaskExecutor.submit(mSplitTaskFactory.createLoadSplitsTask(mSplitsFilterQueryStringFromConfig), listener); } diff --git a/src/main/java/io/split/android/client/service/synchronizer/Synchronizer.java b/src/main/java/io/split/android/client/service/synchronizer/Synchronizer.java index 5c5992d53..6f8e614a4 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/Synchronizer.java +++ b/src/main/java/io/split/android/client/service/synchronizer/Synchronizer.java @@ -8,8 +8,6 @@ public interface Synchronizer extends SplitLifecycleAware { void loadAndSynchronizeSplits(); - void loadSplitsFromCache(); - void loadMySegmentsFromCache(); void loadAttributesFromCache(); diff --git a/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java b/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java index ad4ad8f9a..a21eeb436 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java @@ -63,7 +63,8 @@ public SynchronizerImpl(@NonNull SplitClientConfig splitClientConfig, @NonNull ImpressionManager impressionManager, @NonNull StoragePusher eventsStorage, @NonNull ISplitEventsManager eventsManagerCoordinator, - @Nullable PushManagerEventBroadcaster pushManagerEventBroadcaster) { + @Nullable PushManagerEventBroadcaster pushManagerEventBroadcaster, + @NonNull String splitsFilterQueryStringFromConfig) { this(splitClientConfig, taskExecutor, splitSingleThreadTaskExecutor, @@ -79,7 +80,8 @@ public SynchronizerImpl(@NonNull SplitClientConfig splitClientConfig, splitTaskFactory, eventsManagerCoordinator, retryBackoffCounterTimerFactory, - pushManagerEventBroadcaster), + pushManagerEventBroadcaster, + splitsFilterQueryStringFromConfig), eventsStorage); } @@ -122,11 +124,6 @@ public SynchronizerImpl(@NonNull SplitClientConfig splitClientConfig, } } - @Override - public void loadSplitsFromCache() { - mFeatureFlagsSynchronizer.loadFromCache(); - } - @Override public void loadMySegmentsFromCache() { mMySegmentsSynchronizerRegistry.loadMySegmentsFromCache(); diff --git a/src/main/java/io/split/android/client/storage/splits/SplitsStorageImpl.java b/src/main/java/io/split/android/client/storage/splits/SplitsStorageImpl.java index a35e7b0c6..2f3eb99f6 100644 --- a/src/main/java/io/split/android/client/storage/splits/SplitsStorageImpl.java +++ b/src/main/java/io/split/android/client/storage/splits/SplitsStorageImpl.java @@ -135,6 +135,7 @@ public String getSplitsFilterQueryString() { @WorkerThread public void updateSplitsFilterQueryString(String queryString) { mPersistentStorage.updateFilterQueryString(queryString); + mSplitsFilterQueryString = queryString; } @Override diff --git a/src/test/java/io/split/android/client/service/LoadSplitsTaskTest.java b/src/test/java/io/split/android/client/service/LoadSplitsTaskTest.java new file mode 100644 index 000000000..ded9b3931 --- /dev/null +++ b/src/test/java/io/split/android/client/service/LoadSplitsTaskTest.java @@ -0,0 +1,123 @@ +package io.split.android.client.service; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.Before; +import org.junit.Test; + +import io.split.android.client.service.executor.SplitTaskExecutionInfo; +import io.split.android.client.service.executor.SplitTaskExecutionStatus; +import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.splits.LoadSplitsTask; +import io.split.android.client.storage.splits.SplitsStorage; + +public class LoadSplitsTaskTest { + + private SplitsStorage mSplitsStorage; + private LoadSplitsTask mLoadSplitsTask; + + @Before + public void setUp() { + mSplitsStorage = mock(SplitsStorage.class); + } + + @Test + public void resultIsErrorWhenQueryStringHasChanged() { + when(mSplitsStorage.getTill()).thenReturn(123456677L); + when(mSplitsStorage.getSplitsFilterQueryString()).thenReturn("previous"); + + mLoadSplitsTask = new LoadSplitsTask(mSplitsStorage, "new"); + + SplitTaskExecutionInfo info = mLoadSplitsTask.execute(); + + assertEquals(SplitTaskExecutionStatus.ERROR, info.getStatus()); + assertEquals(SplitTaskType.LOAD_LOCAL_SPLITS, info.getTaskType()); + } + + @Test + public void resultIsSuccessWhenQueryStringIsSame() { + when(mSplitsStorage.getTill()).thenReturn(123456677L); + when(mSplitsStorage.getSplitsFilterQueryString()).thenReturn("previous"); + + mLoadSplitsTask = new LoadSplitsTask(mSplitsStorage, "previous"); + + SplitTaskExecutionInfo info = mLoadSplitsTask.execute(); + + assertEquals(SplitTaskExecutionStatus.SUCCESS, info.getStatus()); + assertEquals(SplitTaskType.LOAD_LOCAL_SPLITS, info.getTaskType()); + } + + @Test + public void loadLocalIsCalledOnStorageWhenExecutingTask() { + when(mSplitsStorage.getTill()).thenReturn(123456677L); + + mLoadSplitsTask = new LoadSplitsTask(mSplitsStorage, null); + + mLoadSplitsTask.execute(); + + verify(mSplitsStorage).loadLocal(); + } + + @Test + public void resultIsErrorWhenTillIsNegativeOne() { + when(mSplitsStorage.getTill()).thenReturn(-1L); + + mLoadSplitsTask = new LoadSplitsTask(mSplitsStorage, null); + + SplitTaskExecutionInfo info = mLoadSplitsTask.execute(); + + assertEquals(SplitTaskExecutionStatus.ERROR, info.getStatus()); + assertEquals(SplitTaskType.LOAD_LOCAL_SPLITS, info.getTaskType()); + } + + @Test + public void clearIsNotCalledWhenTillIsNegativeOne() { + when(mSplitsStorage.getTill()).thenReturn(-1L); + + mLoadSplitsTask = new LoadSplitsTask(mSplitsStorage, null); + + mLoadSplitsTask.execute(); + + verify(mSplitsStorage, times(0)).clear(); + } + + @Test + public void clearIsCalledOnStorageWhenQueryStringsDiffer() { + when(mSplitsStorage.getTill()).thenReturn(123456677L); + when(mSplitsStorage.getSplitsFilterQueryString()).thenReturn("previous"); + + mLoadSplitsTask = new LoadSplitsTask(mSplitsStorage, "new"); + + mLoadSplitsTask.execute(); + + verify(mSplitsStorage).clear(); + } + + @Test + public void clearIsNotCalledOnStorageWhenQueryStringsAreEquallyNull() { + when(mSplitsStorage.getTill()).thenReturn(123456677L); + when(mSplitsStorage.getSplitsFilterQueryString()).thenReturn(null); + + mLoadSplitsTask = new LoadSplitsTask(mSplitsStorage, null); + + mLoadSplitsTask.execute(); + + verify(mSplitsStorage, times(0)).clear(); + } + + @Test + public void clearIsNotCalledOnStorageWhenQueryStringsAreEqual() { + when(mSplitsStorage.getTill()).thenReturn(123456677L); + when(mSplitsStorage.getSplitsFilterQueryString()).thenReturn(""); + + mLoadSplitsTask = new LoadSplitsTask(mSplitsStorage, ""); + + mLoadSplitsTask.execute(); + + verify(mSplitsStorage, times(0)).clear(); + } +} diff --git a/src/test/java/io/split/android/client/service/SynchronizerTest.java b/src/test/java/io/split/android/client/service/SynchronizerTest.java index 571fd4dba..088288d08 100644 --- a/src/test/java/io/split/android/client/service/SynchronizerTest.java +++ b/src/test/java/io/split/android/client/service/SynchronizerTest.java @@ -176,7 +176,7 @@ public void setup(SplitClientConfig splitClientConfig, ImpressionManagerConfig.M when(mMySegmentsTaskFactory.createMySegmentsSyncTask(anyBoolean())).thenReturn(Mockito.mock(MySegmentsSyncTask.class)); when(mTaskFactory.createImpressionsRecorderTask()).thenReturn(Mockito.mock(ImpressionsRecorderTask.class)); when(mTaskFactory.createEventsRecorderTask()).thenReturn(Mockito.mock(EventsRecorderTask.class)); - when(mTaskFactory.createLoadSplitsTask()).thenReturn(Mockito.mock(LoadSplitsTask.class)); + when(mTaskFactory.createLoadSplitsTask(any())).thenReturn(Mockito.mock(LoadSplitsTask.class)); when(mTaskFactory.createFilterSplitsInCacheTask()).thenReturn(Mockito.mock(FilterSplitsInCacheTask.class)); when(mTaskFactory.createImpressionsCountRecorderTask()).thenReturn(Mockito.mock(ImpressionsCountRecorderTask.class)); when(mTaskFactory.createSaveImpressionsCountTask(any())).thenReturn(Mockito.mock(SaveImpressionsCountTask.class)); @@ -524,12 +524,12 @@ public void loadLocalData() { ((MySegmentsSynchronizerRegistry) mSynchronizer).registerMySegmentsSynchronizer("", mMySegmentsSynchronizer); - mSynchronizer.loadSplitsFromCache(); + mSynchronizer.loadAndSynchronizeSplits(); mSynchronizer.loadMySegmentsFromCache(); mSynchronizer.loadAttributesFromCache(); + verify(mFeatureFlagsSynchronizer).loadAndSynchronize(); verify(mMySegmentsSynchronizerRegistry).loadMySegmentsFromCache(); verify(mAttributesSynchronizerRegistry).loadAttributesFromCache(); - verify(mFeatureFlagsSynchronizer).loadFromCache(); } @Test @@ -668,7 +668,7 @@ public void beingNotifiedOfSplitsSyncTaskTriggersSplitsLoad() { setup(SplitClientConfig.builder().persistentAttributesEnabled(false).build()); LoadSplitsTask task = mock(LoadSplitsTask.class); - when(mTaskFactory.createLoadSplitsTask()).thenReturn(task); + when(mTaskFactory.createLoadSplitsTask(any())).thenReturn(task); mSynchronizer.taskExecuted(SplitTaskExecutionInfo.success(SplitTaskType.SPLITS_SYNC)); diff --git a/src/test/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizerImplTest.java b/src/test/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizerImplTest.java index e4d3b2217..91f0800a8 100644 --- a/src/test/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizerImplTest.java +++ b/src/test/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizerImplTest.java @@ -64,7 +64,7 @@ public void setUp() { mFeatureFlagsSynchronizer = new FeatureFlagsSynchronizerImpl(mConfig, mTaskExecutor, mSingleThreadTaskExecutor, mTaskFactory, - mEventsManager, mRetryBackoffCounterFactory, mPushManagerEventBroadcaster); + mEventsManager, mRetryBackoffCounterFactory, mPushManagerEventBroadcaster, ""); } @Test @@ -78,25 +78,11 @@ public void synchronizeSplitsWithSince() { verify(mRetryTimerSplitsUpdate).start(); } - @Test - public void loadLocalData() { - LoadSplitsTask mockTask = mock(LoadSplitsTask.class); - when(mockTask.execute()).thenReturn(SplitTaskExecutionInfo.success(SplitTaskType.LOAD_LOCAL_SPLITS)); - when(mTaskFactory.createLoadSplitsTask()).thenReturn(mockTask); - when(mRetryBackoffCounterFactory.create(any(), anyInt())) - .thenReturn(mRetryTimerSplitsSync) - .thenReturn(mRetryTimerSplitsUpdate); - - mFeatureFlagsSynchronizer.loadFromCache(); - - verify(mTaskExecutor).submit(eq(mockTask), argThat(Objects::nonNull)); - } - @Test public void loadAndSynchronizeSplits() { LoadSplitsTask mockLoadTask = mock(LoadSplitsTask.class); when(mockLoadTask.execute()).thenReturn(SplitTaskExecutionInfo.success(SplitTaskType.LOAD_LOCAL_SPLITS)); - when(mTaskFactory.createLoadSplitsTask()).thenReturn(mockLoadTask); + when(mTaskFactory.createLoadSplitsTask(any())).thenReturn(mockLoadTask); FilterSplitsInCacheTask mockFilterTask = mock(FilterSplitsInCacheTask.class); when(mockFilterTask.execute()).thenReturn(SplitTaskExecutionInfo.success(SplitTaskType.FILTER_SPLITS_CACHE)); @@ -113,7 +99,7 @@ public void loadAndSynchronizeSplits() { mFeatureFlagsSynchronizer.loadAndSynchronize(); verify(mTaskFactory).createFilterSplitsInCacheTask(); - verify(mTaskFactory).createLoadSplitsTask(); + verify(mTaskFactory).createLoadSplitsTask(any()); ArgumentCaptor> argument = ArgumentCaptor.forClass(List.class); verify(mTaskExecutor).executeSerially(argument.capture()); diff --git a/src/test/java/io/split/android/client/service/telemetry/SynchronizerImplTelemetryTest.java b/src/test/java/io/split/android/client/service/telemetry/SynchronizerImplTelemetryTest.java index ff4e6c69f..35922f2d7 100644 --- a/src/test/java/io/split/android/client/service/telemetry/SynchronizerImplTelemetryTest.java +++ b/src/test/java/io/split/android/client/service/telemetry/SynchronizerImplTelemetryTest.java @@ -103,7 +103,8 @@ public void setUp() { mTaskFactory, mEventsManager, mRetryBackoffCounterFactory, - mPushManagerEventBroadcaster), + mPushManagerEventBroadcaster, + ""), mSplitStorageContainer.getEventsStorage()); } From 052f77453563cbd8b397405fb72ac4eee624166c Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 30 Aug 2023 17:50:15 -0300 Subject: [PATCH 25/89] Fix name --- .../split/android/client/service/executor/SplitTaskFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactory.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactory.java index b6e04b8a7..defc9109f 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactory.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactory.java @@ -18,7 +18,7 @@ public interface SplitTaskFactory extends TelemetryTaskFactory, ImpressionsTaskF SplitsSyncTask createSplitsSyncTask(boolean checkCacheExpiration); - LoadSplitsTask createLoadSplitsTask(String createLoadSplitsTask); + LoadSplitsTask createLoadSplitsTask(String splitsFilterQueryStringFromConfig); SplitKillTask createSplitKillTask(Split split); From 3837f8f3777a155af95582a8d899b6ccfdf82607 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 31 Aug 2023 19:32:28 -0300 Subject: [PATCH 26/89] Non retryable task --- .../executor/SplitTaskExecutionInfo.java | 1 + .../service/http/HttpGeneralException.java | 1 + .../client/service/http/HttpStatus.java | 38 +++++++++++++ .../service/splits/SplitsSyncHelper.java | 7 +++ .../sseclient/RetryBackoffCounterTimer.java | 23 +++++--- .../client/service/SplitsSyncHelperTest.java | 36 +++++++++++- .../RetryBackoffCounterTimerTest.java | 56 ++++++++++++++++++- 7 files changed, 153 insertions(+), 9 deletions(-) create mode 100644 src/main/java/io/split/android/client/service/http/HttpStatus.java diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskExecutionInfo.java b/src/main/java/io/split/android/client/service/executor/SplitTaskExecutionInfo.java index 0a314fb6c..bf5beca60 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskExecutionInfo.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskExecutionInfo.java @@ -13,6 +13,7 @@ public class SplitTaskExecutionInfo { public static final String NON_SENT_RECORDS = "NON_SENT_RECORDS"; public static final String NON_SENT_BYTES = "NON_SENT_BYTES"; + public static final String DO_NOT_RETRY = "DO_NOT_RETRY"; final private SplitTaskType taskType; final private SplitTaskExecutionStatus status; diff --git a/src/main/java/io/split/android/client/service/http/HttpGeneralException.java b/src/main/java/io/split/android/client/service/http/HttpGeneralException.java index 57ef473cf..ac011d87c 100644 --- a/src/main/java/io/split/android/client/service/http/HttpGeneralException.java +++ b/src/main/java/io/split/android/client/service/http/HttpGeneralException.java @@ -4,6 +4,7 @@ import androidx.annotation.Nullable; public abstract class HttpGeneralException extends Exception { + private final Integer mHttpStatus; public HttpGeneralException(String path, String message, @Nullable Integer httpStatus) { diff --git a/src/main/java/io/split/android/client/service/http/HttpStatus.java b/src/main/java/io/split/android/client/service/http/HttpStatus.java new file mode 100644 index 000000000..e78873e55 --- /dev/null +++ b/src/main/java/io/split/android/client/service/http/HttpStatus.java @@ -0,0 +1,38 @@ +package io.split.android.client.service.http; + +import androidx.annotation.Nullable; + +public enum HttpStatus { + + URI_TOO_LONG(414, "URI Too Long"); + + private final int mCode; + private final String mDescription; + + HttpStatus(int code, String description) { + mCode = code; + mDescription = description; + } + + public int getCode() { + return mCode; + } + + public String getDescription() { + return mDescription; + } + + @Nullable + public static HttpStatus fromCode(Integer code) { + if (code == null) { + return null; + } + + for (HttpStatus status : values()) { + if (status.getCode() == code) { + return status; + } + } + return null; + } +} diff --git a/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java b/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java index 9693f4875..c3c8e8b9a 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java +++ b/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java @@ -7,6 +7,7 @@ import org.jetbrains.annotations.Nullable; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -18,6 +19,7 @@ import io.split.android.client.service.executor.SplitTaskType; import io.split.android.client.service.http.HttpFetcher; import io.split.android.client.service.http.HttpFetcherException; +import io.split.android.client.service.http.HttpStatus; import io.split.android.client.service.sseclient.BackoffCounter; import io.split.android.client.service.sseclient.ReconnectBackoffCounter; import io.split.android.client.storage.splits.SplitsStorage; @@ -81,6 +83,11 @@ private SplitTaskExecutionInfo sync(long till, boolean clearBeforeUpdate, boolea logError("Network error while fetching feature flags" + e.getLocalizedMessage()); mTelemetryRuntimeProducer.recordSyncError(OperationType.SPLITS, e.getHttpStatus()); + if (HttpStatus.fromCode(e.getHttpStatus()) == HttpStatus.URI_TOO_LONG) { + return SplitTaskExecutionInfo.error(SplitTaskType.SPLITS_SYNC, + Collections.singletonMap(SplitTaskExecutionInfo.DO_NOT_RETRY, true)); + } + return SplitTaskExecutionInfo.error(SplitTaskType.SPLITS_SYNC); } catch (Exception e) { logError("Unexpected while fetching feature flags" + e.getLocalizedMessage()); diff --git a/src/main/java/io/split/android/client/service/sseclient/sseclient/RetryBackoffCounterTimer.java b/src/main/java/io/split/android/client/service/sseclient/sseclient/RetryBackoffCounterTimer.java index e579373f4..018dfedf4 100644 --- a/src/main/java/io/split/android/client/service/sseclient/sseclient/RetryBackoffCounterTimer.java +++ b/src/main/java/io/split/android/client/service/sseclient/sseclient/RetryBackoffCounterTimer.java @@ -30,7 +30,7 @@ public class RetryBackoffCounterTimer implements SplitTaskExecutionListener { /** * Creates an instance which retries tasks indefinitely, using the strategy defined by backoffCounter. * - * @param taskExecutor Implementation of SplitTaskExecutor. + * @param taskExecutor Implementation of SplitTaskExecutor. * @param backoffCounter Will determine the retry interval. */ public RetryBackoffCounterTimer(@NonNull SplitTaskExecutor taskExecutor, @@ -43,8 +43,8 @@ public RetryBackoffCounterTimer(@NonNull SplitTaskExecutor taskExecutor, /** * Creates an instance which retries tasks up to the number of times specified by retryAttemptsLimit. * - * @param taskExecutor Implementation of SplitTaskExecutor. - * @param backoffCounter Will determine the retry interval. + * @param taskExecutor Implementation of SplitTaskExecutor. + * @param backoffCounter Will determine the retry interval. * @param retryAttemptsLimit Maximum number of attempts for task retry. */ public RetryBackoffCounterTimer(@NonNull SplitTaskExecutor taskExecutor, @@ -65,7 +65,7 @@ synchronized public void setTask(@NonNull SplitTask task) { } synchronized public void stop() { - if(mTask == null) { + if (mTask == null) { return; } mTaskExecutor.stopTask(mTaskId); @@ -73,7 +73,7 @@ synchronized public void stop() { } synchronized public void start() { - if(mTask == null || mTaskId != null) { + if (mTask == null || mTaskId != null) { return; } mBackoffCounter.resetCounter(); @@ -94,16 +94,25 @@ synchronized private void schedule() { @Override public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { mTaskId = null; - if (taskInfo.getStatus() == SplitTaskExecutionStatus.ERROR) { + if (taskInfo.getStatus() == SplitTaskExecutionStatus.ERROR && + (taskInfo.getBoolValue(SplitTaskExecutionInfo.DO_NOT_RETRY) == null || + Boolean.FALSE.equals(taskInfo.getBoolValue(SplitTaskExecutionInfo.DO_NOT_RETRY)))) { + if (mRetryAttemptsLimit == DEFAULT_MAX_ATTEMPTS || mCurrentAttempts.get() < mRetryAttemptsLimit) { schedule(); } + return; } mBackoffCounter.resetCounter(); + if (mListener != null) { - mListener.taskExecuted(SplitTaskExecutionInfo.success(taskInfo.getTaskType())); + if (taskInfo.getStatus() == SplitTaskExecutionStatus.SUCCESS) { + mListener.taskExecuted(SplitTaskExecutionInfo.success(taskInfo.getTaskType())); + } else if (taskInfo.getStatus() == SplitTaskExecutionStatus.ERROR) { + mListener.taskExecuted(SplitTaskExecutionInfo.error(taskInfo.getTaskType())); + } } } } diff --git a/src/test/java/io/split/android/client/service/SplitsSyncHelperTest.java b/src/test/java/io/split/android/client/service/SplitsSyncHelperTest.java index e160aa2a1..668064db3 100644 --- a/src/test/java/io/split/android/client/service/SplitsSyncHelperTest.java +++ b/src/test/java/io/split/android/client/service/SplitsSyncHelperTest.java @@ -1,5 +1,6 @@ package io.split.android.client.service; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -27,6 +28,7 @@ import io.split.android.helpers.FileHelper; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.eq; @@ -56,10 +58,11 @@ public class SplitsSyncHelperTest { private final Map mDefaultParams = new HashMap<>(); private final Map mSecondFetchParams = new HashMap<>(); + private AutoCloseable mAutoCloseable; @Before public void setup() { - MockitoAnnotations.openMocks(this); + mAutoCloseable = MockitoAnnotations.openMocks(this); mDefaultParams.clear(); mDefaultParams.put("since", -1L); mSecondFetchParams.clear(); @@ -68,6 +71,15 @@ public void setup() { loadSplitChanges(); } + @After + public void tearDown() { + try { + mAutoCloseable.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + @Test public void correctSyncExecution() throws HttpFetcherException { // On correct execution without having clear param @@ -332,6 +344,28 @@ public void replaceTillWhenFilterHasChanged() throws HttpFetcherException { verifyNoMoreInteractions(mSplitsFetcher); } + @Test + public void returnTaskInfoToDoNotRetryWhenHttpFetcherExceptionStatusCodeIs414() throws HttpFetcherException { + when(mSplitsFetcher.execute(eq(mDefaultParams), any())) + .thenThrow(new HttpFetcherException("error", "error", 414)); + when(mSplitsStorage.getTill()).thenReturn(-1L); + + SplitTaskExecutionInfo result = mSplitsSyncHelper.sync(-1); + + assertEquals(true, result.getBoolValue(SplitTaskExecutionInfo.DO_NOT_RETRY)); + } + + @Test + public void doNotRetryFlagIsNullWhenFetcherExceptionStatusCodeIsNot414() throws HttpFetcherException { + when(mSplitsFetcher.execute(eq(mDefaultParams), any())) + .thenThrow(new HttpFetcherException("error", "error", 500)); + when(mSplitsStorage.getTill()).thenReturn(-1L); + + SplitTaskExecutionInfo result = mSplitsSyncHelper.sync(-1); + + assertNull(result.getBoolValue(SplitTaskExecutionInfo.DO_NOT_RETRY)); + } + private void loadSplitChanges() { if (mSplitChange == null) { FileHelper fileHelper = new FileHelper(); diff --git a/src/test/java/io/split/android/client/service/sseclient/sseclient/RetryBackoffCounterTimerTest.java b/src/test/java/io/split/android/client/service/sseclient/sseclient/RetryBackoffCounterTimerTest.java index eed9d7635..d0d07fbaf 100644 --- a/src/test/java/io/split/android/client/service/sseclient/sseclient/RetryBackoffCounterTimerTest.java +++ b/src/test/java/io/split/android/client/service/sseclient/sseclient/RetryBackoffCounterTimerTest.java @@ -1,23 +1,28 @@ package io.split.android.client.service.sseclient.sseclient; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import androidx.annotation.NonNull; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Collections; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import io.split.android.client.service.executor.SplitTask; import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutionListener; +import io.split.android.client.service.executor.SplitTaskExecutionStatus; import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.executor.SplitTaskType; import io.split.android.client.service.sseclient.BackoffCounter; @@ -31,13 +36,23 @@ public class RetryBackoffCounterTimerTest { @Mock private SplitTask mockTask; private RetryBackoffCounterTimer counterTimer; + private AutoCloseable mAutoCloseable; @Before public void setUp() { - MockitoAnnotations.openMocks(this); + mAutoCloseable = MockitoAnnotations.openMocks(this); counterTimer = new RetryBackoffCounterTimer(taskExecutor, backoffCounter); } + @After + public void tearDown() { + try { + mAutoCloseable.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + @Test public void stopCallsStopInTaskExecutorWhenTaskIsNotNull() { when(taskExecutor.schedule(mockTask, @@ -115,4 +130,43 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { assertTrue(await); verify(taskExecutor, times(2)).schedule(mockTask, 0L, counterTimer); } + + @Test + public void nonRetryableErrorTaskIsNotRetried() { + counterTimer = new RetryBackoffCounterTimer(taskExecutor, backoffCounter); + + SplitTaskExecutionListener mockListener = mock(SplitTaskExecutionListener.class); + when(taskExecutor.schedule(mockTask, + 0L, + counterTimer)).then(invocation -> { + counterTimer.taskExecuted(SplitTaskExecutionInfo.error(SplitTaskType.SPLITS_SYNC, Collections.singletonMap("DO_NOT_RETRY", true))); + return "100"; + }); + + counterTimer.setTask(mockTask, mockListener); + + counterTimer.start(); + + verify(taskExecutor).schedule(mockTask, 0L, counterTimer); + } + + @Test + public void nonRetryableErrorTaskNotifiesListenerWithErrorStatus() { + counterTimer = new RetryBackoffCounterTimer(taskExecutor, backoffCounter); + + SplitTaskExecutionListener mockListener = mock(SplitTaskExecutionListener.class); + when(taskExecutor.schedule(mockTask, + 0L, + counterTimer)).then(invocation -> { + counterTimer.taskExecuted(SplitTaskExecutionInfo.error(SplitTaskType.SPLITS_SYNC, Collections.singletonMap("DO_NOT_RETRY", true))); + return "100"; + }); + + counterTimer.setTask(mockTask, mockListener); + + counterTimer.start(); + + verify(mockListener).taskExecuted(argThat(taskInfo -> taskInfo.getStatus() == SplitTaskExecutionStatus.ERROR && + taskInfo.getTaskType() == SplitTaskType.SPLITS_SYNC)); + } } From 535f7b5633415ce8565c43cc3235894599218c2b Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 1 Sep 2023 12:53:18 -0300 Subject: [PATCH 27/89] Evaluation by sets in SplitClient interface --- .../io/split/android/client/SplitClient.java | 143 +++++++++++------- 1 file changed, 89 insertions(+), 54 deletions(-) diff --git a/src/main/java/io/split/android/client/SplitClient.java b/src/main/java/io/split/android/client/SplitClient.java index bd2fb1473..7214ffcc6 100644 --- a/src/main/java/io/split/android/client/SplitClient.java +++ b/src/main/java/io/split/android/client/SplitClient.java @@ -1,5 +1,8 @@ package io.split.android.client; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import java.util.List; import java.util.Map; @@ -50,8 +53,8 @@ public interface SplitClient extends AttributesManager { * vs. premium plan. Another example is to show a different treatment * to users created after a certain date. * - * @param featureFlagName the feature flag we want to evaluate. MUST NOT be null. - * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty. + * @param featureFlagName the feature flag we want to evaluate. MUST NOT be null. + * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty. * @return the evaluated treatment, the default treatment of this feature flag, or 'control'. */ String getTreatment(String featureFlagName, Map attributes); @@ -67,10 +70,10 @@ public interface SplitClient extends AttributesManager { * vs. premium plan. Another example is to show a different treatment * to users created after a certain date. * - * @param featureFlagName the feature flag we want to evaluate. MUST NOT be null. - * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty. + * @param featureFlagName the feature flag we want to evaluate. MUST NOT be null. + * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty. * @return the evaluated treatment, the default treatment of this feature flag, or 'control' - * with its corresponding configurations if it has one. + * with its corresponding configurations if it has one. */ SplitResult getTreatmentWithConfig(String featureFlagName, Map attributes); @@ -81,8 +84,8 @@ public interface SplitClient extends AttributesManager { *

* It can be used to cache treatments you know it won't change very often. * - * @param featureFlagNames the feature flags you want to evaluate. MUST NOT be null. - * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty. + * @param featureFlagNames the feature flags you want to evaluate. MUST NOT be null. + * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty. * @return the evaluated treatments, the default treatment of a feature, or 'control'. */ Map getTreatments(List featureFlagNames, Map attributes); @@ -95,13 +98,53 @@ public interface SplitClient extends AttributesManager { *

* It can be used to cache treatments you know it won't change very often. * - * @param featureFlagNames the feature flags you want to evaluate. MUST NOT be null. - * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty. + * @param featureFlagNames the feature flags you want to evaluate. MUST NOT be null. + * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty. * @return the evaluated treatments, the default treatment of a feature flag, or 'control' * with its corresponding configurations if it has one. */ Map getTreatmentsWithConfig(List featureFlagNames, Map attributes); + /** + * 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. + * + * @param flagSet the Flag Set name that you want to evaluate. Must not be null or empty + * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty + * @return a {@link Map} containing for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control' + */ + Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes); + + /** + * 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. + * + * @param flagSets the Flag Sets names that you want to evaluate. Must not be null or empty + * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty + * @return a {@link Map} containing for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control' + */ + Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes); + + /** + * This method is useful when you want to determine the treatment of several feature flags + * belonging to a specific Flag Set + * + * @param flagSet the Flag Set name that you want to evaluate. Must not be null or empty + * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty + * @return a {@link Map} containing for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control' + */ + Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes); + + /** + * This method is useful when you want to determine the treatment of several feature flags + * belonging to a specific list of Flag Sets + * + * @param flagSets the Flag Sets names that you want to evaluate. Must not be null or empty + * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty + * @return a {@link Map} containing for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control' + */ + Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes); + /** * 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 @@ -115,6 +158,7 @@ public interface SplitClient extends AttributesManager { /** * Checks if cached data is ready to perform treatment evaluations + * * @return true if the sdk is ready, if false, calls to getTreatment will return control */ boolean isReady(); @@ -123,118 +167,109 @@ public interface SplitClient extends AttributesManager { /** * Enqueue a new event to be sent to Split data collection services. - * + *

* The traffic type used is the one set by trafficType() in SplitClientConfig. - * + *

* Example: - * client.track(“checkout”) + * client.track(“checkout”) * * @param eventType the type of the event - * * @return true if the track was successful, false otherwise */ boolean track(String eventType); /** * Enqueue a new event to be sent to Split data collection services - * + *

* Example: - * client.track(“account”, “checkout”, 200.00) + * client.track(“account”, “checkout”, 200.00) * * @param trafficType the type of the event - * @param eventType the type of the event - * @param value the value of the event - * + * @param eventType the type of the event + * @param value the value of the event * @return true if the track was successful, false otherwise */ boolean track(String trafficType, String eventType, double value); /** * Enqueue a new event to be sent to Split data collection services - * + *

* Example: - * client.track(“account”, “checkout”) + * client.track(“account”, “checkout”) * * @param trafficType the type of the event - * @param eventType the type of the event - * + * @param eventType the type of the event * @return true if the track was successful, false otherwise */ boolean track(String trafficType, String eventType); /** * Enqueue a new event to be sent to Split data collection services - * + *

* The traffic type used is the one set by trafficType() in SplitClientConfig. - + *

* Example: - * client.track(“checkout”, 200.00) + * client.track(“checkout”, 200.00) * * @param eventType the type of the event - * @param value the value of the event - * + * @param value the value of the event * @return true if the track was successful, false otherwise */ boolean track(String eventType, double value); /** * Enqueue a new event to be sent to Split data collection services. - * + *

* The traffic type used is the one set by trafficType() in SplitClientConfig. - * + *

* Example: - * client.track(“checkout”) + * client.track(“checkout”) * - * @param eventType the type of the event + * @param eventType the type of the event * @param properties custom user data map - * * @return true if the track was successful, false otherwise */ - boolean track(String eventType, Map properties); + boolean track(String eventType, Map properties); /** * Enqueue a new event to be sent to Split data collection services - * + *

* Example: - * client.track(“account”, “checkout”, 200.00) + * client.track(“account”, “checkout”, 200.00) * * @param trafficType the type of the event - * @param eventType the type of the event - * @param value the value of the event - * @param properties custom user data map - * + * @param eventType the type of the event + * @param value the value of the event + * @param properties custom user data map * @return true if the track was successful, false otherwise */ - boolean track(String trafficType, String eventType, double value, Map properties); + boolean track(String trafficType, String eventType, double value, Map properties); /** * Enqueue a new event to be sent to split data collection services - * + *

* Example: - * client.track(“account”, “checkout”) + * client.track(“account”, “checkout”) * * @param trafficType the type of the event - * @param eventType the type of the event - * @param properties custom user data map - * + * @param eventType the type of the event + * @param properties custom user data map * @return true if the track was successful, false otherwise */ - boolean track(String trafficType, String eventType, Map properties); + boolean track(String trafficType, String eventType, Map properties); /** * Enqueue a new event to be sent to Split data collection services - * + *

* The traffic type used is the one set by trafficType() in SplitClientConfig. - + *

* Example: - * client.track(“checkout”, 200.00) + * client.track(“checkout”, 200.00) * - * @param eventType the type of the event - * @param value the value of the event + * @param eventType the type of the event + * @param value the value of the event * @param properties custom user data map - * * @return true if the track was successful, false otherwise */ - boolean track(String eventType, double value, Map properties); - + boolean track(String eventType, double value, Map properties); } From 0ae7326ba180bce6e618378f526f56d809eac4fc Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 1 Sep 2023 15:31:39 -0300 Subject: [PATCH 28/89] Setup for new client methods --- .../java/fake/SplitClientStub.java | 20 +++++++++++ .../AlwaysReturnControlSplitClient.java | 24 +++++++++++-- .../split/android/client/FilterBuilder.java | 2 +- .../client/SplitClientFactoryImpl.java | 8 +++-- .../split/android/client/SplitClientImpl.java | 36 +++++++++++++++++++ .../android/client/SplitFactoryImpl.java | 2 +- .../localhost/LocalhostSplitClient.java | 26 ++++++++++++-- .../localhost/LocalhostSplitFactory.java | 18 +++++++++- .../LocalhostSplitClientContainerImpl.java | 10 ++++-- .../shared/SplitClientContainerImpl.java | 7 ++-- .../TreatmentManagerFactoryImpl.java | 11 ++++-- .../validators/TreatmentManagerImpl.java | 6 +++- .../client/TreatmentManagerTelemetryTest.java | 7 ++-- .../android/client/TreatmentManagerTest.java | 9 ++--- ...LocalhostSplitClientContainerImplTest.java | 15 +++++++- .../client/utils/SplitClientImplFactory.java | 12 +++---- 16 files changed, 182 insertions(+), 31 deletions(-) diff --git a/src/androidTest/java/fake/SplitClientStub.java b/src/androidTest/java/fake/SplitClientStub.java index b20df3182..d500ef366 100644 --- a/src/androidTest/java/fake/SplitClientStub.java +++ b/src/androidTest/java/fake/SplitClientStub.java @@ -38,6 +38,26 @@ public Map getTreatmentsWithConfig(List featureFlag return null; } + @Override + public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { + return null; + } + + @Override + public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { + return null; + } + + @Override + public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { + return null; + } + + @Override + public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { + return null; + } + @Override public void destroy() { diff --git a/src/main/java/io/split/android/client/AlwaysReturnControlSplitClient.java b/src/main/java/io/split/android/client/AlwaysReturnControlSplitClient.java index 70b6356f2..8ec6576f8 100644 --- a/src/main/java/io/split/android/client/AlwaysReturnControlSplitClient.java +++ b/src/main/java/io/split/android/client/AlwaysReturnControlSplitClient.java @@ -6,6 +6,8 @@ import io.split.android.client.events.SplitEvent; import io.split.android.client.events.SplitEventTask; import io.split.android.grammar.Treatments; + +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -58,6 +60,26 @@ public SplitResult getTreatmentWithConfig(String featureFlagName, Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { + return Collections.singletonMap(flagSet, Treatments.CONTROL); //TODO + } + + @Override + public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { + return Collections.singletonMap(flagSets.get(0), Treatments.CONTROL); //TODO + } + + @Override + public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { + return Collections.singletonMap(flagSet, new SplitResult(Treatments.CONTROL)); //TODO + } + + @Override + public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { + return Collections.singletonMap(flagSets.get(0), new SplitResult(Treatments.CONTROL)); //TODO + } + @Override public boolean setAttribute(String attributeName, Object value) { return true; @@ -149,6 +171,4 @@ public boolean track(String eventType, Map properties) { public boolean track(String eventType, double value, Map properties) { return false; } - - } diff --git a/src/main/java/io/split/android/client/FilterBuilder.java b/src/main/java/io/split/android/client/FilterBuilder.java index 2892d0a42..335fcb841 100644 --- a/src/main/java/io/split/android/client/FilterBuilder.java +++ b/src/main/java/io/split/android/client/FilterBuilder.java @@ -63,7 +63,7 @@ public String buildQueryString() { } @NonNull - public ArrayList getGroupedFilter() { + public List getGroupedFilter() { return new ArrayList<>(mFilterGrouper.group(mFilters)); } diff --git a/src/main/java/io/split/android/client/SplitClientFactoryImpl.java b/src/main/java/io/split/android/client/SplitClientFactoryImpl.java index ea104754e..eb03d36b2 100644 --- a/src/main/java/io/split/android/client/SplitClientFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitClientFactoryImpl.java @@ -4,6 +4,8 @@ import androidx.annotation.NonNull; +import java.util.Set; + import io.split.android.client.api.Key; import io.split.android.client.attributes.AttributesManagerFactory; import io.split.android.client.attributes.AttributesManagerFactoryImpl; @@ -55,7 +57,8 @@ public SplitClientFactoryImpl(@NonNull SplitFactory splitFactory, @NonNull ValidationMessageLogger validationLogger, @NonNull KeyValidator keyValidator, @NonNull EventsTracker eventsTracker, - @NonNull ImpressionListener customerImpressionListener) { + @NonNull ImpressionListener customerImpressionListener, + @NonNull Set configuredFlagSets) { mSplitFactory = checkNotNull(splitFactory); mClientContainer = checkNotNull(clientContainer); mConfig = checkNotNull(config); @@ -79,7 +82,8 @@ public SplitClientFactoryImpl(@NonNull SplitFactory splitFactory, config.labelsEnabled(), new AttributesMergerImpl(), mStorageContainer.getTelemetryStorage(), - new EvaluatorImpl(mStorageContainer.getSplitsStorage(), mSplitParser) + new EvaluatorImpl(mStorageContainer.getSplitsStorage(), mSplitParser), + configuredFlagSets ); } diff --git a/src/main/java/io/split/android/client/SplitClientImpl.java b/src/main/java/io/split/android/client/SplitClientImpl.java index 00f4feaf7..08bbabd76 100644 --- a/src/main/java/io/split/android/client/SplitClientImpl.java +++ b/src/main/java/io/split/android/client/SplitClientImpl.java @@ -188,6 +188,42 @@ public Map getTreatmentsWithConfig(List featureFlag } } + @Override + public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { + try { + return Collections.emptyMap(); //TODO + } catch (Exception exception) { + return null; + } + } + + @Override + public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { + try { + return Collections.emptyMap(); //TODO + } catch (Exception exception) { + return null; + } + } + + @Override + public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { + try { + return Collections.emptyMap(); //TODO + } catch (Exception exception) { + return null; + } + } + + @Override + public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { + try { + return Collections.emptyMap(); //TODO + } catch (Exception exception) { + return null; + } + } + public void on(SplitEvent event, SplitEventTask task) { checkNotNull(event); checkNotNull(task); diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index e693aca8d..a6404513a 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -269,7 +269,7 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { telemetrySynchronizer, mStorageContainer, splitTaskExecutor, splitApiFacade, validationLogger, keyValidator, customerImpressionListener, streamingComponents.getPushNotificationManager(), componentsRegister, workManagerWrapper, - eventsTracker); + eventsTracker, configuredFlagSets); mDestroyer = new Runnable() { public void run() { Logger.w("Shutdown called for split"); 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 4023e0060..d01393122 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java @@ -9,6 +9,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import io.split.android.client.Evaluator; import io.split.android.client.EvaluatorImpl; @@ -58,7 +59,8 @@ public LocalhostSplitClient(@NonNull LocalhostSplitFactory container, @NonNull SplitParser splitParser, @NonNull AttributesManager attributesManager, @NonNull AttributesMerger attributesMerger, - @NonNull TelemetryStorageProducer telemetryStorageProducer) { + @NonNull TelemetryStorageProducer telemetryStorageProducer, + @NonNull Set configuredFlagSets) { mFactoryRef = new WeakReference<>(checkNotNull(container)); mClientContainer = new WeakReference<>(checkNotNull(clientContainer)); @@ -68,7 +70,7 @@ public LocalhostSplitClient(@NonNull LocalhostSplitFactory container, mTreatmentManager = new TreatmentManagerImpl(mKey.matchingKey(), mKey.bucketingKey(), mEvaluator, new KeyValidatorImpl(), new SplitValidatorImpl(), getImpressionsListener(splitClientConfig), - splitClientConfig.labelsEnabled(), eventsManager, attributesManager, attributesMerger, telemetryStorageProducer); + splitClientConfig.labelsEnabled(), eventsManager, attributesManager, attributesMerger, telemetryStorageProducer, configuredFlagSets); } @Override @@ -138,6 +140,26 @@ public Map getTreatmentsWithConfig(List featureFlag } } + @Override + public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { + return null; + } + + @Override + public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { + return null; + } + + @Override + public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { + return null; + } + + @Override + public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { + return null; + } + @Override public void destroy() { mIsClientDestroyed = true; diff --git a/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java b/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java index ba942771c..e0ece35ca 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java @@ -6,11 +6,16 @@ import androidx.annotation.VisibleForTesting; import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import io.split.android.client.FilterBuilder; import io.split.android.client.SplitClient; import io.split.android.client.SplitClientConfig; import io.split.android.client.SplitFactory; +import io.split.android.client.SplitFilter; import io.split.android.client.SplitManager; import io.split.android.client.SplitManagerImpl; import io.split.android.client.SyncConfig; @@ -69,6 +74,16 @@ public LocalhostSplitFactory(String key, Context context, mManager = new SplitManagerImpl(splitsStorage, new SplitValidatorImpl(), splitParser); + Set configuredSets = new HashSet<>(); + if (config.syncConfig() != null) { + List groupedFilters = new FilterBuilder(config.syncConfig().getFilters()) + .getGroupedFilter(); + + if (!groupedFilters.isEmpty() && groupedFilters.get(0).getType() == SplitFilter.Type.BY_SET) { + configuredSets.addAll(groupedFilters.get(0).getValues()); + } + } + mClientContainer = new LocalhostSplitClientContainerImpl(this, config, splitsStorage, @@ -77,7 +92,8 @@ public LocalhostSplitFactory(String key, Context context, new AttributesMergerImpl(), new NoOpTelemetryStorage(), eventsManagerCoordinator, - taskExecutor); + taskExecutor, + configuredSets); mSynchronizer = new LocalhostSynchronizer(taskExecutor, config, splitsStorage, buildQueryString(config.syncConfig())); mSynchronizer.start(); diff --git a/src/main/java/io/split/android/client/localhost/shared/LocalhostSplitClientContainerImpl.java b/src/main/java/io/split/android/client/localhost/shared/LocalhostSplitClientContainerImpl.java index ba9775e37..e4a6d2a69 100644 --- a/src/main/java/io/split/android/client/localhost/shared/LocalhostSplitClientContainerImpl.java +++ b/src/main/java/io/split/android/client/localhost/shared/LocalhostSplitClientContainerImpl.java @@ -1,5 +1,7 @@ package io.split.android.client.localhost.shared; +import java.util.Set; + import io.split.android.client.SplitClient; import io.split.android.client.SplitClientConfig; import io.split.android.client.api.Key; @@ -29,6 +31,7 @@ public class LocalhostSplitClientContainerImpl extends BaseSplitClientContainer private final TelemetryStorageProducer mTelemetryStorageProducer; private final EventsManagerCoordinator mEventsManagerCoordinator; private final SplitTaskExecutor mSplitTaskExecutor; + private final Set mConfiguredSets; public LocalhostSplitClientContainerImpl(LocalhostSplitFactory splitFactory, SplitClientConfig config, @@ -38,7 +41,8 @@ public LocalhostSplitClientContainerImpl(LocalhostSplitFactory splitFactory, AttributesMerger attributesMerger, TelemetryStorageProducer telemetryStorageProducer, EventsManagerCoordinator eventsManagerCoordinator, - SplitTaskExecutor taskExecutor) { + SplitTaskExecutor taskExecutor, + Set configuredSets) { mSplitFactory = splitFactory; mConfig = config; mSplitStorage = splitsStorage; @@ -48,6 +52,7 @@ public LocalhostSplitClientContainerImpl(LocalhostSplitFactory splitFactory, mTelemetryStorageProducer = telemetryStorageProducer; mEventsManagerCoordinator = eventsManagerCoordinator; mSplitTaskExecutor = taskExecutor; + mConfiguredSets = configuredSets; } @Override @@ -70,7 +75,8 @@ protected void createNewClient(Key key) { mSplitParser, attributesManager, mAttributesMerger, - mTelemetryStorageProducer + mTelemetryStorageProducer, + mConfiguredSets ); eventsManager.getExecutorResources().setSplitClient(client); diff --git a/src/main/java/io/split/android/client/shared/SplitClientContainerImpl.java b/src/main/java/io/split/android/client/shared/SplitClientContainerImpl.java index 742c85c68..a23d5e73c 100644 --- a/src/main/java/io/split/android/client/shared/SplitClientContainerImpl.java +++ b/src/main/java/io/split/android/client/shared/SplitClientContainerImpl.java @@ -6,6 +6,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import io.split.android.client.EventsTracker; @@ -70,7 +71,8 @@ public SplitClientContainerImpl(@NonNull String defaultMatchingKey, @Nullable PushNotificationManager pushNotificationManager, @NonNull ClientComponentsRegister clientComponentsRegister, @NonNull MySegmentsWorkManagerWrapper workManagerWrapper, - @NonNull EventsTracker eventsTracker) { + @NonNull EventsTracker eventsTracker, + @NonNull Set configuredFlagSets) { mDefaultMatchingKey = checkNotNull(defaultMatchingKey); mPushNotificationManager = pushNotificationManager; mStreamingEnabled = config.streamingEnabled(); @@ -88,7 +90,8 @@ public SplitClientContainerImpl(@NonNull String defaultMatchingKey, validationLogger, keyValidator, eventsTracker, - customerImpressionListener + customerImpressionListener, + configuredFlagSets ); mClientComponentsRegister = checkNotNull(clientComponentsRegister); mSplitTaskExecutor = checkNotNull(splitTaskExecutor); diff --git a/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java b/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java index be6309fb0..ce95d1441 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java @@ -4,11 +4,12 @@ import androidx.annotation.NonNull; +import java.util.Set; + import io.split.android.client.Evaluator; import io.split.android.client.api.Key; import io.split.android.client.attributes.AttributesManager; import io.split.android.client.attributes.AttributesMerger; -import io.split.android.client.events.ISplitEventsManager; import io.split.android.client.events.ListenableEventsManager; import io.split.android.client.impressions.ImpressionListener; import io.split.android.client.telemetry.storage.TelemetryStorageProducer; @@ -22,6 +23,7 @@ public class TreatmentManagerFactoryImpl implements TreatmentManagerFactory { private final AttributesMerger mAttributesMerger; private final TelemetryStorageProducer mTelemetryStorageProducer; private final Evaluator mEvaluator; + private final Set mConfiguredFlagSets; public TreatmentManagerFactoryImpl(@NonNull KeyValidator keyValidator, @NonNull SplitValidator splitValidator, @@ -29,7 +31,8 @@ public TreatmentManagerFactoryImpl(@NonNull KeyValidator keyValidator, boolean labelsEnabled, @NonNull AttributesMerger attributesMerger, @NonNull TelemetryStorageProducer telemetryStorageProducer, - @NonNull Evaluator evaluator) { + @NonNull Evaluator evaluator, + @NonNull Set configuredFlagSets) { mKeyValidator = checkNotNull(keyValidator); mSplitValidator = checkNotNull(splitValidator); mCustomerImpressionListener = checkNotNull(customerImpressionListener); @@ -37,6 +40,7 @@ public TreatmentManagerFactoryImpl(@NonNull KeyValidator keyValidator, mAttributesMerger = checkNotNull(attributesMerger); mTelemetryStorageProducer = checkNotNull(telemetryStorageProducer); mEvaluator = checkNotNull(evaluator); + mConfiguredFlagSets = configuredFlagSets; } @Override @@ -52,7 +56,8 @@ public TreatmentManager getTreatmentManager(Key key, ListenableEventsManager eve eventsManager, attributesManager, mAttributesMerger, - mTelemetryStorageProducer + mTelemetryStorageProducer, + mConfiguredFlagSets ); } } 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 8cc4b55b4..ae52d9377 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java @@ -7,6 +7,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import io.split.android.client.EvaluationResult; import io.split.android.client.Evaluator; @@ -48,6 +49,7 @@ private static class ValidationTag { @NonNull private final AttributesMerger mAttributesMerger; private final TelemetryStorageProducer mTelemetryStorageProducer; + private final Set mConfiguredFlagSets; public TreatmentManagerImpl(String matchingKey, String bucketingKey, @@ -59,7 +61,8 @@ public TreatmentManagerImpl(String matchingKey, ListenableEventsManager eventsManager, @NonNull AttributesManager attributesManager, @NonNull AttributesMerger attributesMerger, - @NonNull TelemetryStorageProducer telemetryStorageProducer) { + @NonNull TelemetryStorageProducer telemetryStorageProducer, + @NonNull Set configuredFlagSets) { mEvaluator = evaluator; mKeyValidator = keyValidator; mSplitValidator = splitValidator; @@ -72,6 +75,7 @@ public TreatmentManagerImpl(String matchingKey, mAttributesManager = checkNotNull(attributesManager); mAttributesMerger = checkNotNull(attributesMerger); mTelemetryStorageProducer = checkNotNull(telemetryStorageProducer); + mConfiguredFlagSets = checkNotNull(configuredFlagSets); } @Override diff --git a/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java b/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java index 86b8230d0..b93f59aa2 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java @@ -15,6 +15,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; import io.split.android.client.attributes.AttributesManager; import io.split.android.client.attributes.AttributesMerger; @@ -46,6 +48,7 @@ public class TreatmentManagerTelemetryTest { @Mock TelemetryStorageProducer telemetryStorageProducer; + private Set mConfiguredFlagSets = new HashSet<>(); private TreatmentManagerImpl treatmentManager; @Before @@ -63,8 +66,8 @@ public void setUp() { eventsManager, attributesManager, attributesMerger, - telemetryStorageProducer - ); + telemetryStorageProducer, + mConfiguredFlagSets); when(evaluator.getTreatment(anyString(), anyString(), anyString(), anyMap())).thenReturn(new EvaluationResult("test", "label")); } diff --git a/src/test/java/io/split/android/client/TreatmentManagerTest.java b/src/test/java/io/split/android/client/TreatmentManagerTest.java index 02ab324e9..43959f5d6 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerTest.java @@ -28,7 +28,6 @@ import io.split.android.client.attributes.AttributesManager; import io.split.android.client.attributes.AttributesMerger; import io.split.android.client.dtos.Split; -import io.split.android.client.events.ISplitEventsManager; import io.split.android.client.events.ListenableEventsManager; import io.split.android.client.events.SplitEvent; import io.split.android.client.impressions.ImpressionListener; @@ -56,10 +55,12 @@ public class TreatmentManagerTest { ListenableEventsManager eventsManagerStub; AttributesManager attributesManager = mock(AttributesManager.class); TelemetryStorageProducer telemetryStorageProducer = mock(TelemetryStorageProducer.class); + private Set mConfiguredFlagSets; TreatmentManagerImpl treatmentManager = initializeTreatmentManager(); @Before public void loadSplitsFromFile() { + mConfiguredFlagSets = new HashSet<>(); if (evaluator == null) { FileHelper fileHelper = new FileHelper(); MySegmentsStorageContainer mySegmentsStorageContainer = mock(MySegmentsStorageContainer.class); @@ -320,7 +321,7 @@ private TreatmentManager createTreatmentManager(String matchingKey, String bucke return new TreatmentManagerImpl( matchingKey, bucketingKey, evaluator, new KeyValidatorImpl(), new SplitValidatorImpl(), - new ImpressionListenerMock(), config.labelsEnabled(), eventsManagerStub, mock(AttributesManager.class), mock(AttributesMerger.class), mock(TelemetryStorageProducer.class)); + new ImpressionListenerMock(), config.labelsEnabled(), eventsManagerStub, mock(AttributesManager.class), mock(AttributesMerger.class), mock(TelemetryStorageProducer.class), mConfiguredFlagSets); } private TreatmentManagerImpl initializeTreatmentManager() { @@ -347,8 +348,8 @@ private TreatmentManagerImpl initializeTreatmentManager(Evaluator evaluator) { eventsManager, attributesManager, mock(AttributesMerger.class), - telemetryStorageProducer - ); + telemetryStorageProducer, + mConfiguredFlagSets); } private Map splitsMap(List splits) { diff --git a/src/test/java/io/split/android/client/localhost/shared/LocalhostSplitClientContainerImplTest.java b/src/test/java/io/split/android/client/localhost/shared/LocalhostSplitClientContainerImplTest.java index 7a557cfff..9d855de66 100644 --- a/src/test/java/io/split/android/client/localhost/shared/LocalhostSplitClientContainerImplTest.java +++ b/src/test/java/io/split/android/client/localhost/shared/LocalhostSplitClientContainerImplTest.java @@ -16,6 +16,8 @@ import org.mockito.MockitoAnnotations; import java.util.Collection; +import java.util.HashSet; +import java.util.Set; import io.split.android.client.SplitClient; import io.split.android.client.SplitClientConfig; @@ -50,12 +52,14 @@ public class LocalhostSplitClientContainerImplTest { private SplitClientConfig mConfig; @Mock private SplitTaskExecutor mTaskExecutor; + private Set mConfiguredSets; private LocalhostSplitClientContainerImpl mClientContainer; @Before public void setUp() { MockitoAnnotations.openMocks(this); when(mAttributesManagerFactory.getManager(any(), any())).thenReturn(mock(AttributesManager.class)); + mConfiguredSets = new HashSet<>(); mClientContainer = getClientContainer(); } @@ -95,6 +99,15 @@ public void gettingNewClientRegistersEventManager() { @NonNull private LocalhostSplitClientContainerImpl getClientContainer() { - return new LocalhostSplitClientContainerImpl(mFactory, mConfig, mSplitsStorage, mSplitParser, mAttributesManagerFactory, mAttributesMerger, mTelemetryStorageProducer, mEventsManagerCoordinator, mTaskExecutor); + return new LocalhostSplitClientContainerImpl(mFactory, + mConfig, + mSplitsStorage, + mSplitParser, + mAttributesManagerFactory, + mAttributesMerger, + mTelemetryStorageProducer, + mEventsManagerCoordinator, + mTaskExecutor, + mConfiguredSets); } } diff --git a/src/test/java/io/split/android/client/utils/SplitClientImplFactory.java b/src/test/java/io/split/android/client/utils/SplitClientImplFactory.java index ad91b25a0..3a8cb6388 100644 --- a/src/test/java/io/split/android/client/utils/SplitClientImplFactory.java +++ b/src/test/java/io/split/android/client/utils/SplitClientImplFactory.java @@ -2,6 +2,8 @@ import static org.mockito.Mockito.mock; +import java.util.Collections; + import io.split.android.client.EvaluatorImpl; import io.split.android.client.EventsTracker; import io.split.android.client.SplitClientConfig; @@ -13,7 +15,6 @@ import io.split.android.client.events.SplitEventsManager; import io.split.android.client.events.SplitInternalEvent; import io.split.android.client.impressions.ImpressionListener; -import io.split.android.client.service.synchronizer.SyncManager; import io.split.android.client.shared.SplitClientContainer; import io.split.android.client.storage.mysegments.MySegmentsStorageContainer; import io.split.android.client.storage.splits.SplitsStorage; @@ -27,10 +28,7 @@ import io.split.android.engine.experiments.SplitParser; import io.split.android.fake.SplitTaskExecutorStub; -/** - * Created by fernandomartin on 2/17/18. - */ - +@Deprecated public class SplitClientImplFactory { public static SplitClientImpl get(Key key, SplitsStorage splitsStorage) { @@ -40,8 +38,8 @@ public static SplitClientImpl get(Key key, SplitsStorage splitsStorage) { TelemetryStorage telemetryStorage = mock(TelemetryStorage.class); TreatmentManagerFactory treatmentManagerFactory = new TreatmentManagerFactoryImpl( new KeyValidatorImpl(), new SplitValidatorImpl(), new ImpressionListener.NoopImpressionListener(), - false, new AttributesMergerImpl(), telemetryStorage, new EvaluatorImpl(splitsStorage, splitParser) - ); + false, new AttributesMergerImpl(), telemetryStorage, new EvaluatorImpl(splitsStorage, splitParser), + Collections.emptySet()); AttributesManager attributesManager = mock(AttributesManager.class); SplitClientImpl c = new SplitClientImpl( From b2cf6882625fb110edad8d785242012f8e17289c Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 1 Sep 2023 15:42:57 -0300 Subject: [PATCH 29/89] Fixes --- .../android/client/AlwaysReturnControlSplitClient.java | 8 ++++---- .../io/split/android/client/SplitClientFactoryImpl.java | 2 +- .../io/split/android/client/TreatmentManagerTest.java | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/split/android/client/AlwaysReturnControlSplitClient.java b/src/main/java/io/split/android/client/AlwaysReturnControlSplitClient.java index 8ec6576f8..59a8ddca4 100644 --- a/src/main/java/io/split/android/client/AlwaysReturnControlSplitClient.java +++ b/src/main/java/io/split/android/client/AlwaysReturnControlSplitClient.java @@ -62,22 +62,22 @@ public SplitResult getTreatmentWithConfig(String featureFlagName, Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { - return Collections.singletonMap(flagSet, Treatments.CONTROL); //TODO + return Collections.emptyMap(); //TODO } @Override public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { - return Collections.singletonMap(flagSets.get(0), Treatments.CONTROL); //TODO + return Collections.emptyMap(); //TODO } @Override public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { - return Collections.singletonMap(flagSet, new SplitResult(Treatments.CONTROL)); //TODO + return Collections.emptyMap(); //TODO } @Override public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { - return Collections.singletonMap(flagSets.get(0), new SplitResult(Treatments.CONTROL)); //TODO + return Collections.emptyMap(); //TODO } @Override diff --git a/src/main/java/io/split/android/client/SplitClientFactoryImpl.java b/src/main/java/io/split/android/client/SplitClientFactoryImpl.java index eb03d36b2..22a4195d6 100644 --- a/src/main/java/io/split/android/client/SplitClientFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitClientFactoryImpl.java @@ -83,7 +83,7 @@ public SplitClientFactoryImpl(@NonNull SplitFactory splitFactory, new AttributesMergerImpl(), mStorageContainer.getTelemetryStorage(), new EvaluatorImpl(mStorageContainer.getSplitsStorage(), mSplitParser), - configuredFlagSets + checkNotNull(configuredFlagSets) ); } diff --git a/src/test/java/io/split/android/client/TreatmentManagerTest.java b/src/test/java/io/split/android/client/TreatmentManagerTest.java index 43959f5d6..d1e8794a1 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerTest.java @@ -56,11 +56,12 @@ public class TreatmentManagerTest { AttributesManager attributesManager = mock(AttributesManager.class); TelemetryStorageProducer telemetryStorageProducer = mock(TelemetryStorageProducer.class); private Set mConfiguredFlagSets; - TreatmentManagerImpl treatmentManager = initializeTreatmentManager(); + TreatmentManagerImpl treatmentManager; @Before public void loadSplitsFromFile() { mConfiguredFlagSets = new HashSet<>(); + treatmentManager = initializeTreatmentManager(); if (evaluator == null) { FileHelper fileHelper = new FileHelper(); MySegmentsStorageContainer mySegmentsStorageContainer = mock(MySegmentsStorageContainer.class); From 135e3e4e6ee8f05d388695c2a12f8bb772054952 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 1 Sep 2023 17:18:23 -0300 Subject: [PATCH 30/89] treatments by flag set --- .../client/SplitClientFactoryImpl.java | 7 ++- .../localhost/LocalhostSplitClient.java | 3 +- .../client/telemetry/model/Method.java | 1 + .../validators/FlagSetsValidatorImpl.java | 5 ++ .../validators/SplitFilterValidator.java | 2 + .../client/validators/TreatmentManager.java | 11 ++++ .../TreatmentManagerFactoryImpl.java | 18 ++++-- .../validators/TreatmentManagerImpl.java | 60 ++++++++++++++++++- .../client/utils/SplitClientImplFactory.java | 4 +- 9 files changed, 100 insertions(+), 11 deletions(-) diff --git a/src/main/java/io/split/android/client/SplitClientFactoryImpl.java b/src/main/java/io/split/android/client/SplitClientFactoryImpl.java index 22a4195d6..13137a0b8 100644 --- a/src/main/java/io/split/android/client/SplitClientFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitClientFactoryImpl.java @@ -21,6 +21,7 @@ import io.split.android.client.storage.common.SplitStorageContainer; import io.split.android.client.storage.attributes.AttributesStorage; import io.split.android.client.storage.attributes.PersistentAttributesStorage; +import io.split.android.client.storage.splits.SplitsStorage; import io.split.android.client.telemetry.TelemetrySynchronizer; import io.split.android.client.telemetry.storage.TelemetryInitProducer; import io.split.android.client.validators.AttributesValidatorImpl; @@ -75,6 +76,7 @@ public SplitClientFactoryImpl(@NonNull SplitFactory splitFactory, mStorageContainer.getPersistentAttributesStorage()); mSplitParser = new SplitParser(mStorageContainer.getMySegmentsStorageContainer()); mSplitValidator = new SplitValidatorImpl(); + SplitsStorage splitsStorage = mStorageContainer.getSplitsStorage(); mTreatmentManagerFactory = new TreatmentManagerFactoryImpl( keyValidator, mSplitValidator, @@ -82,8 +84,9 @@ public SplitClientFactoryImpl(@NonNull SplitFactory splitFactory, config.labelsEnabled(), new AttributesMergerImpl(), mStorageContainer.getTelemetryStorage(), - new EvaluatorImpl(mStorageContainer.getSplitsStorage(), mSplitParser), - checkNotNull(configuredFlagSets) + mSplitParser, + checkNotNull(configuredFlagSets), + splitsStorage ); } 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 d01393122..eeb8627d7 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java @@ -70,7 +70,8 @@ public LocalhostSplitClient(@NonNull LocalhostSplitFactory container, mTreatmentManager = new TreatmentManagerImpl(mKey.matchingKey(), mKey.bucketingKey(), mEvaluator, new KeyValidatorImpl(), new SplitValidatorImpl(), getImpressionsListener(splitClientConfig), - splitClientConfig.labelsEnabled(), eventsManager, attributesManager, attributesMerger, telemetryStorageProducer, configuredFlagSets); + splitClientConfig.labelsEnabled(), eventsManager, attributesManager, attributesMerger, + telemetryStorageProducer, configuredFlagSets, splitsStorage); } @Override diff --git a/src/main/java/io/split/android/client/telemetry/model/Method.java b/src/main/java/io/split/android/client/telemetry/model/Method.java index da5a4d6a8..3f4ae0626 100644 --- a/src/main/java/io/split/android/client/telemetry/model/Method.java +++ b/src/main/java/io/split/android/client/telemetry/model/Method.java @@ -5,6 +5,7 @@ public enum Method { TREATMENTS("getTreatments"), TREATMENT_WITH_CONFIG("getTreatmentWithConfig"), TREATMENTS_WITH_CONFIG("getTreatmentsWithConfig"), + TREATMENTS_BY_FLAG_SET("getTreatmentsByFlagSet"), TRACK("track"); private final String _method; diff --git a/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java b/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java index 72daeb055..2be41e83f 100644 --- a/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java +++ b/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java @@ -49,4 +49,9 @@ public List cleanup(List values) { return new ArrayList<>(cleanedUpSets); } + + @Override + public boolean isValid(String value) { + return value != null && value.trim().matches(FLAG_SET_REGEX); + } } diff --git a/src/main/java/io/split/android/client/validators/SplitFilterValidator.java b/src/main/java/io/split/android/client/validators/SplitFilterValidator.java index 95a521972..d605f17c0 100644 --- a/src/main/java/io/split/android/client/validators/SplitFilterValidator.java +++ b/src/main/java/io/split/android/client/validators/SplitFilterValidator.java @@ -5,4 +5,6 @@ public interface SplitFilterValidator { List cleanup(List values); + + boolean isValid(String value); } 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 0f0d87895..fbb790052 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManager.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManager.java @@ -1,5 +1,8 @@ package io.split.android.client.validators; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import java.util.List; import java.util.Map; import io.split.android.client.SplitResult; @@ -13,4 +16,12 @@ public interface TreatmentManager { Map getTreatments(List splits, Map attributes, boolean isClientDestroyed); Map getTreatmentsWithConfig(List splits, Map attributes, boolean isClientDestroyed); + + Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed); + + Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed); + + Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed); + + Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed); } diff --git a/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java b/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java index ce95d1441..956a94b32 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java @@ -7,12 +7,16 @@ import java.util.Set; import io.split.android.client.Evaluator; +import io.split.android.client.EvaluatorImpl; import io.split.android.client.api.Key; import io.split.android.client.attributes.AttributesManager; import io.split.android.client.attributes.AttributesMerger; +import io.split.android.client.events.ISplitEventsManager; import io.split.android.client.events.ListenableEventsManager; import io.split.android.client.impressions.ImpressionListener; +import io.split.android.client.storage.splits.SplitsStorage; import io.split.android.client.telemetry.storage.TelemetryStorageProducer; +import io.split.android.engine.experiments.SplitParser; public class TreatmentManagerFactoryImpl implements TreatmentManagerFactory { @@ -24,6 +28,7 @@ public class TreatmentManagerFactoryImpl implements TreatmentManagerFactory { private final TelemetryStorageProducer mTelemetryStorageProducer; private final Evaluator mEvaluator; private final Set mConfiguredFlagSets; + private final SplitsStorage mSplitsStorage; public TreatmentManagerFactoryImpl(@NonNull KeyValidator keyValidator, @NonNull SplitValidator splitValidator, @@ -31,16 +36,18 @@ public TreatmentManagerFactoryImpl(@NonNull KeyValidator keyValidator, boolean labelsEnabled, @NonNull AttributesMerger attributesMerger, @NonNull TelemetryStorageProducer telemetryStorageProducer, - @NonNull Evaluator evaluator, - @NonNull Set configuredFlagSets) { + @NonNull SplitParser splitParser, + @NonNull Set configuredFlagSets, + @NonNull SplitsStorage splitsStorage) { mKeyValidator = checkNotNull(keyValidator); mSplitValidator = checkNotNull(splitValidator); mCustomerImpressionListener = checkNotNull(customerImpressionListener); mLabelsEnabled = labelsEnabled; mAttributesMerger = checkNotNull(attributesMerger); mTelemetryStorageProducer = checkNotNull(telemetryStorageProducer); - mEvaluator = checkNotNull(evaluator); - mConfiguredFlagSets = configuredFlagSets; + mEvaluator = new EvaluatorImpl(splitsStorage, splitParser); + mConfiguredFlagSets = checkNotNull(configuredFlagSets); + mSplitsStorage = checkNotNull(splitsStorage); } @Override @@ -57,7 +64,8 @@ public TreatmentManager getTreatmentManager(Key key, ListenableEventsManager eve attributesManager, mAttributesMerger, mTelemetryStorageProducer, - mConfiguredFlagSets + mConfiguredFlagSets, + mSplitsStorage ); } } 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 ae52d9377..284d28f04 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java @@ -3,7 +3,9 @@ import static com.google.common.base.Preconditions.checkNotNull; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -19,6 +21,7 @@ import io.split.android.client.events.SplitEvent; import io.split.android.client.impressions.Impression; import io.split.android.client.impressions.ImpressionListener; +import io.split.android.client.storage.splits.SplitsStorage; import io.split.android.client.telemetry.model.Method; import io.split.android.client.telemetry.storage.TelemetryStorageProducer; import io.split.android.client.utils.logger.Logger; @@ -31,6 +34,10 @@ private static class ValidationTag { public static final String GET_TREATMENTS = "getTreatments"; public static final String GET_TREATMENT_WITH_CONFIG = "getTreatmentWithConfig"; public static final String GET_TREATMENTS_WITH_CONFIG = "getTreatmentsWithConfig"; + public static final String GET_TREATMENTS_BY_FLAG_SET = "getTreatmentsByFlagSet"; + public static final String GET_TREATMENTS_BY_FLAG_SETS = "getTreatmentsByFlagSets"; + public static final String GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET = "getTreatmentsWithConfigByFlagSet"; + public static final String GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS = "getTreatmentsWithConfigByFlagSets"; } private final String CLIENT_DESTROYED_MESSAGE = "Client has already been destroyed - no calls possible"; @@ -50,6 +57,8 @@ private static class ValidationTag { private final AttributesMerger mAttributesMerger; private final TelemetryStorageProducer mTelemetryStorageProducer; private final Set mConfiguredFlagSets; + private final SplitsStorage mSplitsStorage; + private final SplitFilterValidator mFlagSetsValidator; public TreatmentManagerImpl(String matchingKey, String bucketingKey, @@ -62,7 +71,8 @@ public TreatmentManagerImpl(String matchingKey, @NonNull AttributesManager attributesManager, @NonNull AttributesMerger attributesMerger, @NonNull TelemetryStorageProducer telemetryStorageProducer, - @NonNull Set configuredFlagSets) { + @NonNull Set configuredFlagSets, + @NonNull SplitsStorage splitsStorage) { mEvaluator = evaluator; mKeyValidator = keyValidator; mSplitValidator = splitValidator; @@ -76,6 +86,8 @@ public TreatmentManagerImpl(String matchingKey, mAttributesMerger = checkNotNull(attributesMerger); mTelemetryStorageProducer = checkNotNull(telemetryStorageProducer); mConfiguredFlagSets = checkNotNull(configuredFlagSets); + mSplitsStorage = checkNotNull(splitsStorage); + mFlagSetsValidator = new FlagSetsValidatorImpl(); } @Override @@ -166,6 +178,52 @@ public Map getTreatmentsWithConfig(List splits, Map return result; } + @Override + public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed) { + String validationTag = ValidationTag.GET_TREATMENTS_BY_FLAG_SET; + if (isClientDestroyed) { + mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); + return new HashMap<>(); + } + + if (mFlagSetsValidator.isValid(flagSet)) { + mValidationLogger.e("you passed " + flagSet + " which is not valid.", validationTag); + return new HashMap<>(); + } + + if (!mConfiguredFlagSets.isEmpty() && !mConfiguredFlagSets.contains(flagSet)) { + mValidationLogger.e("you passed " + flagSet + " which is not defined in the configuration.", validationTag); + return new HashMap<>(); + } + + long start = System.currentTimeMillis(); + + Map result = new HashMap<>(); + Set featureFlagNamesInSet = mSplitsStorage.getNamesByFlagSets(Collections.singletonList(flagSet)); + for (String featureFlagName : featureFlagNamesInSet) { + result.put(featureFlagName, getTreatmentWithConfigWithoutMetrics(featureFlagName, attributes, validationTag).treatment()); + } + + recordLatency(Method.TREATMENTS_BY_FLAG_SET, start); + + return result; + } + + @Override + public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed) { + return null; + } + + @Override + public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed) { + return null; + } + + @Override + public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed) { + return null; + } + private SplitResult getTreatmentWithConfigWithoutMetrics(String split, Map attributes, String validationTag) { ValidationErrorInfo errorInfo = mKeyValidator.validate(mMatchingKey, mBucketingKey); diff --git a/src/test/java/io/split/android/client/utils/SplitClientImplFactory.java b/src/test/java/io/split/android/client/utils/SplitClientImplFactory.java index 3a8cb6388..03fe34446 100644 --- a/src/test/java/io/split/android/client/utils/SplitClientImplFactory.java +++ b/src/test/java/io/split/android/client/utils/SplitClientImplFactory.java @@ -38,8 +38,8 @@ public static SplitClientImpl get(Key key, SplitsStorage splitsStorage) { TelemetryStorage telemetryStorage = mock(TelemetryStorage.class); TreatmentManagerFactory treatmentManagerFactory = new TreatmentManagerFactoryImpl( new KeyValidatorImpl(), new SplitValidatorImpl(), new ImpressionListener.NoopImpressionListener(), - false, new AttributesMergerImpl(), telemetryStorage, new EvaluatorImpl(splitsStorage, splitParser), - Collections.emptySet()); + false, new AttributesMergerImpl(), telemetryStorage, splitParser, + Collections.emptySet(), splitsStorage); AttributesManager attributesManager = mock(AttributesManager.class); SplitClientImpl c = new SplitClientImpl( From ff1d93ecef4901f5914114ab28fd3a724d8afa45 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 1 Sep 2023 17:47:55 -0300 Subject: [PATCH 31/89] Test --- .../validators/TreatmentManagerImpl.java | 2 +- .../client/TreatmentManagerTelemetryTest.java | 19 +++- .../android/client/TreatmentManagerTest.java | 10 +- .../TreatmentManagerWithFlagSetsTest.java | 102 ++++++++++++++++++ 4 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java 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 284d28f04..9890e60f5 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java @@ -186,7 +186,7 @@ public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Null return new HashMap<>(); } - if (mFlagSetsValidator.isValid(flagSet)) { + if (!mFlagSetsValidator.isValid(flagSet)) { mValidationLogger.e("you passed " + flagSet + " which is not valid.", validationTag); return new HashMap<>(); } diff --git a/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java b/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java index b93f59aa2..feb47d7ce 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java @@ -7,6 +7,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -23,6 +24,7 @@ import io.split.android.client.events.ListenableEventsManager; import io.split.android.client.events.SplitEvent; import io.split.android.client.impressions.ImpressionListener; +import io.split.android.client.storage.splits.SplitsStorage; import io.split.android.client.telemetry.model.Method; import io.split.android.client.telemetry.storage.TelemetryStorageProducer; import io.split.android.client.validators.KeyValidator; @@ -47,13 +49,16 @@ public class TreatmentManagerTelemetryTest { AttributesMerger attributesMerger; @Mock TelemetryStorageProducer telemetryStorageProducer; + @Mock + private SplitsStorage mSplitsStorage; private Set mConfiguredFlagSets = new HashSet<>(); private TreatmentManagerImpl treatmentManager; + private AutoCloseable mAutoCloseable; @Before public void setUp() { - MockitoAnnotations.openMocks(this); + mAutoCloseable = MockitoAnnotations.openMocks(this); treatmentManager = new TreatmentManagerImpl( "test_key", @@ -67,11 +72,21 @@ public void setUp() { attributesManager, attributesMerger, telemetryStorageProducer, - mConfiguredFlagSets); + mConfiguredFlagSets, + mSplitsStorage); when(evaluator.getTreatment(anyString(), anyString(), anyString(), anyMap())).thenReturn(new EvaluationResult("test", "label")); } + @After + public void tearDown() { + try { + mAutoCloseable.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + @Test public void getTreatmentRecordsLatencyInTelemetry() { diff --git a/src/test/java/io/split/android/client/TreatmentManagerTest.java b/src/test/java/io/split/android/client/TreatmentManagerTest.java index d1e8794a1..edb116fd3 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerTest.java @@ -7,7 +7,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; - import static io.split.android.client.TreatmentLabels.DEFINITION_NOT_FOUND; import com.google.common.base.Strings; @@ -57,10 +56,12 @@ public class TreatmentManagerTest { TelemetryStorageProducer telemetryStorageProducer = mock(TelemetryStorageProducer.class); private Set mConfiguredFlagSets; TreatmentManagerImpl treatmentManager; + private SplitsStorage mSplitsStorage; @Before public void loadSplitsFromFile() { mConfiguredFlagSets = new HashSet<>(); + mSplitsStorage = mock(SplitsStorage.class); treatmentManager = initializeTreatmentManager(); if (evaluator == null) { FileHelper fileHelper = new FileHelper(); @@ -322,7 +323,9 @@ private TreatmentManager createTreatmentManager(String matchingKey, String bucke return new TreatmentManagerImpl( matchingKey, bucketingKey, evaluator, new KeyValidatorImpl(), new SplitValidatorImpl(), - new ImpressionListenerMock(), config.labelsEnabled(), eventsManagerStub, mock(AttributesManager.class), mock(AttributesMerger.class), mock(TelemetryStorageProducer.class), mConfiguredFlagSets); + new ImpressionListenerMock(), config.labelsEnabled(), eventsManagerStub, + mock(AttributesManager.class), mock(AttributesMerger.class), + mock(TelemetryStorageProducer.class), mConfiguredFlagSets, mSplitsStorage); } private TreatmentManagerImpl initializeTreatmentManager() { @@ -350,7 +353,8 @@ private TreatmentManagerImpl initializeTreatmentManager(Evaluator evaluator) { attributesManager, mock(AttributesMerger.class), telemetryStorageProducer, - mConfiguredFlagSets); + mConfiguredFlagSets, + mSplitsStorage); } private Map splitsMap(List splits) { diff --git a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java new file mode 100644 index 000000000..84b572b75 --- /dev/null +++ b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java @@ -0,0 +1,102 @@ +package io.split.android.client; + +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import io.split.android.client.attributes.AttributesManager; +import io.split.android.client.attributes.AttributesMerger; +import io.split.android.client.events.ListenableEventsManager; +import io.split.android.client.events.SplitEvent; +import io.split.android.client.impressions.ImpressionListener; +import io.split.android.client.storage.splits.SplitsStorage; +import io.split.android.client.telemetry.storage.TelemetryStorageProducer; +import io.split.android.client.validators.KeyValidator; +import io.split.android.client.validators.SplitValidator; +import io.split.android.client.validators.TreatmentManagerImpl; + +public class TreatmentManagerWithFlagSetsTest { + + + @Mock + private Evaluator mEvaluator; + @Mock + private KeyValidator mKeyValidator; + @Mock + private SplitValidator mSplitValidator; + @Mock + private ImpressionListener mImpressionListener; + @Mock + private ListenableEventsManager mEventsManager; + @Mock + private AttributesManager mAttributesManager; + @Mock + private AttributesMerger mAttributesMerger; + @Mock + private TelemetryStorageProducer mTelemetryStorageProducer; + @Mock + private SplitsStorage mSplitsStorage; + + private Set mConfiguredFlagSets = new HashSet<>(); + private TreatmentManagerImpl mTreatmentManager; + private AutoCloseable mAutoCloseable; + + @Before + public void setUp() { + mAutoCloseable = MockitoAnnotations.openMocks(this); + + when(mEventsManager.eventAlreadyTriggered(SplitEvent.SDK_READY)).thenReturn(true); + when(mEventsManager.eventAlreadyTriggered(SplitEvent.SDK_READY_FROM_CACHE)).thenReturn(true); + + mTreatmentManager = new TreatmentManagerImpl( + "matching_key", + "bucketing_key", + mEvaluator, + mKeyValidator, + mSplitValidator, + mImpressionListener, + SplitClientConfig.builder().build().labelsEnabled(), + mEventsManager, + mAttributesManager, + mAttributesMerger, + mTelemetryStorageProducer, + mConfiguredFlagSets, + mSplitsStorage); + + when(mEvaluator.getTreatment(anyString(), anyString(), anyString(), anyMap())) + .thenReturn(new EvaluationResult("test", "label")); + } + + @After + public void tearDown() { + try { + mAutoCloseable.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void getTreatmentByFlagSetWithNoConfiguredSets() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); + + mTreatmentManager.getTreatmentsByFlagSet("set_1", null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mEvaluator).getTreatment(eq("matching_key"), eq("bucketing_key"), eq("test_split"), anyMap()); + } +} From 1232ed6978c55e99ef030afc0b80a202c6e18a43 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 1 Sep 2023 17:59:20 -0300 Subject: [PATCH 32/89] More tests --- .../TreatmentManagerWithFlagSetsTest.java | 48 +++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java index 84b572b75..662ff589d 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java @@ -1,9 +1,10 @@ package io.split.android.client; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -30,7 +31,6 @@ public class TreatmentManagerWithFlagSetsTest { - @Mock private Evaluator mEvaluator; @Mock @@ -50,7 +50,7 @@ public class TreatmentManagerWithFlagSetsTest { @Mock private SplitsStorage mSplitsStorage; - private Set mConfiguredFlagSets = new HashSet<>(); + private Set mConfiguredFlagSets; private TreatmentManagerImpl mTreatmentManager; private AutoCloseable mAutoCloseable; @@ -58,6 +58,7 @@ public class TreatmentManagerWithFlagSetsTest { public void setUp() { mAutoCloseable = MockitoAnnotations.openMocks(this); + mConfiguredFlagSets = new HashSet<>(); when(mEventsManager.eventAlreadyTriggered(SplitEvent.SDK_READY)).thenReturn(true); when(mEventsManager.eventAlreadyTriggered(SplitEvent.SDK_READY_FROM_CACHE)).thenReturn(true); @@ -89,6 +90,14 @@ public void tearDown() { } } + @Test + public void getTreatmentByFlagSetDestroyed() { + mTreatmentManager.getTreatmentsByFlagSet("set_1", null, true); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + @Test public void getTreatmentByFlagSetWithNoConfiguredSets() { when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) @@ -99,4 +108,37 @@ public void getTreatmentByFlagSetWithNoConfiguredSets() { verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); verify(mEvaluator).getTreatment(eq("matching_key"), eq("bucketing_key"), eq("test_split"), anyMap()); } + + @Test + public void getTreatmentByFlagSetWithNoConfiguredSetsInvalidSet() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); + + mTreatmentManager.getTreatmentsByFlagSet("SET!", null, false); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + } + + @Test + public void getTreatmentByFlagSetWithConfiguredSetsExistingSet() { + mConfiguredFlagSets.add("set_1"); + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); + + mTreatmentManager.getTreatmentsByFlagSet("set_1", null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mEvaluator).getTreatment(eq("matching_key"), eq("bucketing_key"), eq("test_split"), anyMap()); + } + + @Test + public void getTreatmentByFlagSetWithConfiguredSetsNonExistingSet() { + mConfiguredFlagSets.add("set_1"); + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); + + mTreatmentManager.getTreatmentsByFlagSet("set_2", null, false); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + } } From b327ce70753fb02de5702b9077b5499db1375fee Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 4 Sep 2023 10:47:41 -0300 Subject: [PATCH 33/89] WIP treatment multiple sets --- .../validators/TreatmentManagerImpl.java | 51 ++++++++++++++++++- .../TreatmentManagerWithFlagSetsTest.java | 12 +++-- 2 files changed, 57 insertions(+), 6 deletions(-) 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 9890e60f5..b80e007fd 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java @@ -5,8 +5,10 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -211,7 +213,54 @@ public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Null @Override public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed) { - return null; + String validationTag = ValidationTag.GET_TREATMENTS_BY_FLAG_SET; + if (isClientDestroyed) { + mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); + return new HashMap<>(); + } + + if (flagSets == null) { + return new HashMap<>(); + } + + List validSets = new ArrayList<>(); + for (String flagSet : flagSets) { + // Avoid duplicated validations + if (validSets.contains(flagSet)) { + continue; + } + + if (!mFlagSetsValidator.isValid(flagSet)) { + mValidationLogger.e("you passed " + flagSet + " which is not valid.", validationTag); + } else { + validSets.add(flagSet); + } + } + + if (validSets.isEmpty()) { + return new HashMap<>(); + } + + if (!mConfiguredFlagSets.isEmpty()) { + for (String flagSet : validSets) { + if (!mConfiguredFlagSets.contains(flagSet)) { + mValidationLogger.e("you passed " + flagSet + " which is not defined in the configuration.", validationTag); + validSets.remove(flagSet); + } + } + } + + long start = System.currentTimeMillis(); + + Map result = new HashMap<>(); + Set featureFlagNamesInSet = mSplitsStorage.getNamesByFlagSets(validSets); + for (String featureFlagName : featureFlagNamesInSet) { + result.put(featureFlagName, getTreatmentWithConfigWithoutMetrics(featureFlagName, attributes, validationTag).treatment()); + } + + recordLatency(Method.TREATMENTS_BY_FLAG_SET, start); + + return result; } @Override diff --git a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java index 662ff589d..75e8b6bc4 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java @@ -91,7 +91,7 @@ public void tearDown() { } @Test - public void getTreatmentByFlagSetDestroyed() { + public void getTreatmentByFlagSetDestroyedDoesNotQueryStorageOrUseEvaluator() { mTreatmentManager.getTreatmentsByFlagSet("set_1", null, true); verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); @@ -99,7 +99,7 @@ public void getTreatmentByFlagSetDestroyed() { } @Test - public void getTreatmentByFlagSetWithNoConfiguredSets() { + public void getTreatmentByFlagSetWithNoConfiguredSetsQueriesStorageAndUsesEvaluator() { when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); @@ -110,17 +110,18 @@ public void getTreatmentByFlagSetWithNoConfiguredSets() { } @Test - public void getTreatmentByFlagSetWithNoConfiguredSetsInvalidSet() { + public void getTreatmentByFlagSetWithNoConfiguredSetsInvalidSetDoesNotQueryStorageNorUseEvaluator() { when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); mTreatmentManager.getTreatmentsByFlagSet("SET!", null, false); verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); } @Test - public void getTreatmentByFlagSetWithConfiguredSetsExistingSet() { + public void getTreatmentByFlagSetWithConfiguredSetsExistingSetQueriesStorageAndUsesEvaluator() { mConfiguredFlagSets.add("set_1"); when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); @@ -132,7 +133,7 @@ public void getTreatmentByFlagSetWithConfiguredSetsExistingSet() { } @Test - public void getTreatmentByFlagSetWithConfiguredSetsNonExistingSet() { + public void getTreatmentByFlagSetWithConfiguredSetsNonExistingSetDoesNotQueryStorageNorUseEvaluator() { mConfiguredFlagSets.add("set_1"); when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); @@ -140,5 +141,6 @@ public void getTreatmentByFlagSetWithConfiguredSetsNonExistingSet() { mTreatmentManager.getTreatmentsByFlagSet("set_2", null, false); verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); } } From ec5952925f390e32fa4ed112d629018330dea9f9 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 4 Sep 2023 11:49:20 -0300 Subject: [PATCH 34/89] Tests for multiple set evaluation --- .../validators/TreatmentManagerImpl.java | 15 ++- .../TreatmentManagerWithFlagSetsTest.java | 116 +++++++++++++++++- 2 files changed, 121 insertions(+), 10 deletions(-) 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 b80e007fd..f17cf8759 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java @@ -8,7 +8,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -223,6 +222,8 @@ public Map getTreatmentsByFlagSets(@NonNull List flagSet return new HashMap<>(); } + long start = System.currentTimeMillis(); + List validSets = new ArrayList<>(); for (String flagSet : flagSets) { // Avoid duplicated validations @@ -241,19 +242,25 @@ public Map getTreatmentsByFlagSets(@NonNull List flagSet return new HashMap<>(); } + List setsToEvaluate = new ArrayList<>(); if (!mConfiguredFlagSets.isEmpty()) { for (String flagSet : validSets) { if (!mConfiguredFlagSets.contains(flagSet)) { mValidationLogger.e("you passed " + flagSet + " which is not defined in the configuration.", validationTag); - validSets.remove(flagSet); + } else { + setsToEvaluate.add(flagSet); } } + } else { + setsToEvaluate.addAll(validSets); } - long start = System.currentTimeMillis(); + if (setsToEvaluate.isEmpty()) { + return new HashMap<>(); + } Map result = new HashMap<>(); - Set featureFlagNamesInSet = mSplitsStorage.getNamesByFlagSets(validSets); + Set featureFlagNamesInSet = mSplitsStorage.getNamesByFlagSets(setsToEvaluate); for (String featureFlagName : featureFlagNamesInSet) { result.put(featureFlagName, getTreatmentWithConfigWithoutMetrics(featureFlagName, attributes, validationTag).treatment()); } diff --git a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java index 75e8b6bc4..34b46e183 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java @@ -1,5 +1,6 @@ package io.split.android.client; +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyString; @@ -14,8 +15,10 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.Map; import java.util.Set; import io.split.android.client.attributes.AttributesManager; @@ -77,8 +80,10 @@ public void setUp() { mConfiguredFlagSets, mSplitsStorage); - when(mEvaluator.getTreatment(anyString(), anyString(), anyString(), anyMap())) - .thenReturn(new EvaluationResult("test", "label")); + when(mEvaluator.getTreatment(anyString(), anyString(), eq("test_1"), anyMap())) + .thenReturn(new EvaluationResult("result_1", "label")); + when(mEvaluator.getTreatment(anyString(), anyString(), eq("test_2"), anyMap())) + .thenReturn(new EvaluationResult("result_2", "label")); } @After @@ -101,12 +106,12 @@ public void getTreatmentByFlagSetDestroyedDoesNotQueryStorageOrUseEvaluator() { @Test public void getTreatmentByFlagSetWithNoConfiguredSetsQueriesStorageAndUsesEvaluator() { when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) - .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); + .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); mTreatmentManager.getTreatmentsByFlagSet("set_1", null, false); verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); - verify(mEvaluator).getTreatment(eq("matching_key"), eq("bucketing_key"), eq("test_split"), anyMap()); + verify(mEvaluator).getTreatment(eq("matching_key"), eq("bucketing_key"), eq("test_1"), anyMap()); } @Test @@ -124,12 +129,12 @@ public void getTreatmentByFlagSetWithNoConfiguredSetsInvalidSetDoesNotQueryStora public void getTreatmentByFlagSetWithConfiguredSetsExistingSetQueriesStorageAndUsesEvaluator() { mConfiguredFlagSets.add("set_1"); when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) - .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); + .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); mTreatmentManager.getTreatmentsByFlagSet("set_1", null, false); verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); - verify(mEvaluator).getTreatment(eq("matching_key"), eq("bucketing_key"), eq("test_split"), anyMap()); + verify(mEvaluator).getTreatment(eq("matching_key"), eq("bucketing_key"), eq("test_1"), anyMap()); } @Test @@ -143,4 +148,103 @@ public void getTreatmentByFlagSetWithConfiguredSetsNonExistingSetDoesNotQuerySto verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); } + + @Test + public void getTreatmentByFlagSetReturnsCorrectFormat() { + Set mockNames = new HashSet<>(); + mockNames.add("test_1"); + mockNames.add("test_2"); + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(mockNames); + mConfiguredFlagSets.add("set_1"); + + Map result = mTreatmentManager.getTreatmentsByFlagSet("set_1", null, false); + + assertEquals(2, result.size()); + assertEquals("result_1", result.get("test_1")); + assertEquals("result_2", result.get("test_2")); + } + + /// + @Test + public void getTreatmentsByFlagSetsDestroyedDoesNotQueryStorageOrUseEvaluator() { + mTreatmentManager.getTreatmentsByFlagSets(Collections.singletonList("set_1"), null, true); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + + @Test + public void getTreatmentsByFlagSetsWithNoConfiguredSetsQueriesStorageAndUsesEvaluator() { + when(mSplitsStorage.getNamesByFlagSets(Arrays.asList("set_1", "set_2"))) + .thenReturn(new HashSet<>(Arrays.asList("test_1", "test_2"))); + + mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Arrays.asList("set_1", "set_2")); + verify(mEvaluator).getTreatment(anyString(), anyString(), eq("test_1"), anyMap()); + verify(mEvaluator).getTreatment(anyString(), anyString(), eq("test_2"), anyMap()); + } + + @Test + public void getTreatmentsByFlagSetsWithNoConfiguredSetsInvalidSetDoesNotQueryStorageForInvalidSet() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); + + mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_1", "SET!"), null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mEvaluator).getTreatment(any(), any(), eq("test_1"), anyMap()); + } + + @Test + public void getTreatmentsByFlagSetsWithConfiguredSetsExistingSetQueriesStorageForConfiguredSetOnlyAndUsesEvaluator() { + mConfiguredFlagSets.add("set_1"); + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); + + mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mEvaluator).getTreatment(anyString(), anyString(), eq("test_1"), anyMap()); + } + + @Test + public void getTreatmentsByFlagSetsWithConfiguredSetsNonExistingSetDoesNotQueryStorageNorUseEvaluator() { + mConfiguredFlagSets.add("set_1"); + + mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_2", "set_3"), null, false); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + + @Test + public void getTreatmentsByFlagSetsReturnsCorrectFormat() { + Set mockNames = new HashSet<>(); + mockNames.add("test_1"); + mockNames.add("test_2"); + when(mSplitsStorage.getNamesByFlagSets(Arrays.asList("set_1", "set_2"))).thenReturn(mockNames); + + Map result = mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + assertEquals(2, result.size()); + assertEquals("result_1", result.get("test_1")); + assertEquals("result_2", result.get("test_2")); + } + + @Test + public void getTreatmentsByFlagSetsWithDuplicatedSetDeduplicates() { + mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_1", "set_1"), null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + } + + @Test + public void getTreatmentsByFlagSetsWithNullSetListReturnsEmpty() { + Map result = mTreatmentManager.getTreatmentsByFlagSets(null, null, false); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + assertEquals(0, result.size()); + } } From d655538488287997d8b1e36ba8e4abb7c24d0661 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 4 Sep 2023 13:38:28 -0300 Subject: [PATCH 35/89] Improvement --- .../validators/TreatmentManagerImpl.java | 34 ++++++------------- 1 file changed, 11 insertions(+), 23 deletions(-) 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 f17cf8759..ed8821442 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -212,7 +213,7 @@ public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Null @Override public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed) { - String validationTag = ValidationTag.GET_TREATMENTS_BY_FLAG_SET; + String validationTag = ValidationTag.GET_TREATMENTS_BY_FLAG_SETS; if (isClientDestroyed) { mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); return new HashMap<>(); @@ -224,35 +225,22 @@ public Map getTreatmentsByFlagSets(@NonNull List flagSet long start = System.currentTimeMillis(); - List validSets = new ArrayList<>(); + List setsToEvaluate = new ArrayList<>(); for (String flagSet : flagSets) { - // Avoid duplicated validations - if (validSets.contains(flagSet)) { + if (setsToEvaluate.contains(flagSet)) { continue; } - if (!mFlagSetsValidator.isValid(flagSet)) { + boolean isValid = mFlagSetsValidator.isValid(flagSet); + boolean isConfigured = mConfiguredFlagSets.isEmpty() || mConfiguredFlagSets.contains(flagSet); + + if (!isValid) { mValidationLogger.e("you passed " + flagSet + " which is not valid.", validationTag); + } else if (!isConfigured) { + mValidationLogger.e("you passed " + flagSet + " which is not defined in the configuration.", validationTag); } else { - validSets.add(flagSet); - } - } - - if (validSets.isEmpty()) { - return new HashMap<>(); - } - - List setsToEvaluate = new ArrayList<>(); - if (!mConfiguredFlagSets.isEmpty()) { - for (String flagSet : validSets) { - if (!mConfiguredFlagSets.contains(flagSet)) { - mValidationLogger.e("you passed " + flagSet + " which is not defined in the configuration.", validationTag); - } else { - setsToEvaluate.add(flagSet); - } + setsToEvaluate.add(flagSet); } - } else { - setsToEvaluate.addAll(validSets); } if (setsToEvaluate.isEmpty()) { From 8ef9ed18f1dd123d089888c2c2b5306f82b21d95 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 4 Sep 2023 16:12:39 -0300 Subject: [PATCH 36/89] Tests --- .../client/telemetry/model/Method.java | 3 + .../validators/TreatmentManagerImpl.java | 141 ++++++------ .../TreatmentManagerWithFlagSetsTest.java | 204 +++++++++++++++++- 3 files changed, 273 insertions(+), 75 deletions(-) diff --git a/src/main/java/io/split/android/client/telemetry/model/Method.java b/src/main/java/io/split/android/client/telemetry/model/Method.java index 3f4ae0626..2303325d0 100644 --- a/src/main/java/io/split/android/client/telemetry/model/Method.java +++ b/src/main/java/io/split/android/client/telemetry/model/Method.java @@ -6,6 +6,9 @@ public enum Method { TREATMENT_WITH_CONFIG("getTreatmentWithConfig"), TREATMENTS_WITH_CONFIG("getTreatmentsWithConfig"), TREATMENTS_BY_FLAG_SET("getTreatmentsByFlagSet"), + TREATMENTS_BY_FLAG_SETS("getTreatmentsByFlagSets"), + TREATMENTS_WITH_CONFIG_BY_FLAG_SET("getTreatmentsWithConfigByFlagSet"), + TREATMENTS_WITH_CONFIG_BY_FLAG_SETS("getTreatmentsWithConfigByFlagSets"), TRACK("track"); private final String _method; 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 ed8821442..930802f6b 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java @@ -37,7 +37,7 @@ private static class ValidationTag { public static final String GET_TREATMENT_WITH_CONFIG = "getTreatmentWithConfig"; public static final String GET_TREATMENTS_WITH_CONFIG = "getTreatmentsWithConfig"; public static final String GET_TREATMENTS_BY_FLAG_SET = "getTreatmentsByFlagSet"; - public static final String GET_TREATMENTS_BY_FLAG_SETS = "getTreatmentsByFlagSets"; + public static final String GET_TREATMENTS_BY_FLAG_SETS = "getFeatureFlagNamesToEvaluate"; public static final String GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET = "getTreatmentsWithConfigByFlagSet"; public static final String GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS = "getTreatmentsWithConfigByFlagSets"; } @@ -182,90 +182,42 @@ public Map getTreatmentsWithConfig(List splits, Map @Override public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed) { - String validationTag = ValidationTag.GET_TREATMENTS_BY_FLAG_SET; - if (isClientDestroyed) { - mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); - return new HashMap<>(); - } - - if (!mFlagSetsValidator.isValid(flagSet)) { - mValidationLogger.e("you passed " + flagSet + " which is not valid.", validationTag); - return new HashMap<>(); - } - - if (!mConfiguredFlagSets.isEmpty() && !mConfiguredFlagSets.contains(flagSet)) { - mValidationLogger.e("you passed " + flagSet + " which is not defined in the configuration.", validationTag); - return new HashMap<>(); - } - long start = System.currentTimeMillis(); - - Map result = new HashMap<>(); - Set featureFlagNamesInSet = mSplitsStorage.getNamesByFlagSets(Collections.singletonList(flagSet)); - for (String featureFlagName : featureFlagNamesInSet) { - result.put(featureFlagName, getTreatmentWithConfigWithoutMetrics(featureFlagName, attributes, validationTag).treatment()); + try { + return evaluateFeaturesGeneric(getFeatureFlagNamesToEvaluate(ValidationTag.GET_TREATMENTS_BY_FLAG_SET, Collections.singletonList(flagSet), isClientDestroyed), attributes, ValidationTag.GET_TREATMENTS_BY_FLAG_SET, SplitResult::treatment); + } finally { + recordLatency(Method.TREATMENTS_BY_FLAG_SET, start); } - - recordLatency(Method.TREATMENTS_BY_FLAG_SET, start); - - return result; } @Override public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed) { - String validationTag = ValidationTag.GET_TREATMENTS_BY_FLAG_SETS; - if (isClientDestroyed) { - mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); - return new HashMap<>(); - } - - if (flagSets == null) { - return new HashMap<>(); - } - long start = System.currentTimeMillis(); - - List setsToEvaluate = new ArrayList<>(); - for (String flagSet : flagSets) { - if (setsToEvaluate.contains(flagSet)) { - continue; - } - - boolean isValid = mFlagSetsValidator.isValid(flagSet); - boolean isConfigured = mConfiguredFlagSets.isEmpty() || mConfiguredFlagSets.contains(flagSet); - - if (!isValid) { - mValidationLogger.e("you passed " + flagSet + " which is not valid.", validationTag); - } else if (!isConfigured) { - mValidationLogger.e("you passed " + flagSet + " which is not defined in the configuration.", validationTag); - } else { - setsToEvaluate.add(flagSet); - } - } - - if (setsToEvaluate.isEmpty()) { - return new HashMap<>(); - } - - Map result = new HashMap<>(); - Set featureFlagNamesInSet = mSplitsStorage.getNamesByFlagSets(setsToEvaluate); - for (String featureFlagName : featureFlagNamesInSet) { - result.put(featureFlagName, getTreatmentWithConfigWithoutMetrics(featureFlagName, attributes, validationTag).treatment()); + try { + return evaluateFeaturesGeneric(getFeatureFlagNamesToEvaluate(ValidationTag.GET_TREATMENTS_BY_FLAG_SETS, flagSets, isClientDestroyed), attributes, ValidationTag.GET_TREATMENTS_BY_FLAG_SETS, SplitResult::treatment); + } finally { + recordLatency(Method.TREATMENTS_BY_FLAG_SETS, start); } - - recordLatency(Method.TREATMENTS_BY_FLAG_SET, start); - - return result; } @Override public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed) { - return null; + long start = System.currentTimeMillis(); + try { + return evaluateFeaturesGeneric(getFeatureFlagNamesToEvaluate(ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, Collections.singletonList(flagSet), isClientDestroyed), attributes, ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, splitResult -> splitResult); + } finally { + recordLatency(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SET, start); + } } @Override public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed) { - return null; + long start = System.currentTimeMillis(); + try { + return evaluateFeaturesGeneric(getFeatureFlagNamesToEvaluate(ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, flagSets, isClientDestroyed), attributes, ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, splitResult -> splitResult); + } finally { + recordLatency(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, start); + } } private SplitResult getTreatmentWithConfigWithoutMetrics(String split, Map attributes, String validationTag) { @@ -387,4 +339,55 @@ private EvaluationResult evaluateIfReady(String splitName, private void recordLatency(Method treatment, long startTime) { mTelemetryStorageProducer.recordLatency(treatment, System.currentTimeMillis() - startTime); } + + @NonNull + private Set getFeatureFlagNamesToEvaluate(String validationTag, + @NonNull List flagSets, + boolean isClientDestroyed) { + if (isClientDestroyed) { + mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); + return new HashSet<>(); + } + + if (flagSets == null) { + return new HashSet<>(); + } + + List setsToEvaluate = new ArrayList<>(); + for (String flagSet : flagSets) { + if (setsToEvaluate.contains(flagSet)) { + continue; + } + + boolean isValid = mFlagSetsValidator.isValid(flagSet); + boolean isConfigured = mConfiguredFlagSets.isEmpty() || mConfiguredFlagSets.contains(flagSet); + + if (!isValid) { + mValidationLogger.e("you passed " + flagSet + " which is not valid.", validationTag); + } else if (!isConfigured) { + mValidationLogger.e("you passed " + flagSet + " which is not defined in the configuration.", validationTag); + } else { + setsToEvaluate.add(flagSet); + } + } + + if (setsToEvaluate.isEmpty()) { + return new HashSet<>(); + } + + return mSplitsStorage.getNamesByFlagSets(setsToEvaluate); + } + + private Map evaluateFeaturesGeneric(Set names, @Nullable Map attributes, String validationTag, ResultTransformer transformer) { + Map result = new HashMap<>(); + for (String featureFlagName : names) { + SplitResult splitResult = getTreatmentWithConfigWithoutMetrics(featureFlagName, attributes, validationTag); + result.put(featureFlagName, transformer.transform(splitResult)); + } + return result; + } + + private interface ResultTransformer { + T transform(SplitResult splitResult); + } } diff --git a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java index 34b46e183..7e4cca25a 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java @@ -2,6 +2,7 @@ import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -27,6 +28,7 @@ import io.split.android.client.events.SplitEvent; import io.split.android.client.impressions.ImpressionListener; import io.split.android.client.storage.splits.SplitsStorage; +import io.split.android.client.telemetry.model.Method; import io.split.android.client.telemetry.storage.TelemetryStorageProducer; import io.split.android.client.validators.KeyValidator; import io.split.android.client.validators.SplitValidator; @@ -96,7 +98,7 @@ public void tearDown() { } @Test - public void getTreatmentByFlagSetDestroyedDoesNotQueryStorageOrUseEvaluator() { + public void getTreatmentsByFlagSetDestroyedDoesNotQueryStorageOrUseEvaluator() { mTreatmentManager.getTreatmentsByFlagSet("set_1", null, true); verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); @@ -104,7 +106,7 @@ public void getTreatmentByFlagSetDestroyedDoesNotQueryStorageOrUseEvaluator() { } @Test - public void getTreatmentByFlagSetWithNoConfiguredSetsQueriesStorageAndUsesEvaluator() { + public void getTreatmentsByFlagSetWithNoConfiguredSetsQueriesStorageAndUsesEvaluator() { when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); @@ -115,7 +117,7 @@ public void getTreatmentByFlagSetWithNoConfiguredSetsQueriesStorageAndUsesEvalua } @Test - public void getTreatmentByFlagSetWithNoConfiguredSetsInvalidSetDoesNotQueryStorageNorUseEvaluator() { + public void getTreatmentsByFlagSetWithNoConfiguredSetsInvalidSetDoesNotQueryStorageNorUseEvaluator() { when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); @@ -126,7 +128,7 @@ public void getTreatmentByFlagSetWithNoConfiguredSetsInvalidSetDoesNotQueryStora } @Test - public void getTreatmentByFlagSetWithConfiguredSetsExistingSetQueriesStorageAndUsesEvaluator() { + public void getTreatmentsByFlagSetWithConfiguredSetsExistingSetQueriesStorageAndUsesEvaluator() { mConfiguredFlagSets.add("set_1"); when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); @@ -138,7 +140,7 @@ public void getTreatmentByFlagSetWithConfiguredSetsExistingSetQueriesStorageAndU } @Test - public void getTreatmentByFlagSetWithConfiguredSetsNonExistingSetDoesNotQueryStorageNorUseEvaluator() { + public void getTreatmentsByFlagSetWithConfiguredSetsNonExistingSetDoesNotQueryStorageNorUseEvaluator() { mConfiguredFlagSets.add("set_1"); when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); @@ -150,7 +152,7 @@ public void getTreatmentByFlagSetWithConfiguredSetsNonExistingSetDoesNotQuerySto } @Test - public void getTreatmentByFlagSetReturnsCorrectFormat() { + public void getTreatmentsByFlagSetReturnsCorrectFormat() { Set mockNames = new HashSet<>(); mockNames.add("test_1"); mockNames.add("test_2"); @@ -164,6 +166,15 @@ public void getTreatmentByFlagSetReturnsCorrectFormat() { assertEquals("result_2", result.get("test_2")); } + @Test + public void getTreatmentsByFlagSetRecordsTelemetry() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(Collections.singleton("test_1")); + + mTreatmentManager.getTreatmentsByFlagSet("set_1", null, false); + + verify(mTelemetryStorageProducer).recordLatency(eq(Method.TREATMENTS_BY_FLAG_SET), anyLong()); + } + /// @Test public void getTreatmentsByFlagSetsDestroyedDoesNotQueryStorageOrUseEvaluator() { @@ -247,4 +258,185 @@ public void getTreatmentsByFlagSetsWithNullSetListReturnsEmpty() { verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); assertEquals(0, result.size()); } + + @Test + public void getTreatmentsByFlagSetsRecordsTelemetry() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(Collections.singleton("test_1")); + + mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + verify(mTelemetryStorageProducer).recordLatency(eq(Method.TREATMENTS_BY_FLAG_SETS), anyLong()); + } + + /// + @Test + public void getTreatmentsWithConfigByFlagSetDestroyedDoesNotQueryStorageOrUseEvaluator() { + mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_1", null, true); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetWithNoConfiguredSetsQueriesStorageAndUsesEvaluator() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); + + mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_1", null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mEvaluator).getTreatment(eq("matching_key"), eq("bucketing_key"), eq("test_1"), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetWithNoConfiguredSetsInvalidSetDoesNotQueryStorageNorUseEvaluator() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); + + mTreatmentManager.getTreatmentsWithConfigByFlagSet("SET!", null, false); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetWithConfiguredSetsExistingSetQueriesStorageAndUsesEvaluator() { + mConfiguredFlagSets.add("set_1"); + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); + + mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_1", null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mEvaluator).getTreatment(eq("matching_key"), eq("bucketing_key"), eq("test_1"), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetWithConfiguredSetsNonExistingSetDoesNotQueryStorageNorUseEvaluator() { + mConfiguredFlagSets.add("set_1"); + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); + + mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_2", null, false); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetReturnsCorrectFormat() { + Set mockNames = new HashSet<>(); + mockNames.add("test_1"); + mockNames.add("test_2"); + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(mockNames); + mConfiguredFlagSets.add("set_1"); + + Map result = mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_1", null, false); + + assertEquals(2, result.size()); + assertEquals("result_1", result.get("test_1").treatment()); + assertEquals("result_2", result.get("test_2").treatment()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetRecordsTelemetry() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(Collections.singleton("test_1")); + + mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_1", null, false); + + verify(mTelemetryStorageProducer).recordLatency(eq(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SET), anyLong()); + } + + /// + @Test + public void getTreatmentsWithConfigByFlagSetsDestroyedDoesNotQueryStorageOrUseEvaluator() { + mTreatmentManager.getTreatmentsWithConfigByFlagSets(Collections.singletonList("set_1"), null, true); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsWithNoConfiguredSetsQueriesStorageAndUsesEvaluator() { + when(mSplitsStorage.getNamesByFlagSets(Arrays.asList("set_1", "set_2"))) + .thenReturn(new HashSet<>(Arrays.asList("test_1", "test_2"))); + + mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Arrays.asList("set_1", "set_2")); + verify(mEvaluator).getTreatment(anyString(), anyString(), eq("test_1"), anyMap()); + verify(mEvaluator).getTreatment(anyString(), anyString(), eq("test_2"), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsWithNoConfiguredSetsInvalidSetDoesNotQueryStorageForInvalidSet() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); + + mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "SET!"), null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mEvaluator).getTreatment(any(), any(), eq("test_1"), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsWithConfiguredSetsExistingSetQueriesStorageForConfiguredSetOnlyAndUsesEvaluator() { + mConfiguredFlagSets.add("set_1"); + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); + + mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mEvaluator).getTreatment(anyString(), anyString(), eq("test_1"), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsWithConfiguredSetsNonExistingSetDoesNotQueryStorageNorUseEvaluator() { + mConfiguredFlagSets.add("set_1"); + + mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_2", "set_3"), null, false); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsReturnsCorrectFormat() { + Set mockNames = new HashSet<>(); + mockNames.add("test_1"); + mockNames.add("test_2"); + when(mSplitsStorage.getNamesByFlagSets(Arrays.asList("set_1", "set_2"))).thenReturn(mockNames); + + Map result = mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + assertEquals(2, result.size()); + assertEquals("result_1", result.get("test_1").treatment()); + assertEquals("result_2", result.get("test_2").treatment()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsWithDuplicatedSetDeduplicates() { + mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_1"), null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsWithNullSetListReturnsEmpty() { + Map result = mTreatmentManager.getTreatmentsWithConfigByFlagSets(null, null, false); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + assertEquals(0, result.size()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsRecordsTelemetry() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(Collections.singleton("test_1")); + + mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + verify(mTelemetryStorageProducer).recordLatency(eq(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS), anyLong()); + } } From 4eaaa30c2c72d12b0f30637d423b8950078bf2eb Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 4 Sep 2023 16:26:41 -0300 Subject: [PATCH 37/89] Add warning log --- .../java/io/split/android/client/SplitClientFactoryImpl.java | 2 +- .../split/android/client/service/splits/SplitsSyncHelper.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/split/android/client/SplitClientFactoryImpl.java b/src/main/java/io/split/android/client/SplitClientFactoryImpl.java index 13137a0b8..69e7dc998 100644 --- a/src/main/java/io/split/android/client/SplitClientFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitClientFactoryImpl.java @@ -85,7 +85,7 @@ public SplitClientFactoryImpl(@NonNull SplitFactory splitFactory, new AttributesMergerImpl(), mStorageContainer.getTelemetryStorage(), mSplitParser, - checkNotNull(configuredFlagSets), + configuredFlagSets, splitsStorage ); } diff --git a/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java b/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java index c3c8e8b9a..8f1b8ced5 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java +++ b/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java @@ -84,6 +84,7 @@ private SplitTaskExecutionInfo sync(long till, boolean clearBeforeUpdate, boolea mTelemetryRuntimeProducer.recordSyncError(OperationType.SPLITS, e.getHttpStatus()); if (HttpStatus.fromCode(e.getHttpStatus()) == HttpStatus.URI_TOO_LONG) { + Logger.e("SDK initialization: the amount of flag sets provided is big, causing URI length error"); return SplitTaskExecutionInfo.error(SplitTaskType.SPLITS_SYNC, Collections.singletonMap(SplitTaskExecutionInfo.DO_NOT_RETRY, true)); } From 56bd11a7ed42929fdd10c106a4e3725f45c4c889 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 4 Sep 2023 16:32:37 -0300 Subject: [PATCH 38/89] Readability changes --- .../validators/TreatmentManagerImpl.java | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) 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 930802f6b..4d9bdf780 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java @@ -37,7 +37,7 @@ private static class ValidationTag { public static final String GET_TREATMENT_WITH_CONFIG = "getTreatmentWithConfig"; public static final String GET_TREATMENTS_WITH_CONFIG = "getTreatmentsWithConfig"; public static final String GET_TREATMENTS_BY_FLAG_SET = "getTreatmentsByFlagSet"; - public static final String GET_TREATMENTS_BY_FLAG_SETS = "getFeatureFlagNamesToEvaluate"; + public static final String GET_TREATMENTS_BY_FLAG_SETS = "getTreatmentsByFlagSets"; public static final String GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET = "getTreatmentsWithConfigByFlagSet"; public static final String GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS = "getTreatmentsWithConfigByFlagSets"; } @@ -184,7 +184,8 @@ public Map getTreatmentsWithConfig(List splits, Map public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed) { long start = System.currentTimeMillis(); try { - return evaluateFeaturesGeneric(getFeatureFlagNamesToEvaluate(ValidationTag.GET_TREATMENTS_BY_FLAG_SET, Collections.singletonList(flagSet), isClientDestroyed), attributes, ValidationTag.GET_TREATMENTS_BY_FLAG_SET, SplitResult::treatment); + Set names = getNamesFromSet(ValidationTag.GET_TREATMENTS_BY_FLAG_SET, Collections.singletonList(flagSet), isClientDestroyed); + return evaluateFeatures(names, attributes, ValidationTag.GET_TREATMENTS_BY_FLAG_SET, SplitResult::treatment); } finally { recordLatency(Method.TREATMENTS_BY_FLAG_SET, start); } @@ -194,7 +195,8 @@ public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Null public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed) { long start = System.currentTimeMillis(); try { - return evaluateFeaturesGeneric(getFeatureFlagNamesToEvaluate(ValidationTag.GET_TREATMENTS_BY_FLAG_SETS, flagSets, isClientDestroyed), attributes, ValidationTag.GET_TREATMENTS_BY_FLAG_SETS, SplitResult::treatment); + Set names = getNamesFromSet(ValidationTag.GET_TREATMENTS_BY_FLAG_SETS, flagSets, isClientDestroyed); + return evaluateFeatures(names, attributes, ValidationTag.GET_TREATMENTS_BY_FLAG_SETS, SplitResult::treatment); } finally { recordLatency(Method.TREATMENTS_BY_FLAG_SETS, start); } @@ -204,7 +206,8 @@ public Map getTreatmentsByFlagSets(@NonNull List flagSet public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed) { long start = System.currentTimeMillis(); try { - return evaluateFeaturesGeneric(getFeatureFlagNamesToEvaluate(ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, Collections.singletonList(flagSet), isClientDestroyed), attributes, ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, splitResult -> splitResult); + Set names = getNamesFromSet(ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, Collections.singletonList(flagSet), isClientDestroyed); + return evaluateFeatures(names, attributes, ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, splitResult -> splitResult); } finally { recordLatency(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SET, start); } @@ -214,7 +217,8 @@ public Map getTreatmentsWithConfigByFlagSet(@NonNull String public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed) { long start = System.currentTimeMillis(); try { - return evaluateFeaturesGeneric(getFeatureFlagNamesToEvaluate(ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, flagSets, isClientDestroyed), attributes, ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, splitResult -> splitResult); + Set names = getNamesFromSet(ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, flagSets, isClientDestroyed); + return evaluateFeatures(names, attributes, ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, splitResult -> splitResult); } finally { recordLatency(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, start); } @@ -341,9 +345,9 @@ private void recordLatency(Method treatment, long startTime) { } @NonNull - private Set getFeatureFlagNamesToEvaluate(String validationTag, - @NonNull List flagSets, - boolean isClientDestroyed) { + private Set getNamesFromSet(String validationTag, + @NonNull List flagSets, + boolean isClientDestroyed) { if (isClientDestroyed) { mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); return new HashSet<>(); @@ -378,7 +382,7 @@ private Set getFeatureFlagNamesToEvaluate(String validationTag, return mSplitsStorage.getNamesByFlagSets(setsToEvaluate); } - private Map evaluateFeaturesGeneric(Set names, @Nullable Map attributes, String validationTag, ResultTransformer transformer) { + private Map evaluateFeatures(Set names, @Nullable Map attributes, String validationTag, ResultTransformer transformer) { Map result = new HashMap<>(); for (String featureFlagName : names) { SplitResult splitResult = getTreatmentWithConfigWithoutMetrics(featureFlagName, attributes, validationTag); From ca511ac762836155e247cc005eb8ff70195ec47d Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 4 Sep 2023 17:49:43 -0300 Subject: [PATCH 39/89] Fix latency logic --- .../validators/TreatmentManagerImpl.java | 52 ++++++++++++++----- .../TreatmentManagerWithFlagSetsTest.java | 16 +++--- 2 files changed, 46 insertions(+), 22 deletions(-) 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 4d9bdf780..8aa242ebd 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java @@ -182,10 +182,16 @@ public Map getTreatmentsWithConfig(List splits, Map @Override public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed) { + String validationTag = ValidationTag.GET_TREATMENTS_BY_FLAG_SET; + Set names = getNamesFromSet(validationTag, Collections.singletonList(flagSet)); + if (isClientDestroyed) { + mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); + return controlTreatmentsForSplits(new ArrayList<>(names), validationTag); + } + long start = System.currentTimeMillis(); try { - Set names = getNamesFromSet(ValidationTag.GET_TREATMENTS_BY_FLAG_SET, Collections.singletonList(flagSet), isClientDestroyed); - return evaluateFeatures(names, attributes, ValidationTag.GET_TREATMENTS_BY_FLAG_SET, SplitResult::treatment); + return evaluateFeatures(names, attributes, validationTag, SplitResult::treatment); } finally { recordLatency(Method.TREATMENTS_BY_FLAG_SET, start); } @@ -193,10 +199,16 @@ public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Null @Override public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed) { + String validationTag = ValidationTag.GET_TREATMENTS_BY_FLAG_SETS; + Set names = getNamesFromSet(validationTag, flagSets); + if (isClientDestroyed) { + mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); + return controlTreatmentsForSplits(new ArrayList<>(names), validationTag); + } + long start = System.currentTimeMillis(); try { - Set names = getNamesFromSet(ValidationTag.GET_TREATMENTS_BY_FLAG_SETS, flagSets, isClientDestroyed); - return evaluateFeatures(names, attributes, ValidationTag.GET_TREATMENTS_BY_FLAG_SETS, SplitResult::treatment); + return evaluateFeatures(names, attributes, validationTag, SplitResult::treatment); } finally { recordLatency(Method.TREATMENTS_BY_FLAG_SETS, start); } @@ -204,10 +216,16 @@ public Map getTreatmentsByFlagSets(@NonNull List flagSet @Override public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed) { + String validationTag = ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET; + Set names = getNamesFromSet(validationTag, Collections.singletonList(flagSet)); + if (isClientDestroyed) { + mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); + return controlTreatmentsForSplitsWithConfig(new ArrayList<>(names), validationTag); + } + long start = System.currentTimeMillis(); try { - Set names = getNamesFromSet(ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, Collections.singletonList(flagSet), isClientDestroyed); - return evaluateFeatures(names, attributes, ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, splitResult -> splitResult); + return evaluateFeatures(names, attributes, validationTag, ResultTransformer::identity); } finally { recordLatency(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SET, start); } @@ -215,10 +233,16 @@ public Map getTreatmentsWithConfigByFlagSet(@NonNull String @Override public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed) { + String validationTag = ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS; + Set names = getNamesFromSet(validationTag, flagSets); + if (isClientDestroyed) { + mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); + return controlTreatmentsForSplitsWithConfig(new ArrayList<>(names), validationTag); + } + long start = System.currentTimeMillis(); try { - Set names = getNamesFromSet(ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, flagSets, isClientDestroyed); - return evaluateFeatures(names, attributes, ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, splitResult -> splitResult); + return evaluateFeatures(names, attributes, validationTag, ResultTransformer::identity); } finally { recordLatency(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, start); } @@ -346,12 +370,7 @@ private void recordLatency(Method treatment, long startTime) { @NonNull private Set getNamesFromSet(String validationTag, - @NonNull List flagSets, - boolean isClientDestroyed) { - if (isClientDestroyed) { - mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); - return new HashSet<>(); - } + @NonNull List flagSets) { if (flagSets == null) { return new HashSet<>(); @@ -392,6 +411,11 @@ private Map evaluateFeatures(Set names, @Nullable Map { + T transform(SplitResult splitResult); + + static SplitResult identity(SplitResult splitResult) { + return splitResult; + } } } diff --git a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java index 7e4cca25a..90539b17b 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java @@ -98,10 +98,10 @@ public void tearDown() { } @Test - public void getTreatmentsByFlagSetDestroyedDoesNotQueryStorageOrUseEvaluator() { + public void getTreatmentsByFlagSetDestroyedDoesNotUseEvaluator() { mTreatmentManager.getTreatmentsByFlagSet("set_1", null, true); - verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mSplitsStorage).getNamesByFlagSets(any()); verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); } @@ -177,10 +177,10 @@ public void getTreatmentsByFlagSetRecordsTelemetry() { /// @Test - public void getTreatmentsByFlagSetsDestroyedDoesNotQueryStorageOrUseEvaluator() { + public void getTreatmentsByFlagSetsDestroyedDoesNotUseEvaluator() { mTreatmentManager.getTreatmentsByFlagSets(Collections.singletonList("set_1"), null, true); - verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mSplitsStorage).getNamesByFlagSets(any()); verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); } @@ -270,10 +270,10 @@ public void getTreatmentsByFlagSetsRecordsTelemetry() { /// @Test - public void getTreatmentsWithConfigByFlagSetDestroyedDoesNotQueryStorageOrUseEvaluator() { + public void getTreatmentsWithConfigByFlagSetDestroyedDoesNotUseEvaluator() { mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_1", null, true); - verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mSplitsStorage).getNamesByFlagSets(any()); verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); } @@ -349,10 +349,10 @@ public void getTreatmentsWithConfigByFlagSetRecordsTelemetry() { /// @Test - public void getTreatmentsWithConfigByFlagSetsDestroyedDoesNotQueryStorageOrUseEvaluator() { + public void getTreatmentsWithConfigByFlagSetsDestroyedDoesNotUseEvaluator() { mTreatmentManager.getTreatmentsWithConfigByFlagSets(Collections.singletonList("set_1"), null, true); - verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mSplitsStorage).getNamesByFlagSets(any()); verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); } From 1ba11e35fce0509292e0261d27274bb979b49559 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 4 Sep 2023 19:11:30 -0300 Subject: [PATCH 40/89] Sets evaluation in clients --- .../AlwaysReturnControlSplitClient.java | 10 +- .../split/android/client/SplitClientImpl.java | 24 +--- .../localhost/LocalhostSplitClient.java | 63 ++++++++-- .../validators/TreatmentManagerImpl.java | 112 +++++++++++------- 4 files changed, 137 insertions(+), 72 deletions(-) diff --git a/src/main/java/io/split/android/client/AlwaysReturnControlSplitClient.java b/src/main/java/io/split/android/client/AlwaysReturnControlSplitClient.java index 59a8ddca4..2275622fb 100644 --- a/src/main/java/io/split/android/client/AlwaysReturnControlSplitClient.java +++ b/src/main/java/io/split/android/client/AlwaysReturnControlSplitClient.java @@ -17,7 +17,7 @@ * Useful for testing * */ -public class AlwaysReturnControlSplitClient implements io.split.android.client.SplitClient { +public class AlwaysReturnControlSplitClient implements SplitClient { @Override public String getTreatment(String featureFlagName) { @@ -62,22 +62,22 @@ public SplitResult getTreatmentWithConfig(String featureFlagName, Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { - return Collections.emptyMap(); //TODO + return Collections.emptyMap(); } @Override public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { - return Collections.emptyMap(); //TODO + return Collections.emptyMap(); } @Override public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { - return Collections.emptyMap(); //TODO + return Collections.emptyMap(); } @Override public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { - return Collections.emptyMap(); //TODO + return Collections.emptyMap(); } @Override diff --git a/src/main/java/io/split/android/client/SplitClientImpl.java b/src/main/java/io/split/android/client/SplitClientImpl.java index 08bbabd76..9ab93ad7c 100644 --- a/src/main/java/io/split/android/client/SplitClientImpl.java +++ b/src/main/java/io/split/android/client/SplitClientImpl.java @@ -190,38 +190,22 @@ public Map getTreatmentsWithConfig(List featureFlag @Override public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { - try { - return Collections.emptyMap(); //TODO - } catch (Exception exception) { - return null; - } + return mTreatmentManager.getTreatmentsByFlagSet(flagSet, attributes, mIsClientDestroyed); } @Override public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { - try { - return Collections.emptyMap(); //TODO - } catch (Exception exception) { - return null; - } + return mTreatmentManager.getTreatmentsByFlagSets(flagSets, attributes, mIsClientDestroyed); } @Override public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { - try { - return Collections.emptyMap(); //TODO - } catch (Exception exception) { - return null; - } + return mTreatmentManager.getTreatmentsWithConfigByFlagSet(flagSet, attributes, mIsClientDestroyed); } @Override public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { - try { - return Collections.emptyMap(); //TODO - } catch (Exception exception) { - return null; - } + return mTreatmentManager.getTreatmentsWithConfigByFlagSets(flagSets, attributes, 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 eeb8627d7..e82e33f16 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java @@ -6,6 +6,7 @@ import androidx.annotation.Nullable; import java.lang.ref.WeakReference; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -46,9 +47,9 @@ public final class LocalhostSplitClient implements SplitClient { private final WeakReference mClientContainer; private final Key mKey; private final SplitEventsManager mEventsManager; - private final Evaluator mEvaluator; private final TreatmentManager mTreatmentManager; private boolean mIsClientDestroyed = false; + private final SplitsStorage mSplitsStorage; public LocalhostSplitClient(@NonNull LocalhostSplitFactory container, @NonNull SplitClientContainer clientContainer, @@ -66,9 +67,9 @@ public LocalhostSplitClient(@NonNull LocalhostSplitFactory container, mClientContainer = new WeakReference<>(checkNotNull(clientContainer)); mKey = checkNotNull(key); mEventsManager = checkNotNull(eventsManager); - mEvaluator = new EvaluatorImpl(splitsStorage, splitParser); + mSplitsStorage = splitsStorage; mTreatmentManager = new TreatmentManagerImpl(mKey.matchingKey(), mKey.bucketingKey(), - mEvaluator, new KeyValidatorImpl(), + new EvaluatorImpl(splitsStorage, splitParser), new KeyValidatorImpl(), new SplitValidatorImpl(), getImpressionsListener(splitClientConfig), splitClientConfig.labelsEnabled(), eventsManager, attributesManager, attributesMerger, telemetryStorageProducer, configuredFlagSets, splitsStorage); @@ -143,22 +144,70 @@ public Map getTreatmentsWithConfig(List featureFlag @Override public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { - return null; + try { + return mTreatmentManager.getTreatmentsByFlagSet(flagSet, attributes, mIsClientDestroyed); + } catch (Exception exception) { + Logger.e(exception); + + Map result = new HashMap<>(); + Set namesByFlagSets = mSplitsStorage.getNamesByFlagSets(Collections.singletonList(flagSet)); + for (String featureFlagName : namesByFlagSets) { + result.put(featureFlagName, Treatments.CONTROL); + } + + return result; + } } @Override public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { - return null; + try { + return mTreatmentManager.getTreatmentsByFlagSets(flagSets, attributes, mIsClientDestroyed); + } catch (Exception exception) { + Logger.e(exception); + + Map result = new HashMap<>(); + Set namesByFlagSets = mSplitsStorage.getNamesByFlagSets(flagSets); + for (String featureFlagName : namesByFlagSets) { + result.put(featureFlagName, Treatments.CONTROL); + } + + return result; + } } @Override public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes) { - return null; + try { + return mTreatmentManager.getTreatmentsWithConfigByFlagSet(flagSet, attributes, mIsClientDestroyed); + } catch (Exception exception) { + Logger.e(exception); + + Map result = new HashMap<>(); + Set namesByFlagSets = mSplitsStorage.getNamesByFlagSets(Collections.singletonList(flagSet)); + for (String featureFlagName : namesByFlagSets) { + result.put(featureFlagName, new SplitResult(Treatments.CONTROL)); + } + + return result; + } } @Override public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes) { - return null; + try { + return mTreatmentManager.getTreatmentsWithConfigByFlagSets(flagSets, attributes, mIsClientDestroyed); + } catch (Exception exception) { + Logger.e(exception); + + Map result = new HashMap<>(); + Set namesByFlagSets = mSplitsStorage.getNamesByFlagSets(flagSets); + for (String featureFlagName : namesByFlagSets) { + result.put(featureFlagName, new SplitResult(Treatments.CONTROL)); + } + + return result; + } } @Override 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 8aa242ebd..c45f553d5 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java @@ -183,68 +183,100 @@ public Map getTreatmentsWithConfig(List splits, Map @Override public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed) { String validationTag = ValidationTag.GET_TREATMENTS_BY_FLAG_SET; - Set names = getNamesFromSet(validationTag, Collections.singletonList(flagSet)); - if (isClientDestroyed) { - mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); - return controlTreatmentsForSplits(new ArrayList<>(names), validationTag); - } - - long start = System.currentTimeMillis(); + Set names = new HashSet<>(); try { - return evaluateFeatures(names, attributes, validationTag, SplitResult::treatment); - } finally { - recordLatency(Method.TREATMENTS_BY_FLAG_SET, start); + names = getNamesFromSet(validationTag, Collections.singletonList(flagSet)); + if (isClientDestroyed) { + mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); + return controlTreatmentsForSplits(new ArrayList<>(names), validationTag); + } + + long start = System.currentTimeMillis(); + try { + return evaluateFeatures(names, attributes, validationTag, SplitResult::treatment); + } finally { + recordLatency(Method.TREATMENTS_BY_FLAG_SET, start); + } + } catch (Exception exception) { + Logger.e("Client getTreatmentsByFlagSet exception", exception); + mTelemetryStorageProducer.recordException(Method.TREATMENTS_BY_FLAG_SET); + + return controlTreatmentsForSplits(new ArrayList<>(names), validationTag); } } @Override public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed) { String validationTag = ValidationTag.GET_TREATMENTS_BY_FLAG_SETS; - Set names = getNamesFromSet(validationTag, flagSets); - if (isClientDestroyed) { - mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); - return controlTreatmentsForSplits(new ArrayList<>(names), validationTag); - } - - long start = System.currentTimeMillis(); + Set names = new HashSet<>(); try { - return evaluateFeatures(names, attributes, validationTag, SplitResult::treatment); - } finally { - recordLatency(Method.TREATMENTS_BY_FLAG_SETS, start); + names = getNamesFromSet(validationTag, flagSets); + if (isClientDestroyed) { + mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); + return controlTreatmentsForSplits(new ArrayList<>(names), validationTag); + } + + long start = System.currentTimeMillis(); + try { + return evaluateFeatures(names, attributes, validationTag, SplitResult::treatment); + } finally { + recordLatency(Method.TREATMENTS_BY_FLAG_SETS, start); + } + } catch (Exception exception) { + Logger.e("Client getTreatmentsByFlagSets exception", exception); + mTelemetryStorageProducer.recordException(Method.TREATMENTS_BY_FLAG_SETS); + + return controlTreatmentsForSplits(new ArrayList<>(names), validationTag); } } @Override public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed) { String validationTag = ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET; - Set names = getNamesFromSet(validationTag, Collections.singletonList(flagSet)); - if (isClientDestroyed) { - mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); - return controlTreatmentsForSplitsWithConfig(new ArrayList<>(names), validationTag); - } - - long start = System.currentTimeMillis(); + Set names = new HashSet<>(); try { - return evaluateFeatures(names, attributes, validationTag, ResultTransformer::identity); - } finally { - recordLatency(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SET, start); + names = getNamesFromSet(validationTag, Collections.singletonList(flagSet)); + if (isClientDestroyed) { + mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); + return controlTreatmentsForSplitsWithConfig(new ArrayList<>(names), validationTag); + } + + long start = System.currentTimeMillis(); + try { + return evaluateFeatures(names, attributes, validationTag, ResultTransformer::identity); + } finally { + recordLatency(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SET, start); + } + } catch (Exception exception) { + Logger.e("Client getTreatmentsWithConfigByFlagSet exception", exception); + mTelemetryStorageProducer.recordException(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SET); + + return controlTreatmentsForSplitsWithConfig(new ArrayList<>(names), validationTag); } } @Override public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed) { String validationTag = ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS; - Set names = getNamesFromSet(validationTag, flagSets); - if (isClientDestroyed) { - mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); - return controlTreatmentsForSplitsWithConfig(new ArrayList<>(names), validationTag); - } - - long start = System.currentTimeMillis(); + Set names = new HashSet<>(); try { - return evaluateFeatures(names, attributes, validationTag, ResultTransformer::identity); - } finally { - recordLatency(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, start); + names = getNamesFromSet(validationTag, flagSets); + if (isClientDestroyed) { + mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); + return controlTreatmentsForSplitsWithConfig(new ArrayList<>(names), validationTag); + } + + long start = System.currentTimeMillis(); + try { + return evaluateFeatures(names, attributes, validationTag, ResultTransformer::identity); + } finally { + recordLatency(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, start); + } + } catch (Exception exception) { + Logger.e("Client getTreatmentsWithConfigByFlagSets exception", exception); + mTelemetryStorageProducer.recordException(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS); + + return controlTreatmentsForSplitsWithConfig(new ArrayList<>(names), validationTag); } } From 894d98ac3b3f8b6f78b9fda1fd39500c52430cb4 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 5 Sep 2023 17:12:07 -0300 Subject: [PATCH 41/89] New treatment methods telemetry --- .../telemetry/model/MethodExceptions.java | 44 +++++++++++++++++++ .../telemetry/model/MethodLatencies.java | 44 +++++++++++++++++++ .../storage/InMemoryTelemetryStorage.java | 20 ++++++++- .../client/SplitClientImplFlagSetsTest.java | 43 ++++++++++++++++++ .../TelemetryStatsBodySerializerTest.java | 10 ++++- .../storage/InMemoryTelemetryStorageTest.java | 36 +++++++++++++++ 6 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 src/test/java/io/split/android/client/SplitClientImplFlagSetsTest.java diff --git a/src/main/java/io/split/android/client/telemetry/model/MethodExceptions.java b/src/main/java/io/split/android/client/telemetry/model/MethodExceptions.java index 2a580e2c5..ff87dedab 100644 --- a/src/main/java/io/split/android/client/telemetry/model/MethodExceptions.java +++ b/src/main/java/io/split/android/client/telemetry/model/MethodExceptions.java @@ -16,6 +16,18 @@ public class MethodExceptions { @SerializedName("tcs") private long treatmentsWithConfig; + @SerializedName("tf") + private long treatmentsByFlagSet; + + @SerializedName("tfs") + private long treatmentsByFlagSets; + + @SerializedName("tfc") + private long treatmentsWithConfigByFlagSet; + + @SerializedName("tfcs") + private long treatmentsWithConfigByFlagSets; + @SerializedName("tr") private long track; @@ -51,6 +63,38 @@ public void setTreatmentsWithConfig(long treatmentsWithConfig) { this.treatmentsWithConfig = treatmentsWithConfig; } + public void setTreatmentsByFlagSet(long treatmentsByFlagSet) { + this.treatmentsByFlagSet = treatmentsByFlagSet; + } + + public long getTreatmentsByFlagSet() { + return treatmentsByFlagSet; + } + + public void setTreatmentsByFlagSets(long treatmentsByFlagSets) { + this.treatmentsByFlagSets = treatmentsByFlagSets; + } + + public long getTreatmentsByFlagSets() { + return treatmentsByFlagSets; + } + + public void setTreatmentsWithConfigByFlagSet(long treatmentsWithConfigByFlagSet) { + this.treatmentsWithConfigByFlagSet = treatmentsWithConfigByFlagSet; + } + + public long getTreatmentsWithConfigByFlagSet() { + return treatmentsWithConfigByFlagSet; + } + + public void setTreatmentsWithConfigByFlagSets(long treatmentsWithConfigByFlagSets) { + this.treatmentsWithConfigByFlagSets = treatmentsWithConfigByFlagSets; + } + + public long getTreatmentsWithConfigByFlagSets() { + return treatmentsWithConfigByFlagSets; + } + public long getTrack() { return track; } diff --git a/src/main/java/io/split/android/client/telemetry/model/MethodLatencies.java b/src/main/java/io/split/android/client/telemetry/model/MethodLatencies.java index df631118e..3c9d95391 100644 --- a/src/main/java/io/split/android/client/telemetry/model/MethodLatencies.java +++ b/src/main/java/io/split/android/client/telemetry/model/MethodLatencies.java @@ -18,6 +18,18 @@ public class MethodLatencies { @SerializedName("tcs") private List treatmentsWithConfig; + @SerializedName("tf") + private List treatmentsByFlagSet; + + @SerializedName("tfs") + private List treatmentsByFlagSets; + + @SerializedName("tfc") + private List treatmentsWithConfigByFlagSet; + + @SerializedName("tfcs") + private List treatmentsWithConfigByFlagSets; + @SerializedName("tr") private List track; @@ -53,6 +65,38 @@ public void setTreatmentsWithConfig(List treatmentsWithConfig) { this.treatmentsWithConfig = treatmentsWithConfig; } + public void setTreatmentsByFlagSet(List treatmentsByFlagSet) { + this.treatmentsByFlagSet = treatmentsByFlagSet; + } + + public List getTreatmentsByFlagSet() { + return treatmentsByFlagSet; + } + + public void setTreatmentsByFlagSets(List treatmentsByFlagSets) { + this.treatmentsByFlagSets = treatmentsByFlagSets; + } + + public List getTreatmentsByFlagSets() { + return treatmentsByFlagSets; + } + + public void setTreatmentsWithConfigByFlagSet(List treatmentsWithConfigByFlagSet) { + this.treatmentsWithConfigByFlagSet = treatmentsWithConfigByFlagSet; + } + + public List getTreatmentsWithConfigByFlagSet() { + return treatmentsWithConfigByFlagSet; + } + + public void setTreatmentsWithConfigByFlagSets(List treatmentsWithConfigByFlagSets) { + this.treatmentsWithConfigByFlagSets = treatmentsWithConfigByFlagSets; + } + + public List getTreatmentsWithConfigByFlagSets() { + return treatmentsWithConfigByFlagSets; + } + public List getTrack() { return track; } diff --git a/src/main/java/io/split/android/client/telemetry/storage/InMemoryTelemetryStorage.java b/src/main/java/io/split/android/client/telemetry/storage/InMemoryTelemetryStorage.java index 5f22b414c..8a237a741 100644 --- a/src/main/java/io/split/android/client/telemetry/storage/InMemoryTelemetryStorage.java +++ b/src/main/java/io/split/android/client/telemetry/storage/InMemoryTelemetryStorage.java @@ -73,6 +73,10 @@ public MethodExceptions popExceptions() { methodExceptions.setTreatmentWithConfig(methodExceptionsCounter.get(Method.TREATMENT_WITH_CONFIG).getAndSet(0L)); methodExceptions.setTreatmentsWithConfig(methodExceptionsCounter.get(Method.TREATMENTS_WITH_CONFIG).getAndSet(0L)); methodExceptions.setTrack(methodExceptionsCounter.get(Method.TRACK).getAndSet(0L)); + methodExceptions.setTreatmentsByFlagSet(methodExceptionsCounter.get(Method.TREATMENTS_BY_FLAG_SET).getAndSet(0L)); + methodExceptions.setTreatmentsByFlagSets(methodExceptionsCounter.get(Method.TREATMENTS_BY_FLAG_SETS).getAndSet(0L)); + methodExceptions.setTreatmentsWithConfigByFlagSet(methodExceptionsCounter.get(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SET).getAndSet(0L)); + methodExceptions.setTreatmentsWithConfigByFlagSets(methodExceptionsCounter.get(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS).getAndSet(0L)); return methodExceptions; } @@ -86,6 +90,10 @@ public MethodLatencies popLatencies() { latencies.setTreatments(popLatencies(Method.TREATMENTS)); latencies.setTreatmentWithConfig(popLatencies(Method.TREATMENT_WITH_CONFIG)); latencies.setTreatmentsWithConfig(popLatencies(Method.TREATMENTS_WITH_CONFIG)); + latencies.setTreatmentsByFlagSet(popLatencies(Method.TREATMENTS_BY_FLAG_SET)); + latencies.setTreatmentsByFlagSets(popLatencies(Method.TREATMENTS_BY_FLAG_SETS)); + latencies.setTreatmentsWithConfigByFlagSet(popLatencies(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SET)); + latencies.setTreatmentsWithConfigByFlagSets(popLatencies(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS)); latencies.setTrack(popLatencies(Method.TRACK)); return latencies; @@ -354,7 +362,7 @@ public void recordUpdatesFromSSE(UpdatesFromSSEEnum sseUpdate) { private void initializeProperties() { initializeMethodExceptionsCounter(); - initializeHttpLatenciesCounter(); + initializeMethodLatenciesCounter(); initializeFactoryCounters(); initializeImpressionsData(); initializeEventsData(); @@ -365,12 +373,16 @@ private void initializeProperties() { initializeUpdatesFromSSE(); } - private void initializeHttpLatenciesCounter() { + private void initializeMethodLatenciesCounter() { methodLatencies.put(Method.TREATMENT, new BinarySearchLatencyTracker()); methodLatencies.put(Method.TREATMENTS, new BinarySearchLatencyTracker()); methodLatencies.put(Method.TREATMENT_WITH_CONFIG, new BinarySearchLatencyTracker()); methodLatencies.put(Method.TREATMENTS_WITH_CONFIG, new BinarySearchLatencyTracker()); methodLatencies.put(Method.TRACK, new BinarySearchLatencyTracker()); + methodLatencies.put(Method.TREATMENTS_BY_FLAG_SET, new BinarySearchLatencyTracker()); + methodLatencies.put(Method.TREATMENTS_BY_FLAG_SETS, new BinarySearchLatencyTracker()); + methodLatencies.put(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SET, new BinarySearchLatencyTracker()); + methodLatencies.put(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, new BinarySearchLatencyTracker()); } private void initializeMethodExceptionsCounter() { @@ -379,6 +391,10 @@ private void initializeMethodExceptionsCounter() { methodExceptionsCounter.put(Method.TREATMENT_WITH_CONFIG, new AtomicLong()); methodExceptionsCounter.put(Method.TREATMENTS_WITH_CONFIG, new AtomicLong()); methodExceptionsCounter.put(Method.TRACK, new AtomicLong()); + methodExceptionsCounter.put(Method.TREATMENTS_BY_FLAG_SET, new AtomicLong()); + methodExceptionsCounter.put(Method.TREATMENTS_BY_FLAG_SETS, new AtomicLong()); + methodExceptionsCounter.put(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SET, new AtomicLong()); + methodExceptionsCounter.put(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, new AtomicLong()); } private void initializeFactoryCounters() { diff --git a/src/test/java/io/split/android/client/SplitClientImplFlagSetsTest.java b/src/test/java/io/split/android/client/SplitClientImplFlagSetsTest.java new file mode 100644 index 000000000..a3f621553 --- /dev/null +++ b/src/test/java/io/split/android/client/SplitClientImplFlagSetsTest.java @@ -0,0 +1,43 @@ +package io.split.android.client; + +import static org.mockito.Mockito.verify; + +import org.junit.Test; + +import java.util.Collections; +import java.util.Map; + +public class SplitClientImplFlagSetsTest extends SplitClientImplBaseTest { + + @Test + public void getTreatmentsByFlagSetDelegatesToTreatmentManager() { + Map attributes = Collections.singletonMap("key", "value"); + splitClient.getTreatmentsByFlagSet("set", attributes); + + verify(treatmentManager).getTreatmentsByFlagSet("set", attributes, false); + } + + @Test + public void getTreatmentsByFlagSetsDelegatesToTreatmentManager() { + Map attributes = Collections.singletonMap("key", "value"); + splitClient.getTreatmentsByFlagSets(Collections.singletonList("set"), attributes); + + verify(treatmentManager).getTreatmentsByFlagSets(Collections.singletonList("set"), attributes, false); + } + + @Test + public void getTreatmentsWithConfigByFlagSetDelegatesToTreatmentManager() { + Map attributes = Collections.singletonMap("key", "value"); + splitClient.getTreatmentsWithConfigByFlagSet("set", attributes); + + verify(treatmentManager).getTreatmentsWithConfigByFlagSet("set", attributes, false); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsDelegatesToTreatmentManager() { + Map attributes = Collections.singletonMap("key", "value"); + splitClient.getTreatmentsWithConfigByFlagSets(Collections.singletonList("set"), attributes); + + verify(treatmentManager).getTreatmentsWithConfigByFlagSets(Collections.singletonList("set"), attributes, false); + } +} diff --git a/src/test/java/io/split/android/client/telemetry/TelemetryStatsBodySerializerTest.java b/src/test/java/io/split/android/client/telemetry/TelemetryStatsBodySerializerTest.java index a0dc2bdbe..b363cc2e4 100644 --- a/src/test/java/io/split/android/client/telemetry/TelemetryStatsBodySerializerTest.java +++ b/src/test/java/io/split/android/client/telemetry/TelemetryStatsBodySerializerTest.java @@ -30,7 +30,7 @@ public void setUp() { public void jsonIsBuiltAsExpected() { String serializedStats = telemetryStatsBodySerializer.serialize(getMockStats()); - assertEquals("{\"lS\":{\"sp\":1000,\"ms\":2000,\"im\":3000,\"ic\":4000,\"ev\":5000,\"te\":6000,\"to\":7000},\"mL\":{\"t\":[0,0,2,0],\"ts\":[0,0,3,0],\"tc\":[0,0,5,0],\"tcs\":[0,0,4,0],\"tr\":[0,0,1,0]},\"mE\":{\"t\":2,\"ts\":3,\"tc\":5,\"tcs\":4,\"tr\":1},\"hE\":{},\"hL\":{\"sp\":[0,0,3,0],\"ms\":[0,0,5,0],\"im\":[0,0,1,0],\"ic\":[0,0,4,0],\"ev\":[0,0,2,0],\"te\":[1,0,0,0],\"to\":[0,0,6,0]},\"tR\":4,\"aR\":5,\"iQ\":2,\"iDe\":5,\"iDr\":4,\"spC\":456,\"seC\":4,\"skC\":1,\"sL\":2000,\"eQ\":4,\"eD\":2,\"sE\":[{\"e\":0,\"t\":5000},{\"e\":20,\"d\":4,\"t\":2000}],\"t\":[\"tag1\",\"tag2\"],\"ufs\":{\"sp\":4,\"ms\":8}}", serializedStats); + assertEquals("{\"lS\":{\"sp\":1000,\"ms\":2000,\"im\":3000,\"ic\":4000,\"ev\":5000,\"te\":6000,\"to\":7000},\"mL\":{\"t\":[0,0,2,0],\"ts\":[0,0,3,0],\"tc\":[0,0,5,0],\"tcs\":[0,0,4,0],\"tf\":[1,0,0,0],\"tfs\":[2,0,0,0],\"tfc\":[3,0,0,0],\"tfcs\":[4,0,0,0],\"tr\":[0,0,1,0]},\"mE\":{\"t\":2,\"ts\":3,\"tc\":5,\"tcs\":4,\"tf\":10,\"tfs\":20,\"tfc\":30,\"tfcs\":40,\"tr\":1},\"hE\":{},\"hL\":{\"sp\":[0,0,3,0],\"ms\":[0,0,5,0],\"im\":[0,0,1,0],\"ic\":[0,0,4,0],\"ev\":[0,0,2,0],\"te\":[1,0,0,0],\"to\":[0,0,6,0]},\"tR\":4,\"aR\":5,\"iQ\":2,\"iDe\":5,\"iDr\":4,\"spC\":456,\"seC\":4,\"skC\":1,\"sL\":2000,\"eQ\":4,\"eD\":2,\"sE\":[{\"e\":0,\"t\":5000},{\"e\":20,\"d\":4,\"t\":2000}],\"t\":[\"tag1\",\"tag2\"],\"ufs\":{\"sp\":4,\"ms\":8}}", serializedStats); } private Stats getMockStats() { @@ -54,6 +54,10 @@ private Stats getMockStats() { methodLatencies.setTreatments(Arrays.asList(0L, 0L, 3L, 0L)); methodLatencies.setTreatmentsWithConfig(Arrays.asList(0L, 0L, 4L, 0L)); methodLatencies.setTreatmentWithConfig(Arrays.asList(0L, 0L, 5L, 0L)); + methodLatencies.setTreatmentsByFlagSet(Arrays.asList(1L, 0L, 0L, 0L)); + methodLatencies.setTreatmentsByFlagSets(Arrays.asList(2L, 0L, 0L, 0L)); + methodLatencies.setTreatmentsWithConfigByFlagSet(Arrays.asList(3L, 0L, 0L, 0L)); + methodLatencies.setTreatmentsWithConfigByFlagSets(Arrays.asList(4L, 0L, 0L, 0L)); MethodExceptions methodExceptions = new MethodExceptions(); methodExceptions.setTrack(1); @@ -61,6 +65,10 @@ private Stats getMockStats() { methodExceptions.setTreatments(3); methodExceptions.setTreatmentsWithConfig(4); methodExceptions.setTreatmentWithConfig(5); + methodExceptions.setTreatmentsByFlagSet(10); + methodExceptions.setTreatmentsByFlagSets(20); + methodExceptions.setTreatmentsWithConfigByFlagSet(30); + methodExceptions.setTreatmentsWithConfigByFlagSets(40); stats.setHttpLatencies(httpLatencies); stats.setAuthRejections(5); diff --git a/src/test/java/io/split/android/client/telemetry/storage/InMemoryTelemetryStorageTest.java b/src/test/java/io/split/android/client/telemetry/storage/InMemoryTelemetryStorageTest.java index 97c6adbe5..4227802af 100644 --- a/src/test/java/io/split/android/client/telemetry/storage/InMemoryTelemetryStorageTest.java +++ b/src/test/java/io/split/android/client/telemetry/storage/InMemoryTelemetryStorageTest.java @@ -50,6 +50,10 @@ public void popExceptionsReturnsCorrectlyBuiltMethodExceptions() { telemetryStorage.recordException(Method.TREATMENTS); telemetryStorage.recordException(Method.TREATMENTS_WITH_CONFIG); telemetryStorage.recordException(Method.TREATMENT_WITH_CONFIG); + telemetryStorage.recordException(Method.TREATMENTS_BY_FLAG_SET); + telemetryStorage.recordException(Method.TREATMENTS_BY_FLAG_SETS); + telemetryStorage.recordException(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SET); + telemetryStorage.recordException(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS); MethodExceptions methodExceptions = telemetryStorage.popExceptions(); @@ -58,6 +62,10 @@ public void popExceptionsReturnsCorrectlyBuiltMethodExceptions() { assertEquals(1, methodExceptions.getTreatments()); assertEquals(1, methodExceptions.getTreatmentsWithConfig()); assertEquals(1, methodExceptions.getTreatmentWithConfig()); + assertEquals(1, methodExceptions.getTreatmentsByFlagSet()); + assertEquals(1, methodExceptions.getTreatmentsByFlagSets()); + assertEquals(1, methodExceptions.getTreatmentsWithConfigByFlagSet()); + assertEquals(1, methodExceptions.getTreatmentsWithConfigByFlagSets()); } @Test @@ -68,6 +76,10 @@ public void popExceptionsEmptiesCounters() { telemetryStorage.recordException(Method.TREATMENTS); telemetryStorage.recordException(Method.TREATMENTS_WITH_CONFIG); telemetryStorage.recordException(Method.TREATMENT_WITH_CONFIG); + telemetryStorage.recordException(Method.TREATMENTS_BY_FLAG_SET); + telemetryStorage.recordException(Method.TREATMENTS_BY_FLAG_SETS); + telemetryStorage.recordException(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SET); + telemetryStorage.recordException(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS); telemetryStorage.popExceptions(); @@ -78,6 +90,10 @@ public void popExceptionsEmptiesCounters() { assertEquals(0, secondPop.getTreatments()); assertEquals(0, secondPop.getTreatmentsWithConfig()); assertEquals(0, secondPop.getTreatmentWithConfig()); + assertEquals(0, secondPop.getTreatmentsByFlagSet()); + assertEquals(0, secondPop.getTreatmentsByFlagSets()); + assertEquals(0, secondPop.getTreatmentsWithConfigByFlagSet()); + assertEquals(0, secondPop.getTreatmentsWithConfigByFlagSets()); } @Test @@ -89,6 +105,10 @@ public void popLatenciesReturnsCorrectlyBuiltObject() { telemetryStorage.recordLatency(Method.TREATMENTS, 200); telemetryStorage.recordLatency(Method.TREATMENTS_WITH_CONFIG, 10); telemetryStorage.recordLatency(Method.TREATMENT_WITH_CONFIG, 2000); + telemetryStorage.recordLatency(Method.TREATMENTS_BY_FLAG_SET, 15); + telemetryStorage.recordLatency(Method.TREATMENTS_BY_FLAG_SETS, 14); + telemetryStorage.recordLatency(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SET, 15); + telemetryStorage.recordLatency(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, 14); MethodLatencies methodLatencies = telemetryStorage.popLatencies(); @@ -97,12 +117,20 @@ public void popLatenciesReturnsCorrectlyBuiltObject() { assertFalse(methodLatencies.getTreatments().stream().allMatch(l -> l == 0)); assertFalse(methodLatencies.getTreatmentsWithConfig().stream().allMatch(l -> l == 0)); assertFalse(methodLatencies.getTreatmentWithConfig().stream().allMatch(l -> l == 0)); + assertFalse(methodLatencies.getTreatmentsByFlagSet().stream().allMatch(l -> l == 0)); + assertFalse(methodLatencies.getTreatmentsByFlagSets().stream().allMatch(l -> l == 0)); + assertFalse(methodLatencies.getTreatmentsWithConfigByFlagSet().stream().allMatch(l -> l == 0)); + assertFalse(methodLatencies.getTreatmentsWithConfigByFlagSets().stream().allMatch(l -> l == 0)); assertEquals(1, (long) methodLatencies.getTreatment().get(15)); assertEquals(1, (long) methodLatencies.getTreatments().get(14)); assertEquals(1, (long) methodLatencies.getTreatmentWithConfig().get(19)); assertEquals(1, (long) methodLatencies.getTreatmentsWithConfig().get(6)); assertEquals(1, (long) methodLatencies.getTrack().get(14)); + assertEquals(1, (long) methodLatencies.getTreatmentsByFlagSet().get(7)); + assertEquals(1, (long) methodLatencies.getTreatmentsByFlagSets().get(7)); + assertEquals(1, (long) methodLatencies.getTreatmentsWithConfigByFlagSet().get(7)); + assertEquals(1, (long) methodLatencies.getTreatmentsWithConfigByFlagSets().get(7)); } @Test @@ -113,6 +141,10 @@ public void secondLatenciesPopHasArraysSetIn0() { telemetryStorage.recordLatency(Method.TREATMENTS, 200); telemetryStorage.recordLatency(Method.TREATMENTS_WITH_CONFIG, 10); telemetryStorage.recordLatency(Method.TREATMENT_WITH_CONFIG, 2000); + telemetryStorage.recordLatency(Method.TREATMENTS_BY_FLAG_SET, 15); + telemetryStorage.recordLatency(Method.TREATMENTS_BY_FLAG_SETS, 14); + telemetryStorage.recordLatency(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SET, 15); + telemetryStorage.recordLatency(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, 14); telemetryStorage.popLatencies(); @@ -123,6 +155,10 @@ public void secondLatenciesPopHasArraysSetIn0() { assertTrue(methodLatencies.getTreatments().stream().allMatch(l -> l == 0)); assertTrue(methodLatencies.getTreatmentsWithConfig().stream().allMatch(l -> l == 0)); assertTrue(methodLatencies.getTreatmentWithConfig().stream().allMatch(l -> l == 0)); + assertTrue(methodLatencies.getTreatmentsByFlagSet().stream().allMatch(l -> l == 0)); + assertTrue(methodLatencies.getTreatmentsByFlagSets().stream().allMatch(l -> l == 0)); + assertTrue(methodLatencies.getTreatmentsWithConfigByFlagSet().stream().allMatch(l -> l == 0)); + assertTrue(methodLatencies.getTreatmentsWithConfigByFlagSets().stream().allMatch(l -> l == 0)); } @Test From b6592e605c85a1a5f2eb6b216587400a7e864dfb Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 5 Sep 2023 17:16:42 -0300 Subject: [PATCH 42/89] More treatment manager tests; --- .../TreatmentManagerWithFlagSetsTest.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java index 90539b17b..42281896f 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java @@ -439,4 +439,40 @@ public void getTreatmentsWithConfigByFlagSetsRecordsTelemetry() { verify(mTelemetryStorageProducer).recordLatency(eq(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS), anyLong()); } + + @Test + public void getTreatmentsByFlagSetExceptionIsRecordedInTelemetry() { + when(mSplitsStorage.getNamesByFlagSets(any())).thenThrow(new RuntimeException("test")); + + mTreatmentManager.getTreatmentsByFlagSet("set_1", null, false); + + verify(mTelemetryStorageProducer).recordException(eq(Method.TREATMENTS_BY_FLAG_SET)); + } + + @Test + public void getTreatmentsByFlagSetsExceptionIsRecordedInTelemetry() { + when(mSplitsStorage.getNamesByFlagSets(any())).thenThrow(new RuntimeException("test")); + + mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + verify(mTelemetryStorageProducer).recordException(eq(Method.TREATMENTS_BY_FLAG_SETS)); + } + + @Test + public void getTreatmentsWithConfigByFlagSetExceptionIsRecordedInTelemetry() { + when(mSplitsStorage.getNamesByFlagSets(any())).thenThrow(new RuntimeException("test")); + + mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_1", null, false); + + verify(mTelemetryStorageProducer).recordException(eq(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SET)); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsExceptionIsRecordedInTelemetry() { + when(mSplitsStorage.getNamesByFlagSets(any())).thenThrow(new RuntimeException("test")); + + mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + verify(mTelemetryStorageProducer).recordException(eq(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS)); + } } From a41f7db1fc16e387573f72f2d21e798d7719f7b7 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 5 Sep 2023 17:36:20 -0300 Subject: [PATCH 43/89] Fix json key --- .../android/client/telemetry/model/MethodExceptions.java | 4 ++-- .../split/android/client/telemetry/model/MethodLatencies.java | 4 ++-- .../client/telemetry/TelemetryStatsBodySerializerTest.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/split/android/client/telemetry/model/MethodExceptions.java b/src/main/java/io/split/android/client/telemetry/model/MethodExceptions.java index ff87dedab..0a936f565 100644 --- a/src/main/java/io/split/android/client/telemetry/model/MethodExceptions.java +++ b/src/main/java/io/split/android/client/telemetry/model/MethodExceptions.java @@ -22,10 +22,10 @@ public class MethodExceptions { @SerializedName("tfs") private long treatmentsByFlagSets; - @SerializedName("tfc") + @SerializedName("tcf") private long treatmentsWithConfigByFlagSet; - @SerializedName("tfcs") + @SerializedName("tcfs") private long treatmentsWithConfigByFlagSets; @SerializedName("tr") diff --git a/src/main/java/io/split/android/client/telemetry/model/MethodLatencies.java b/src/main/java/io/split/android/client/telemetry/model/MethodLatencies.java index 3c9d95391..15cc73fe8 100644 --- a/src/main/java/io/split/android/client/telemetry/model/MethodLatencies.java +++ b/src/main/java/io/split/android/client/telemetry/model/MethodLatencies.java @@ -24,10 +24,10 @@ public class MethodLatencies { @SerializedName("tfs") private List treatmentsByFlagSets; - @SerializedName("tfc") + @SerializedName("tcf") private List treatmentsWithConfigByFlagSet; - @SerializedName("tfcs") + @SerializedName("tcfs") private List treatmentsWithConfigByFlagSets; @SerializedName("tr") diff --git a/src/test/java/io/split/android/client/telemetry/TelemetryStatsBodySerializerTest.java b/src/test/java/io/split/android/client/telemetry/TelemetryStatsBodySerializerTest.java index b363cc2e4..1a6ec1cd4 100644 --- a/src/test/java/io/split/android/client/telemetry/TelemetryStatsBodySerializerTest.java +++ b/src/test/java/io/split/android/client/telemetry/TelemetryStatsBodySerializerTest.java @@ -30,7 +30,7 @@ public void setUp() { public void jsonIsBuiltAsExpected() { String serializedStats = telemetryStatsBodySerializer.serialize(getMockStats()); - assertEquals("{\"lS\":{\"sp\":1000,\"ms\":2000,\"im\":3000,\"ic\":4000,\"ev\":5000,\"te\":6000,\"to\":7000},\"mL\":{\"t\":[0,0,2,0],\"ts\":[0,0,3,0],\"tc\":[0,0,5,0],\"tcs\":[0,0,4,0],\"tf\":[1,0,0,0],\"tfs\":[2,0,0,0],\"tfc\":[3,0,0,0],\"tfcs\":[4,0,0,0],\"tr\":[0,0,1,0]},\"mE\":{\"t\":2,\"ts\":3,\"tc\":5,\"tcs\":4,\"tf\":10,\"tfs\":20,\"tfc\":30,\"tfcs\":40,\"tr\":1},\"hE\":{},\"hL\":{\"sp\":[0,0,3,0],\"ms\":[0,0,5,0],\"im\":[0,0,1,0],\"ic\":[0,0,4,0],\"ev\":[0,0,2,0],\"te\":[1,0,0,0],\"to\":[0,0,6,0]},\"tR\":4,\"aR\":5,\"iQ\":2,\"iDe\":5,\"iDr\":4,\"spC\":456,\"seC\":4,\"skC\":1,\"sL\":2000,\"eQ\":4,\"eD\":2,\"sE\":[{\"e\":0,\"t\":5000},{\"e\":20,\"d\":4,\"t\":2000}],\"t\":[\"tag1\",\"tag2\"],\"ufs\":{\"sp\":4,\"ms\":8}}", serializedStats); + assertEquals("{\"lS\":{\"sp\":1000,\"ms\":2000,\"im\":3000,\"ic\":4000,\"ev\":5000,\"te\":6000,\"to\":7000},\"mL\":{\"t\":[0,0,2,0],\"ts\":[0,0,3,0],\"tc\":[0,0,5,0],\"tcs\":[0,0,4,0],\"tf\":[1,0,0,0],\"tfs\":[2,0,0,0],\"tcf\":[3,0,0,0],\"tcfs\":[4,0,0,0],\"tr\":[0,0,1,0]},\"mE\":{\"t\":2,\"ts\":3,\"tc\":5,\"tcs\":4,\"tf\":10,\"tfs\":20,\"tcf\":30,\"tcfs\":40,\"tr\":1},\"hE\":{},\"hL\":{\"sp\":[0,0,3,0],\"ms\":[0,0,5,0],\"im\":[0,0,1,0],\"ic\":[0,0,4,0],\"ev\":[0,0,2,0],\"te\":[1,0,0,0],\"to\":[0,0,6,0]},\"tR\":4,\"aR\":5,\"iQ\":2,\"iDe\":5,\"iDr\":4,\"spC\":456,\"seC\":4,\"skC\":1,\"sL\":2000,\"eQ\":4,\"eD\":2,\"sE\":[{\"e\":0,\"t\":5000},{\"e\":20,\"d\":4,\"t\":2000}],\"t\":[\"tag1\",\"tag2\"],\"ufs\":{\"sp\":4,\"ms\":8}}", serializedStats); } private Stats getMockStats() { From 705a281f5fc3f8da49ab4196f92ac3151b58fbbf Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 6 Sep 2023 13:43:49 -0300 Subject: [PATCH 44/89] Sets config telemetry --- .../android/client/SplitFactoryHelper.java | 2 +- .../io/split/android/client/SplitFilter.java | 11 ++- .../localhost/LocalhostSplitFactory.java | 2 +- .../executor/SplitTaskFactoryImpl.java | 15 +++- .../telemetry/TelemetryTaskFactoryImpl.java | 11 ++- .../client/telemetry/model/Config.java | 22 +++++ .../storage/TelemetryConfigProviderImpl.java | 10 ++- .../validators/FlagSetsValidatorImpl.java | 10 ++- .../validators/SplitFilterValidator.java | 22 ++++- .../TelemetryConfigBodySerializerTest.java | 6 +- .../TelemetryConfigProviderImplTest.java | 4 +- .../validators/FlagSetsValidatorImplTest.java | 84 +++++++++++-------- 12 files changed, 143 insertions(+), 56 deletions(-) diff --git a/src/main/java/io/split/android/client/SplitFactoryHelper.java b/src/main/java/io/split/android/client/SplitFactoryHelper.java index 8e224773e..76393dc34 100644 --- a/src/main/java/io/split/android/client/SplitFactoryHelper.java +++ b/src/main/java/io/split/android/client/SplitFactoryHelper.java @@ -428,7 +428,7 @@ Pair, String>, Set> getFilterConfiguration(SyncCo SplitFilter splitFilter = groupedFilters.get(0); // In the case of BY_SET, all filters will be grouped into one with the {@link SplitFilter.Type#BY_SET} type - if (splitFilter.getType() == SplitFilter.Type.BY_SET) { + if (splitFilter != null && splitFilter.getType() == SplitFilter.Type.BY_SET) { configuredFlagSets.addAll(splitFilter.getValues()); } } diff --git a/src/main/java/io/split/android/client/SplitFilter.java b/src/main/java/io/split/android/client/SplitFilter.java index cdd7ce283..908f01c34 100644 --- a/src/main/java/io/split/android/client/SplitFilter.java +++ b/src/main/java/io/split/android/client/SplitFilter.java @@ -60,6 +60,7 @@ public int maxValuesCount() { private final SplitFilter.Type mType; private final List mValues; + private int mInvalidValueCount; public static SplitFilter byName(@NonNull List values) { return new SplitFilter(Type.BY_NAME, values); @@ -74,7 +75,7 @@ public static SplitFilter bySet(@NonNull List values) { } // This constructor is not private (but default) to allow Split Sync Config builder be agnostic when creating filters - // Also is not public to force SDK users to use static functions "byName" and "byPrefix" + // Also is not public to force SDK users to use static functions "byName", "byPrefix", "bySet" SplitFilter(Type type, List values) { if (values == null) { throw new IllegalArgumentException("Values can't be null for " + type.toString() + " filter"); @@ -85,7 +86,9 @@ public static SplitFilter bySet(@NonNull List values) { SplitFilter(Type type, List values, SplitFilterValidator validator) { mType = type; - mValues = validator.cleanup(values); + SplitFilterValidator.ValidationResult validationResult = validator.cleanup(values); + mValues = validationResult.getValues(); + mInvalidValueCount = validationResult.getInvalidValueCount(); } public Type getType() { @@ -95,4 +98,8 @@ public Type getType() { public List getValues() { return mValues; } + + public int getInvalidValueCount() { + return mInvalidValueCount; + } } diff --git a/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java b/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java index e0ece35ca..de3efad14 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java @@ -79,7 +79,7 @@ public LocalhostSplitFactory(String key, Context context, List groupedFilters = new FilterBuilder(config.syncConfig().getFilters()) .getGroupedFilter(); - if (!groupedFilters.isEmpty() && groupedFilters.get(0).getType() == SplitFilter.Type.BY_SET) { + if (!groupedFilters.isEmpty() && groupedFilters.get(0) != null && groupedFilters.get(0).getType() == SplitFilter.Type.BY_SET) { configuredSets.addAll(groupedFilters.get(0).getValues()); } } diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java index d3f191424..142d8f14b 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java @@ -92,14 +92,23 @@ public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig, mTelemetryRuntimeProducer); } + mFilters = (filters == null) ? new ArrayList<>() : filters; + + int flagSetCount = 0; + int invalidFlagSetCount = 0; + if (!mFilters.isEmpty() && mFilters.get(0) != null && mFilters.get(0).getType() == SplitFilter.Type.BY_SET) { + flagSetCount = mFilters.get(0).getValues().size(); + invalidFlagSetCount = mFilters.get(0).getInvalidValueCount(); + } + mTelemetryTaskFactory = new TelemetryTaskFactoryImpl(mSplitApiFacade.getTelemetryConfigRecorder(), mSplitApiFacade.getTelemetryStatsRecorder(), telemetryStorage, splitClientConfig, mSplitsStorageContainer.getSplitsStorage(), - mSplitsStorageContainer.getMySegmentsStorageContainer()); - - mFilters = (filters == null) ? new ArrayList<>() : filters; + mSplitsStorageContainer.getMySegmentsStorageContainer(), + flagSetCount, + invalidFlagSetCount); } @Override diff --git a/src/main/java/io/split/android/client/service/telemetry/TelemetryTaskFactoryImpl.java b/src/main/java/io/split/android/client/service/telemetry/TelemetryTaskFactoryImpl.java index 624b0f948..08a24f672 100644 --- a/src/main/java/io/split/android/client/service/telemetry/TelemetryTaskFactoryImpl.java +++ b/src/main/java/io/split/android/client/service/telemetry/TelemetryTaskFactoryImpl.java @@ -2,9 +2,11 @@ import androidx.annotation.NonNull; +import java.util.List; + import io.split.android.client.SplitClientConfig; +import io.split.android.client.SplitFilter; import io.split.android.client.service.http.HttpRecorder; -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.model.Config; @@ -15,7 +17,6 @@ import io.split.android.client.telemetry.storage.TelemetryStatsProvider; import io.split.android.client.telemetry.storage.TelemetryStatsProviderImpl; import io.split.android.client.telemetry.storage.TelemetryStorage; -import io.split.android.client.telemetry.storage.TelemetryStorageConsumer; public class TelemetryTaskFactoryImpl implements TelemetryTaskFactory { @@ -30,9 +31,11 @@ public TelemetryTaskFactoryImpl(@NonNull HttpRecorder telemetryConfigRec @NonNull TelemetryStorage telemetryStorage, @NonNull SplitClientConfig splitClientConfig, @NonNull SplitsStorage splitsStorage, - @NonNull MySegmentsStorageContainer mySegmentsStorageContainer) { + @NonNull MySegmentsStorageContainer mySegmentsStorageContainer, + int flagSetCount, + int invalidFlagSetCount) { mTelemetryConfigRecorder = telemetryConfigRecorder; - mTelemetryConfigProvider = new TelemetryConfigProviderImpl(telemetryStorage, splitClientConfig); + mTelemetryConfigProvider = new TelemetryConfigProviderImpl(telemetryStorage, splitClientConfig, flagSetCount, invalidFlagSetCount); mTelemetryStatsRecorder = telemetryStatsRecorder; mTelemetryStatsProvider = new TelemetryStatsProviderImpl(telemetryStorage, splitsStorage, mySegmentsStorageContainer); mTelemetryRuntimeProducer = telemetryStorage; diff --git a/src/main/java/io/split/android/client/telemetry/model/Config.java b/src/main/java/io/split/android/client/telemetry/model/Config.java index 25ec3ee42..bf6849271 100644 --- a/src/main/java/io/split/android/client/telemetry/model/Config.java +++ b/src/main/java/io/split/android/client/telemetry/model/Config.java @@ -60,6 +60,12 @@ public class Config { @SerializedName("i") private List integrations; + @SerializedName("fsT") + private int flagSetsTotal; + + @SerializedName("fsI") + private int flagSetsInvalid; + public int getOperationMode() { return operationMode; } @@ -195,4 +201,20 @@ public List getIntegrations() { public void setIntegrations(List integrations) { this.integrations = integrations; } + + public int getFlagSetsTotal() { + return flagSetsTotal; + } + + public void setFlagSetsTotal(int flagSetsTotal) { + this.flagSetsTotal = flagSetsTotal; + } + + public int getFlagSetsInvalid() { + return flagSetsInvalid; + } + + public void setFlagSetsInvalid(int flagSetsInvalid) { + this.flagSetsInvalid = flagSetsInvalid; + } } diff --git a/src/main/java/io/split/android/client/telemetry/storage/TelemetryConfigProviderImpl.java b/src/main/java/io/split/android/client/telemetry/storage/TelemetryConfigProviderImpl.java index adfb6f97f..b937411c4 100644 --- a/src/main/java/io/split/android/client/telemetry/storage/TelemetryConfigProviderImpl.java +++ b/src/main/java/io/split/android/client/telemetry/storage/TelemetryConfigProviderImpl.java @@ -20,11 +20,17 @@ public class TelemetryConfigProviderImpl implements TelemetryConfigProvider { private final TelemetryStorageConsumer mTelemetryConsumer; private final SplitClientConfig mSplitClientConfig; + private final int mFlagSetCount; + private final int mInvalidFlagSetCount; public TelemetryConfigProviderImpl(@NonNull TelemetryStorageConsumer telemetryConsumer, - @NonNull SplitClientConfig splitClientConfig) { + @NonNull SplitClientConfig splitClientConfig, + int flagSetCount, + int invalidFlagSetCount) { mTelemetryConsumer = checkNotNull(telemetryConsumer); mSplitClientConfig = checkNotNull(splitClientConfig); + mFlagSetCount = flagSetCount; + mInvalidFlagSetCount = invalidFlagSetCount; } @Override @@ -44,6 +50,8 @@ public Config getConfigTelemetry() { config.setImpressionsQueueSize(mSplitClientConfig.impressionsQueueSize()); config.setEventsQueueSize(mSplitClientConfig.eventsQueueSize()); config.setUserConsent(mSplitClientConfig.userConsent().intValue()); + config.setFlagSetsTotal(mFlagSetCount); + config.setFlagSetsInvalid(mInvalidFlagSetCount); if (mSplitClientConfig.impressionsMode() == ImpressionsMode.DEBUG) { config.setImpressionsMode(io.split.android.client.telemetry.model.ImpressionsMode.DEBUG.intValue()); } else if (mSplitClientConfig.impressionsMode() == ImpressionsMode.OPTIMIZED) { diff --git a/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java b/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java index 2be41e83f..5e14a45a3 100644 --- a/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java +++ b/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java @@ -19,14 +19,17 @@ public class FlagSetsValidatorImpl implements SplitFilterValidator { * @return list of unique alphanumerically ordered valid flag sets */ @Override - public List cleanup(List values) { + public ValidationResult cleanup(List values) { if (values == null || values.isEmpty()) { - return Collections.emptyList(); + return new ValidationResult(Collections.emptyList(), 0); } + int invalidValueCount = 0; + TreeSet cleanedUpSets = new TreeSet<>(); for (String set : values) { if (set == null || set.isEmpty()) { + invalidValueCount++; continue; } @@ -43,11 +46,12 @@ public List cleanup(List values) { if (set.matches(FLAG_SET_REGEX)) { cleanedUpSets.add(set); } else { + invalidValueCount++; Logger.w("SDK config: you passed "+ set +", Flag Set must adhere to the regular expressions "+ FLAG_SET_REGEX +". This means a Flag Set must be start with a letter, be in lowercase, alphanumeric and have a max length of 50 characters. "+ set +" was discarded."); } } - return new ArrayList<>(cleanedUpSets); + return new ValidationResult(new ArrayList<>(cleanedUpSets), invalidValueCount); } @Override diff --git a/src/main/java/io/split/android/client/validators/SplitFilterValidator.java b/src/main/java/io/split/android/client/validators/SplitFilterValidator.java index d605f17c0..110043d47 100644 --- a/src/main/java/io/split/android/client/validators/SplitFilterValidator.java +++ b/src/main/java/io/split/android/client/validators/SplitFilterValidator.java @@ -4,7 +4,27 @@ public interface SplitFilterValidator { - List cleanup(List values); + ValidationResult cleanup(List values); boolean isValid(String value); + + class ValidationResult { + + private final List mValues; + + private final int mInvalidValueCount; + + public ValidationResult(List values, int invalidValueCount) { + mValues = values; + mInvalidValueCount = invalidValueCount; + } + + public List getValues() { + return mValues; + } + + public int getInvalidValueCount() { + return mInvalidValueCount; + } + } } diff --git a/src/test/java/io/split/android/client/telemetry/TelemetryConfigBodySerializerTest.java b/src/test/java/io/split/android/client/telemetry/TelemetryConfigBodySerializerTest.java index 183ea2f42..f15584ed5 100644 --- a/src/test/java/io/split/android/client/telemetry/TelemetryConfigBodySerializerTest.java +++ b/src/test/java/io/split/android/client/telemetry/TelemetryConfigBodySerializerTest.java @@ -24,7 +24,7 @@ public void setUp() { @Test public void jsonIsBuiltAsExpected() { - final String expectedJson = "{\"oM\":0,\"st\":\"memory\",\"sE\":true,\"rR\":{\"sp\":4000,\"ms\":5000,\"im\":3000,\"ev\":2000,\"te\":1000},\"uO\":{\"s\":true,\"e\":true,\"a\":true,\"st\":true,\"t\":true},\"iQ\":4000,\"eQ\":3000,\"iM\":1,\"iL\":true,\"hP\":true,\"aF\":1,\"rF\":0,\"tR\":300,\"tC\":0,\"nR\":3,\"uC\":1,\"t\":[\"tag1\",\"tag2\"],\"i\":[\"integration1\",\"integration2\"]}"; + final String expectedJson = "{\"oM\":0,\"st\":\"memory\",\"sE\":true,\"rR\":{\"sp\":4000,\"ms\":5000,\"im\":3000,\"ev\":2000,\"te\":1000},\"uO\":{\"s\":true,\"e\":true,\"a\":true,\"st\":true,\"t\":true},\"iQ\":4000,\"eQ\":3000,\"iM\":1,\"iL\":true,\"hP\":true,\"aF\":1,\"rF\":0,\"tR\":300,\"tC\":0,\"nR\":3,\"uC\":1,\"t\":[\"tag1\",\"tag2\"],\"i\":[\"integration1\",\"integration2\"],\"fsT\":4,\"fsI\":2}"; final String serializedConfig = telemetryConfigBodySerializer.serialize(buildMockConfig()); assertEquals(expectedJson, serializedConfig); @@ -33,7 +33,7 @@ public void jsonIsBuiltAsExpected() { @Test public void nullValuesAreIgnoredForJson() { - final String expectedJson = "{\"oM\":0,\"st\":\"memory\",\"sE\":true,\"iQ\":4000,\"eQ\":3000,\"iM\":1,\"iL\":true,\"hP\":true,\"aF\":1,\"rF\":0,\"tR\":300,\"tC\":0,\"nR\":3,\"uC\":0,\"t\":[\"tag1\",\"tag2\"],\"i\":[\"integration1\",\"integration2\"]}"; + final String expectedJson = "{\"oM\":0,\"st\":\"memory\",\"sE\":true,\"iQ\":4000,\"eQ\":3000,\"iM\":1,\"iL\":true,\"hP\":true,\"aF\":1,\"rF\":0,\"tR\":300,\"tC\":0,\"nR\":3,\"uC\":0,\"t\":[\"tag1\",\"tag2\"],\"i\":[\"integration1\",\"integration2\"],\"fsT\":0,\"fsI\":0}"; final String serializedConfig = telemetryConfigBodySerializer.serialize(buildMockConfigWithNulls()); assertEquals(expectedJson, serializedConfig); @@ -71,6 +71,8 @@ private Config buildMockConfig() { config.setUserConsent(1); config.setTags(Arrays.asList("tag1", "tag2")); config.setIntegrations(Arrays.asList("integration1", "integration2")); + config.setFlagSetsTotal(4); + config.setFlagSetsInvalid(2); return config; } diff --git a/src/test/java/io/split/android/client/telemetry/storage/TelemetryConfigProviderImplTest.java b/src/test/java/io/split/android/client/telemetry/storage/TelemetryConfigProviderImplTest.java index 6047c2c20..ab4352c1f 100644 --- a/src/test/java/io/split/android/client/telemetry/storage/TelemetryConfigProviderImplTest.java +++ b/src/test/java/io/split/android/client/telemetry/storage/TelemetryConfigProviderImplTest.java @@ -60,7 +60,7 @@ public void close() { } }) .build(); - mTelemetryConfigProvider = new TelemetryConfigProviderImpl(mTelemetryStorageConsumer, mSplitClientConfig); + mTelemetryConfigProvider = new TelemetryConfigProviderImpl(mTelemetryStorageConsumer, mSplitClientConfig, 4, 2); Config configTelemetry = mTelemetryConfigProvider.getConfigTelemetry(); @@ -81,5 +81,7 @@ public void close() { assertTrue(configTelemetry.getUrlOverrides().isEvents()); assertTrue(configTelemetry.getUrlOverrides().isAuth()); assertTrue(configTelemetry.getUrlOverrides().isStream()); + assertEquals(4, configTelemetry.getFlagSetsTotal()); + assertEquals(2, configTelemetry.getFlagSetsInvalid()); } } diff --git a/src/test/java/io/split/android/client/validators/FlagSetsValidatorImplTest.java b/src/test/java/io/split/android/client/validators/FlagSetsValidatorImplTest.java index cd8bc9604..72e3482db 100644 --- a/src/test/java/io/split/android/client/validators/FlagSetsValidatorImplTest.java +++ b/src/test/java/io/split/android/client/validators/FlagSetsValidatorImplTest.java @@ -15,80 +15,90 @@ public class FlagSetsValidatorImplTest { @Test public void nullInputReturnsEmptyList() { - List result = mValidator.cleanup(null); - assertTrue(result.isEmpty()); + SplitFilterValidator.ValidationResult result = mValidator.cleanup(null); + assertTrue(result.getValues().isEmpty()); + assertEquals(0, result.getInvalidValueCount()); } @Test public void emptyInputReturnsEmptyList() { - List result = mValidator.cleanup(Collections.emptyList()); - assertTrue(result.isEmpty()); + SplitFilterValidator.ValidationResult result = mValidator.cleanup(Collections.emptyList()); + assertTrue(result.getValues().isEmpty()); + assertEquals(0, result.getInvalidValueCount()); } @Test public void duplicatedInputValuesAreRemoved() { - List result = mValidator.cleanup(Arrays.asList("set1", "set1")); - assertEquals(1, result.size()); - assertTrue(result.contains("set1")); + SplitFilterValidator.ValidationResult result = mValidator.cleanup(Arrays.asList("set1", "set1")); + assertEquals(1, result.getValues().size()); + assertTrue(result.getValues().contains("set1")); + assertEquals(0, result.getInvalidValueCount()); } @Test public void valuesAreSortedAlphanumerically() { - List result = mValidator.cleanup(Arrays.asList("set2", "set1", "set_1", "1set")); - assertEquals(4, result.size()); - assertEquals("1set", result.get(0)); - assertEquals("set1", result.get(1)); - assertEquals("set2", result.get(2)); - assertEquals("set_1", result.get(3)); + SplitFilterValidator.ValidationResult result = mValidator.cleanup(Arrays.asList("set2", "set1", "set_1", "1set")); + assertEquals(4, result.getValues().size()); + assertEquals("1set", result.getValues().get(0)); + assertEquals("set1", result.getValues().get(1)); + assertEquals("set2", result.getValues().get(2)); + assertEquals("set_1", result.getValues().get(3)); + assertEquals(0, result.getInvalidValueCount()); } @Test public void invalidValuesAreRemoved() { - List result = mValidator.cleanup(Arrays.asList("set1", "set2", "set_1", "set-1", "set 1", "set 2")); - assertEquals(3, result.size()); - assertEquals("set1", result.get(0)); - assertEquals("set2", result.get(1)); - assertEquals("set_1", result.get(2)); + SplitFilterValidator.ValidationResult result = mValidator.cleanup(Arrays.asList("set1", "set2", "set_1", "set-1", "set 1", "set 2")); + assertEquals(3, result.getValues().size()); + assertEquals("set1", result.getValues().get(0)); + assertEquals("set2", result.getValues().get(1)); + assertEquals("set_1", result.getValues().get(2)); + assertEquals(3, result.getInvalidValueCount()); } @Test public void setWithMoreThan50CharsIsRemoved() { String longSet = "abcdfghijklmnopqrstuvwxyz1234567890abcdfghijklmnopq"; - List result = mValidator.cleanup(Arrays.asList("set1", longSet)); + SplitFilterValidator.ValidationResult result = mValidator.cleanup(Arrays.asList("set1", longSet)); assertEquals(51, longSet.length()); - assertEquals(1, result.size()); - assertEquals("set1", result.get(0)); + assertEquals(1, result.getValues().size()); + assertEquals("set1", result.getValues().get(0)); + assertEquals(1, result.getInvalidValueCount()); } @Test public void setWithLessThanOneCharIsOrEmptyRemoved() { - List result = mValidator.cleanup(Arrays.asList("set1", "", " ")); - assertEquals(1, result.size()); - assertEquals("set1", result.get(0)); + SplitFilterValidator.ValidationResult result = mValidator.cleanup(Arrays.asList("set1", "", " ")); + assertEquals(1, result.getValues().size()); + assertEquals("set1", result.getValues().get(0)); + assertEquals(2, result.getInvalidValueCount()); } @Test public void nullSetIsRemoved() { - List result = mValidator.cleanup(Arrays.asList("set1", null)); - assertEquals(1, result.size()); - assertEquals("set1", result.get(0)); + SplitFilterValidator.ValidationResult result = mValidator.cleanup(Arrays.asList("set1", null)); + assertEquals(1, result.getValues().size()); + assertEquals("set1", result.getValues().get(0)); + assertEquals(1, result.getInvalidValueCount()); } @Test public void setWithExtraWhitespaceIsTrimmed() { - List result = mValidator.cleanup(Arrays.asList("set1 ", " set2\r", "set3 ", "set 4\n")); - assertEquals(3, result.size()); - assertEquals("set1", result.get(0)); - assertEquals("set2", result.get(1)); - assertEquals("set3", result.get(2)); + SplitFilterValidator.ValidationResult result = mValidator.cleanup(Arrays.asList("set1 ", " set2\r", "set3 ", "set 4\n")); + assertEquals(3, result.getValues().size()); + assertEquals("set1", result.getValues().get(0)); + assertEquals("set2", result.getValues().get(1)); + assertEquals("set3", result.getValues().get(2)); + assertEquals(1, result.getInvalidValueCount()); } @Test public void setsAreLowercase() { - List result = mValidator.cleanup(Arrays.asList("SET1", "Set2", "SET_3")); - assertEquals(3, result.size()); - assertEquals("set1", result.get(0)); - assertEquals("set2", result.get(1)); - assertEquals("set_3", result.get(2)); + SplitFilterValidator.ValidationResult result = mValidator.cleanup(Arrays.asList("SET1", "Set2", "SET_3")); + assertEquals(3, result.getValues().size()); + assertEquals("set1", result.getValues().get(0)); + assertEquals("set2", result.getValues().get(1)); + assertEquals("set_3", result.getValues().get(2)); + assertEquals(0, result.getInvalidValueCount()); } } From c9662a3fe6b32b3b2c92b6e43d2689fdbe877a55 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 6 Sep 2023 17:17:14 -0300 Subject: [PATCH 45/89] Fix fst count --- .../telemetry/storage/TelemetryConfigProviderImpl.java | 8 ++++---- .../storage/TelemetryConfigProviderImplTest.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/split/android/client/telemetry/storage/TelemetryConfigProviderImpl.java b/src/main/java/io/split/android/client/telemetry/storage/TelemetryConfigProviderImpl.java index b937411c4..1e98944f2 100644 --- a/src/main/java/io/split/android/client/telemetry/storage/TelemetryConfigProviderImpl.java +++ b/src/main/java/io/split/android/client/telemetry/storage/TelemetryConfigProviderImpl.java @@ -20,16 +20,16 @@ public class TelemetryConfigProviderImpl implements TelemetryConfigProvider { private final TelemetryStorageConsumer mTelemetryConsumer; private final SplitClientConfig mSplitClientConfig; - private final int mFlagSetCount; + private final int mValidFlagSetCount; private final int mInvalidFlagSetCount; public TelemetryConfigProviderImpl(@NonNull TelemetryStorageConsumer telemetryConsumer, @NonNull SplitClientConfig splitClientConfig, - int flagSetCount, + int validFlagSetCount, int invalidFlagSetCount) { mTelemetryConsumer = checkNotNull(telemetryConsumer); mSplitClientConfig = checkNotNull(splitClientConfig); - mFlagSetCount = flagSetCount; + mValidFlagSetCount = validFlagSetCount; mInvalidFlagSetCount = invalidFlagSetCount; } @@ -50,7 +50,7 @@ public Config getConfigTelemetry() { config.setImpressionsQueueSize(mSplitClientConfig.impressionsQueueSize()); config.setEventsQueueSize(mSplitClientConfig.eventsQueueSize()); config.setUserConsent(mSplitClientConfig.userConsent().intValue()); - config.setFlagSetsTotal(mFlagSetCount); + config.setFlagSetsTotal(mValidFlagSetCount + mInvalidFlagSetCount); config.setFlagSetsInvalid(mInvalidFlagSetCount); if (mSplitClientConfig.impressionsMode() == ImpressionsMode.DEBUG) { config.setImpressionsMode(io.split.android.client.telemetry.model.ImpressionsMode.DEBUG.intValue()); diff --git a/src/test/java/io/split/android/client/telemetry/storage/TelemetryConfigProviderImplTest.java b/src/test/java/io/split/android/client/telemetry/storage/TelemetryConfigProviderImplTest.java index ab4352c1f..8e4b773f6 100644 --- a/src/test/java/io/split/android/client/telemetry/storage/TelemetryConfigProviderImplTest.java +++ b/src/test/java/io/split/android/client/telemetry/storage/TelemetryConfigProviderImplTest.java @@ -81,7 +81,7 @@ public void close() { assertTrue(configTelemetry.getUrlOverrides().isEvents()); assertTrue(configTelemetry.getUrlOverrides().isAuth()); assertTrue(configTelemetry.getUrlOverrides().isStream()); - assertEquals(4, configTelemetry.getFlagSetsTotal()); + assertEquals(6, configTelemetry.getFlagSetsTotal()); assertEquals(2, configTelemetry.getFlagSetsInvalid()); } } From 844452d7271e1484ba91f958d053e3b4fc39d536 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 6 Sep 2023 17:51:15 -0300 Subject: [PATCH 46/89] byName & bySet processing of changes --- .../android/client/SplitFactoryHelper.java | 4 +- .../android/client/SplitFactoryImpl.java | 4 +- .../client/service/ServiceConstants.java | 3 +- .../executor/SplitTaskFactoryImpl.java | 3 +- .../service/splits/SplitChangeProcessor.java | 45 ++++++++++++---- .../synchronizer/WorkManagerWrapper.java | 11 ++-- .../service/workmanager/SplitsSyncWorker.java | 33 +++++++++--- .../splits/SplitChangeProcessorTest.java | 52 +++++++++++++++++-- .../synchronizer/WorkManagerWrapperTest.java | 7 ++- 9 files changed, 124 insertions(+), 38 deletions(-) rename src/test/java/io/split/android/{engine => client/service}/splits/SplitChangeProcessorTest.java (79%) diff --git a/src/main/java/io/split/android/client/SplitFactoryHelper.java b/src/main/java/io/split/android/client/SplitFactoryHelper.java index 76393dc34..e4c2fd4e7 100644 --- a/src/main/java/io/split/android/client/SplitFactoryHelper.java +++ b/src/main/java/io/split/android/client/SplitFactoryHelper.java @@ -189,9 +189,9 @@ SplitApiFacade buildApiFacade(SplitClientConfig splitClientConfig, } WorkManagerWrapper buildWorkManagerWrapper(Context context, SplitClientConfig splitClientConfig, - String apiKey, String databaseName, Set configuredFlagSets) { + String apiKey, String databaseName, List filters) { return new WorkManagerWrapper( - WorkManager.getInstance(context), splitClientConfig, apiKey, databaseName, configuredFlagSets); + WorkManager.getInstance(context), splitClientConfig, apiKey, databaseName, (filters != null && filters.size() == 1) ? filters.get(0) : null); } diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index a6404513a..af1cecedc 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -181,10 +181,10 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { SplitTaskFactory splitTaskFactory = new SplitTaskFactoryImpl( config, splitApiFacade, mStorageContainer, splitsFilterQueryStringFromConfig, mEventsManagerCoordinator, - filters, configuredFlagSets, testingConfig); + filters, testingConfig); cleanUpDabase(splitTaskExecutor, splitTaskFactory); - WorkManagerWrapper workManagerWrapper = factoryHelper.buildWorkManagerWrapper(context, config, apiToken, databaseName, configuredFlagSets); + WorkManagerWrapper workManagerWrapper = factoryHelper.buildWorkManagerWrapper(context, config, apiToken, databaseName, filters); SplitSingleThreadTaskExecutor splitSingleThreadTaskExecutor = new SplitSingleThreadTaskExecutor(); ImpressionManager impressionManager = new StrategyImpressionManager(factoryHelper.getImpressionStrategy(splitTaskExecutor, splitTaskFactory, mStorageContainer, config)); diff --git a/src/main/java/io/split/android/client/service/ServiceConstants.java b/src/main/java/io/split/android/client/service/ServiceConstants.java index f68dd503a..1ae1ffc41 100644 --- a/src/main/java/io/split/android/client/service/ServiceConstants.java +++ b/src/main/java/io/split/android/client/service/ServiceConstants.java @@ -29,7 +29,8 @@ public class ServiceConstants { public static final String WORKER_PARAM_UNIQUE_KEYS_PER_PUSH = "unique_keys_per_push"; public static final String WORKER_PARAM_UNIQUE_KEYS_ESTIMATED_SIZE_IN_BYTES = "unique_keys_estimated_size_in_bytes"; public static final String WORKER_PARAM_ENCRYPTION_ENABLED = "encryptionEnabled"; - public static final String WORKER_PARAM_CONFIGURED_SETS = "configuredSets"; + public static final String WORKER_PARAM_CONFIGURED_FILTER_VALUES = "configuredFilterValues"; + public static final String WORKER_PARAM_CONFIGURED_FILTER_TYPE = "configuredFilterType"; public static final long LAST_SEEN_IMPRESSION_CACHE_SIZE = 500; public static final int MY_SEGMENT_V2_DATA_SIZE = 1024 * 10;// bytes diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java index 142d8f14b..e828a1cdb 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java @@ -67,7 +67,6 @@ public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig, @Nullable String splitsFilterQueryString, ISplitEventsManager eventsManager, @Nullable List filters, - @NonNull Set configuredFlagSets, @Nullable TestingConfig testingConfig) { mSplitClientConfig = checkNotNull(splitClientConfig); @@ -75,7 +74,7 @@ public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig, mSplitsStorageContainer = checkNotNull(splitStorageContainer); mSplitsFilterQueryStringFromConfig = splitsFilterQueryString; mEventsManager = eventsManager; - mSplitChangeProcessor = new SplitChangeProcessor(configuredFlagSets); + mSplitChangeProcessor = new SplitChangeProcessor(filters); TelemetryStorage telemetryStorage = mSplitsStorageContainer.getTelemetryStorage(); mTelemetryRuntimeProducer = telemetryStorage; diff --git a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java index 6f004b77b..0b88c0b25 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java +++ b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java @@ -1,15 +1,14 @@ package io.split.android.client.service.splits; -import static com.google.common.base.Preconditions.checkNotNull; - import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Set; +import io.split.android.client.SplitFilter; import io.split.android.client.dtos.Split; import io.split.android.client.dtos.SplitChange; import io.split.android.client.dtos.Status; @@ -17,15 +16,24 @@ public class SplitChangeProcessor { - private final Set mConfiguredSets; + private final SplitFilter mSplitFilter; @VisibleForTesting SplitChangeProcessor() { - this(Collections.emptySet()); + this((SplitFilter) null); + } + + public SplitChangeProcessor(@Nullable List filters) { + // We're only supporting one filter type + if (filters == null || filters.isEmpty()) { + mSplitFilter = null; + } else { + mSplitFilter = filters.get(0); + } } - public SplitChangeProcessor(@NonNull Set configuredSets) { - mConfiguredSets = checkNotNull(configuredSets); + public SplitChangeProcessor(@Nullable SplitFilter splitFilter) { + mSplitFilter = splitFilter; } public ProcessedSplitChange process(SplitChange splitChange) { @@ -44,15 +52,20 @@ public ProcessedSplitChange process(Split featureFlag, long changeNumber) { private ProcessedSplitChange buildProcessedSplitChange(List featureFlags, long changeNumber) { List activeFeatureFlags = new ArrayList<>(); List archivedFeatureFlags = new ArrayList<>(); + for (Split featureFlag : featureFlags) { if (featureFlag.name == null) { continue; } - if (mConfiguredSets.isEmpty()) { + if (mSplitFilter == null || mSplitFilter.getValues().isEmpty()) { processAccordingToStatus(activeFeatureFlags, archivedFeatureFlags, featureFlag); } else { - processAccordingToSets(activeFeatureFlags, archivedFeatureFlags, featureFlag); + if (mSplitFilter.getType() == SplitFilter.Type.BY_NAME) { + processAccordingToNames(activeFeatureFlags, archivedFeatureFlags, featureFlag, mSplitFilter.getValues()); + } else if (mSplitFilter.getType() == SplitFilter.Type.BY_SET) { + processAccordingToSets(activeFeatureFlags, archivedFeatureFlags, featureFlag, mSplitFilter.getValues()); + } } } @@ -70,10 +83,20 @@ private void processAccordingToStatus(List activeFeatureFlags, List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag, List configuredValues) { + // If the feature flag name is in the filter, we process it according to its status. Otherwise it's ignored + if (configuredValues.contains(featureFlag.name)) { + processAccordingToStatus(activeFeatureFlags, archivedFeatureFlags, featureFlag); + } + } + /** * Process the feature flag according to its sets */ - private void processAccordingToSets(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag) { + private void processAccordingToSets(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag, List configuredValues) { if (featureFlag.sets == null || featureFlag.sets.isEmpty()) { archivedFeatureFlags.add(featureFlag); return; @@ -81,7 +104,7 @@ private void processAccordingToSets(List activeFeatureFlags, List boolean shouldArchive = true; for (String set : featureFlag.sets) { - if (mConfiguredSets.contains(set)) { + if (configuredValues.contains(set)) { // If the feature flag has at least one set that matches the configured sets, // we process it according to its status processAccordingToStatus(activeFeatureFlags, archivedFeatureFlags, featureFlag); diff --git a/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java b/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java index 31c9cad96..07a60bc02 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java +++ b/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java @@ -4,7 +4,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; import androidx.lifecycle.Observer; import androidx.lifecycle.ProcessLifecycleOwner; import androidx.work.Constraints; @@ -23,6 +22,7 @@ import java.util.concurrent.TimeUnit; import io.split.android.client.SplitClientConfig; +import io.split.android.client.SplitFilter; import io.split.android.client.service.ServiceConstants; import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutionListener; @@ -46,20 +46,20 @@ public class WorkManagerWrapper implements MySegmentsWorkManagerWrapper { // This variable is used to avoid loading data first time // we receive enqueued event private final Set mShouldLoadFromLocal; - private final Set mConfiguredFlagSets; + private final SplitFilter mFilter; public WorkManagerWrapper(@NonNull WorkManager workManager, @NonNull SplitClientConfig splitClientConfig, @NonNull String apiKey, @NonNull String databaseName, - @NonNull Set configuredFlagSets) { + @Nullable SplitFilter filter) { mWorkManager = checkNotNull(workManager); mDatabaseName = checkNotNull(databaseName); mSplitClientConfig = checkNotNull(splitClientConfig); mApiKey = checkNotNull(apiKey); mShouldLoadFromLocal = new HashSet<>(); mConstraints = buildConstraints(); - mConfiguredFlagSets = checkNotNull(configuredFlagSets); + mFilter = filter; } public void setFetcherExecutionListener(SplitTaskExecutionListener fetcherExecutionListener) { @@ -187,7 +187,8 @@ private Data buildSplitSyncInputData() { dataBuilder.putLong(ServiceConstants.WORKER_PARAM_SPLIT_CACHE_EXPIRATION, mSplitClientConfig.cacheExpirationInSeconds()); dataBuilder.putString(ServiceConstants.WORKER_PARAM_ENDPOINT, mSplitClientConfig.endpoint()); dataBuilder.putBoolean(ServiceConstants.SHOULD_RECORD_TELEMETRY, mSplitClientConfig.shouldRecordTelemetry()); - dataBuilder.putStringArray(ServiceConstants.WORKER_PARAM_CONFIGURED_SETS, mConfiguredFlagSets.toArray(new String[0])); + dataBuilder.putString(ServiceConstants.WORKER_PARAM_CONFIGURED_FILTER_TYPE, (mFilter != null) ? mFilter.getType().queryStringField() : null); + dataBuilder.putStringArray(ServiceConstants.WORKER_PARAM_CONFIGURED_FILTER_VALUES, (mFilter != null) ? mFilter.getValues().toArray(new String[0]) : new String[0]); return buildInputData(dataBuilder.build()); } diff --git a/src/main/java/io/split/android/client/service/workmanager/SplitsSyncWorker.java b/src/main/java/io/split/android/client/service/workmanager/SplitsSyncWorker.java index f1dab760e..bf7633d17 100644 --- a/src/main/java/io/split/android/client/service/workmanager/SplitsSyncWorker.java +++ b/src/main/java/io/split/android/client/service/workmanager/SplitsSyncWorker.java @@ -3,14 +3,16 @@ import android.content.Context; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import androidx.work.WorkerParameters; import java.net.URISyntaxException; +import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; +import java.util.List; +import io.split.android.client.SplitFilter; import io.split.android.client.dtos.SplitChange; import io.split.android.client.service.ServiceConstants; import io.split.android.client.service.ServiceFactory; @@ -35,11 +37,8 @@ public SplitsSyncWorker(@NonNull Context context, String apiKey = workerParams.getInputData().getString(ServiceConstants.WORKER_PARAM_API_KEY); boolean encryptionEnabled = workerParams.getInputData().getBoolean(ServiceConstants.WORKER_PARAM_ENCRYPTION_ENABLED, false); - String[] configuredFlagSetsArray = workerParams.getInputData().getStringArray(ServiceConstants.WORKER_PARAM_CONFIGURED_SETS); - Set configuredFlagSets = new HashSet<>(); - if (configuredFlagSetsArray != null) { - configuredFlagSets = new HashSet<>(Arrays.asList(configuredFlagSetsArray)); - } + SplitFilter filter = buildFilter(workerParams.getInputData().getString(ServiceConstants.WORKER_PARAM_CONFIGURED_FILTER_TYPE), + workerParams.getInputData().getStringArray(ServiceConstants.WORKER_PARAM_CONFIGURED_FILTER_VALUES)); SplitsStorage splitsStorage = StorageFactory.getSplitsStorageForWorker(getDatabase(), apiKey, encryptionEnabled); // StorageFactory.getSplitsStorageForWorker creates a new storage instance, so it needs @@ -51,7 +50,7 @@ public SplitsSyncWorker(@NonNull Context context, TelemetryStorage telemetryStorage = StorageFactory.getTelemetryStorage(shouldRecordTelemetry); SplitsSyncHelper splitsSyncHelper = new SplitsSyncHelper(splitsFetcher, splitsStorage, - new SplitChangeProcessor(configuredFlagSets), telemetryStorage); + new SplitChangeProcessor(filter), telemetryStorage); mSplitTask = buildSplitSyncTask(splitsStorage, telemetryStorage, splitsSyncHelper); } catch (URISyntaxException e) { @@ -59,6 +58,24 @@ public SplitsSyncWorker(@NonNull Context context, } } + @Nullable + private static SplitFilter buildFilter(String filterType, String[] filterValuesArray) { + SplitFilter filter = null; + if (filterType != null) { + List configuredFilterValues = new ArrayList<>(); + if (filterValuesArray != null) { + configuredFilterValues = Arrays.asList(filterValuesArray); + } + + if (SplitFilter.Type.BY_NAME.queryStringField().equals(filterType)) { + filter = SplitFilter.byName(configuredFilterValues); + } else if (SplitFilter.Type.BY_SET.queryStringField().equals(filterType)) { + filter = SplitFilter.bySet(configuredFilterValues); + } + } + return filter; + } + @NonNull private SplitTask buildSplitSyncTask(SplitsStorage splitsStorage, TelemetryStorage telemetryStorage, SplitsSyncHelper splitsSyncHelper) { return SplitsSyncTask.buildForBackground(splitsSyncHelper, diff --git a/src/test/java/io/split/android/engine/splits/SplitChangeProcessorTest.java b/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java similarity index 79% rename from src/test/java/io/split/android/engine/splits/SplitChangeProcessorTest.java rename to src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java index f727fded3..a2830c7ef 100644 --- a/src/test/java/io/split/android/engine/splits/SplitChangeProcessorTest.java +++ b/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java @@ -1,4 +1,4 @@ -package io.split.android.engine.splits; +package io.split.android.client.service.splits; import androidx.annotation.Nullable; @@ -13,11 +13,11 @@ import java.util.List; import java.util.Set; +import io.split.android.client.SplitFilter; import io.split.android.client.dtos.Split; import io.split.android.client.dtos.SplitChange; import io.split.android.client.dtos.Status; import io.split.android.client.storage.splits.ProcessedSplitChange; -import io.split.android.client.service.splits.SplitChangeProcessor; public class SplitChangeProcessorTest { @@ -25,7 +25,7 @@ public class SplitChangeProcessorTest { @Before public void setup() { - mProcessor = new SplitChangeProcessor(Collections.emptySet()); + mProcessor = new SplitChangeProcessor(); } @Test @@ -148,7 +148,8 @@ public void processAddingWithFlagSets() { Set configuredSets = new HashSet<>(); configuredSets.add("set_1"); configuredSets.add("set_2"); - mProcessor = new SplitChangeProcessor(configuredSets); + SplitFilter filter = SplitFilter.bySet(new ArrayList<>(configuredSets)); + mProcessor = new SplitChangeProcessor(filter); Split split1 = newSplit("split_1", Status.ACTIVE, new HashSet<>(Arrays.asList("set_3", "set_1"))); Split split2 = newSplit("split_2", Status.ACTIVE, Collections.singleton("set_2")); @@ -168,7 +169,9 @@ public void featureFlagWithNoSetsIsArchivedWhenProcessingWithFlagSets() { Set configuredSets = new HashSet<>(); configuredSets.add("set_1"); configuredSets.add("set_2"); - mProcessor = new SplitChangeProcessor(configuredSets); + SplitFilter filter = SplitFilter.bySet(new ArrayList<>(configuredSets)); + + mProcessor = new SplitChangeProcessor(filter); Split split1 = newSplit("split_1", Status.ACTIVE, null); @@ -181,6 +184,45 @@ public void featureFlagWithNoSetsIsArchivedWhenProcessingWithFlagSets() { Assert.assertEquals(1, result.getArchivedSplits().size()); } + @Test + public void featureFlagsAreFilteredByNameWhenThereIsSplitFilterByName() { + SplitFilter filter = SplitFilter.byName(Arrays.asList("split_1", "split_2")); + + mProcessor = new SplitChangeProcessor(filter); + + Split split1 = newSplit("split_1", Status.ACTIVE); + Split split2 = newSplit("split_2", Status.ARCHIVED); + Split split3 = newSplit("split_3", Status.ACTIVE); + Split split4 = newSplit("split_4", Status.ARCHIVED); + + SplitChange splitChange = new SplitChange(); + splitChange.splits = Arrays.asList(split1, split2, split3, split4); + + ProcessedSplitChange result = mProcessor.process(splitChange); + + Assert.assertEquals(1, result.getActiveSplits().size()); + Assert.assertEquals(1, result.getArchivedSplits().size()); + } + + @Test + public void creatingWithNullFilterProcessesEverything() { + List filterList = null; + mProcessor = new SplitChangeProcessor(filterList); + + Split split1 = newSplit("split_1", Status.ACTIVE); + Split split2 = newSplit("split_2", Status.ARCHIVED); + Split split3 = newSplit("split_3", Status.ACTIVE); + Split split4 = newSplit("split_4", Status.ARCHIVED); + + SplitChange splitChange = new SplitChange(); + splitChange.splits = Arrays.asList(split1, split2, split3, split4); + + ProcessedSplitChange result = mProcessor.process(splitChange); + + Assert.assertEquals(2, result.getActiveSplits().size()); + Assert.assertEquals(2, result.getArchivedSplits().size()); + } + private List createSplits(int from, int count, Status status) { List splits = new ArrayList<>(); for (int i = from; i < count + from; i++) { diff --git a/src/test/java/io/split/android/client/service/synchronizer/WorkManagerWrapperTest.java b/src/test/java/io/split/android/client/service/synchronizer/WorkManagerWrapperTest.java index 13b9ce2dd..913e77351 100644 --- a/src/test/java/io/split/android/client/service/synchronizer/WorkManagerWrapperTest.java +++ b/src/test/java/io/split/android/client/service/synchronizer/WorkManagerWrapperTest.java @@ -19,12 +19,14 @@ import org.mockito.MockitoAnnotations; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.concurrent.TimeUnit; import io.split.android.client.ServiceEndpoints; import io.split.android.client.SplitClientConfig; +import io.split.android.client.SplitFilter; import io.split.android.client.service.executor.SplitTaskType; import io.split.android.client.service.workmanager.EventsRecorderWorker; import io.split.android.client.service.workmanager.ImpressionsRecorderWorker; @@ -70,7 +72,7 @@ public void setUp() throws Exception { splitClientConfig, "api_key", "test_database_name", - new HashSet<>(Arrays.asList("set_1", "set_2")) + SplitFilter.bySet(Arrays.asList("set_1", "set_2")) ); } @@ -92,7 +94,8 @@ public void scheduleWorkSchedulesSplitsJob() { .putLong("splitCacheExpiration", 864000) .putString("endpoint", "https://test.split.io/api") .putBoolean("shouldRecordTelemetry", true) - .putStringArray("configuredSets", new String[]{"set_1", "set_2"}) + .putStringArray("configuredFilterValues", new String[]{"set_1", "set_2"}) + .putString("configuredFilterType", SplitFilter.Type.BY_SET.queryStringField()) .build(); PeriodicWorkRequest expectedRequest = new PeriodicWorkRequest From 9b0e8fcb9410f1f96b8bcc9b8c56a9d27503a644 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 6 Sep 2023 19:06:27 -0300 Subject: [PATCH 47/89] Flag processing strategies --- .../splits/FeatureFlagProcessStrategy.java | 78 +++++++++++++++++++ .../service/splits/SplitChangeProcessor.java | 69 ++++------------ 2 files changed, 95 insertions(+), 52 deletions(-) create mode 100644 src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java diff --git a/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java b/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java new file mode 100644 index 000000000..91f979d25 --- /dev/null +++ b/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java @@ -0,0 +1,78 @@ +package io.split.android.client.service.splits; + +import androidx.annotation.NonNull; + +import java.util.List; + +import io.split.android.client.dtos.Split; +import io.split.android.client.dtos.Status; + +interface FeatureFlagProcessStrategy { + + void process(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag, List filterValues); +} + +class StatusProcessStrategy implements FeatureFlagProcessStrategy { + + @Override + public void process(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag, List filterValues) { + if (featureFlag.status == Status.ACTIVE) { + activeFeatureFlags.add(featureFlag); + } else { + archivedFeatureFlags.add(featureFlag); + } + } +} + +class NamesProcessStrategy implements FeatureFlagProcessStrategy { + + private final List mConfiguredValues; + private final StatusProcessStrategy mStatusProcessStrategy; + + NamesProcessStrategy(@NonNull List configuredValues, @NonNull StatusProcessStrategy statusProcessStrategy) { + mConfiguredValues = configuredValues; + mStatusProcessStrategy = statusProcessStrategy; + } + + @Override + public void process(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag, List filterValues) { + // If the feature flag name is in the filter, we process it according to its status. Otherwise it is ignored + if (mConfiguredValues.contains(featureFlag.name)) { + mStatusProcessStrategy.process(activeFeatureFlags, archivedFeatureFlags, featureFlag, filterValues); + } + } +} + +class SetsProcessStrategy implements FeatureFlagProcessStrategy { + + private final List mConfiguredValues; + private final StatusProcessStrategy mStatusProcessStrategy; + + SetsProcessStrategy(@NonNull List configuredValues, @NonNull StatusProcessStrategy statusProcessStrategy) { + mConfiguredValues = configuredValues; + mStatusProcessStrategy = statusProcessStrategy; + } + + @Override + public void process(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag, List filterValues) { + if (featureFlag.sets == null || featureFlag.sets.isEmpty()) { + archivedFeatureFlags.add(featureFlag); + return; + } + + boolean shouldArchive = true; + for (String set : featureFlag.sets) { + if (mConfiguredValues.contains(set)) { + // If the feature flag has at least one set that matches the configured sets, + // we process it according to its status + mStatusProcessStrategy.process(activeFeatureFlags, archivedFeatureFlags, featureFlag, filterValues); + shouldArchive = false; + break; + } + } + + if (shouldArchive) { + archivedFeatureFlags.add(featureFlag); + } + } +} diff --git a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java index 0b88c0b25..62eaa9660 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java +++ b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java @@ -18,6 +18,8 @@ public class SplitChangeProcessor { private final SplitFilter mSplitFilter; + private final StatusProcessStrategy mStatusProcessStrategy; + @VisibleForTesting SplitChangeProcessor() { this((SplitFilter) null); @@ -30,10 +32,13 @@ public SplitChangeProcessor(@Nullable List filters) { } else { mSplitFilter = filters.get(0); } + + mStatusProcessStrategy = new StatusProcessStrategy(); } public SplitChangeProcessor(@Nullable SplitFilter splitFilter) { mSplitFilter = splitFilter; + mStatusProcessStrategy = new StatusProcessStrategy(); } public ProcessedSplitChange process(SplitChange splitChange) { @@ -53,68 +58,28 @@ private ProcessedSplitChange buildProcessedSplitChange(List featureFlags, List activeFeatureFlags = new ArrayList<>(); List archivedFeatureFlags = new ArrayList<>(); + SplitFilter.Type filterType = (mSplitFilter != null) ? mSplitFilter.getType() : null; + List filterValues = (mSplitFilter != null) ? mSplitFilter.getValues() : null; + + FeatureFlagProcessStrategy processStrategy = getProcessStrategy(filterType, filterValues); + for (Split featureFlag : featureFlags) { if (featureFlag.name == null) { continue; } - - if (mSplitFilter == null || mSplitFilter.getValues().isEmpty()) { - processAccordingToStatus(activeFeatureFlags, archivedFeatureFlags, featureFlag); - } else { - if (mSplitFilter.getType() == SplitFilter.Type.BY_NAME) { - processAccordingToNames(activeFeatureFlags, archivedFeatureFlags, featureFlag, mSplitFilter.getValues()); - } else if (mSplitFilter.getType() == SplitFilter.Type.BY_SET) { - processAccordingToSets(activeFeatureFlags, archivedFeatureFlags, featureFlag, mSplitFilter.getValues()); - } - } + processStrategy.process(activeFeatureFlags, archivedFeatureFlags, featureFlag, filterValues); } return new ProcessedSplitChange(activeFeatureFlags, archivedFeatureFlags, changeNumber, System.currentTimeMillis() / 100); } - /** - * Process the feature flag according to its status - */ - private void processAccordingToStatus(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag) { - if (featureFlag.status == Status.ACTIVE) { - activeFeatureFlags.add(featureFlag); + private FeatureFlagProcessStrategy getProcessStrategy(SplitFilter.Type filterType, List filterValues) { + if (filterType == SplitFilter.Type.BY_SET) { + return new NamesProcessStrategy(filterValues, mStatusProcessStrategy); + } else if (filterType == SplitFilter.Type.BY_NAME) { + return new SetsProcessStrategy(filterValues, mStatusProcessStrategy); } else { - archivedFeatureFlags.add(featureFlag); - } - } - - /** - * Process the feature flag according to its name - */ - private void processAccordingToNames(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag, List configuredValues) { - // If the feature flag name is in the filter, we process it according to its status. Otherwise it's ignored - if (configuredValues.contains(featureFlag.name)) { - processAccordingToStatus(activeFeatureFlags, archivedFeatureFlags, featureFlag); - } - } - - /** - * Process the feature flag according to its sets - */ - private void processAccordingToSets(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag, List configuredValues) { - if (featureFlag.sets == null || featureFlag.sets.isEmpty()) { - archivedFeatureFlags.add(featureFlag); - return; - } - - boolean shouldArchive = true; - for (String set : featureFlag.sets) { - if (configuredValues.contains(set)) { - // If the feature flag has at least one set that matches the configured sets, - // we process it according to its status - processAccordingToStatus(activeFeatureFlags, archivedFeatureFlags, featureFlag); - shouldArchive = false; - break; - } - } - - if (shouldArchive) { - archivedFeatureFlags.add(featureFlag); + return mStatusProcessStrategy; } } } From bba3517cce6c1bf969657d040233f58ef40e06f2 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 7 Sep 2023 10:47:17 -0300 Subject: [PATCH 48/89] Fix --- .../android/client/service/splits/SplitChangeProcessor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java index 62eaa9660..ff75b2d61 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java +++ b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java @@ -75,9 +75,9 @@ private ProcessedSplitChange buildProcessedSplitChange(List featureFlags, private FeatureFlagProcessStrategy getProcessStrategy(SplitFilter.Type filterType, List filterValues) { if (filterType == SplitFilter.Type.BY_SET) { - return new NamesProcessStrategy(filterValues, mStatusProcessStrategy); - } else if (filterType == SplitFilter.Type.BY_NAME) { return new SetsProcessStrategy(filterValues, mStatusProcessStrategy); + } else if (filterType == SplitFilter.Type.BY_NAME) { + return new NamesProcessStrategy(filterValues, mStatusProcessStrategy); } else { return mStatusProcessStrategy; } From beb3d47fa39c74c8183ad2e02acec9fd487a3856 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 7 Sep 2023 12:21:17 -0300 Subject: [PATCH 49/89] Remove unnecessary param --- .../splits/FeatureFlagProcessStrategy.java | 12 +++++----- .../service/splits/SplitChangeProcessor.java | 22 ++++++++++--------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java b/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java index 91f979d25..4de68e5a7 100644 --- a/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java +++ b/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java @@ -9,13 +9,13 @@ interface FeatureFlagProcessStrategy { - void process(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag, List filterValues); + void process(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag); } class StatusProcessStrategy implements FeatureFlagProcessStrategy { @Override - public void process(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag, List filterValues) { + public void process(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag) { if (featureFlag.status == Status.ACTIVE) { activeFeatureFlags.add(featureFlag); } else { @@ -35,10 +35,10 @@ class NamesProcessStrategy implements FeatureFlagProcessStrategy { } @Override - public void process(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag, List filterValues) { + public void process(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag) { // If the feature flag name is in the filter, we process it according to its status. Otherwise it is ignored if (mConfiguredValues.contains(featureFlag.name)) { - mStatusProcessStrategy.process(activeFeatureFlags, archivedFeatureFlags, featureFlag, filterValues); + mStatusProcessStrategy.process(activeFeatureFlags, archivedFeatureFlags, featureFlag); } } } @@ -54,7 +54,7 @@ class SetsProcessStrategy implements FeatureFlagProcessStrategy { } @Override - public void process(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag, List filterValues) { + public void process(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag) { if (featureFlag.sets == null || featureFlag.sets.isEmpty()) { archivedFeatureFlags.add(featureFlag); return; @@ -65,7 +65,7 @@ public void process(List activeFeatureFlags, List archivedFeatureF if (mConfiguredValues.contains(set)) { // If the feature flag has at least one set that matches the configured sets, // we process it according to its status - mStatusProcessStrategy.process(activeFeatureFlags, archivedFeatureFlags, featureFlag, filterValues); + mStatusProcessStrategy.process(activeFeatureFlags, archivedFeatureFlags, featureFlag); shouldArchive = false; break; } diff --git a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java index ff75b2d61..b709beb68 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java +++ b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java @@ -58,26 +58,28 @@ private ProcessedSplitChange buildProcessedSplitChange(List featureFlags, List activeFeatureFlags = new ArrayList<>(); List archivedFeatureFlags = new ArrayList<>(); - SplitFilter.Type filterType = (mSplitFilter != null) ? mSplitFilter.getType() : null; - List filterValues = (mSplitFilter != null) ? mSplitFilter.getValues() : null; - - FeatureFlagProcessStrategy processStrategy = getProcessStrategy(filterType, filterValues); + FeatureFlagProcessStrategy processStrategy = getProcessStrategy(mSplitFilter); for (Split featureFlag : featureFlags) { if (featureFlag.name == null) { continue; } - processStrategy.process(activeFeatureFlags, archivedFeatureFlags, featureFlag, filterValues); + + processStrategy.process(activeFeatureFlags, archivedFeatureFlags, featureFlag); } return new ProcessedSplitChange(activeFeatureFlags, archivedFeatureFlags, changeNumber, System.currentTimeMillis() / 100); } - private FeatureFlagProcessStrategy getProcessStrategy(SplitFilter.Type filterType, List filterValues) { - if (filterType == SplitFilter.Type.BY_SET) { - return new SetsProcessStrategy(filterValues, mStatusProcessStrategy); - } else if (filterType == SplitFilter.Type.BY_NAME) { - return new NamesProcessStrategy(filterValues, mStatusProcessStrategy); + private FeatureFlagProcessStrategy getProcessStrategy(SplitFilter splitFilter) { + if (splitFilter == null) { + return mStatusProcessStrategy; + } + + if (splitFilter.getType() == SplitFilter.Type.BY_SET) { + return new SetsProcessStrategy(splitFilter.getValues(), mStatusProcessStrategy); + } else if (splitFilter.getType() == SplitFilter.Type.BY_NAME) { + return new NamesProcessStrategy(splitFilter.getValues(), mStatusProcessStrategy); } else { return mStatusProcessStrategy; } From a0b9a08552897305a76e0d4225dcb7bae5d76a64 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 7 Sep 2023 13:47:42 -0300 Subject: [PATCH 50/89] Fix --- .../java/io/split/android/client/SplitFactoryHelper.java | 2 +- .../client/service/synchronizer/WorkManagerWrapper.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/split/android/client/SplitFactoryHelper.java b/src/main/java/io/split/android/client/SplitFactoryHelper.java index e4c2fd4e7..83cb7a841 100644 --- a/src/main/java/io/split/android/client/SplitFactoryHelper.java +++ b/src/main/java/io/split/android/client/SplitFactoryHelper.java @@ -191,7 +191,7 @@ SplitApiFacade buildApiFacade(SplitClientConfig splitClientConfig, WorkManagerWrapper buildWorkManagerWrapper(Context context, SplitClientConfig splitClientConfig, String apiKey, String databaseName, List filters) { return new WorkManagerWrapper( - WorkManager.getInstance(context), splitClientConfig, apiKey, databaseName, (filters != null && filters.size() == 1) ? filters.get(0) : null); + WorkManager.getInstance(context), splitClientConfig, apiKey, databaseName, filters); } diff --git a/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java b/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java index 07a60bc02..b2beb4b08 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java +++ b/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java @@ -46,20 +46,21 @@ public class WorkManagerWrapper implements MySegmentsWorkManagerWrapper { // This variable is used to avoid loading data first time // we receive enqueued event private final Set mShouldLoadFromLocal; + @Nullable private final SplitFilter mFilter; public WorkManagerWrapper(@NonNull WorkManager workManager, @NonNull SplitClientConfig splitClientConfig, @NonNull String apiKey, @NonNull String databaseName, - @Nullable SplitFilter filter) { + @Nullable List filters) { mWorkManager = checkNotNull(workManager); mDatabaseName = checkNotNull(databaseName); mSplitClientConfig = checkNotNull(splitClientConfig); mApiKey = checkNotNull(apiKey); mShouldLoadFromLocal = new HashSet<>(); mConstraints = buildConstraints(); - mFilter = filter; + mFilter = (filters != null && filters.size() == 1) ? filters.get(0) : null; } public void setFetcherExecutionListener(SplitTaskExecutionListener fetcherExecutionListener) { From d41b6ea8cde6eed61439446be6ed7f1deea38973 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 7 Sep 2023 14:33:47 -0300 Subject: [PATCH 51/89] Fix test; remove duplicated code --- .../localhost/LocalhostSplitClient.java | 52 +++++++++---------- .../synchronizer/WorkManagerWrapperTest.java | 3 +- 2 files changed, 26 insertions(+), 29 deletions(-) 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 e82e33f16..b4dfbdbf3 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java @@ -149,13 +149,7 @@ public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Null } catch (Exception exception) { Logger.e(exception); - Map result = new HashMap<>(); - Set namesByFlagSets = mSplitsStorage.getNamesByFlagSets(Collections.singletonList(flagSet)); - for (String featureFlagName : namesByFlagSets) { - result.put(featureFlagName, Treatments.CONTROL); - } - - return result; + return buildExceptionResult(Collections.singletonList(flagSet)); } } @@ -166,13 +160,7 @@ public Map getTreatmentsByFlagSets(@NonNull List flagSet } catch (Exception exception) { Logger.e(exception); - Map result = new HashMap<>(); - Set namesByFlagSets = mSplitsStorage.getNamesByFlagSets(flagSets); - for (String featureFlagName : namesByFlagSets) { - result.put(featureFlagName, Treatments.CONTROL); - } - - return result; + return buildExceptionResult(flagSets); } } @@ -183,13 +171,7 @@ public Map getTreatmentsWithConfigByFlagSet(@NonNull String } catch (Exception exception) { Logger.e(exception); - Map result = new HashMap<>(); - Set namesByFlagSets = mSplitsStorage.getNamesByFlagSets(Collections.singletonList(flagSet)); - for (String featureFlagName : namesByFlagSets) { - result.put(featureFlagName, new SplitResult(Treatments.CONTROL)); - } - - return result; + return buildExceptionResultWithConfig(Collections.singletonList(flagSet)); } } @@ -200,13 +182,7 @@ public Map getTreatmentsWithConfigByFlagSets(@NonNull List< } catch (Exception exception) { Logger.e(exception); - Map result = new HashMap<>(); - Set namesByFlagSets = mSplitsStorage.getNamesByFlagSets(flagSets); - for (String featureFlagName : namesByFlagSets) { - result.put(featureFlagName, new SplitResult(Treatments.CONTROL)); - } - - return result; + return buildExceptionResultWithConfig(flagSets); } } @@ -324,4 +300,24 @@ public boolean removeAttribute(String attributeName) { public boolean clearAttributes() { return true; } + + private Map buildExceptionResult(List flagSets) { + Map result = new HashMap<>(); + Set namesByFlagSets = mSplitsStorage.getNamesByFlagSets(flagSets); + for (String featureFlagName : namesByFlagSets) { + result.put(featureFlagName, Treatments.CONTROL); + } + + return result; + } + + private Map buildExceptionResultWithConfig(List flagSets) { + Map result = new HashMap<>(); + Set namesByFlagSets = mSplitsStorage.getNamesByFlagSets(flagSets); + for (String featureFlagName : namesByFlagSets) { + result.put(featureFlagName, new SplitResult(Treatments.CONTROL)); + } + + return result; + } } diff --git a/src/test/java/io/split/android/client/service/synchronizer/WorkManagerWrapperTest.java b/src/test/java/io/split/android/client/service/synchronizer/WorkManagerWrapperTest.java index 913e77351..a79498fa4 100644 --- a/src/test/java/io/split/android/client/service/synchronizer/WorkManagerWrapperTest.java +++ b/src/test/java/io/split/android/client/service/synchronizer/WorkManagerWrapperTest.java @@ -21,6 +21,7 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.concurrent.TimeUnit; @@ -72,7 +73,7 @@ public void setUp() throws Exception { splitClientConfig, "api_key", "test_database_name", - SplitFilter.bySet(Arrays.asList("set_1", "set_2")) + Collections.singletonList(SplitFilter.bySet(Arrays.asList("set_1", "set_2"))) ); } From 9820f6563d991b631a61fe1be39c5b7cd3bcc154 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 7 Sep 2023 18:40:11 -0300 Subject: [PATCH 52/89] Refactor internal filter tracking to use a Map --- .../split/android/client/FilterBuilder.java | 28 +++++++-------- .../split/android/client/FilterGrouper.java | 12 +++++-- .../android/client/SplitFactoryHelper.java | 28 +++++---------- .../android/client/SplitFactoryImpl.java | 10 +++--- .../localhost/LocalhostSplitFactory.java | 10 ++++-- .../executor/SplitTaskFactoryImpl.java | 13 ++++--- .../service/splits/SplitChangeProcessor.java | 6 ++-- .../synchronizer/WorkManagerWrapper.java | 4 +-- .../android/client/FilterGrouperTest.java | 36 ++++++++++++------- .../splits/SplitChangeProcessorTest.java | 5 +-- 10 files changed, 84 insertions(+), 68 deletions(-) diff --git a/src/main/java/io/split/android/client/FilterBuilder.java b/src/main/java/io/split/android/client/FilterBuilder.java index 335fcb841..88d1cd5c2 100644 --- a/src/main/java/io/split/android/client/FilterBuilder.java +++ b/src/main/java/io/split/android/client/FilterBuilder.java @@ -6,14 +6,14 @@ import androidx.annotation.Nullable; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.SortedSet; +import java.util.TreeMap; import java.util.TreeSet; import io.split.android.client.utils.logger.Logger; -import io.split.android.client.utils.StringHelper; public class FilterBuilder { @@ -30,18 +30,15 @@ public FilterBuilder(List filters) { } public String buildQueryString() { - - if (mFilters.size() == 0) { + if (mFilters.isEmpty()) { return ""; } - StringHelper stringHelper = new StringHelper(); StringBuilder queryString = new StringBuilder(); - List sortedFilters = getGroupedFilter(); - Collections.sort(sortedFilters, new SplitFilterComparator()); + Map sortedFilters = getGroupedFilter(); - for (SplitFilter splitFilter : sortedFilters) { + for (SplitFilter splitFilter : sortedFilters.values()) { SplitFilter.Type filterType = splitFilter.getType(); SortedSet deduptedValues = new TreeSet<>(splitFilter.getValues()); if (deduptedValues.size() < splitFilter.getValues().size()) { @@ -56,15 +53,18 @@ public String buildQueryString() { queryString.append("&"); queryString.append(filterType.queryStringField()); queryString.append("="); - queryString.append(stringHelper.join(",", deduptedValues)); + queryString.append(String.join(",", deduptedValues)); } return queryString.toString(); } @NonNull - public List getGroupedFilter() { - return new ArrayList<>(mFilterGrouper.group(mFilters)); + public Map getGroupedFilter() { + TreeMap sortedFilters = new TreeMap<>(new SplitFilterTypeComparator()); + sortedFilters.putAll(mFilterGrouper.group(mFilters)); + + return sortedFilters; } private void addFilters(List filters) { @@ -103,10 +103,10 @@ private void validateFilterSize(SplitFilter.Type type, int size) { } } - private static class SplitFilterComparator implements Comparator { + private static class SplitFilterTypeComparator implements Comparator { @Override - public int compare(SplitFilter o1, SplitFilter o2) { - return o1.getType().compareTo(o2.getType()); + public int compare(SplitFilter.Type o1, SplitFilter.Type o2) { + return o1.compareTo(o2); } } } diff --git a/src/main/java/io/split/android/client/FilterGrouper.java b/src/main/java/io/split/android/client/FilterGrouper.java index 6e5e39fbb..7b9f52746 100644 --- a/src/main/java/io/split/android/client/FilterGrouper.java +++ b/src/main/java/io/split/android/client/FilterGrouper.java @@ -7,7 +7,12 @@ class FilterGrouper { - List group(List filters) { + /** + * Groups filters by type + * @param filters list of filters to group + * @return map of grouped filters. The key is the filter type, the value is the filter + */ + Map group(List filters) { Map> groupedValues = new HashMap<>(); for (SplitFilter filter : filters) { List groupValues = groupedValues.get(filter.getType()); @@ -18,12 +23,13 @@ List group(List filters) { groupValues.addAll(filter.getValues()); } - List groupedFilters = new ArrayList<>(); + Map groupedFilters = new HashMap<>(); for (Map.Entry> filterEntry : groupedValues.entrySet()) { if (filterEntry.getValue().size() > 0) { - groupedFilters.add(new SplitFilter(filterEntry.getKey(), filterEntry.getValue())); + groupedFilters.put(filterEntry.getKey(), new SplitFilter(filterEntry.getKey(), filterEntry.getValue())); } } + return groupedFilters; } } diff --git a/src/main/java/io/split/android/client/SplitFactoryHelper.java b/src/main/java/io/split/android/client/SplitFactoryHelper.java index 83cb7a841..6541a05e7 100644 --- a/src/main/java/io/split/android/client/SplitFactoryHelper.java +++ b/src/main/java/io/split/android/client/SplitFactoryHelper.java @@ -10,11 +10,8 @@ import java.io.File; import java.net.URI; import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; +import java.util.HashMap; import java.util.Map; -import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; @@ -189,9 +186,12 @@ SplitApiFacade buildApiFacade(SplitClientConfig splitClientConfig, } WorkManagerWrapper buildWorkManagerWrapper(Context context, SplitClientConfig splitClientConfig, - String apiKey, String databaseName, List filters) { + String apiKey, String databaseName, Map filters) { + SplitFilter filter = filters.get(SplitFilter.Type.BY_SET) != null ? + filters.get(SplitFilter.Type.BY_SET) : + filters.get(SplitFilter.Type.BY_NAME); return new WorkManagerWrapper( - WorkManager.getInstance(context), splitClientConfig, apiKey, databaseName, filters); + WorkManager.getInstance(context), splitClientConfig, apiKey, databaseName, filter); } @@ -414,27 +414,17 @@ SplitUpdatesWorker getSplitUpdatesWorker(SplitClientConfig config, return null; } - Pair, String>, Set> getFilterConfiguration(SyncConfig syncConfig) { + Pair, String> getFilterConfiguration(SyncConfig syncConfig) { String splitsFilterQueryString = null; - List groupedFilters = new ArrayList<>(); - Set configuredFlagSets = new HashSet<>(); + Map groupedFilters = new HashMap<>(); if (syncConfig != null) { FilterBuilder filterBuilder = new FilterBuilder(syncConfig.getFilters()); groupedFilters = filterBuilder.getGroupedFilter(); splitsFilterQueryString = filterBuilder.buildQueryString(); - - if (!groupedFilters.isEmpty()) { - SplitFilter splitFilter = groupedFilters.get(0); - - // In the case of BY_SET, all filters will be grouped into one with the {@link SplitFilter.Type#BY_SET} type - if (splitFilter != null && splitFilter.getType() == SplitFilter.Type.BY_SET) { - configuredFlagSets.addAll(splitFilter.getValues()); - } - } } - return new Pair<>(new Pair<>(groupedFilters, splitsFilterQueryString), configuredFlagSets); + return new Pair<>(groupedFilters, splitsFilterQueryString); } private TelemetryStorage getTelemetryStorage(boolean shouldRecordTelemetry, TelemetryStorage telemetryStorage) { diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index af1cecedc..a6b3421c7 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -7,7 +7,9 @@ import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import io.split.android.client.api.Key; @@ -171,10 +173,10 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { mStorageContainer = factoryHelper.buildStorageContainer(config.userConsent(), splitDatabase, config.shouldRecordTelemetry(), splitCipher, telemetryStorage); - Pair, String>, Set> filtersConfig = factoryHelper.getFilterConfiguration(config.syncConfig()); - List filters = filtersConfig.first.first; - String splitsFilterQueryStringFromConfig = filtersConfig.first.second; - Set configuredFlagSets = filtersConfig.second; + Pair, String> filtersConfig = factoryHelper.getFilterConfiguration(config.syncConfig()); + Map filters = filtersConfig.first; + String splitsFilterQueryStringFromConfig = filtersConfig.second; + Set configuredFlagSets = (filters.get(SplitFilter.Type.BY_SET) != null) ? new HashSet<>(filters.get(SplitFilter.Type.BY_SET).getValues()) : new HashSet<>(); SplitApiFacade splitApiFacade = factoryHelper.buildApiFacade( config, defaultHttpClient, splitsFilterQueryStringFromConfig); diff --git a/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java b/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java index de3efad14..1f79f0e8d 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import io.split.android.client.FilterBuilder; @@ -76,11 +77,14 @@ public LocalhostSplitFactory(String key, Context context, Set configuredSets = new HashSet<>(); if (config.syncConfig() != null) { - List groupedFilters = new FilterBuilder(config.syncConfig().getFilters()) + Map groupedFilters = new FilterBuilder(config.syncConfig().getFilters()) .getGroupedFilter(); - if (!groupedFilters.isEmpty() && groupedFilters.get(0) != null && groupedFilters.get(0).getType() == SplitFilter.Type.BY_SET) { - configuredSets.addAll(groupedFilters.get(0).getValues()); + if (!groupedFilters.isEmpty()) { + SplitFilter bySetFilter = groupedFilters.get(SplitFilter.Type.BY_SET); + if (bySetFilter != null) { + configuredSets.addAll(bySetFilter.getValues()); + } } } diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java index e828a1cdb..8df128d86 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java @@ -66,7 +66,7 @@ public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig, @NonNull SplitStorageContainer splitStorageContainer, @Nullable String splitsFilterQueryString, ISplitEventsManager eventsManager, - @Nullable List filters, + @Nullable Map filters, @Nullable TestingConfig testingConfig) { mSplitClientConfig = checkNotNull(splitClientConfig); @@ -91,13 +91,16 @@ public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig, mTelemetryRuntimeProducer); } - mFilters = (filters == null) ? new ArrayList<>() : filters; + mFilters = (filters == null) ? new ArrayList<>() : new ArrayList<>(filters.values()); int flagSetCount = 0; int invalidFlagSetCount = 0; - if (!mFilters.isEmpty() && mFilters.get(0) != null && mFilters.get(0).getType() == SplitFilter.Type.BY_SET) { - flagSetCount = mFilters.get(0).getValues().size(); - invalidFlagSetCount = mFilters.get(0).getInvalidValueCount(); + if (filters != null && !filters.isEmpty()) { + SplitFilter bySetFilter = filters.get(SplitFilter.Type.BY_SET); + if (bySetFilter != null) { + flagSetCount = bySetFilter.getValues().size(); + invalidFlagSetCount = bySetFilter.getInvalidValueCount(); + } } mTelemetryTaskFactory = new TelemetryTaskFactoryImpl(mSplitApiFacade.getTelemetryConfigRecorder(), diff --git a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java index b709beb68..e92ea1bf9 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java +++ b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java @@ -7,11 +7,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import io.split.android.client.SplitFilter; import io.split.android.client.dtos.Split; import io.split.android.client.dtos.SplitChange; -import io.split.android.client.dtos.Status; import io.split.android.client.storage.splits.ProcessedSplitChange; public class SplitChangeProcessor { @@ -25,12 +25,12 @@ public class SplitChangeProcessor { this((SplitFilter) null); } - public SplitChangeProcessor(@Nullable List filters) { + public SplitChangeProcessor(@Nullable Map filters) { // We're only supporting one filter type if (filters == null || filters.isEmpty()) { mSplitFilter = null; } else { - mSplitFilter = filters.get(0); + mSplitFilter = filters.values().iterator().next(); } mStatusProcessStrategy = new StatusProcessStrategy(); diff --git a/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java b/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java index b2beb4b08..8a255706e 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java +++ b/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java @@ -53,14 +53,14 @@ public WorkManagerWrapper(@NonNull WorkManager workManager, @NonNull SplitClientConfig splitClientConfig, @NonNull String apiKey, @NonNull String databaseName, - @Nullable List filters) { + @Nullable SplitFilter filter) { mWorkManager = checkNotNull(workManager); mDatabaseName = checkNotNull(databaseName); mSplitClientConfig = checkNotNull(splitClientConfig); mApiKey = checkNotNull(apiKey); mShouldLoadFromLocal = new HashSet<>(); mConstraints = buildConstraints(); - mFilter = (filters != null && filters.size() == 1) ? filters.get(0) : null; + mFilter = filter; } public void setFetcherExecutionListener(SplitTaskExecutionListener fetcherExecutionListener) { diff --git a/src/test/java/io/split/android/client/FilterGrouperTest.java b/src/test/java/io/split/android/client/FilterGrouperTest.java index c5b5e6868..e3a7fbcec 100644 --- a/src/test/java/io/split/android/client/FilterGrouperTest.java +++ b/src/test/java/io/split/android/client/FilterGrouperTest.java @@ -1,11 +1,16 @@ package io.split.android.client; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import org.junit.Assert; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; public class FilterGrouperTest { @@ -13,18 +18,23 @@ public class FilterGrouperTest { @Test public void groupingFilters() { - List ungropedFilters = new ArrayList<>(); - ungropedFilters.add(SplitFilter.byName(Arrays.asList("f1", "f2", "f3"))); - ungropedFilters.add(SplitFilter.byName(Arrays.asList("f2", "f3", "f4"))); - ungropedFilters.add(SplitFilter.byName(Arrays.asList("f4", "f5", "f6"))); - ungropedFilters.add(SplitFilter.byPrefix(Arrays.asList("f1", "f2", "f3"))); - ungropedFilters.add(SplitFilter.byPrefix(Arrays.asList("f2", "f3", "f4"))); - ungropedFilters.add(SplitFilter.byPrefix(Arrays.asList("f4", "f5", "f6"))); - - List groupedFiltes = mFilterGrouper.group(ungropedFilters); - - /// This compoe - Assert.assertEquals(2, groupedFiltes.size()); + List ungroupedFilters = new ArrayList<>(); + ungroupedFilters.add(SplitFilter.byName(Arrays.asList("f1", "f2", "f3"))); + ungroupedFilters.add(SplitFilter.byName(Arrays.asList("f2", "f3", "f4"))); + ungroupedFilters.add(SplitFilter.byName(Arrays.asList("f4", "f5", "f6"))); + ungroupedFilters.add(SplitFilter.byPrefix(Arrays.asList("f1", "f2", "f3"))); + ungroupedFilters.add(SplitFilter.byPrefix(Arrays.asList("f2", "f3", "f4"))); + ungroupedFilters.add(SplitFilter.byPrefix(Arrays.asList("f4", "f5", "f6"))); + ungroupedFilters.add(SplitFilter.bySet(Arrays.asList("f1", "f2", "f3"))); + ungroupedFilters.add(SplitFilter.bySet(Arrays.asList("f2", "f3", "f4"))); + ungroupedFilters.add(SplitFilter.bySet(Arrays.asList("f4", "f5", "f6"))); + + Map groupedFilters = mFilterGrouper.group(ungroupedFilters); + + // this class only merges filters of the same type + assertEquals(3, groupedFilters.size()); + assertTrue(groupedFilters.containsKey(SplitFilter.Type.BY_NAME)); + assertTrue(groupedFilters.containsKey(SplitFilter.Type.BY_PREFIX)); + assertTrue(groupedFilters.containsKey(SplitFilter.Type.BY_SET)); } - } diff --git a/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java b/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java index a2830c7ef..237228a61 100644 --- a/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java +++ b/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java @@ -11,6 +11,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import io.split.android.client.SplitFilter; @@ -206,8 +207,8 @@ public void featureFlagsAreFilteredByNameWhenThereIsSplitFilterByName() { @Test public void creatingWithNullFilterProcessesEverything() { - List filterList = null; - mProcessor = new SplitChangeProcessor(filterList); + Map filterMap = null; + mProcessor = new SplitChangeProcessor(filterMap); Split split1 = newSplit("split_1", Status.ACTIVE); Split split2 = newSplit("split_2", Status.ARCHIVED); From 0b9f11619483969a02fe2318a3c8b594cb0abb3e Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 7 Sep 2023 18:47:37 -0300 Subject: [PATCH 53/89] Fix test --- .../client/service/synchronizer/WorkManagerWrapperTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/split/android/client/service/synchronizer/WorkManagerWrapperTest.java b/src/test/java/io/split/android/client/service/synchronizer/WorkManagerWrapperTest.java index a79498fa4..edfbd899e 100644 --- a/src/test/java/io/split/android/client/service/synchronizer/WorkManagerWrapperTest.java +++ b/src/test/java/io/split/android/client/service/synchronizer/WorkManagerWrapperTest.java @@ -73,7 +73,7 @@ public void setUp() throws Exception { splitClientConfig, "api_key", "test_database_name", - Collections.singletonList(SplitFilter.bySet(Arrays.asList("set_1", "set_2"))) + SplitFilter.bySet(Arrays.asList("set_1", "set_2")) ); } From 13cae88fbc3c13febfb9717df5e3fe21479ed1f2 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 8 Sep 2023 10:24:00 -0300 Subject: [PATCH 54/89] Cleanup --- .../android/client/SplitFactoryHelper.java | 14 ++++++ .../android/client/SplitFactoryImpl.java | 3 +- .../executor/SplitTaskFactoryImpl.java | 44 +++++++++++-------- 3 files changed, 40 insertions(+), 21 deletions(-) diff --git a/src/main/java/io/split/android/client/SplitFactoryHelper.java b/src/main/java/io/split/android/client/SplitFactoryHelper.java index 6541a05e7..399a8740a 100644 --- a/src/main/java/io/split/android/client/SplitFactoryHelper.java +++ b/src/main/java/io/split/android/client/SplitFactoryHelper.java @@ -11,7 +11,9 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; @@ -427,6 +429,18 @@ Pair, String> getFilterConfiguration(SyncConf return new Pair<>(groupedFilters, splitsFilterQueryString); } + @NonNull + Set getConfiguredFlagSets(Map filters) { + Set configuredFlagSets; + SplitFilter flagSetSplitFilter = filters.get(SplitFilter.Type.BY_SET); + if (flagSetSplitFilter != null) { + configuredFlagSets = new HashSet<>(flagSetSplitFilter.getValues()); + } else { + configuredFlagSets = new HashSet<>(); + } + return configuredFlagSets; + } + private TelemetryStorage getTelemetryStorage(boolean shouldRecordTelemetry, TelemetryStorage telemetryStorage) { if (telemetryStorage != null) { return telemetryStorage; diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index a6b3421c7..c387d9d55 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -176,7 +176,6 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { Pair, String> filtersConfig = factoryHelper.getFilterConfiguration(config.syncConfig()); Map filters = filtersConfig.first; String splitsFilterQueryStringFromConfig = filtersConfig.second; - Set configuredFlagSets = (filters.get(SplitFilter.Type.BY_SET) != null) ? new HashSet<>(filters.get(SplitFilter.Type.BY_SET).getValues()) : new HashSet<>(); SplitApiFacade splitApiFacade = factoryHelper.buildApiFacade( config, defaultHttpClient, splitsFilterQueryStringFromConfig); @@ -271,7 +270,7 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { telemetrySynchronizer, mStorageContainer, splitTaskExecutor, splitApiFacade, validationLogger, keyValidator, customerImpressionListener, streamingComponents.getPushNotificationManager(), componentsRegister, workManagerWrapper, - eventsTracker, configuredFlagSets); + eventsTracker, factoryHelper.getConfiguredFlagSets(filters)); mDestroyer = new Runnable() { public void run() { Logger.w("Shutdown called for split"); diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java index 8df128d86..e91cd8c3a 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java @@ -92,25 +92,7 @@ public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig, } mFilters = (filters == null) ? new ArrayList<>() : new ArrayList<>(filters.values()); - - int flagSetCount = 0; - int invalidFlagSetCount = 0; - if (filters != null && !filters.isEmpty()) { - SplitFilter bySetFilter = filters.get(SplitFilter.Type.BY_SET); - if (bySetFilter != null) { - flagSetCount = bySetFilter.getValues().size(); - invalidFlagSetCount = bySetFilter.getInvalidValueCount(); - } - } - - mTelemetryTaskFactory = new TelemetryTaskFactoryImpl(mSplitApiFacade.getTelemetryConfigRecorder(), - mSplitApiFacade.getTelemetryStatsRecorder(), - telemetryStorage, - splitClientConfig, - mSplitsStorageContainer.getSplitsStorage(), - mSplitsStorageContainer.getMySegmentsStorageContainer(), - flagSetCount, - invalidFlagSetCount); + mTelemetryTaskFactory = initializeTelemetryTaskFactory(splitClientConfig, filters, telemetryStorage); } @Override @@ -212,4 +194,28 @@ public TelemetryStatsRecorderTask getTelemetryStatsRecorderTask() { public SplitInPlaceUpdateTask createSplitsUpdateTask(Split featureFlag, long since) { return new SplitInPlaceUpdateTask(mSplitsStorageContainer.getSplitsStorage(), mSplitChangeProcessor, mEventsManager, mTelemetryRuntimeProducer, featureFlag, since); } + + @NonNull + private TelemetryTaskFactory initializeTelemetryTaskFactory(@NonNull SplitClientConfig splitClientConfig, @Nullable Map filters, TelemetryStorage telemetryStorage) { + final TelemetryTaskFactory mTelemetryTaskFactory; + int flagSetCount = 0; + int invalidFlagSetCount = 0; + if (filters != null && !filters.isEmpty()) { + SplitFilter bySetFilter = filters.get(SplitFilter.Type.BY_SET); + if (bySetFilter != null) { + flagSetCount = bySetFilter.getValues().size(); + invalidFlagSetCount = bySetFilter.getInvalidValueCount(); + } + } + + mTelemetryTaskFactory = new TelemetryTaskFactoryImpl(mSplitApiFacade.getTelemetryConfigRecorder(), + mSplitApiFacade.getTelemetryStatsRecorder(), + telemetryStorage, + splitClientConfig, + mSplitsStorageContainer.getSplitsStorage(), + mSplitsStorageContainer.getMySegmentsStorageContainer(), + flagSetCount, + invalidFlagSetCount); + return mTelemetryTaskFactory; + } } From b7c77fa7071fe6e78cdbf9cd863a78fe62f58692 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 8 Sep 2023 13:54:30 -0300 Subject: [PATCH 55/89] Discard sets that are not configured; do not process according to filter when there are no values --- .../splits/FeatureFlagProcessStrategy.java | 3 +- .../service/splits/SplitChangeProcessor.java | 2 +- .../splits/SplitChangeProcessorTest.java | 56 +++++++++++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java b/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java index 4de68e5a7..2f50f6bbb 100644 --- a/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java +++ b/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java @@ -63,7 +63,8 @@ public void process(List activeFeatureFlags, List archivedFeatureF boolean shouldArchive = true; for (String set : featureFlag.sets) { if (mConfiguredValues.contains(set)) { - // If the feature flag has at least one set that matches the configured sets, + featureFlag.sets.retainAll(mConfiguredValues); // Remove all sets that don't match the configured sets + // Since the feature flag has at least one set that matches the configured sets, // we process it according to its status mStatusProcessStrategy.process(activeFeatureFlags, archivedFeatureFlags, featureFlag); shouldArchive = false; diff --git a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java index b709beb68..47ea3541f 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java +++ b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java @@ -72,7 +72,7 @@ private ProcessedSplitChange buildProcessedSplitChange(List featureFlags, } private FeatureFlagProcessStrategy getProcessStrategy(SplitFilter splitFilter) { - if (splitFilter == null) { + if (splitFilter == null || splitFilter.getValues().isEmpty()) { return mStatusProcessStrategy; } diff --git a/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java b/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java index a2830c7ef..8821cbf5a 100644 --- a/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java +++ b/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java @@ -223,6 +223,62 @@ public void creatingWithNullFilterProcessesEverything() { Assert.assertEquals(2, result.getArchivedSplits().size()); } + @Test + public void creatingWithFilterWithEmptyConfiguredValuesProcessesEverything() { + List filterList = Collections.singletonList(SplitFilter.bySet(Collections.emptyList())); + + mProcessor = new SplitChangeProcessor(filterList); + + Split split1 = newSplit("split_1", Status.ACTIVE); + Split split2 = newSplit("split_2", Status.ARCHIVED); + Split split3 = newSplit("split_3", Status.ACTIVE); + Split split4 = newSplit("split_4", Status.ARCHIVED); + + SplitChange splitChange = new SplitChange(); + splitChange.splits = Arrays.asList(split1, split2, split3, split4); + + ProcessedSplitChange result = mProcessor.process(splitChange); + + Assert.assertEquals(2, result.getActiveSplits().size()); + Assert.assertEquals(2, result.getArchivedSplits().size()); + } + + @Test + public void nonConfiguredSetsAreRemovedFromSplit() { + Set configuredSets = new HashSet<>(); + configuredSets.add("set_1"); + configuredSets.add("set_2"); + SplitFilter filter = SplitFilter.bySet(new ArrayList<>(configuredSets)); + mProcessor = new SplitChangeProcessor(filter); + + Split split1 = newSplit("split_1", Status.ACTIVE, new HashSet<>(Arrays.asList("set_1", "set_3"))); + Split split2 = newSplit("split_2", Status.ACTIVE, new HashSet<>(Arrays.asList("set_2", "set_asd"))); + Split split3 = newSplit("split_3", Status.ACTIVE, new HashSet<>(Collections.singletonList("set_3"))); + int initialSplit1Sets = split1.sets.size(); + int initialSplit2Sets = split2.sets.size(); + + SplitChange splitChange = new SplitChange(); + splitChange.splits = Arrays.asList(split1, split2, split3); + + ProcessedSplitChange result = mProcessor.process(splitChange); + + Assert.assertEquals(2, result.getActiveSplits().size()); + Assert.assertEquals(1, result.getArchivedSplits().size()); + + Split processedSplit1 = result.getActiveSplits().get(0); + Assert.assertEquals(split1.name, processedSplit1.name); + Assert.assertEquals(2, initialSplit1Sets); + Assert.assertEquals(1, processedSplit1.sets.size()); + Assert.assertTrue(processedSplit1.sets.contains("set_1")); + Assert.assertFalse(processedSplit1.sets.contains("set_3")); + + Split processedSplit2 = result.getActiveSplits().get(1); + Assert.assertEquals(split2.name, processedSplit2.name); + Assert.assertEquals(2, initialSplit2Sets); + Assert.assertEquals(1, processedSplit2.sets.size()); + Assert.assertTrue(processedSplit2.sets.contains("set_2")); + } + private List createSplits(int from, int count, Status status) { List splits = new ArrayList<>(); for (int i = from; i < count + from; i++) { From 78070d13119f9484cdbca1c9ea503febcae8c5b2 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 7 Sep 2023 18:40:11 -0300 Subject: [PATCH 56/89] Refactor internal filter tracking to use a Map --- .../split/android/client/FilterBuilder.java | 28 +++++++-------- .../split/android/client/FilterGrouper.java | 12 +++++-- .../android/client/SplitFactoryHelper.java | 28 +++++---------- .../android/client/SplitFactoryImpl.java | 10 +++--- .../localhost/LocalhostSplitFactory.java | 10 ++++-- .../executor/SplitTaskFactoryImpl.java | 13 ++++--- .../service/splits/SplitChangeProcessor.java | 6 ++-- .../synchronizer/WorkManagerWrapper.java | 4 +-- .../android/client/FilterGrouperTest.java | 36 ++++++++++++------- .../splits/SplitChangeProcessorTest.java | 5 +-- 10 files changed, 84 insertions(+), 68 deletions(-) diff --git a/src/main/java/io/split/android/client/FilterBuilder.java b/src/main/java/io/split/android/client/FilterBuilder.java index 335fcb841..88d1cd5c2 100644 --- a/src/main/java/io/split/android/client/FilterBuilder.java +++ b/src/main/java/io/split/android/client/FilterBuilder.java @@ -6,14 +6,14 @@ import androidx.annotation.Nullable; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.SortedSet; +import java.util.TreeMap; import java.util.TreeSet; import io.split.android.client.utils.logger.Logger; -import io.split.android.client.utils.StringHelper; public class FilterBuilder { @@ -30,18 +30,15 @@ public FilterBuilder(List filters) { } public String buildQueryString() { - - if (mFilters.size() == 0) { + if (mFilters.isEmpty()) { return ""; } - StringHelper stringHelper = new StringHelper(); StringBuilder queryString = new StringBuilder(); - List sortedFilters = getGroupedFilter(); - Collections.sort(sortedFilters, new SplitFilterComparator()); + Map sortedFilters = getGroupedFilter(); - for (SplitFilter splitFilter : sortedFilters) { + for (SplitFilter splitFilter : sortedFilters.values()) { SplitFilter.Type filterType = splitFilter.getType(); SortedSet deduptedValues = new TreeSet<>(splitFilter.getValues()); if (deduptedValues.size() < splitFilter.getValues().size()) { @@ -56,15 +53,18 @@ public String buildQueryString() { queryString.append("&"); queryString.append(filterType.queryStringField()); queryString.append("="); - queryString.append(stringHelper.join(",", deduptedValues)); + queryString.append(String.join(",", deduptedValues)); } return queryString.toString(); } @NonNull - public List getGroupedFilter() { - return new ArrayList<>(mFilterGrouper.group(mFilters)); + public Map getGroupedFilter() { + TreeMap sortedFilters = new TreeMap<>(new SplitFilterTypeComparator()); + sortedFilters.putAll(mFilterGrouper.group(mFilters)); + + return sortedFilters; } private void addFilters(List filters) { @@ -103,10 +103,10 @@ private void validateFilterSize(SplitFilter.Type type, int size) { } } - private static class SplitFilterComparator implements Comparator { + private static class SplitFilterTypeComparator implements Comparator { @Override - public int compare(SplitFilter o1, SplitFilter o2) { - return o1.getType().compareTo(o2.getType()); + public int compare(SplitFilter.Type o1, SplitFilter.Type o2) { + return o1.compareTo(o2); } } } diff --git a/src/main/java/io/split/android/client/FilterGrouper.java b/src/main/java/io/split/android/client/FilterGrouper.java index 6e5e39fbb..7b9f52746 100644 --- a/src/main/java/io/split/android/client/FilterGrouper.java +++ b/src/main/java/io/split/android/client/FilterGrouper.java @@ -7,7 +7,12 @@ class FilterGrouper { - List group(List filters) { + /** + * Groups filters by type + * @param filters list of filters to group + * @return map of grouped filters. The key is the filter type, the value is the filter + */ + Map group(List filters) { Map> groupedValues = new HashMap<>(); for (SplitFilter filter : filters) { List groupValues = groupedValues.get(filter.getType()); @@ -18,12 +23,13 @@ List group(List filters) { groupValues.addAll(filter.getValues()); } - List groupedFilters = new ArrayList<>(); + Map groupedFilters = new HashMap<>(); for (Map.Entry> filterEntry : groupedValues.entrySet()) { if (filterEntry.getValue().size() > 0) { - groupedFilters.add(new SplitFilter(filterEntry.getKey(), filterEntry.getValue())); + groupedFilters.put(filterEntry.getKey(), new SplitFilter(filterEntry.getKey(), filterEntry.getValue())); } } + return groupedFilters; } } diff --git a/src/main/java/io/split/android/client/SplitFactoryHelper.java b/src/main/java/io/split/android/client/SplitFactoryHelper.java index 83cb7a841..6541a05e7 100644 --- a/src/main/java/io/split/android/client/SplitFactoryHelper.java +++ b/src/main/java/io/split/android/client/SplitFactoryHelper.java @@ -10,11 +10,8 @@ import java.io.File; import java.net.URI; import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; +import java.util.HashMap; import java.util.Map; -import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; @@ -189,9 +186,12 @@ SplitApiFacade buildApiFacade(SplitClientConfig splitClientConfig, } WorkManagerWrapper buildWorkManagerWrapper(Context context, SplitClientConfig splitClientConfig, - String apiKey, String databaseName, List filters) { + String apiKey, String databaseName, Map filters) { + SplitFilter filter = filters.get(SplitFilter.Type.BY_SET) != null ? + filters.get(SplitFilter.Type.BY_SET) : + filters.get(SplitFilter.Type.BY_NAME); return new WorkManagerWrapper( - WorkManager.getInstance(context), splitClientConfig, apiKey, databaseName, filters); + WorkManager.getInstance(context), splitClientConfig, apiKey, databaseName, filter); } @@ -414,27 +414,17 @@ SplitUpdatesWorker getSplitUpdatesWorker(SplitClientConfig config, return null; } - Pair, String>, Set> getFilterConfiguration(SyncConfig syncConfig) { + Pair, String> getFilterConfiguration(SyncConfig syncConfig) { String splitsFilterQueryString = null; - List groupedFilters = new ArrayList<>(); - Set configuredFlagSets = new HashSet<>(); + Map groupedFilters = new HashMap<>(); if (syncConfig != null) { FilterBuilder filterBuilder = new FilterBuilder(syncConfig.getFilters()); groupedFilters = filterBuilder.getGroupedFilter(); splitsFilterQueryString = filterBuilder.buildQueryString(); - - if (!groupedFilters.isEmpty()) { - SplitFilter splitFilter = groupedFilters.get(0); - - // In the case of BY_SET, all filters will be grouped into one with the {@link SplitFilter.Type#BY_SET} type - if (splitFilter != null && splitFilter.getType() == SplitFilter.Type.BY_SET) { - configuredFlagSets.addAll(splitFilter.getValues()); - } - } } - return new Pair<>(new Pair<>(groupedFilters, splitsFilterQueryString), configuredFlagSets); + return new Pair<>(groupedFilters, splitsFilterQueryString); } private TelemetryStorage getTelemetryStorage(boolean shouldRecordTelemetry, TelemetryStorage telemetryStorage) { diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index af1cecedc..a6b3421c7 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -7,7 +7,9 @@ import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import io.split.android.client.api.Key; @@ -171,10 +173,10 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { mStorageContainer = factoryHelper.buildStorageContainer(config.userConsent(), splitDatabase, config.shouldRecordTelemetry(), splitCipher, telemetryStorage); - Pair, String>, Set> filtersConfig = factoryHelper.getFilterConfiguration(config.syncConfig()); - List filters = filtersConfig.first.first; - String splitsFilterQueryStringFromConfig = filtersConfig.first.second; - Set configuredFlagSets = filtersConfig.second; + Pair, String> filtersConfig = factoryHelper.getFilterConfiguration(config.syncConfig()); + Map filters = filtersConfig.first; + String splitsFilterQueryStringFromConfig = filtersConfig.second; + Set configuredFlagSets = (filters.get(SplitFilter.Type.BY_SET) != null) ? new HashSet<>(filters.get(SplitFilter.Type.BY_SET).getValues()) : new HashSet<>(); SplitApiFacade splitApiFacade = factoryHelper.buildApiFacade( config, defaultHttpClient, splitsFilterQueryStringFromConfig); diff --git a/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java b/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java index de3efad14..1f79f0e8d 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import io.split.android.client.FilterBuilder; @@ -76,11 +77,14 @@ public LocalhostSplitFactory(String key, Context context, Set configuredSets = new HashSet<>(); if (config.syncConfig() != null) { - List groupedFilters = new FilterBuilder(config.syncConfig().getFilters()) + Map groupedFilters = new FilterBuilder(config.syncConfig().getFilters()) .getGroupedFilter(); - if (!groupedFilters.isEmpty() && groupedFilters.get(0) != null && groupedFilters.get(0).getType() == SplitFilter.Type.BY_SET) { - configuredSets.addAll(groupedFilters.get(0).getValues()); + if (!groupedFilters.isEmpty()) { + SplitFilter bySetFilter = groupedFilters.get(SplitFilter.Type.BY_SET); + if (bySetFilter != null) { + configuredSets.addAll(bySetFilter.getValues()); + } } } diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java index e828a1cdb..8df128d86 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java @@ -66,7 +66,7 @@ public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig, @NonNull SplitStorageContainer splitStorageContainer, @Nullable String splitsFilterQueryString, ISplitEventsManager eventsManager, - @Nullable List filters, + @Nullable Map filters, @Nullable TestingConfig testingConfig) { mSplitClientConfig = checkNotNull(splitClientConfig); @@ -91,13 +91,16 @@ public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig, mTelemetryRuntimeProducer); } - mFilters = (filters == null) ? new ArrayList<>() : filters; + mFilters = (filters == null) ? new ArrayList<>() : new ArrayList<>(filters.values()); int flagSetCount = 0; int invalidFlagSetCount = 0; - if (!mFilters.isEmpty() && mFilters.get(0) != null && mFilters.get(0).getType() == SplitFilter.Type.BY_SET) { - flagSetCount = mFilters.get(0).getValues().size(); - invalidFlagSetCount = mFilters.get(0).getInvalidValueCount(); + if (filters != null && !filters.isEmpty()) { + SplitFilter bySetFilter = filters.get(SplitFilter.Type.BY_SET); + if (bySetFilter != null) { + flagSetCount = bySetFilter.getValues().size(); + invalidFlagSetCount = bySetFilter.getInvalidValueCount(); + } } mTelemetryTaskFactory = new TelemetryTaskFactoryImpl(mSplitApiFacade.getTelemetryConfigRecorder(), diff --git a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java index 47ea3541f..a24848d42 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java +++ b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java @@ -7,11 +7,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import io.split.android.client.SplitFilter; import io.split.android.client.dtos.Split; import io.split.android.client.dtos.SplitChange; -import io.split.android.client.dtos.Status; import io.split.android.client.storage.splits.ProcessedSplitChange; public class SplitChangeProcessor { @@ -25,12 +25,12 @@ public class SplitChangeProcessor { this((SplitFilter) null); } - public SplitChangeProcessor(@Nullable List filters) { + public SplitChangeProcessor(@Nullable Map filters) { // We're only supporting one filter type if (filters == null || filters.isEmpty()) { mSplitFilter = null; } else { - mSplitFilter = filters.get(0); + mSplitFilter = filters.values().iterator().next(); } mStatusProcessStrategy = new StatusProcessStrategy(); diff --git a/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java b/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java index b2beb4b08..8a255706e 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java +++ b/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java @@ -53,14 +53,14 @@ public WorkManagerWrapper(@NonNull WorkManager workManager, @NonNull SplitClientConfig splitClientConfig, @NonNull String apiKey, @NonNull String databaseName, - @Nullable List filters) { + @Nullable SplitFilter filter) { mWorkManager = checkNotNull(workManager); mDatabaseName = checkNotNull(databaseName); mSplitClientConfig = checkNotNull(splitClientConfig); mApiKey = checkNotNull(apiKey); mShouldLoadFromLocal = new HashSet<>(); mConstraints = buildConstraints(); - mFilter = (filters != null && filters.size() == 1) ? filters.get(0) : null; + mFilter = filter; } public void setFetcherExecutionListener(SplitTaskExecutionListener fetcherExecutionListener) { diff --git a/src/test/java/io/split/android/client/FilterGrouperTest.java b/src/test/java/io/split/android/client/FilterGrouperTest.java index c5b5e6868..e3a7fbcec 100644 --- a/src/test/java/io/split/android/client/FilterGrouperTest.java +++ b/src/test/java/io/split/android/client/FilterGrouperTest.java @@ -1,11 +1,16 @@ package io.split.android.client; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import org.junit.Assert; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; public class FilterGrouperTest { @@ -13,18 +18,23 @@ public class FilterGrouperTest { @Test public void groupingFilters() { - List ungropedFilters = new ArrayList<>(); - ungropedFilters.add(SplitFilter.byName(Arrays.asList("f1", "f2", "f3"))); - ungropedFilters.add(SplitFilter.byName(Arrays.asList("f2", "f3", "f4"))); - ungropedFilters.add(SplitFilter.byName(Arrays.asList("f4", "f5", "f6"))); - ungropedFilters.add(SplitFilter.byPrefix(Arrays.asList("f1", "f2", "f3"))); - ungropedFilters.add(SplitFilter.byPrefix(Arrays.asList("f2", "f3", "f4"))); - ungropedFilters.add(SplitFilter.byPrefix(Arrays.asList("f4", "f5", "f6"))); - - List groupedFiltes = mFilterGrouper.group(ungropedFilters); - - /// This compoe - Assert.assertEquals(2, groupedFiltes.size()); + List ungroupedFilters = new ArrayList<>(); + ungroupedFilters.add(SplitFilter.byName(Arrays.asList("f1", "f2", "f3"))); + ungroupedFilters.add(SplitFilter.byName(Arrays.asList("f2", "f3", "f4"))); + ungroupedFilters.add(SplitFilter.byName(Arrays.asList("f4", "f5", "f6"))); + ungroupedFilters.add(SplitFilter.byPrefix(Arrays.asList("f1", "f2", "f3"))); + ungroupedFilters.add(SplitFilter.byPrefix(Arrays.asList("f2", "f3", "f4"))); + ungroupedFilters.add(SplitFilter.byPrefix(Arrays.asList("f4", "f5", "f6"))); + ungroupedFilters.add(SplitFilter.bySet(Arrays.asList("f1", "f2", "f3"))); + ungroupedFilters.add(SplitFilter.bySet(Arrays.asList("f2", "f3", "f4"))); + ungroupedFilters.add(SplitFilter.bySet(Arrays.asList("f4", "f5", "f6"))); + + Map groupedFilters = mFilterGrouper.group(ungroupedFilters); + + // this class only merges filters of the same type + assertEquals(3, groupedFilters.size()); + assertTrue(groupedFilters.containsKey(SplitFilter.Type.BY_NAME)); + assertTrue(groupedFilters.containsKey(SplitFilter.Type.BY_PREFIX)); + assertTrue(groupedFilters.containsKey(SplitFilter.Type.BY_SET)); } - } diff --git a/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java b/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java index 8821cbf5a..d270ec649 100644 --- a/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java +++ b/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java @@ -11,6 +11,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import io.split.android.client.SplitFilter; @@ -206,8 +207,8 @@ public void featureFlagsAreFilteredByNameWhenThereIsSplitFilterByName() { @Test public void creatingWithNullFilterProcessesEverything() { - List filterList = null; - mProcessor = new SplitChangeProcessor(filterList); + Map filterMap = null; + mProcessor = new SplitChangeProcessor(filterMap); Split split1 = newSplit("split_1", Status.ACTIVE); Split split2 = newSplit("split_2", Status.ARCHIVED); From a0359e8641698404b82c9e1a15bb50166f46c15b Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 7 Sep 2023 18:47:37 -0300 Subject: [PATCH 57/89] Fix test --- .../client/service/synchronizer/WorkManagerWrapperTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/split/android/client/service/synchronizer/WorkManagerWrapperTest.java b/src/test/java/io/split/android/client/service/synchronizer/WorkManagerWrapperTest.java index a79498fa4..edfbd899e 100644 --- a/src/test/java/io/split/android/client/service/synchronizer/WorkManagerWrapperTest.java +++ b/src/test/java/io/split/android/client/service/synchronizer/WorkManagerWrapperTest.java @@ -73,7 +73,7 @@ public void setUp() throws Exception { splitClientConfig, "api_key", "test_database_name", - Collections.singletonList(SplitFilter.bySet(Arrays.asList("set_1", "set_2"))) + SplitFilter.bySet(Arrays.asList("set_1", "set_2")) ); } From 1b7004c20ad46da622f7c4f81c5bc3b9edbb2e0a Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 8 Sep 2023 10:24:00 -0300 Subject: [PATCH 58/89] Cleanup --- .../android/client/SplitFactoryHelper.java | 14 ++++++ .../android/client/SplitFactoryImpl.java | 3 +- .../executor/SplitTaskFactoryImpl.java | 44 +++++++++++-------- 3 files changed, 40 insertions(+), 21 deletions(-) diff --git a/src/main/java/io/split/android/client/SplitFactoryHelper.java b/src/main/java/io/split/android/client/SplitFactoryHelper.java index 6541a05e7..399a8740a 100644 --- a/src/main/java/io/split/android/client/SplitFactoryHelper.java +++ b/src/main/java/io/split/android/client/SplitFactoryHelper.java @@ -11,7 +11,9 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; @@ -427,6 +429,18 @@ Pair, String> getFilterConfiguration(SyncConf return new Pair<>(groupedFilters, splitsFilterQueryString); } + @NonNull + Set getConfiguredFlagSets(Map filters) { + Set configuredFlagSets; + SplitFilter flagSetSplitFilter = filters.get(SplitFilter.Type.BY_SET); + if (flagSetSplitFilter != null) { + configuredFlagSets = new HashSet<>(flagSetSplitFilter.getValues()); + } else { + configuredFlagSets = new HashSet<>(); + } + return configuredFlagSets; + } + private TelemetryStorage getTelemetryStorage(boolean shouldRecordTelemetry, TelemetryStorage telemetryStorage) { if (telemetryStorage != null) { return telemetryStorage; diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index a6b3421c7..c387d9d55 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -176,7 +176,6 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { Pair, String> filtersConfig = factoryHelper.getFilterConfiguration(config.syncConfig()); Map filters = filtersConfig.first; String splitsFilterQueryStringFromConfig = filtersConfig.second; - Set configuredFlagSets = (filters.get(SplitFilter.Type.BY_SET) != null) ? new HashSet<>(filters.get(SplitFilter.Type.BY_SET).getValues()) : new HashSet<>(); SplitApiFacade splitApiFacade = factoryHelper.buildApiFacade( config, defaultHttpClient, splitsFilterQueryStringFromConfig); @@ -271,7 +270,7 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { telemetrySynchronizer, mStorageContainer, splitTaskExecutor, splitApiFacade, validationLogger, keyValidator, customerImpressionListener, streamingComponents.getPushNotificationManager(), componentsRegister, workManagerWrapper, - eventsTracker, configuredFlagSets); + eventsTracker, factoryHelper.getConfiguredFlagSets(filters)); mDestroyer = new Runnable() { public void run() { Logger.w("Shutdown called for split"); diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java index 8df128d86..e91cd8c3a 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java @@ -92,25 +92,7 @@ public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig, } mFilters = (filters == null) ? new ArrayList<>() : new ArrayList<>(filters.values()); - - int flagSetCount = 0; - int invalidFlagSetCount = 0; - if (filters != null && !filters.isEmpty()) { - SplitFilter bySetFilter = filters.get(SplitFilter.Type.BY_SET); - if (bySetFilter != null) { - flagSetCount = bySetFilter.getValues().size(); - invalidFlagSetCount = bySetFilter.getInvalidValueCount(); - } - } - - mTelemetryTaskFactory = new TelemetryTaskFactoryImpl(mSplitApiFacade.getTelemetryConfigRecorder(), - mSplitApiFacade.getTelemetryStatsRecorder(), - telemetryStorage, - splitClientConfig, - mSplitsStorageContainer.getSplitsStorage(), - mSplitsStorageContainer.getMySegmentsStorageContainer(), - flagSetCount, - invalidFlagSetCount); + mTelemetryTaskFactory = initializeTelemetryTaskFactory(splitClientConfig, filters, telemetryStorage); } @Override @@ -212,4 +194,28 @@ public TelemetryStatsRecorderTask getTelemetryStatsRecorderTask() { public SplitInPlaceUpdateTask createSplitsUpdateTask(Split featureFlag, long since) { return new SplitInPlaceUpdateTask(mSplitsStorageContainer.getSplitsStorage(), mSplitChangeProcessor, mEventsManager, mTelemetryRuntimeProducer, featureFlag, since); } + + @NonNull + private TelemetryTaskFactory initializeTelemetryTaskFactory(@NonNull SplitClientConfig splitClientConfig, @Nullable Map filters, TelemetryStorage telemetryStorage) { + final TelemetryTaskFactory mTelemetryTaskFactory; + int flagSetCount = 0; + int invalidFlagSetCount = 0; + if (filters != null && !filters.isEmpty()) { + SplitFilter bySetFilter = filters.get(SplitFilter.Type.BY_SET); + if (bySetFilter != null) { + flagSetCount = bySetFilter.getValues().size(); + invalidFlagSetCount = bySetFilter.getInvalidValueCount(); + } + } + + mTelemetryTaskFactory = new TelemetryTaskFactoryImpl(mSplitApiFacade.getTelemetryConfigRecorder(), + mSplitApiFacade.getTelemetryStatsRecorder(), + telemetryStorage, + splitClientConfig, + mSplitsStorageContainer.getSplitsStorage(), + mSplitsStorageContainer.getMySegmentsStorageContainer(), + flagSetCount, + invalidFlagSetCount); + return mTelemetryTaskFactory; + } } From 77a894a7ee96794b98931688a08f93f7195e73c7 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 8 Sep 2023 18:06:16 -0300 Subject: [PATCH 59/89] WIP tests --- .../assets/split_changes_flag_set-0.json | 108 +++++++++ .../assets/split_changes_flag_set-1.json | 108 +++++++++ .../assets/split_changes_flag_set-2.json | 210 +++++++++++++++++ .../integration/sets/FlagSetsPollingTest.java | 223 ++++++++++++++++++ 4 files changed, 649 insertions(+) create mode 100644 src/androidTest/assets/split_changes_flag_set-0.json create mode 100644 src/androidTest/assets/split_changes_flag_set-1.json create mode 100644 src/androidTest/assets/split_changes_flag_set-2.json create mode 100644 src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java diff --git a/src/androidTest/assets/split_changes_flag_set-0.json b/src/androidTest/assets/split_changes_flag_set-0.json new file mode 100644 index 000000000..b3c0556d1 --- /dev/null +++ b/src/androidTest/assets/split_changes_flag_set-0.json @@ -0,0 +1,108 @@ +{ + "splits": [ + { + "trafficTypeName": "client", + "name": "workm", + "trafficAllocation": 100, + "trafficAllocationSeed": 147392224, + "seed": 524417105, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "on", + "changeNumber": 1602798638344, + "algo": 2, + "configurations": {}, + "sets": ["set_3"], + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "client", + "attribute": null + }, + "matcherType": "IN_SEGMENT", + "negate": false, + "userDefinedSegmentMatcherData": { + "segmentName": "new_segment" + }, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 0 + }, + { + "treatment": "free", + "size": 100 + }, + { + "treatment": "conta", + "size": 0 + } + ], + "label": "in segment new_segment" + }, + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "client", + "attribute": null + }, + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + }, + { + "treatment": "free", + "size": 0 + }, + { + "treatment": "conta", + "size": 0 + } + ], + "label": "default rule" + } + ] + } + ], + "since": 1602797638344, + "till": 1602798638344 +} diff --git a/src/androidTest/assets/split_changes_flag_set-1.json b/src/androidTest/assets/split_changes_flag_set-1.json new file mode 100644 index 000000000..6ed91ced6 --- /dev/null +++ b/src/androidTest/assets/split_changes_flag_set-1.json @@ -0,0 +1,108 @@ +{ + "splits": [ + { + "trafficTypeName": "client", + "name": "workm", + "trafficAllocation": 100, + "trafficAllocationSeed": 147392224, + "seed": 524417105, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "on", + "changeNumber": 1602797638344, + "algo": 2, + "configurations": {}, + "sets": ["set_1"], + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "client", + "attribute": null + }, + "matcherType": "IN_SEGMENT", + "negate": false, + "userDefinedSegmentMatcherData": { + "segmentName": "new_segment" + }, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 0 + }, + { + "treatment": "free", + "size": 100 + }, + { + "treatment": "conta", + "size": 0 + } + ], + "label": "in segment new_segment" + }, + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "client", + "attribute": null + }, + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + }, + { + "treatment": "free", + "size": 0 + }, + { + "treatment": "conta", + "size": 0 + } + ], + "label": "default rule" + } + ] + } + ], + "since": 1602796638344, + "till": 1602797638344 +} diff --git a/src/androidTest/assets/split_changes_flag_set-2.json b/src/androidTest/assets/split_changes_flag_set-2.json new file mode 100644 index 000000000..2d28b7582 --- /dev/null +++ b/src/androidTest/assets/split_changes_flag_set-2.json @@ -0,0 +1,210 @@ +{ + "splits": [ + { + "trafficTypeName": "client", + "name": "workm", + "trafficAllocation": 100, + "trafficAllocationSeed": 147392224, + "seed": 524417105, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "on", + "changeNumber": 1602796638344, + "algo": 2, + "configurations": {}, + "sets": ["set_1", "set_2"], + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "client", + "attribute": null + }, + "matcherType": "IN_SEGMENT", + "negate": false, + "userDefinedSegmentMatcherData": { + "segmentName": "new_segment" + }, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 0 + }, + { + "treatment": "free", + "size": 100 + }, + { + "treatment": "conta", + "size": 0 + } + ], + "label": "in segment new_segment" + }, + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "client", + "attribute": null + }, + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + }, + { + "treatment": "free", + "size": 0 + }, + { + "treatment": "conta", + "size": 0 + } + ], + "label": "default rule" + } + ] + }, + { + "trafficTypeName": "client", + "name": "workm_set_3", + "trafficAllocation": 100, + "trafficAllocationSeed": 147392224, + "seed": 524417105, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "on", + "changeNumber": 1602796638344, + "algo": 2, + "configurations": {}, + "sets": ["set_3"], + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "client", + "attribute": null + }, + "matcherType": "IN_SEGMENT", + "negate": false, + "userDefinedSegmentMatcherData": { + "segmentName": "new_segment" + }, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 0 + }, + { + "treatment": "free", + "size": 100 + }, + { + "treatment": "conta", + "size": 0 + } + ], + "label": "in segment new_segment" + }, + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "client", + "attribute": null + }, + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + }, + { + "treatment": "free", + "size": 0 + }, + { + "treatment": "conta", + "size": 0 + } + ], + "label": "default rule" + } + ] + } + ], + "since": -1, + "till": 1602796638344 +} diff --git a/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java b/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java new file mode 100644 index 000000000..126032207 --- /dev/null +++ b/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java @@ -0,0 +1,223 @@ +package tests.integration.sets; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import fake.HttpClientMock; +import fake.HttpResponseMock; +import fake.HttpResponseMockDispatcher; +import helper.DatabaseHelper; +import helper.FileHelper; +import helper.IntegrationHelper; +import helper.TestableSplitConfigBuilder; +import io.split.android.client.SplitClient; +import io.split.android.client.SplitClientConfig; +import io.split.android.client.SplitFactory; +import io.split.android.client.SplitFilter; +import io.split.android.client.SyncConfig; +import io.split.android.client.dtos.SplitChange; +import io.split.android.client.events.SplitEvent; +import io.split.android.client.events.SplitEventTask; +import io.split.android.client.storage.db.SplitEntity; +import io.split.android.client.storage.db.SplitRoomDatabase; +import io.split.android.client.utils.Json; +import io.split.android.client.utils.logger.Logger; + +public class FlagSetsPollingTest { + + private final FileHelper fileHelper = new FileHelper(); + private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); + private CountDownLatch hitsLatch; + private CountDownLatch firstChangeLatch; + private CountDownLatch secondChangeLatch; + private CountDownLatch thirdChangeLatch; + private SplitRoomDatabase mRoomDb; + + @Before + public void setUp() throws Exception { + mRoomDb = DatabaseHelper.getTestDatabase(mContext); + mRoomDb.clearAllTables(); + + hitsLatch = new CountDownLatch(3); + firstChangeLatch = new CountDownLatch(1); + secondChangeLatch = new CountDownLatch(1); + thirdChangeLatch = new CountDownLatch(1); + } + + @Test + public void featureFlagIsUpdatedAccordingToSetsWhenTheyAreConfigured() throws IOException, InterruptedException { + /* + This test creates a factory with 2 configured sets. + + The first split change will have 2 splits, one that belongs to set_1 and set_2 and one that belongs to set_3; + -> it should be added to storage + + The second change will have 1 split that belongs to set_1 only. + -> it should remain in storage and be updated + + The third change will have 1 split that belongs to set_3 only. + -> it should be removed from storage + */ + + createFactory(mContext, mRoomDb, false, "set_1", "set_2"); + + boolean awaitFirst = firstChangeLatch.await(5, TimeUnit.SECONDS); + Thread.sleep(200); + int firstSize = mRoomDb.splitDao().getAll().size(); + boolean firstSetsCorrect = mRoomDb.splitDao().getAll().get(0).getBody().contains("[\"set_1\",\"set_2\"]"); + + boolean awaitSecond = secondChangeLatch.await(5, TimeUnit.SECONDS); + Thread.sleep(200); + int secondSize = mRoomDb.splitDao().getAll().size(); + boolean secondSetsCorrect = mRoomDb.splitDao().getAll().get(0).getBody().contains("[\"set_1\"]"); + + boolean awaitThird = thirdChangeLatch.await(5, TimeUnit.SECONDS); + Thread.sleep(200); + int thirdSize = mRoomDb.splitDao().getAll().size(); + + boolean awaitHits = hitsLatch.await(120, TimeUnit.SECONDS); + + assertEquals(1, firstSize); + assertEquals(1, secondSize); + assertEquals(0, thirdSize); + assertTrue(awaitFirst); + assertTrue(awaitSecond); + assertTrue(awaitThird); + assertTrue(firstSetsCorrect); + assertTrue(secondSetsCorrect); + + assertTrue(awaitHits); + } + + @Test + public void featureFlagSetsAreIgnoredWhenSetsAreNotConfigured() throws IOException, InterruptedException { + /* + This test creates a factory with no sets configured. + + The first split change will have 2 splits, one that belongs to set_1 and set_2 and one that belongs to set_3; + -> both should be added to storage. + + The second change will have 1 split that belongs to set_1 only. + -> that split should be updated. + + The third change will have 1 split that belongs to set_3 only. + -> that split should be updated. + */ + + createFactory(mContext, mRoomDb, false); + + boolean awaitFirst = firstChangeLatch.await(5, TimeUnit.SECONDS); + Thread.sleep(800); + int firstSize = mRoomDb.splitDao().getAll().size(); + List firstEntities = mRoomDb.splitDao().getAll(); + boolean firstSetsCorrect = firstEntities.get(0).getBody().contains("[\"set_1\",\"set_2\"]") && + firstEntities.get(1).getBody().contains("[\"set_3\"]"); + + boolean awaitSecond = secondChangeLatch.await(5, TimeUnit.SECONDS); + Thread.sleep(1000); + int secondSize = mRoomDb.splitDao().getAll().size(); + List secondEntities = mRoomDb.splitDao().getAll(); + String body0 = secondEntities.get(0).getBody(); + String body1 = secondEntities.get(1).getBody(); + boolean secondSetsCorrect = body1.contains("[\"set_1\"]") && + body1.contains("\"name\":\"workm\",") && + body0.contains("\"name\":\"workm_set_3\",") && + body0.contains("[\"set_3\"]"); + + Logger.w("body0: " + body0); + Logger.w("body1: " + body1); + + boolean awaitThird = thirdChangeLatch.await(5, TimeUnit.SECONDS); + Thread.sleep(800); + List thirdEntities = mRoomDb.splitDao().getAll(); + int thirdSize = thirdEntities.size(); + boolean thirdSetsCorrect = thirdEntities.get(0).getBody().contains("[\"set_3\"]"); + + boolean awaitHits = hitsLatch.await(120, TimeUnit.SECONDS); + + assertEquals(2, firstSize); + assertEquals(2, secondSize); + assertEquals(2, thirdSize); + assertTrue(awaitFirst); + assertTrue(awaitSecond); + assertTrue(awaitThird); + assertTrue(firstSetsCorrect); + assertTrue(secondSetsCorrect); + assertTrue(thirdSetsCorrect); + + assertTrue(awaitHits); + } + + private SplitFactory createFactory( + Context mContext, + SplitRoomDatabase splitRoomDatabase, + boolean streamingEnabled, + String... sets) throws IOException { + SplitClientConfig config = new TestableSplitConfigBuilder() + .ready(30000) + .trafficType("client") + .enableDebug() + .impressionsRefreshRate(1000) + .impressionsCountersRefreshRate(1000) + .syncConfig(SyncConfig.builder() + .addSplitFilter(SplitFilter.bySet(Arrays.asList(sets))) + .build()) + .featuresRefreshRate(2) + .streamingEnabled(streamingEnabled) + .eventFlushInterval(1000) + .build(); + + Map responses = new HashMap<>(); + responses.put("splitChanges", (uri, httpMethod, body) -> { + String since = uri.getQuery().split("&")[0].split("=")[1]; + + hitsLatch.countDown(); + if (since.equals("-1")) { + firstChangeLatch.countDown(); + return new HttpResponseMock(200, loadSplitChangeWithSet(2)); + } else if (since.equals("1602796638344")) { + secondChangeLatch.countDown(); + return new HttpResponseMock(200, loadSplitChangeWithSet(1)); + } else { + thirdChangeLatch.countDown(); + return new HttpResponseMock(200, loadSplitChangeWithSet(0)); + } + }); + + responses.put("mySegments/CUSTOMER_ID", (uri, httpMethod, body) -> new HttpResponseMock(200, IntegrationHelper.emptyMySegments())); + + HttpResponseMockDispatcher httpResponseMockDispatcher = IntegrationHelper.buildDispatcher(responses); + + return IntegrationHelper.buildFactory( + IntegrationHelper.dummyApiKey(), + IntegrationHelper.dummyUserKey(), + config, + mContext, + new HttpClientMock(httpResponseMockDispatcher), + splitRoomDatabase, null, null, null); + } + + private String loadSplitChangeWithSet(int setsCount) { + String change = fileHelper.loadFileContent(mContext, "split_changes_flag_set-" + setsCount + ".json"); + SplitChange parsedChange = Json.fromJson(change, SplitChange.class); + parsedChange.since = parsedChange.till; + + return Json.toJson(parsedChange); + } +} From ec40928ee22e0327f796654a254266749794f2e7 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 11 Sep 2023 12:17:23 -0300 Subject: [PATCH 60/89] Streaming test setup --- .../java/helper/IntegrationHelper.java | 4 + .../integration/sets/FlagSetsPollingTest.java | 29 ++- .../sets/FlagSetsStreamingTest.java | 196 ++++++++++++++++++ 3 files changed, 218 insertions(+), 11 deletions(-) create mode 100644 src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java diff --git a/src/androidTest/java/helper/IntegrationHelper.java b/src/androidTest/java/helper/IntegrationHelper.java index 243b46dbe..031c70c24 100644 --- a/src/androidTest/java/helper/IntegrationHelper.java +++ b/src/androidTest/java/helper/IntegrationHelper.java @@ -322,6 +322,10 @@ public interface ResponseClosure { HttpResponseMock onResponse(URI uri, HttpMethod httpMethod, String body); + + static String getSinceFromUri(URI uri) { + return uri.getQuery().split("&")[0].split("=")[1]; + } } /** diff --git a/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java b/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java index 126032207..dfc0a8b98 100644 --- a/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java +++ b/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java @@ -3,6 +3,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static helper.IntegrationHelper.ResponseClosure.getSinceFromUri; + import android.content.Context; import androidx.test.platform.app.InstrumentationRegistry; @@ -65,13 +67,13 @@ public void featureFlagIsUpdatedAccordingToSetsWhenTheyAreConfigured() throws IO /* This test creates a factory with 2 configured sets. - The first split change will have 2 splits, one that belongs to set_1 and set_2 and one that belongs to set_3; + The first split change will have 2 splits (workm and workm_set_3), one that belongs to set_1 and set_2 and one that belongs to set_3; -> it should be added to storage - The second change will have 1 split that belongs to set_1 only. + The second change will have 1 split (workm) that belongs to set_1 only. -> it should remain in storage and be updated - The third change will have 1 split that belongs to set_3 only. + The third change will have 1 split (workm) that belongs to set_3 only. -> it should be removed from storage */ @@ -110,27 +112,27 @@ public void featureFlagSetsAreIgnoredWhenSetsAreNotConfigured() throws IOExcepti /* This test creates a factory with no sets configured. - The first split change will have 2 splits, one that belongs to set_1 and set_2 and one that belongs to set_3; + The first split change will have 2 splits (workm and workm_set_3), one that belongs to set_1 and set_2 and one that belongs to set_3; -> both should be added to storage. - The second change will have 1 split that belongs to set_1 only. + The second change will have 1 split (workm) that belongs to set_1 only. -> that split should be updated. - The third change will have 1 split that belongs to set_3 only. + The third change will have 1 split (workm) that belongs to set_3 only. -> that split should be updated. */ createFactory(mContext, mRoomDb, false); boolean awaitFirst = firstChangeLatch.await(5, TimeUnit.SECONDS); - Thread.sleep(800); + Thread.sleep(500); int firstSize = mRoomDb.splitDao().getAll().size(); List firstEntities = mRoomDb.splitDao().getAll(); boolean firstSetsCorrect = firstEntities.get(0).getBody().contains("[\"set_1\",\"set_2\"]") && firstEntities.get(1).getBody().contains("[\"set_3\"]"); boolean awaitSecond = secondChangeLatch.await(5, TimeUnit.SECONDS); - Thread.sleep(1000); + Thread.sleep(500); int secondSize = mRoomDb.splitDao().getAll().size(); List secondEntities = mRoomDb.splitDao().getAll(); String body0 = secondEntities.get(0).getBody(); @@ -144,10 +146,15 @@ public void featureFlagSetsAreIgnoredWhenSetsAreNotConfigured() throws IOExcepti Logger.w("body1: " + body1); boolean awaitThird = thirdChangeLatch.await(5, TimeUnit.SECONDS); - Thread.sleep(800); + Thread.sleep(500); List thirdEntities = mRoomDb.splitDao().getAll(); int thirdSize = thirdEntities.size(); - boolean thirdSetsCorrect = thirdEntities.get(0).getBody().contains("[\"set_3\"]"); + String body30 = thirdEntities.get(0).getBody(); + String body31 = thirdEntities.get(1).getBody(); + boolean thirdSetsCorrect = body31.contains("[\"set_3\"]") && + body31.contains("\"name\":\"workm\",") && + body30.contains("\"name\":\"workm_set_3\",") && + body30.contains("[\"set_3\"]"); boolean awaitHits = hitsLatch.await(120, TimeUnit.SECONDS); @@ -185,7 +192,7 @@ private SplitFactory createFactory( Map responses = new HashMap<>(); responses.put("splitChanges", (uri, httpMethod, body) -> { - String since = uri.getQuery().split("&")[0].split("=")[1]; + String since = getSinceFromUri(uri); hitsLatch.countDown(); if (since.equals("-1")) { diff --git a/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java b/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java new file mode 100644 index 000000000..03e53aac4 --- /dev/null +++ b/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java @@ -0,0 +1,196 @@ +package tests.integration.sets; + +import static org.junit.Assert.assertNotNull; + +import static helper.IntegrationHelper.ResponseClosure.getSinceFromUri; + +import android.content.Context; + +import androidx.annotation.Nullable; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import fake.HttpClientMock; +import fake.HttpResponseMock; +import fake.HttpResponseMockDispatcher; +import helper.DatabaseHelper; +import helper.FileHelper; +import helper.IntegrationHelper; +import helper.TestableSplitConfigBuilder; +import io.split.android.client.SplitClient; +import io.split.android.client.SplitClientConfig; +import io.split.android.client.SplitFactory; +import io.split.android.client.SplitFilter; +import io.split.android.client.SyncConfig; +import io.split.android.client.dtos.SplitChange; +import io.split.android.client.events.SplitEvent; +import io.split.android.client.events.SplitEventTask; +import io.split.android.client.storage.db.SplitRoomDatabase; +import io.split.android.client.utils.Json; + +public class FlagSetsStreamingTest { + + private final FileHelper fileHelper = new FileHelper(); + private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); + private SplitRoomDatabase mRoomDb; + + @Before + public void setUp() { + mRoomDb = DatabaseHelper.getTestDatabase(mContext); + mRoomDb.clearAllTables(); + } + + @Test + public void sdkWithoutSetsConfiguredDoesNotTakeFeatureFlagSetsIntoAccount() throws IOException, InterruptedException { + SplitClient readyClient = getReadyClient(mContext, mRoomDb, true); + + assertNotNull(readyClient); + + + } + + /** + * SDK initialization with config.sets=["a", "b"] : + *

+ * if a SPLIT_UPDATE is received with {name:"test", "sets":["a", "b"]}, it should process it since is part of the config.Sets + *

+ * if a SPLIT_UPDATE is received with {name:"test", "sets":["a"]}, it should process it since is still part of the config.Sets + *

+ * if a SPLIT_UPDATE is received with {name:"test", "sets":[]} and the featureFlag is present in the storage, means that it was part of the config.Sets but not anymore. The featureFlag should be removed. + *

+ * if a SPLIT_UPDATE is received with {name:"test", "sets":["x"]} and the featureFlag is present in the storage, that means that was part of the config.Sets but not anymore. The featureFlag should be removed. + *

+ * if a SPLIT_UPDATE is received with {name:"test", "sets":["x", "y"]}, and the featureFlag is not part of the storage, the notification should be discarded since is NOT part of the config.Sets + *

+ * if a SPLIT_KILL is received with {cn:2, name:"test", "defaultTreatment":"off"} , two scenarios possibles: + *

+ * if featureFlag is present in the storage, the featureFlag should process the local kill behaviour. + *

+ * if not, a fetch must be needed. + */ + + @Test + public void sdkWithSetsConfiguredDeletedDueToEmptySets() { + /* + * Initialize a factory with a & b sets configured. + * + * 1. Receive a SPLIT_UPDATE with {name:"test", "sets":["a", "b"]}. It should process it since is part of the config.Sets + * + * 2. Receive a SPLIT_UPDATE with {name:"test", "sets":["a"]}. It should process it since is still part of the config.Sets + * + * 3. Receive a SPLIT_UPDATE with {name:"test", "sets":[]}. The featureFlag should be removed. + * + */ + } + + @Test + public void sdkWithSetsConfiguredDeletedDueToNonMatchingSets() { + /* + * Initialize a factory with a & b sets configured. + * + * 1. Receive a SPLIT_UPDATE with {name:"test", "sets":["a", "b"]}. It should process it since is part of the config.Sets + * + * 2. Receive a SPLIT_UPDATE with {name:"test", "sets":["x"]}. The featureFlag should be removed. + * + * 3. Receive a SPLIT_UPDATE with {name:"test", "sets":["x", "y"]}. No changes in storage. + */ + } + + @Test + public void sdkWithSetsReceivesSplitKill() { + /* + * Initialize a factory with a & b sets configured. + * + * 1. Receive a SPLIT_UPDATE with {name:"test", "sets":["a", "b"]}. It should process it since is part of the config.Sets + * + * 2. Receive a SPLIT_KILL with {cn:2, name:"test", "defaultTreatment":"off" }. The featureFlag should be removed and a fetch should be performed. + */ + } + + @Test + public void sdkWithSetsReceivesSplitKillForNonExistingFeatureFlag() { + /* + * Initialize a factory with a & b sets configured. + * + * 1. Receive a SPLIT_KILL with {cn:2, name:"test", "defaultTreatment":"off" }. No changes in storage, a fetch should be performed. + */ + } + + @Nullable + private SplitClient getReadyClient( + Context mContext, + SplitRoomDatabase splitRoomDatabase, + boolean streamingEnabled, + String... sets) throws IOException, InterruptedException { + SplitClientConfig config = new TestableSplitConfigBuilder() + .ready(30000) + .trafficType("client") + .enableDebug() + .impressionsRefreshRate(1000) + .impressionsCountersRefreshRate(1000) + .syncConfig(SyncConfig.builder() + .addSplitFilter(SplitFilter.bySet(Arrays.asList(sets))) + .build()) + .featuresRefreshRate(2) + .streamingEnabled(streamingEnabled) + .eventFlushInterval(1000) + .build(); +CountDownLatch authLatch = new CountDownLatch(1); + Map responses = new HashMap<>(); + responses.put("splitChanges", (uri, httpMethod, body) -> { + String since = getSinceFromUri(uri); + + if (since.equals("-1")) { + return new HttpResponseMock(200, loadSplitChangeWithSet(2)); + } else { + return new HttpResponseMock(200, IntegrationHelper.emptySplitChanges(1602796638344L, 1602796638344L)); + } + }); + responses.put("mySegments/CUSTOMER_ID", (uri, httpMethod, body) -> new HttpResponseMock(200, IntegrationHelper.emptyMySegments())); + responses.put("v2/auth", (uri, httpMethod, body) -> { + authLatch.countDown(); + return new HttpResponseMock(200, IntegrationHelper.streamingEnabledToken()); + }); + + HttpResponseMockDispatcher httpResponseMockDispatcher = IntegrationHelper.buildDispatcher(responses); + + SplitFactory splitFactory = IntegrationHelper.buildFactory( + IntegrationHelper.dummyApiKey(), + IntegrationHelper.dummyUserKey(), + config, + mContext, + new HttpClientMock(httpResponseMockDispatcher), + splitRoomDatabase, null, null, null); + + CountDownLatch readyLatch = new CountDownLatch(1); + SplitClient client = splitFactory.client(); + client.on(SplitEvent.SDK_READY, new SplitEventTask() { + @Override + public void onPostExecutionView(SplitClient client) { + readyLatch.countDown(); + } + }); + + boolean await = readyLatch.await(5, TimeUnit.SECONDS); + boolean authAwait = authLatch.await(5, TimeUnit.SECONDS); + + return (await && authAwait) ? client : null; + } + + private String loadSplitChangeWithSet(int setsCount) { + String change = fileHelper.loadFileContent(mContext, "split_changes_flag_set-" + setsCount + ".json"); + SplitChange parsedChange = Json.fromJson(change, SplitChange.class); + parsedChange.since = parsedChange.till; + + return Json.toJson(parsedChange); + } +} From 15175ec9d5f9e5eb6f45359853d678f80d2ed7e2 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 11 Sep 2023 14:45:59 -0300 Subject: [PATCH 61/89] First streaming test --- .../java/helper/IntegrationHelper.java | 20 +++---- .../sets/FlagSetsStreamingTest.java | 55 ++++++++++++++++--- 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/src/androidTest/java/helper/IntegrationHelper.java b/src/androidTest/java/helper/IntegrationHelper.java index 031c70c24..28378942d 100644 --- a/src/androidTest/java/helper/IntegrationHelper.java +++ b/src/androidTest/java/helper/IntegrationHelper.java @@ -2,6 +2,7 @@ import android.content.Context; +import androidx.annotation.Nullable; import androidx.core.util.Pair; import com.google.common.base.Strings; @@ -16,6 +17,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.BlockingQueue; import fake.HttpClientMock; import fake.HttpResponseMock; @@ -257,7 +259,7 @@ public static String splitChangeV2CompressionType0() { "eyJ0cmFmZmljVHlwZU5hbWUiOiJ1c2VyIiwiaWQiOiJkNDMxY2RkMC1iMGJlLTExZWEtOGE4MC0xNjYwYWRhOWNlMzkiLCJuYW1lIjoibWF1cm9famF2YSIsInRyYWZmaWNBbGxvY2F0aW9uIjoxMDAsInRyYWZmaWNBbGxvY2F0aW9uU2VlZCI6LTkyMzkxNDkxLCJzZWVkIjotMTc2OTM3NzYwNCwic3RhdHVzIjoiQUNUSVZFIiwia2lsbGVkIjpmYWxzZSwiZGVmYXVsdFRyZWF0bWVudCI6Im9mZiIsImNoYW5nZU51bWJlciI6MTY4NDMyOTg1NDM4NSwiYWxnbyI6MiwiY29uZmlndXJhdGlvbnMiOnt9LCJjb25kaXRpb25zIjpbeyJjb25kaXRpb25UeXBlIjoiV0hJVEVMSVNUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7Im1hdGNoZXJUeXBlIjoiV0hJVEVMSVNUIiwibmVnYXRlIjpmYWxzZSwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOnsid2hpdGVsaXN0IjpbImFkbWluIiwibWF1cm8iLCJuaWNvIl19fV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvZmYiLCJzaXplIjoxMDB9XSwibGFiZWwiOiJ3aGl0ZWxpc3RlZCJ9LHsiY29uZGl0aW9uVHlwZSI6IlJPTExPVVQiLCJtYXRjaGVyR3JvdXAiOnsiY29tYmluZXIiOiJBTkQiLCJtYXRjaGVycyI6W3sia2V5U2VsZWN0b3IiOnsidHJhZmZpY1R5cGUiOiJ1c2VyIn0sIm1hdGNoZXJUeXBlIjoiSU5fU0VHTUVOVCIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjp7InNlZ21lbnROYW1lIjoibWF1ci0yIn19XX0sInBhcnRpdGlvbnMiOlt7InRyZWF0bWVudCI6Im9uIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJvZmYiLCJzaXplIjoxMDB9LHsidHJlYXRtZW50IjoiVjQiLCJzaXplIjowfSx7InRyZWF0bWVudCI6InY1Iiwic2l6ZSI6MH1dLCJsYWJlbCI6ImluIHNlZ21lbnQgbWF1ci0yIn0seyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6InVzZXIifSwibWF0Y2hlclR5cGUiOiJBTExfS0VZUyIsIm5lZ2F0ZSI6ZmFsc2V9XX0sInBhcnRpdGlvbnMiOlt7InRyZWF0bWVudCI6Im9uIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJvZmYiLCJzaXplIjoxMDB9LHsidHJlYXRtZW50IjoiVjQiLCJzaXplIjowfSx7InRyZWF0bWVudCI6InY1Iiwic2l6ZSI6MH1dLCJsYWJlbCI6ImRlZmF1bHQgcnVsZSJ9XX0="); } - private static String splitChangeV2(String changeNumber, String previousChangeNumber, String compressionType, String compressedPayload) { + public static String splitChangeV2(String changeNumber, String previousChangeNumber, String compressionType, String compressedPayload) { return "id: vQQ61wzBRO:0:0\n" + "event: message\n" + "data: {\"id\":\"m2T85LA4fQ:0:0\",\"clientId\":\"pri:NzIyNjY1MzI4\",\"timestamp\":"+System.currentTimeMillis()+",\"encoding\":\"json\",\"channel\":\"NzM2MDI5Mzc0_MTgyNTg1MTgwNg==_splits\",\"data\":\"{\\\"type\\\":\\\"SPLIT_UPDATE\\\",\\\"changeNumber\\\":"+changeNumber+",\\\"pcn\\\":"+previousChangeNumber+",\\\"c\\\":"+compressionType+",\\\"d\\\":\\\""+compressedPayload+"\\\"}\"}\n"; @@ -270,17 +272,17 @@ private static String splitChangeV2(String changeNumber, String previousChangeNu * @return The dispatcher to be used in {@link HttpClientMock} */ public static HttpResponseMockDispatcher buildDispatcher(Map responses) { - return buildDispatcher(responses, Collections.emptyMap()); + return buildDispatcher(responses, null); } /** * Builds a dispatcher with the given responses. * * @param responses The responses to be returned by the dispatcher. The keys are url paths. - * @param streamingResponses The streaming responses to be returned by the dispatcher. The keys are url paths. + * @param streamingQueue The streaming responses to be returned by the dispatcher. * @return The dispatcher to be used in {@link HttpClientMock} */ - public static HttpResponseMockDispatcher buildDispatcher(Map responses, Map streamingResponses) { + public static HttpResponseMockDispatcher buildDispatcher(Map responses, @Nullable BlockingQueue streamingQueue) { return new HttpResponseMockDispatcher() { @Override public HttpResponseMock getResponse(URI uri, HttpMethod method, String body) { @@ -300,17 +302,11 @@ public HttpResponseMock getResponse(URI uri, HttpMethod method, String body) { @Override public HttpStreamResponseMock getStreamResponse(URI uri) { try { - String path = uri.getPath().replace("/api/", ""); - if (streamingResponses.containsKey(path)) { - return streamingResponses.get(path).onResponse(uri); - } else { - return new HttpStreamResponseMock(200, null); - } + return new HttpStreamResponseMock(200, streamingQueue); } catch (IOException e) { e.printStackTrace(); + return null; } - - return null; } }; } diff --git a/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java b/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java index 03e53aac4..c8315d247 100644 --- a/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java +++ b/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java @@ -1,8 +1,10 @@ package tests.integration.sets; -import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static helper.IntegrationHelper.ResponseClosure.getSinceFromUri; +import static helper.IntegrationHelper.splitChangeV2; import android.content.Context; @@ -16,7 +18,9 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import fake.HttpClientMock; @@ -36,9 +40,18 @@ import io.split.android.client.events.SplitEventTask; import io.split.android.client.storage.db.SplitRoomDatabase; import io.split.android.client.utils.Json; +import io.split.android.client.utils.logger.Logger; +import tests.integration.shared.TestingHelper; public class FlagSetsStreamingTest { + // workm with set_3 + private static final String set0SplitChange = splitChangeV2("1602798638344", "1602797638344", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjE2MDI3OTg2MzgzNDQsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6WyJzZXRfMyJdLCJjb25kaXRpb25zIjpbeyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiSU5fU0VHTUVOVCIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjp7InNlZ21lbnROYW1lIjoibmV3X3NlZ21lbnQifSwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjowfSx7InRyZWF0bWVudCI6Im9mZiIsInNpemUiOjB9LHsidHJlYXRtZW50IjoiZnJlZSIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJpbiBzZWdtZW50IG5ld19zZWdtZW50In0seyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiQUxMX0tFWVMiLCJuZWdhdGUiOmZhbHNlLCJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6bnVsbCwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjoxMDB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJkZWZhdWx0IHJ1bGUifV19"); + // workm with set_1 + private static final String set1SplitChange = splitChangeV2("1602797638344", "1602796638344", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjE2MDI3OTc2MzgzNDQsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6WyJzZXRfMSJdLCJjb25kaXRpb25zIjpbeyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiSU5fU0VHTUVOVCIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjp7InNlZ21lbnROYW1lIjoibmV3X3NlZ21lbnQifSwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjowfSx7InRyZWF0bWVudCI6Im9mZiIsInNpemUiOjB9LHsidHJlYXRtZW50IjoiZnJlZSIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJpbiBzZWdtZW50IG5ld19zZWdtZW50In0seyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiQUxMX0tFWVMiLCJuZWdhdGUiOmZhbHNlLCJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6bnVsbCwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjoxMDB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJkZWZhdWx0IHJ1bGUifV19"); + // mauro_java with no sets + private static final String noSetsSplitChange = splitChangeV2("1602799638344", "1602796638344", "0","eyJ0cmFmZmljVHlwZU5hbWUiOiJ1c2VyIiwiaWQiOiJkNDMxY2RkMC1iMGJlLTExZWEtOGE4MC0xNjYwYWRhOWNlMzkiLCJuYW1lIjoibWF1cm9famF2YSIsInRyYWZmaWNBbGxvY2F0aW9uIjoxMDAsInRyYWZmaWNBbGxvY2F0aW9uU2VlZCI6LTkyMzkxNDkxLCJzZWVkIjotMTc2OTM3NzYwNCwic3RhdHVzIjoiQUNUSVZFIiwia2lsbGVkIjpmYWxzZSwiZGVmYXVsdFRyZWF0bWVudCI6Im9mZiIsImNoYW5nZU51bWJlciI6MTYwMjc5OTYzODM0NCwiYWxnbyI6MiwiY29uZmlndXJhdGlvbnMiOnt9LCJzZXRzIjpbXSwiY29uZGl0aW9ucyI6W3siY29uZGl0aW9uVHlwZSI6IldISVRFTElTVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJtYXRjaGVyVHlwZSI6IldISVRFTElTVCIsIm5lZ2F0ZSI6ZmFsc2UsIndoaXRlbGlzdE1hdGNoZXJEYXRhIjp7IndoaXRlbGlzdCI6WyJhZG1pbiIsIm1hdXJvIiwibmljbyJdfX1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MTAwfV0sImxhYmVsIjoid2hpdGVsaXN0ZWQifSx7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoidXNlciJ9LCJtYXRjaGVyVHlwZSI6IklOX1NFR01FTlQiLCJuZWdhdGUiOmZhbHNlLCJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6eyJzZWdtZW50TmFtZSI6Im1hdXItMiJ9fV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6IlY0Iiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJ2NSIsInNpemUiOjB9XSwibGFiZWwiOiJpbiBzZWdtZW50IG1hdXItMiJ9LHsiY29uZGl0aW9uVHlwZSI6IlJPTExPVVQiLCJtYXRjaGVyR3JvdXAiOnsiY29tYmluZXIiOiJBTkQiLCJtYXRjaGVycyI6W3sia2V5U2VsZWN0b3IiOnsidHJhZmZpY1R5cGUiOiJ1c2VyIn0sIm1hdGNoZXJUeXBlIjoiQUxMX0tFWVMiLCJuZWdhdGUiOmZhbHNlfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6IlY0Iiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJ2NSIsInNpemUiOjB9XSwibGFiZWwiOiJkZWZhdWx0IHJ1bGUifV19"); + private final FileHelper fileHelper = new FileHelper(); private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); private SplitRoomDatabase mRoomDb; @@ -50,12 +63,39 @@ public void setUp() { } @Test - public void sdkWithoutSetsConfiguredDoesNotTakeFeatureFlagSetsIntoAccount() throws IOException, InterruptedException { - SplitClient readyClient = getReadyClient(mContext, mRoomDb, true); + public void sdkWithoutSetsConfiguredDoesExcludeUpdates() throws IOException, InterruptedException { + /* + * Initialize a factory with streaming enabled and no sets. + * + * Receive notification with new feature flag with no sets. + * + * Verify that the feature flag is added. + */ + LinkedBlockingDeque mStreamingData = new LinkedBlockingDeque<>(); + SplitClient readyClient = getReadyClient(mContext, mRoomDb, true, mStreamingData); + + assertEquals(2, mRoomDb.splitDao().getAll().size()); + CountDownLatch updateLatch = new CountDownLatch(1); + readyClient.on(SplitEvent.SDK_UPDATE, new SplitEventTask() { + @Override + public void onPostExecutionView(SplitClient client) { + updateLatch.countDown(); + } + }); + pushToStreaming(mStreamingData, noSetsSplitChange); + boolean updateAwait = updateLatch.await(5, TimeUnit.SECONDS); - assertNotNull(readyClient); + assertTrue(updateAwait); + assertEquals(3, mRoomDb.splitDao().getAll().size()); + } + private static void pushToStreaming(LinkedBlockingDeque streamingData, String message) throws InterruptedException { + try { + streamingData.put(message + "" + "\n"); + Logger.d("Pushed message: " + message); + } catch (InterruptedException e) { + } } /** @@ -77,7 +117,6 @@ public void sdkWithoutSetsConfiguredDoesNotTakeFeatureFlagSetsIntoAccount() thro *

* if not, a fetch must be needed. */ - @Test public void sdkWithSetsConfiguredDeletedDueToEmptySets() { /* @@ -130,6 +169,7 @@ private SplitClient getReadyClient( Context mContext, SplitRoomDatabase splitRoomDatabase, boolean streamingEnabled, + BlockingQueue streamingData, String... sets) throws IOException, InterruptedException { SplitClientConfig config = new TestableSplitConfigBuilder() .ready(30000) @@ -144,7 +184,7 @@ private SplitClient getReadyClient( .streamingEnabled(streamingEnabled) .eventFlushInterval(1000) .build(); -CountDownLatch authLatch = new CountDownLatch(1); + CountDownLatch authLatch = new CountDownLatch(1); Map responses = new HashMap<>(); responses.put("splitChanges", (uri, httpMethod, body) -> { String since = getSinceFromUri(uri); @@ -161,7 +201,7 @@ private SplitClient getReadyClient( return new HttpResponseMock(200, IntegrationHelper.streamingEnabledToken()); }); - HttpResponseMockDispatcher httpResponseMockDispatcher = IntegrationHelper.buildDispatcher(responses); + HttpResponseMockDispatcher httpResponseMockDispatcher = IntegrationHelper.buildDispatcher(responses, streamingData); SplitFactory splitFactory = IntegrationHelper.buildFactory( IntegrationHelper.dummyApiKey(), @@ -182,6 +222,7 @@ public void onPostExecutionView(SplitClient client) { boolean await = readyLatch.await(5, TimeUnit.SECONDS); boolean authAwait = authLatch.await(5, TimeUnit.SECONDS); + TestingHelper.pushKeepAlive(streamingData); return (await && authAwait) ? client : null; } From 55c18bb5cf0a57eefff85609d894755be3d7a718 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 11 Sep 2023 16:56:21 -0300 Subject: [PATCH 62/89] More tests --- .../sets/FlagSetsStreamingTest.java | 93 +++++++++++++------ 1 file changed, 65 insertions(+), 28 deletions(-) diff --git a/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java b/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java index c8315d247..a92cd7100 100644 --- a/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java +++ b/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; @@ -38,6 +39,7 @@ import io.split.android.client.dtos.SplitChange; import io.split.android.client.events.SplitEvent; import io.split.android.client.events.SplitEventTask; +import io.split.android.client.storage.db.SplitEntity; import io.split.android.client.storage.db.SplitRoomDatabase; import io.split.android.client.utils.Json; import io.split.android.client.utils.logger.Logger; @@ -45,12 +47,16 @@ public class FlagSetsStreamingTest { + // workm with no sets + private static final String setNoneSplitChange = splitChangeV2("4", "3", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjUsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6W10sImNvbmRpdGlvbnMiOlt7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJJTl9TRUdNRU5UIiwibmVnYXRlIjpmYWxzZSwidXNlckRlZmluZWRTZWdtZW50TWF0Y2hlckRhdGEiOnsic2VnbWVudE5hbWUiOiJuZXdfc2VnbWVudCJ9LCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImluIHNlZ21lbnQgbmV3X3NlZ21lbnQifSx7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJBTExfS0VZUyIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjpudWxsLCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJvZmYiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImZyZWUiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImRlZmF1bHQgcnVsZSJ9XX0="); // workm with set_3 - private static final String set0SplitChange = splitChangeV2("1602798638344", "1602797638344", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjE2MDI3OTg2MzgzNDQsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6WyJzZXRfMyJdLCJjb25kaXRpb25zIjpbeyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiSU5fU0VHTUVOVCIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjp7InNlZ21lbnROYW1lIjoibmV3X3NlZ21lbnQifSwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjowfSx7InRyZWF0bWVudCI6Im9mZiIsInNpemUiOjB9LHsidHJlYXRtZW50IjoiZnJlZSIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJpbiBzZWdtZW50IG5ld19zZWdtZW50In0seyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiQUxMX0tFWVMiLCJuZWdhdGUiOmZhbHNlLCJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6bnVsbCwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjoxMDB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJkZWZhdWx0IHJ1bGUifV19"); + private static final String set0SplitChange = splitChangeV2("4", "3", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjQsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6WyJzZXRfMyJdLCJjb25kaXRpb25zIjpbeyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiSU5fU0VHTUVOVCIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjp7InNlZ21lbnROYW1lIjoibmV3X3NlZ21lbnQifSwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjowfSx7InRyZWF0bWVudCI6Im9mZiIsInNpemUiOjB9LHsidHJlYXRtZW50IjoiZnJlZSIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJpbiBzZWdtZW50IG5ld19zZWdtZW50In0seyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiQUxMX0tFWVMiLCJuZWdhdGUiOmZhbHNlLCJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6bnVsbCwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjoxMDB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJkZWZhdWx0IHJ1bGUifV19"); // workm with set_1 - private static final String set1SplitChange = splitChangeV2("1602797638344", "1602796638344", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjE2MDI3OTc2MzgzNDQsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6WyJzZXRfMSJdLCJjb25kaXRpb25zIjpbeyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiSU5fU0VHTUVOVCIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjp7InNlZ21lbnROYW1lIjoibmV3X3NlZ21lbnQifSwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjowfSx7InRyZWF0bWVudCI6Im9mZiIsInNpemUiOjB9LHsidHJlYXRtZW50IjoiZnJlZSIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJpbiBzZWdtZW50IG5ld19zZWdtZW50In0seyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiQUxMX0tFWVMiLCJuZWdhdGUiOmZhbHNlLCJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6bnVsbCwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjoxMDB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJkZWZhdWx0IHJ1bGUifV19"); + private static final String set1SplitChange = splitChangeV2("3", "2", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjMsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6WyJzZXRfMSJdLCJjb25kaXRpb25zIjpbeyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiSU5fU0VHTUVOVCIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjp7InNlZ21lbnROYW1lIjoibmV3X3NlZ21lbnQifSwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjowfSx7InRyZWF0bWVudCI6Im9mZiIsInNpemUiOjB9LHsidHJlYXRtZW50IjoiZnJlZSIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJpbiBzZWdtZW50IG5ld19zZWdtZW50In0seyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiQUxMX0tFWVMiLCJuZWdhdGUiOmZhbHNlLCJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6bnVsbCwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjoxMDB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJkZWZhdWx0IHJ1bGUifV19"); + // workm with set_1, set_2 + private static final String set3SplitChange = splitChangeV2("2", "1", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjIsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6WyJzZXRfMSIsInNldF8yIl0sImNvbmRpdGlvbnMiOlt7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJJTl9TRUdNRU5UIiwibmVnYXRlIjpmYWxzZSwidXNlckRlZmluZWRTZWdtZW50TWF0Y2hlckRhdGEiOnsic2VnbWVudE5hbWUiOiJuZXdfc2VnbWVudCJ9LCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImluIHNlZ21lbnQgbmV3X3NlZ21lbnQifSx7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJBTExfS0VZUyIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjpudWxsLCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJvZmYiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImZyZWUiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImRlZmF1bHQgcnVsZSJ9XX0"); // mauro_java with no sets - private static final String noSetsSplitChange = splitChangeV2("1602799638344", "1602796638344", "0","eyJ0cmFmZmljVHlwZU5hbWUiOiJ1c2VyIiwiaWQiOiJkNDMxY2RkMC1iMGJlLTExZWEtOGE4MC0xNjYwYWRhOWNlMzkiLCJuYW1lIjoibWF1cm9famF2YSIsInRyYWZmaWNBbGxvY2F0aW9uIjoxMDAsInRyYWZmaWNBbGxvY2F0aW9uU2VlZCI6LTkyMzkxNDkxLCJzZWVkIjotMTc2OTM3NzYwNCwic3RhdHVzIjoiQUNUSVZFIiwia2lsbGVkIjpmYWxzZSwiZGVmYXVsdFRyZWF0bWVudCI6Im9mZiIsImNoYW5nZU51bWJlciI6MTYwMjc5OTYzODM0NCwiYWxnbyI6MiwiY29uZmlndXJhdGlvbnMiOnt9LCJzZXRzIjpbXSwiY29uZGl0aW9ucyI6W3siY29uZGl0aW9uVHlwZSI6IldISVRFTElTVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJtYXRjaGVyVHlwZSI6IldISVRFTElTVCIsIm5lZ2F0ZSI6ZmFsc2UsIndoaXRlbGlzdE1hdGNoZXJEYXRhIjp7IndoaXRlbGlzdCI6WyJhZG1pbiIsIm1hdXJvIiwibmljbyJdfX1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MTAwfV0sImxhYmVsIjoid2hpdGVsaXN0ZWQifSx7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoidXNlciJ9LCJtYXRjaGVyVHlwZSI6IklOX1NFR01FTlQiLCJuZWdhdGUiOmZhbHNlLCJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6eyJzZWdtZW50TmFtZSI6Im1hdXItMiJ9fV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6IlY0Iiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJ2NSIsInNpemUiOjB9XSwibGFiZWwiOiJpbiBzZWdtZW50IG1hdXItMiJ9LHsiY29uZGl0aW9uVHlwZSI6IlJPTExPVVQiLCJtYXRjaGVyR3JvdXAiOnsiY29tYmluZXIiOiJBTkQiLCJtYXRjaGVycyI6W3sia2V5U2VsZWN0b3IiOnsidHJhZmZpY1R5cGUiOiJ1c2VyIn0sIm1hdGNoZXJUeXBlIjoiQUxMX0tFWVMiLCJuZWdhdGUiOmZhbHNlfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6IlY0Iiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJ2NSIsInNpemUiOjB9XSwibGFiZWwiOiJkZWZhdWx0IHJ1bGUifV19"); + private static final String noSetsSplitChange = splitChangeV2("2", "1", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJ1c2VyIiwiaWQiOiJkNDMxY2RkMC1iMGJlLTExZWEtOGE4MC0xNjYwYWRhOWNlMzkiLCJuYW1lIjoibWF1cm9famF2YSIsInRyYWZmaWNBbGxvY2F0aW9uIjoxMDAsInRyYWZmaWNBbGxvY2F0aW9uU2VlZCI6LTkyMzkxNDkxLCJzZWVkIjotMTc2OTM3NzYwNCwic3RhdHVzIjoiQUNUSVZFIiwia2lsbGVkIjpmYWxzZSwiZGVmYXVsdFRyZWF0bWVudCI6Im9mZiIsImNoYW5nZU51bWJlciI6MTYwMjc5OTYzODM0NCwiYWxnbyI6MiwiY29uZmlndXJhdGlvbnMiOnt9LCJzZXRzIjpbXSwiY29uZGl0aW9ucyI6W3siY29uZGl0aW9uVHlwZSI6IldISVRFTElTVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJtYXRjaGVyVHlwZSI6IldISVRFTElTVCIsIm5lZ2F0ZSI6ZmFsc2UsIndoaXRlbGlzdE1hdGNoZXJEYXRhIjp7IndoaXRlbGlzdCI6WyJhZG1pbiIsIm1hdXJvIiwibmljbyJdfX1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MTAwfV0sImxhYmVsIjoid2hpdGVsaXN0ZWQifSx7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoidXNlciJ9LCJtYXRjaGVyVHlwZSI6IklOX1NFR01FTlQiLCJuZWdhdGUiOmZhbHNlLCJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6eyJzZWdtZW50TmFtZSI6Im1hdXItMiJ9fV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6IlY0Iiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJ2NSIsInNpemUiOjB9XSwibGFiZWwiOiJpbiBzZWdtZW50IG1hdXItMiJ9LHsiY29uZGl0aW9uVHlwZSI6IlJPTExPVVQiLCJtYXRjaGVyR3JvdXAiOnsiY29tYmluZXIiOiJBTkQiLCJtYXRjaGVycyI6W3sia2V5U2VsZWN0b3IiOnsidHJhZmZpY1R5cGUiOiJ1c2VyIn0sIm1hdGNoZXJUeXBlIjoiQUxMX0tFWVMiLCJuZWdhdGUiOmZhbHNlfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6IlY0Iiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJ2NSIsInNpemUiOjB9XSwibGFiZWwiOiJkZWZhdWx0IHJ1bGUifV19"); private final FileHelper fileHelper = new FileHelper(); private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); @@ -74,28 +80,19 @@ public void sdkWithoutSetsConfiguredDoesExcludeUpdates() throws IOException, Int LinkedBlockingDeque mStreamingData = new LinkedBlockingDeque<>(); SplitClient readyClient = getReadyClient(mContext, mRoomDb, true, mStreamingData); - assertEquals(2, mRoomDb.splitDao().getAll().size()); + int initialSplitsSize = mRoomDb.splitDao().getAll().size(); + + // set up update listener CountDownLatch updateLatch = new CountDownLatch(1); - readyClient.on(SplitEvent.SDK_UPDATE, new SplitEventTask() { - @Override - public void onPostExecutionView(SplitClient client) { - updateLatch.countDown(); - } - }); + readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(updateLatch)); + + // push change pushToStreaming(mStreamingData, noSetsSplitChange); boolean updateAwait = updateLatch.await(5, TimeUnit.SECONDS); assertTrue(updateAwait); - assertEquals(3, mRoomDb.splitDao().getAll().size()); - } - - private static void pushToStreaming(LinkedBlockingDeque streamingData, String message) throws InterruptedException { - try { - streamingData.put(message + "" + "\n"); - - Logger.d("Pushed message: " + message); - } catch (InterruptedException e) { - } + assertEquals(0, initialSplitsSize); + assertEquals(1, mRoomDb.splitDao().getAll().size()); } /** @@ -118,17 +115,52 @@ private static void pushToStreaming(LinkedBlockingDeque streamingData, S * if not, a fetch must be needed. */ @Test - public void sdkWithSetsConfiguredDeletedDueToEmptySets() { + public void sdkWithSetsConfiguredDeletedDueToEmptySets() throws IOException, InterruptedException { /* * Initialize a factory with a & b sets configured. * - * 1. Receive a SPLIT_UPDATE with {name:"test", "sets":["a", "b"]}. It should process it since is part of the config.Sets + * 1. Receive a SPLIT_UPDATE with {name:"test", "sets":["set_1", "set_2"]}. It should process it since is part of the config.Sets * - * 2. Receive a SPLIT_UPDATE with {name:"test", "sets":["a"]}. It should process it since is still part of the config.Sets + * 2. Receive a SPLIT_UPDATE with {name:"test", "sets":["set_1"]}. It should process it since is still part of the config.Sets * * 3. Receive a SPLIT_UPDATE with {name:"test", "sets":[]}. The featureFlag should be removed. * */ + LinkedBlockingDeque streamingData = new LinkedBlockingDeque<>(); + SplitClient readyClient = getReadyClient(mContext, mRoomDb, true, streamingData, "set_1", "set_2"); + + // 1. Receive a SPLIT_UPDATE with {name:"test", "sets":["set_1", "set_2"]} + CountDownLatch firstUpdate = new CountDownLatch(1); + readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(firstUpdate)); + pushToStreaming(streamingData, set3SplitChange); + boolean firstUpdateAwait = firstUpdate.await(5, TimeUnit.SECONDS); + List entities = mRoomDb.splitDao().getAll(); + boolean firstUpdateStored = entities.size() == 1 && entities.get(0).getBody().contains("\"sets\":[\"set_1\",\"set_2\"]") && + entities.get(0).getBody().contains("\"name\":\"workm\""); + + // 2. Receive a SPLIT_UPDATE with {name:"test", "sets":["set_1"]} + CountDownLatch secondUpdate = new CountDownLatch(1); + readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(secondUpdate)); + pushToStreaming(streamingData, set1SplitChange); + boolean secondUpdateAwait = secondUpdate.await(5, TimeUnit.SECONDS); + entities = mRoomDb.splitDao().getAll(); + boolean secondUpdateStored = entities.size() == 1 && entities.get(0).getBody().contains("\"sets\":[\"set_1\"]") && + entities.get(0).getBody().contains("\"name\":\"workm\""); + + // 3. Receive a SPLIT_UPDATE with {name:"test", "sets":[]} + CountDownLatch thirdUpdate = new CountDownLatch(1); + readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(thirdUpdate)); + pushToStreaming(streamingData, setNoneSplitChange); + boolean thirdUpdateAwait = thirdUpdate.await(5, TimeUnit.SECONDS); + entities = mRoomDb.splitDao().getAll(); + boolean thirdUpdateStored = entities.isEmpty(); + + assertTrue(firstUpdateAwait); + assertTrue(firstUpdateStored); + assertTrue(secondUpdateAwait); + assertTrue(secondUpdateStored); + assertTrue(thirdUpdateAwait); + assertTrue(thirdUpdateStored); } @Test @@ -189,11 +221,7 @@ private SplitClient getReadyClient( responses.put("splitChanges", (uri, httpMethod, body) -> { String since = getSinceFromUri(uri); - if (since.equals("-1")) { - return new HttpResponseMock(200, loadSplitChangeWithSet(2)); - } else { - return new HttpResponseMock(200, IntegrationHelper.emptySplitChanges(1602796638344L, 1602796638344L)); - } + return new HttpResponseMock(200, IntegrationHelper.emptySplitChanges(-1, 1)); }); responses.put("mySegments/CUSTOMER_ID", (uri, httpMethod, body) -> new HttpResponseMock(200, IntegrationHelper.emptyMySegments())); responses.put("v2/auth", (uri, httpMethod, body) -> { @@ -234,4 +262,13 @@ private String loadSplitChangeWithSet(int setsCount) { return Json.toJson(parsedChange); } + + private static void pushToStreaming(LinkedBlockingDeque streamingData, String message) throws InterruptedException { + try { + streamingData.put(message + "" + "\n"); + + Logger.d("Pushed message: " + message); + } catch (InterruptedException ignored) { + } + } } From b70e8956b43b7368a87a65639c27b93528af7576 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 11 Sep 2023 18:51:58 -0300 Subject: [PATCH 63/89] More tests --- .../sets/FlagSetsStreamingTest.java | 77 ++++++++++++++----- .../splits/FeatureFlagProcessStrategy.java | 3 + .../splits/SplitInPlaceUpdateTask.java | 2 +- 3 files changed, 60 insertions(+), 22 deletions(-) diff --git a/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java b/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java index a92cd7100..61d0cb412 100644 --- a/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java +++ b/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java @@ -2,7 +2,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; - import static helper.IntegrationHelper.ResponseClosure.getSinceFromUri; import static helper.IntegrationHelper.splitChangeV2; @@ -28,7 +27,6 @@ import fake.HttpResponseMock; import fake.HttpResponseMockDispatcher; import helper.DatabaseHelper; -import helper.FileHelper; import helper.IntegrationHelper; import helper.TestableSplitConfigBuilder; import io.split.android.client.SplitClient; @@ -36,29 +34,28 @@ import io.split.android.client.SplitFactory; import io.split.android.client.SplitFilter; import io.split.android.client.SyncConfig; -import io.split.android.client.dtos.SplitChange; import io.split.android.client.events.SplitEvent; import io.split.android.client.events.SplitEventTask; import io.split.android.client.storage.db.SplitEntity; import io.split.android.client.storage.db.SplitRoomDatabase; -import io.split.android.client.utils.Json; import io.split.android.client.utils.logger.Logger; import tests.integration.shared.TestingHelper; public class FlagSetsStreamingTest { + // workm with set_3, set_4 + private static final String splitChange5 = splitChangeV2("5", "4", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjUsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6WyJzZXRfMyIsInNldF80Il0sImNvbmRpdGlvbnMiOlt7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJJTl9TRUdNRU5UIiwibmVnYXRlIjpmYWxzZSwidXNlckRlZmluZWRTZWdtZW50TWF0Y2hlckRhdGEiOnsic2VnbWVudE5hbWUiOiJuZXdfc2VnbWVudCJ9LCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImluIHNlZ21lbnQgbmV3X3NlZ21lbnQifSx7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJBTExfS0VZUyIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjpudWxsLCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJvZmYiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImZyZWUiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImRlZmF1bHQgcnVsZSJ9XX0"); // workm with no sets - private static final String setNoneSplitChange = splitChangeV2("4", "3", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjUsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6W10sImNvbmRpdGlvbnMiOlt7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJJTl9TRUdNRU5UIiwibmVnYXRlIjpmYWxzZSwidXNlckRlZmluZWRTZWdtZW50TWF0Y2hlckRhdGEiOnsic2VnbWVudE5hbWUiOiJuZXdfc2VnbWVudCJ9LCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImluIHNlZ21lbnQgbmV3X3NlZ21lbnQifSx7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJBTExfS0VZUyIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjpudWxsLCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJvZmYiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImZyZWUiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImRlZmF1bHQgcnVsZSJ9XX0="); + private static final String splitChange5None = splitChangeV2("5", "4", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjUsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6W10sImNvbmRpdGlvbnMiOlt7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJJTl9TRUdNRU5UIiwibmVnYXRlIjpmYWxzZSwidXNlckRlZmluZWRTZWdtZW50TWF0Y2hlckRhdGEiOnsic2VnbWVudE5hbWUiOiJuZXdfc2VnbWVudCJ9LCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImluIHNlZ21lbnQgbmV3X3NlZ21lbnQifSx7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJBTExfS0VZUyIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjpudWxsLCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJvZmYiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImZyZWUiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImRlZmF1bHQgcnVsZSJ9XX0="); // workm with set_3 - private static final String set0SplitChange = splitChangeV2("4", "3", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjQsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6WyJzZXRfMyJdLCJjb25kaXRpb25zIjpbeyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiSU5fU0VHTUVOVCIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjp7InNlZ21lbnROYW1lIjoibmV3X3NlZ21lbnQifSwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjowfSx7InRyZWF0bWVudCI6Im9mZiIsInNpemUiOjB9LHsidHJlYXRtZW50IjoiZnJlZSIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJpbiBzZWdtZW50IG5ld19zZWdtZW50In0seyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiQUxMX0tFWVMiLCJuZWdhdGUiOmZhbHNlLCJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6bnVsbCwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjoxMDB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJkZWZhdWx0IHJ1bGUifV19"); + private static final String splitChange4 = splitChangeV2("4", "3", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjQsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6WyJzZXRfMyJdLCJjb25kaXRpb25zIjpbeyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiSU5fU0VHTUVOVCIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjp7InNlZ21lbnROYW1lIjoibmV3X3NlZ21lbnQifSwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjowfSx7InRyZWF0bWVudCI6Im9mZiIsInNpemUiOjB9LHsidHJlYXRtZW50IjoiZnJlZSIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJpbiBzZWdtZW50IG5ld19zZWdtZW50In0seyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiQUxMX0tFWVMiLCJuZWdhdGUiOmZhbHNlLCJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6bnVsbCwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjoxMDB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJkZWZhdWx0IHJ1bGUifV19"); // workm with set_1 - private static final String set1SplitChange = splitChangeV2("3", "2", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjMsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6WyJzZXRfMSJdLCJjb25kaXRpb25zIjpbeyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiSU5fU0VHTUVOVCIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjp7InNlZ21lbnROYW1lIjoibmV3X3NlZ21lbnQifSwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjowfSx7InRyZWF0bWVudCI6Im9mZiIsInNpemUiOjB9LHsidHJlYXRtZW50IjoiZnJlZSIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJpbiBzZWdtZW50IG5ld19zZWdtZW50In0seyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiQUxMX0tFWVMiLCJuZWdhdGUiOmZhbHNlLCJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6bnVsbCwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjoxMDB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJkZWZhdWx0IHJ1bGUifV19"); + private static final String splitChange3 = splitChangeV2("3", "2", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjMsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6WyJzZXRfMSJdLCJjb25kaXRpb25zIjpbeyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiSU5fU0VHTUVOVCIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjp7InNlZ21lbnROYW1lIjoibmV3X3NlZ21lbnQifSwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjowfSx7InRyZWF0bWVudCI6Im9mZiIsInNpemUiOjB9LHsidHJlYXRtZW50IjoiZnJlZSIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJpbiBzZWdtZW50IG5ld19zZWdtZW50In0seyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiQUxMX0tFWVMiLCJuZWdhdGUiOmZhbHNlLCJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6bnVsbCwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjoxMDB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJkZWZhdWx0IHJ1bGUifV19"); // workm with set_1, set_2 - private static final String set3SplitChange = splitChangeV2("2", "1", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjIsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6WyJzZXRfMSIsInNldF8yIl0sImNvbmRpdGlvbnMiOlt7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJJTl9TRUdNRU5UIiwibmVnYXRlIjpmYWxzZSwidXNlckRlZmluZWRTZWdtZW50TWF0Y2hlckRhdGEiOnsic2VnbWVudE5hbWUiOiJuZXdfc2VnbWVudCJ9LCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImluIHNlZ21lbnQgbmV3X3NlZ21lbnQifSx7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJBTExfS0VZUyIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjpudWxsLCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJvZmYiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImZyZWUiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImRlZmF1bHQgcnVsZSJ9XX0"); + private static final String splitChange2 = splitChangeV2("2", "1", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjIsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6WyJzZXRfMSIsInNldF8yIl0sImNvbmRpdGlvbnMiOlt7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJJTl9TRUdNRU5UIiwibmVnYXRlIjpmYWxzZSwidXNlckRlZmluZWRTZWdtZW50TWF0Y2hlckRhdGEiOnsic2VnbWVudE5hbWUiOiJuZXdfc2VnbWVudCJ9LCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImluIHNlZ21lbnQgbmV3X3NlZ21lbnQifSx7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJBTExfS0VZUyIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjpudWxsLCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJvZmYiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImZyZWUiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImRlZmF1bHQgcnVsZSJ9XX0"); // mauro_java with no sets private static final String noSetsSplitChange = splitChangeV2("2", "1", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJ1c2VyIiwiaWQiOiJkNDMxY2RkMC1iMGJlLTExZWEtOGE4MC0xNjYwYWRhOWNlMzkiLCJuYW1lIjoibWF1cm9famF2YSIsInRyYWZmaWNBbGxvY2F0aW9uIjoxMDAsInRyYWZmaWNBbGxvY2F0aW9uU2VlZCI6LTkyMzkxNDkxLCJzZWVkIjotMTc2OTM3NzYwNCwic3RhdHVzIjoiQUNUSVZFIiwia2lsbGVkIjpmYWxzZSwiZGVmYXVsdFRyZWF0bWVudCI6Im9mZiIsImNoYW5nZU51bWJlciI6MTYwMjc5OTYzODM0NCwiYWxnbyI6MiwiY29uZmlndXJhdGlvbnMiOnt9LCJzZXRzIjpbXSwiY29uZGl0aW9ucyI6W3siY29uZGl0aW9uVHlwZSI6IldISVRFTElTVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJtYXRjaGVyVHlwZSI6IldISVRFTElTVCIsIm5lZ2F0ZSI6ZmFsc2UsIndoaXRlbGlzdE1hdGNoZXJEYXRhIjp7IndoaXRlbGlzdCI6WyJhZG1pbiIsIm1hdXJvIiwibmljbyJdfX1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MTAwfV0sImxhYmVsIjoid2hpdGVsaXN0ZWQifSx7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoidXNlciJ9LCJtYXRjaGVyVHlwZSI6IklOX1NFR01FTlQiLCJuZWdhdGUiOmZhbHNlLCJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6eyJzZWdtZW50TmFtZSI6Im1hdXItMiJ9fV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6IlY0Iiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJ2NSIsInNpemUiOjB9XSwibGFiZWwiOiJpbiBzZWdtZW50IG1hdXItMiJ9LHsiY29uZGl0aW9uVHlwZSI6IlJPTExPVVQiLCJtYXRjaGVyR3JvdXAiOnsiY29tYmluZXIiOiJBTkQiLCJtYXRjaGVycyI6W3sia2V5U2VsZWN0b3IiOnsidHJhZmZpY1R5cGUiOiJ1c2VyIn0sIm1hdGNoZXJUeXBlIjoiQUxMX0tFWVMiLCJuZWdhdGUiOmZhbHNlfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6IlY0Iiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJ2NSIsInNpemUiOjB9XSwibGFiZWwiOiJkZWZhdWx0IHJ1bGUifV19"); - private final FileHelper fileHelper = new FileHelper(); private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); private SplitRoomDatabase mRoomDb; @@ -132,7 +129,7 @@ public void sdkWithSetsConfiguredDeletedDueToEmptySets() throws IOException, Int // 1. Receive a SPLIT_UPDATE with {name:"test", "sets":["set_1", "set_2"]} CountDownLatch firstUpdate = new CountDownLatch(1); readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(firstUpdate)); - pushToStreaming(streamingData, set3SplitChange); + pushToStreaming(streamingData, splitChange2); boolean firstUpdateAwait = firstUpdate.await(5, TimeUnit.SECONDS); List entities = mRoomDb.splitDao().getAll(); boolean firstUpdateStored = entities.size() == 1 && entities.get(0).getBody().contains("\"sets\":[\"set_1\",\"set_2\"]") && @@ -141,7 +138,7 @@ public void sdkWithSetsConfiguredDeletedDueToEmptySets() throws IOException, Int // 2. Receive a SPLIT_UPDATE with {name:"test", "sets":["set_1"]} CountDownLatch secondUpdate = new CountDownLatch(1); readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(secondUpdate)); - pushToStreaming(streamingData, set1SplitChange); + pushToStreaming(streamingData, splitChange3); boolean secondUpdateAwait = secondUpdate.await(5, TimeUnit.SECONDS); entities = mRoomDb.splitDao().getAll(); boolean secondUpdateStored = entities.size() == 1 && entities.get(0).getBody().contains("\"sets\":[\"set_1\"]") && @@ -150,7 +147,7 @@ public void sdkWithSetsConfiguredDeletedDueToEmptySets() throws IOException, Int // 3. Receive a SPLIT_UPDATE with {name:"test", "sets":[]} CountDownLatch thirdUpdate = new CountDownLatch(1); readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(thirdUpdate)); - pushToStreaming(streamingData, setNoneSplitChange); + pushToStreaming(streamingData, splitChange5None); boolean thirdUpdateAwait = thirdUpdate.await(5, TimeUnit.SECONDS); entities = mRoomDb.splitDao().getAll(); boolean thirdUpdateStored = entities.isEmpty(); @@ -164,7 +161,7 @@ public void sdkWithSetsConfiguredDeletedDueToEmptySets() throws IOException, Int } @Test - public void sdkWithSetsConfiguredDeletedDueToNonMatchingSets() { + public void sdkWithSetsConfiguredDeletedDueToNonMatchingSets() throws IOException, InterruptedException { /* * Initialize a factory with a & b sets configured. * @@ -174,6 +171,52 @@ public void sdkWithSetsConfiguredDeletedDueToNonMatchingSets() { * * 3. Receive a SPLIT_UPDATE with {name:"test", "sets":["x", "y"]}. No changes in storage. */ + + LinkedBlockingDeque streamingData = new LinkedBlockingDeque<>(); + SplitClient readyClient = getReadyClient(mContext, mRoomDb, true, streamingData, "set_1", "set_2"); + + // 1. Receive a SPLIT_UPDATE with "sets":["set_1", "set_2"] + CountDownLatch firstUpdate = new CountDownLatch(1); + readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(firstUpdate)); + pushToStreaming(streamingData, splitChange2); + boolean firstUpdateAwait = firstUpdate.await(5, TimeUnit.SECONDS); + List entities = mRoomDb.splitDao().getAll(); + boolean firstUpdateStored = entities.size() == 1 && entities.get(0).getBody().contains("\"sets\":[\"set_1\",\"set_2\"]") && + entities.get(0).getBody().contains("\"name\":\"workm\""); + + // 2. Receive a SPLIT_UPDATE with "sets":["set_1"] + CountDownLatch secondUpdate = new CountDownLatch(1); + readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(secondUpdate)); + pushToStreaming(streamingData, splitChange3); + boolean secondUpdateAwait = secondUpdate.await(5, TimeUnit.SECONDS); + entities = mRoomDb.splitDao().getAll(); + boolean secondUpdateStored = entities.size() == 1 && entities.get(0).getBody().contains("\"sets\":[\"set_1\"]") && + entities.get(0).getBody().contains("\"name\":\"workm\""); + + // 3. Receive a SPLIT_UPDATE with "sets":["set_3"] + CountDownLatch thirdUpdate = new CountDownLatch(1); + readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(thirdUpdate)); + pushToStreaming(streamingData, splitChange4); + boolean thirdUpdateAwait = thirdUpdate.await(5, TimeUnit.SECONDS); + entities = mRoomDb.splitDao().getAll(); + boolean thirdUpdateStored = entities.size() == 0; + + // 4. Receive a SPLIT_UPDATE with "sets":["set_3", "set_4"] + CountDownLatch fourthUpdate = new CountDownLatch(1); + readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(fourthUpdate)); + pushToStreaming(streamingData, splitChange5); + boolean fourthUpdateAwait = fourthUpdate.await(5, TimeUnit.SECONDS); + entities = mRoomDb.splitDao().getAll(); + boolean fourthUpdateStored = entities.size() == 0; + + assertTrue(firstUpdateAwait); + assertTrue(firstUpdateStored); + assertTrue(secondUpdateAwait); + assertTrue(secondUpdateStored); + assertTrue(thirdUpdateAwait); + assertTrue(thirdUpdateStored); + assertTrue(fourthUpdateAwait); + assertTrue(fourthUpdateStored); } @Test @@ -255,14 +298,6 @@ public void onPostExecutionView(SplitClient client) { return (await && authAwait) ? client : null; } - private String loadSplitChangeWithSet(int setsCount) { - String change = fileHelper.loadFileContent(mContext, "split_changes_flag_set-" + setsCount + ".json"); - SplitChange parsedChange = Json.fromJson(change, SplitChange.class); - parsedChange.since = parsedChange.till; - - return Json.toJson(parsedChange); - } - private static void pushToStreaming(LinkedBlockingDeque streamingData, String message) throws InterruptedException { try { streamingData.put(message + "" + "\n"); diff --git a/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java b/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java index 2f50f6bbb..fafc48806 100644 --- a/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java +++ b/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java @@ -6,6 +6,7 @@ import io.split.android.client.dtos.Split; import io.split.android.client.dtos.Status; +import io.split.android.client.utils.logger.Logger; interface FeatureFlagProcessStrategy { @@ -36,6 +37,7 @@ class NamesProcessStrategy implements FeatureFlagProcessStrategy { @Override public void process(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag) { + Logger.v("Processing with names"); // If the feature flag name is in the filter, we process it according to its status. Otherwise it is ignored if (mConfiguredValues.contains(featureFlag.name)) { mStatusProcessStrategy.process(activeFeatureFlags, archivedFeatureFlags, featureFlag); @@ -55,6 +57,7 @@ class SetsProcessStrategy implements FeatureFlagProcessStrategy { @Override public void process(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag) { + Logger.v("Processing with sets"); if (featureFlag.sets == null || featureFlag.sets.isEmpty()) { archivedFeatureFlags.add(featureFlag); return; diff --git a/src/main/java/io/split/android/client/service/splits/SplitInPlaceUpdateTask.java b/src/main/java/io/split/android/client/service/splits/SplitInPlaceUpdateTask.java index e9d072cf6..c34dd6539 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitInPlaceUpdateTask.java +++ b/src/main/java/io/split/android/client/service/splits/SplitInPlaceUpdateTask.java @@ -49,7 +49,7 @@ public SplitTaskExecutionInfo execute() { mEventsManager.notifyInternalEvent(SplitInternalEvent.SPLITS_UPDATED); mTelemetryRuntimeProducer.recordUpdatesFromSSE(UpdatesFromSSEEnum.SPLITS); - Logger.d("Updated feature flag: " + mSplit.name); + Logger.v("Updated feature flag"); return SplitTaskExecutionInfo.success(SplitTaskType.SPLITS_SYNC); } catch (Exception ex) { Logger.e("Could not update feature flag"); From 67e32c0bf8e6ac443ed7962aab05fbaa89ab9c98 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 11 Sep 2023 19:14:06 -0300 Subject: [PATCH 64/89] Fix test --- .../sets/FlagSetsStreamingTest.java | 39 ++++++------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java b/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java index 61d0cb412..83f6daaab 100644 --- a/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java +++ b/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java @@ -46,7 +46,7 @@ public class FlagSetsStreamingTest { // workm with set_3, set_4 private static final String splitChange5 = splitChangeV2("5", "4", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjUsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6WyJzZXRfMyIsInNldF80Il0sImNvbmRpdGlvbnMiOlt7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJJTl9TRUdNRU5UIiwibmVnYXRlIjpmYWxzZSwidXNlckRlZmluZWRTZWdtZW50TWF0Y2hlckRhdGEiOnsic2VnbWVudE5hbWUiOiJuZXdfc2VnbWVudCJ9LCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImluIHNlZ21lbnQgbmV3X3NlZ21lbnQifSx7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJBTExfS0VZUyIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjpudWxsLCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJvZmYiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImZyZWUiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImRlZmF1bHQgcnVsZSJ9XX0"); // workm with no sets - private static final String splitChange5None = splitChangeV2("5", "4", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjUsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6W10sImNvbmRpdGlvbnMiOlt7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJJTl9TRUdNRU5UIiwibmVnYXRlIjpmYWxzZSwidXNlckRlZmluZWRTZWdtZW50TWF0Y2hlckRhdGEiOnsic2VnbWVudE5hbWUiOiJuZXdfc2VnbWVudCJ9LCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImluIHNlZ21lbnQgbmV3X3NlZ21lbnQifSx7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJBTExfS0VZUyIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjpudWxsLCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJvZmYiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImZyZWUiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImRlZmF1bHQgcnVsZSJ9XX0="); + private static final String splitChange4None = splitChangeV2("4", "3", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjUsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6W10sImNvbmRpdGlvbnMiOlt7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJJTl9TRUdNRU5UIiwibmVnYXRlIjpmYWxzZSwidXNlckRlZmluZWRTZWdtZW50TWF0Y2hlckRhdGEiOnsic2VnbWVudE5hbWUiOiJuZXdfc2VnbWVudCJ9LCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImluIHNlZ21lbnQgbmV3X3NlZ21lbnQifSx7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJBTExfS0VZUyIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjpudWxsLCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJvZmYiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImZyZWUiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImRlZmF1bHQgcnVsZSJ9XX0="); // workm with set_3 private static final String splitChange4 = splitChangeV2("4", "3", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjQsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6WyJzZXRfMyJdLCJjb25kaXRpb25zIjpbeyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiSU5fU0VHTUVOVCIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjp7InNlZ21lbnROYW1lIjoibmV3X3NlZ21lbnQifSwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjowfSx7InRyZWF0bWVudCI6Im9mZiIsInNpemUiOjB9LHsidHJlYXRtZW50IjoiZnJlZSIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJpbiBzZWdtZW50IG5ld19zZWdtZW50In0seyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiQUxMX0tFWVMiLCJuZWdhdGUiOmZhbHNlLCJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6bnVsbCwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjoxMDB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJkZWZhdWx0IHJ1bGUifV19"); // workm with set_1 @@ -92,29 +92,10 @@ public void sdkWithoutSetsConfiguredDoesExcludeUpdates() throws IOException, Int assertEquals(1, mRoomDb.splitDao().getAll().size()); } - /** - * SDK initialization with config.sets=["a", "b"] : - *

- * if a SPLIT_UPDATE is received with {name:"test", "sets":["a", "b"]}, it should process it since is part of the config.Sets - *

- * if a SPLIT_UPDATE is received with {name:"test", "sets":["a"]}, it should process it since is still part of the config.Sets - *

- * if a SPLIT_UPDATE is received with {name:"test", "sets":[]} and the featureFlag is present in the storage, means that it was part of the config.Sets but not anymore. The featureFlag should be removed. - *

- * if a SPLIT_UPDATE is received with {name:"test", "sets":["x"]} and the featureFlag is present in the storage, that means that was part of the config.Sets but not anymore. The featureFlag should be removed. - *

- * if a SPLIT_UPDATE is received with {name:"test", "sets":["x", "y"]}, and the featureFlag is not part of the storage, the notification should be discarded since is NOT part of the config.Sets - *

- * if a SPLIT_KILL is received with {cn:2, name:"test", "defaultTreatment":"off"} , two scenarios possibles: - *

- * if featureFlag is present in the storage, the featureFlag should process the local kill behaviour. - *

- * if not, a fetch must be needed. - */ @Test public void sdkWithSetsConfiguredDeletedDueToEmptySets() throws IOException, InterruptedException { /* - * Initialize a factory with a & b sets configured. + * Initialize a factory with set_1 & set_2 sets configured. * * 1. Receive a SPLIT_UPDATE with {name:"test", "sets":["set_1", "set_2"]}. It should process it since is part of the config.Sets * @@ -147,7 +128,7 @@ public void sdkWithSetsConfiguredDeletedDueToEmptySets() throws IOException, Int // 3. Receive a SPLIT_UPDATE with {name:"test", "sets":[]} CountDownLatch thirdUpdate = new CountDownLatch(1); readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(thirdUpdate)); - pushToStreaming(streamingData, splitChange5None); + pushToStreaming(streamingData, splitChange4None); boolean thirdUpdateAwait = thirdUpdate.await(5, TimeUnit.SECONDS); entities = mRoomDb.splitDao().getAll(); boolean thirdUpdateStored = entities.isEmpty(); @@ -163,13 +144,15 @@ public void sdkWithSetsConfiguredDeletedDueToEmptySets() throws IOException, Int @Test public void sdkWithSetsConfiguredDeletedDueToNonMatchingSets() throws IOException, InterruptedException { /* - * Initialize a factory with a & b sets configured. + * Initialize a factory with set_1 & set_2 sets configured. + * + * 1. Receive a SPLIT_UPDATE with {name:"test", "sets":["set_1", "set_2"]}. It should process it since is part of the config.Sets * - * 1. Receive a SPLIT_UPDATE with {name:"test", "sets":["a", "b"]}. It should process it since is part of the config.Sets + * 2. Receive a SPLIT_UPDATE with "sets":["set_1"]. The feature flag should be updated. * - * 2. Receive a SPLIT_UPDATE with {name:"test", "sets":["x"]}. The featureFlag should be removed. + * 3. Receive a SPLIT_UPDATE with "sets":["set_3"]. The feature flag should be removed. * - * 3. Receive a SPLIT_UPDATE with {name:"test", "sets":["x", "y"]}. No changes in storage. + * 4. Receive a SPLIT_UPDATE with "sets":["set_3", "set_4"] No changes in storage. */ LinkedBlockingDeque streamingData = new LinkedBlockingDeque<>(); @@ -222,9 +205,9 @@ public void sdkWithSetsConfiguredDeletedDueToNonMatchingSets() throws IOExceptio @Test public void sdkWithSetsReceivesSplitKill() { /* - * Initialize a factory with a & b sets configured. + * Initialize a factory with set_1 & set_2 sets configured. * - * 1. Receive a SPLIT_UPDATE with {name:"test", "sets":["a", "b"]}. It should process it since is part of the config.Sets + * 1. Receive a SPLIT_UPDATE with {name:"test", "sets":["set_1", "set_2"]}. It should process it since is part of the config.Sets * * 2. Receive a SPLIT_KILL with {cn:2, name:"test", "defaultTreatment":"off" }. The featureFlag should be removed and a fetch should be performed. */ From 9ee4b34a98c8bc80bc3135441b0902aa61ad6cdd Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 11 Sep 2023 19:53:44 -0300 Subject: [PATCH 65/89] Kill test --- .../java/helper/IntegrationHelper.java | 6 +++ .../sets/FlagSetsStreamingTest.java | 42 +++++++++++++++++-- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/androidTest/java/helper/IntegrationHelper.java b/src/androidTest/java/helper/IntegrationHelper.java index 28378942d..92fcce547 100644 --- a/src/androidTest/java/helper/IntegrationHelper.java +++ b/src/androidTest/java/helper/IntegrationHelper.java @@ -265,6 +265,12 @@ public static String splitChangeV2(String changeNumber, String previousChangeNum "data: {\"id\":\"m2T85LA4fQ:0:0\",\"clientId\":\"pri:NzIyNjY1MzI4\",\"timestamp\":"+System.currentTimeMillis()+",\"encoding\":\"json\",\"channel\":\"NzM2MDI5Mzc0_MTgyNTg1MTgwNg==_splits\",\"data\":\"{\\\"type\\\":\\\"SPLIT_UPDATE\\\",\\\"changeNumber\\\":"+changeNumber+",\\\"pcn\\\":"+previousChangeNumber+",\\\"c\\\":"+compressionType+",\\\"d\\\":\\\""+compressedPayload+"\\\"}\"}\n"; } + public static String splitKill(String changeNumber, String splitName) { + return "id:cf74eb42-f687-48e4-ad18-af2125110aac\n" + + "event:message\n" + + "data:{\"id\":\"-OT-rGuSwz:0:0\",\"clientId\":\"NDEzMTY5Mzg0MA==:NDIxNjU0NTUyNw==\",\"timestamp\":"+System.currentTimeMillis()+",\"encoding\":\"json\",\"channel\":\"NzM2MDI5Mzc0_MTgyNTg1MTgwNg==_splits\",\"data\":\"{\\\"type\\\":\\\"SPLIT_KILL\\\",\\\"changeNumber\\\":" + changeNumber + ",\\\"defaultTreatment\\\":\\\"off\\\",\\\"splitName\\\":\\\"" + splitName + "\\\"}\"}\n"; + } + /** * Builds a dispatcher with the given responses. * diff --git a/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java b/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java index 83f6daaab..3b89d0f58 100644 --- a/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java +++ b/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java @@ -22,6 +22,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import fake.HttpClientMock; import fake.HttpResponseMock; @@ -57,10 +58,12 @@ public class FlagSetsStreamingTest { private static final String noSetsSplitChange = splitChangeV2("2", "1", "0", "eyJ0cmFmZmljVHlwZU5hbWUiOiJ1c2VyIiwiaWQiOiJkNDMxY2RkMC1iMGJlLTExZWEtOGE4MC0xNjYwYWRhOWNlMzkiLCJuYW1lIjoibWF1cm9famF2YSIsInRyYWZmaWNBbGxvY2F0aW9uIjoxMDAsInRyYWZmaWNBbGxvY2F0aW9uU2VlZCI6LTkyMzkxNDkxLCJzZWVkIjotMTc2OTM3NzYwNCwic3RhdHVzIjoiQUNUSVZFIiwia2lsbGVkIjpmYWxzZSwiZGVmYXVsdFRyZWF0bWVudCI6Im9mZiIsImNoYW5nZU51bWJlciI6MTYwMjc5OTYzODM0NCwiYWxnbyI6MiwiY29uZmlndXJhdGlvbnMiOnt9LCJzZXRzIjpbXSwiY29uZGl0aW9ucyI6W3siY29uZGl0aW9uVHlwZSI6IldISVRFTElTVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJtYXRjaGVyVHlwZSI6IldISVRFTElTVCIsIm5lZ2F0ZSI6ZmFsc2UsIndoaXRlbGlzdE1hdGNoZXJEYXRhIjp7IndoaXRlbGlzdCI6WyJhZG1pbiIsIm1hdXJvIiwibmljbyJdfX1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MTAwfV0sImxhYmVsIjoid2hpdGVsaXN0ZWQifSx7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoidXNlciJ9LCJtYXRjaGVyVHlwZSI6IklOX1NFR01FTlQiLCJuZWdhdGUiOmZhbHNlLCJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6eyJzZWdtZW50TmFtZSI6Im1hdXItMiJ9fV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6IlY0Iiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJ2NSIsInNpemUiOjB9XSwibGFiZWwiOiJpbiBzZWdtZW50IG1hdXItMiJ9LHsiY29uZGl0aW9uVHlwZSI6IlJPTExPVVQiLCJtYXRjaGVyR3JvdXAiOnsiY29tYmluZXIiOiJBTkQiLCJtYXRjaGVycyI6W3sia2V5U2VsZWN0b3IiOnsidHJhZmZpY1R5cGUiOiJ1c2VyIn0sIm1hdGNoZXJUeXBlIjoiQUxMX0tFWVMiLCJuZWdhdGUiOmZhbHNlfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6IlY0Iiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJ2NSIsInNpemUiOjB9XSwibGFiZWwiOiJkZWZhdWx0IHJ1bGUifV19"); private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); + private final AtomicInteger mSplitChangesHits = new AtomicInteger(0); private SplitRoomDatabase mRoomDb; @Before public void setUp() { + mSplitChangesHits.set(0); mRoomDb = DatabaseHelper.getTestDatabase(mContext); mRoomDb.clearAllTables(); } @@ -203,14 +206,45 @@ public void sdkWithSetsConfiguredDeletedDueToNonMatchingSets() throws IOExceptio } @Test - public void sdkWithSetsReceivesSplitKill() { + public void sdkWithSetsReceivesSplitKill() throws IOException, InterruptedException { /* * Initialize a factory with set_1 & set_2 sets configured. * * 1. Receive a SPLIT_UPDATE with {name:"test", "sets":["set_1", "set_2"]}. It should process it since is part of the config.Sets * - * 2. Receive a SPLIT_KILL with {cn:2, name:"test", "defaultTreatment":"off" }. The featureFlag should be removed and a fetch should be performed. + * 2. Receive a SPLIT_KILL with {cn:2, name:"test", "defaultTreatment":"off" }. The featureFlag should be killed and a fetch should be performed. */ + + LinkedBlockingDeque streamingData = new LinkedBlockingDeque<>(); + SplitClient readyClient = getReadyClient(mContext, mRoomDb, true, streamingData, "set_1", "set_2"); + + // 1. Receive a SPLIT_UPDATE with {name:"test", "sets":["set_1", "set_2"]} + CountDownLatch firstUpdate = new CountDownLatch(1); + readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(firstUpdate)); + pushToStreaming(streamingData, splitChange2); + boolean firstUpdateAwait = firstUpdate.await(5, TimeUnit.SECONDS); + List entities = mRoomDb.splitDao().getAll(); + boolean firstUpdateStored = entities.size() == 1 && entities.get(0).getBody().contains("\"sets\":[\"set_1\",\"set_2\"]") && + entities.get(0).getBody().contains("\"killed\":false") && + entities.get(0).getBody().contains("\"name\":\"workm\""); + + // 2. Receive a SPLIT_KILL with {cn:2, name:"test", "defaultTreatment":"off" } + CountDownLatch secondUpdate = new CountDownLatch(1); + readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(secondUpdate)); + pushToStreaming(streamingData, IntegrationHelper.splitKill("5", "workm")); + boolean secondUpdateAwait = secondUpdate.await(5, TimeUnit.SECONDS); + entities = mRoomDb.splitDao().getAll(); + boolean secondUpdateStored = entities.size() == 1 && entities.get(0).getBody().contains("\"killed\":true") && + entities.get(0).getBody().contains("\"name\":\"workm\""); + + // 3. A fetch is triggered due to the SPLIT_KILL + boolean correctAmountOfChanges = mSplitChangesHits.get() == 3; + + assertTrue(firstUpdateAwait); + assertTrue(firstUpdateStored); + assertTrue(secondUpdateAwait); + assertTrue(secondUpdateStored); + assertTrue(correctAmountOfChanges); } @Test @@ -220,6 +254,7 @@ public void sdkWithSetsReceivesSplitKillForNonExistingFeatureFlag() { * * 1. Receive a SPLIT_KILL with {cn:2, name:"test", "defaultTreatment":"off" }. No changes in storage, a fetch should be performed. */ + } @Nullable @@ -245,8 +280,7 @@ private SplitClient getReadyClient( CountDownLatch authLatch = new CountDownLatch(1); Map responses = new HashMap<>(); responses.put("splitChanges", (uri, httpMethod, body) -> { - String since = getSinceFromUri(uri); - + mSplitChangesHits.incrementAndGet(); return new HttpResponseMock(200, IntegrationHelper.emptySplitChanges(-1, 1)); }); responses.put("mySegments/CUSTOMER_ID", (uri, httpMethod, body) -> new HttpResponseMock(200, IntegrationHelper.emptyMySegments())); From 7138fcbc629b5e924df7fccc53c470ba49a02e79 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 12 Sep 2023 14:26:51 -0300 Subject: [PATCH 66/89] Kill test 2 --- .../sets/FlagSetsStreamingTest.java | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java b/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java index 3b89d0f58..736cd2ad5 100644 --- a/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java +++ b/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java @@ -1,8 +1,8 @@ package tests.integration.sets; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static helper.IntegrationHelper.ResponseClosure.getSinceFromUri; import static helper.IntegrationHelper.splitChangeV2; import android.content.Context; @@ -78,7 +78,7 @@ public void sdkWithoutSetsConfiguredDoesExcludeUpdates() throws IOException, Int * Verify that the feature flag is added. */ LinkedBlockingDeque mStreamingData = new LinkedBlockingDeque<>(); - SplitClient readyClient = getReadyClient(mContext, mRoomDb, true, mStreamingData); + SplitClient readyClient = getReadyClient(mContext, mRoomDb, mStreamingData); int initialSplitsSize = mRoomDb.splitDao().getAll().size(); @@ -108,7 +108,7 @@ public void sdkWithSetsConfiguredDeletedDueToEmptySets() throws IOException, Int * */ LinkedBlockingDeque streamingData = new LinkedBlockingDeque<>(); - SplitClient readyClient = getReadyClient(mContext, mRoomDb, true, streamingData, "set_1", "set_2"); + SplitClient readyClient = getReadyClient(mContext, mRoomDb, streamingData, "set_1", "set_2"); // 1. Receive a SPLIT_UPDATE with {name:"test", "sets":["set_1", "set_2"]} CountDownLatch firstUpdate = new CountDownLatch(1); @@ -159,7 +159,7 @@ public void sdkWithSetsConfiguredDeletedDueToNonMatchingSets() throws IOExceptio */ LinkedBlockingDeque streamingData = new LinkedBlockingDeque<>(); - SplitClient readyClient = getReadyClient(mContext, mRoomDb, true, streamingData, "set_1", "set_2"); + SplitClient readyClient = getReadyClient(mContext, mRoomDb, streamingData, "set_1", "set_2"); // 1. Receive a SPLIT_UPDATE with "sets":["set_1", "set_2"] CountDownLatch firstUpdate = new CountDownLatch(1); @@ -212,11 +212,11 @@ public void sdkWithSetsReceivesSplitKill() throws IOException, InterruptedExcept * * 1. Receive a SPLIT_UPDATE with {name:"test", "sets":["set_1", "set_2"]}. It should process it since is part of the config.Sets * - * 2. Receive a SPLIT_KILL with {cn:2, name:"test", "defaultTreatment":"off" }. The featureFlag should be killed and a fetch should be performed. + * 2. Receive a SPLIT_KILL with {cn:2, name:"workm", "defaultTreatment":"off" }. The featureFlag should be killed and a fetch should be performed. */ LinkedBlockingDeque streamingData = new LinkedBlockingDeque<>(); - SplitClient readyClient = getReadyClient(mContext, mRoomDb, true, streamingData, "set_1", "set_2"); + SplitClient readyClient = getReadyClient(mContext, mRoomDb, streamingData, "set_1", "set_2"); // 1. Receive a SPLIT_UPDATE with {name:"test", "sets":["set_1", "set_2"]} CountDownLatch firstUpdate = new CountDownLatch(1); @@ -248,20 +248,39 @@ public void sdkWithSetsReceivesSplitKill() throws IOException, InterruptedExcept } @Test - public void sdkWithSetsReceivesSplitKillForNonExistingFeatureFlag() { + public void sdkWithSetsReceivesSplitKillForNonExistingFeatureFlag() throws IOException, InterruptedException { /* - * Initialize a factory with a & b sets configured. + * Initialize a factory with set_1 & set_2 sets configured. * - * 1. Receive a SPLIT_KILL with {cn:2, name:"test", "defaultTreatment":"off" }. No changes in storage, a fetch should be performed. + * 1. Receive a SPLIT_KILL with {cn:2, name:"workm", "defaultTreatment":"off" }. No changes in storage, a fetch should be performed. */ - + LinkedBlockingDeque streamingData = new LinkedBlockingDeque<>(); + SplitClient readyClient = getReadyClient(mContext, mRoomDb, streamingData, "set_1", "set_2"); + + int initialEntities = mRoomDb.splitDao().getAll().size(); + + // 1. Receive a SPLIT_KILL with {cn:2, name:"test", "defaultTreatment":"off" } + CountDownLatch firstUpdate = new CountDownLatch(1); + readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(firstUpdate)); + int initialChangesHits = mSplitChangesHits.get(); + pushToStreaming(streamingData, IntegrationHelper.splitKill("2", "workm")); + boolean firstUpdateAwait = firstUpdate.await(5, TimeUnit.SECONDS); + List entities = mRoomDb.splitDao().getAll(); + boolean firstUpdateStored = entities.isEmpty(); + + // 2. A fetch is triggered due to the SPLIT_KILL + int finalChangesHits = mSplitChangesHits.get(); + + assertFalse(firstUpdateAwait); + assertTrue(firstUpdateStored); + assertEquals(initialEntities, 0); + assertTrue(finalChangesHits > initialChangesHits); } @Nullable private SplitClient getReadyClient( Context mContext, SplitRoomDatabase splitRoomDatabase, - boolean streamingEnabled, BlockingQueue streamingData, String... sets) throws IOException, InterruptedException { SplitClientConfig config = new TestableSplitConfigBuilder() @@ -274,7 +293,7 @@ private SplitClient getReadyClient( .addSplitFilter(SplitFilter.bySet(Arrays.asList(sets))) .build()) .featuresRefreshRate(2) - .streamingEnabled(streamingEnabled) + .streamingEnabled(true) .eventFlushInterval(1000) .build(); CountDownLatch authLatch = new CountDownLatch(1); From a16d8f48022cc6b0f5223ddf3c2be3d522077831 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 12 Sep 2023 14:27:07 -0300 Subject: [PATCH 67/89] Kill task improvement --- .../client/service/splits/SplitKillTask.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/split/android/client/service/splits/SplitKillTask.java b/src/main/java/io/split/android/client/service/splits/SplitKillTask.java index bdf91da04..06b8be469 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitKillTask.java +++ b/src/main/java/io/split/android/client/service/splits/SplitKillTask.java @@ -31,18 +31,23 @@ public SplitKillTask(@NonNull SplitsStorage splitsStorage, Split split, public SplitTaskExecutionInfo execute() { try { if (mKilledSplit == null) { - logError("Split name to kill could not be null."); + logError("Feature flag name to kill could not be null."); return SplitTaskExecutionInfo.error(SplitTaskType.SPLIT_KILL); } long changeNumber = mSplitsStorage.getTill(); if (mKilledSplit.changeNumber <= changeNumber) { - Logger.d("Skipping killed split notification for old change number: " + Logger.d("Skipping killed feature flag notification for old change number: " + mKilledSplit.changeNumber); return SplitTaskExecutionInfo.success(SplitTaskType.SPLIT_KILL); } Split splitToKill = mSplitsStorage.get(mKilledSplit.name); + if (splitToKill == null) { + Logger.d("Skipping " + mKilledSplit.name + " since not in storage"); + return SplitTaskExecutionInfo.error(SplitTaskType.SPLIT_KILL); + } + splitToKill.killed = true; splitToKill.defaultTreatment = mKilledSplit.defaultTreatment; splitToKill.changeNumber = mKilledSplit.changeNumber; @@ -50,14 +55,14 @@ public SplitTaskExecutionInfo execute() { mSplitsStorage.updateWithoutChecks(splitToKill); mEventsManager.notifyInternalEvent(SplitInternalEvent.SPLIT_KILLED_NOTIFICATION); } catch (Exception e) { - logError("Unknown error while updating killed split: " + e.getLocalizedMessage()); + logError("Unknown error while updating killed feature flag: " + e.getLocalizedMessage()); return SplitTaskExecutionInfo.error(SplitTaskType.SPLIT_KILL); } - Logger.d("Killed split has been updated"); + Logger.d("Killed feature flag has been updated"); return SplitTaskExecutionInfo.success(SplitTaskType.SPLIT_KILL); } private void logError(String message) { - Logger.e("Error while executing Split kill task: " + message); + Logger.e("Error while executing feature flag kill task: " + message); } } From 6ecc1a5f82c6f32a4d45371eade2361c2ca2417c Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 12 Sep 2023 15:47:17 -0300 Subject: [PATCH 68/89] Sets evaluation test --- .../sets/FlagSetsEvaluationTest.java | 172 ++++++++++++++++++ .../integration/sets/FlagSetsPollingTest.java | 11 +- 2 files changed, 175 insertions(+), 8 deletions(-) create mode 100644 src/androidTest/java/tests/integration/sets/FlagSetsEvaluationTest.java diff --git a/src/androidTest/java/tests/integration/sets/FlagSetsEvaluationTest.java b/src/androidTest/java/tests/integration/sets/FlagSetsEvaluationTest.java new file mode 100644 index 000000000..55864f291 --- /dev/null +++ b/src/androidTest/java/tests/integration/sets/FlagSetsEvaluationTest.java @@ -0,0 +1,172 @@ +package tests.integration.sets; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static helper.IntegrationHelper.ResponseClosure.getSinceFromUri; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import fake.HttpClientMock; +import fake.HttpResponseMock; +import fake.HttpResponseMockDispatcher; +import helper.DatabaseHelper; +import helper.FileHelper; +import helper.IntegrationHelper; +import helper.TestableSplitConfigBuilder; +import io.split.android.client.SplitClient; +import io.split.android.client.SplitClientConfig; +import io.split.android.client.SplitFactory; +import io.split.android.client.SplitFilter; +import io.split.android.client.SplitResult; +import io.split.android.client.SyncConfig; +import io.split.android.client.dtos.SplitChange; +import io.split.android.client.events.SplitEvent; +import io.split.android.client.storage.db.SplitRoomDatabase; +import io.split.android.client.utils.Json; +import tests.integration.shared.TestingHelper; + +public class FlagSetsEvaluationTest { + + private final FileHelper fileHelper = new FileHelper(); + private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); + + @Test + public void sdkWithSetsCanOnlyEvaluateSetsConfigured() throws IOException, InterruptedException { + /* + Initialize with set_1 configured. Changes contains 1 flag in set_1 & set_2, and one in set_3. + + Only the flag in set_1 can be evaluated. + */ + SplitClient client = getClient(mContext, DatabaseHelper.getTestDatabase(mContext), "set_1"); + + Map set1Treatments = client.getTreatmentsByFlagSet("set_1", null); + Map set2Treatments = client.getTreatmentsByFlagSet("set_2", null); + Map set3Treatments = client.getTreatmentsByFlagSet("set_3", null); + Map set1Results = client.getTreatmentsWithConfigByFlagSet("set_1", null); + Map set2Results = client.getTreatmentsWithConfigByFlagSet("set_2", null); + Map set3Results = client.getTreatmentsWithConfigByFlagSet("set_3", null); + Map allTreatments = client.getTreatmentsByFlagSets(Arrays.asList("set_1", "set_2", "set_3"), null); + Map allTreatmentsWithConfig = client.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_2", "set_3"), null); + + assertEquals(1, set1Treatments.size()); + assertEquals(0, set2Treatments.size()); + assertEquals(0, set3Treatments.size()); + assertEquals(1, set1Results.size()); + assertEquals(0, set2Results.size()); + assertEquals(0, set3Results.size()); + assertEquals(1, allTreatments.size()); + assertEquals(1, allTreatmentsWithConfig.size()); + assertTrue(set1Treatments.values().stream().noneMatch(t -> t.equals("control"))); + assertTrue(set1Results.values().stream().noneMatch(t -> t.treatment().equals("control"))); + assertTrue(allTreatments.values().stream().noneMatch(t -> t.equals("control"))); + assertTrue(allTreatmentsWithConfig.values().stream().noneMatch(t -> t.treatment().equals("control"))); + } + + @Test + public void sdkWithoutSetsCanEvaluateAnySet() throws IOException, InterruptedException { + /* + Initialize with no sets configured. Changes contains 1 flag in set_1 & set_2, and one in set_3. + + All flags can be evaluated by sets. + */ + SplitClient client = getClient(mContext, DatabaseHelper.getTestDatabase(mContext)); + + Map set1Treatments = client.getTreatmentsByFlagSet("set_1", null); + Map set2Treatments = client.getTreatmentsByFlagSet("set_2", null); + Map set3Treatments = client.getTreatmentsByFlagSet("set_3", null); + Map set1Results = client.getTreatmentsWithConfigByFlagSet("set_1", null); + Map set2Results = client.getTreatmentsWithConfigByFlagSet("set_2", null); + Map set3Results = client.getTreatmentsWithConfigByFlagSet("set_3", null); + Map allTreatments = client.getTreatmentsByFlagSets(Arrays.asList("set_1", "set_2", "set_3"), null); + Map allTreatmentsWithConfig = client.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_2", "set_3"), null); + + assertEquals(1, set1Treatments.size()); + assertEquals(1, set2Treatments.size()); + assertEquals(1, set3Treatments.size()); + assertEquals(1, set1Results.size()); + assertEquals(1, set2Results.size()); + assertEquals(1, set3Results.size()); + assertTrue(set1Treatments.values().stream().noneMatch(t -> t.equals("control"))); + assertTrue(set2Treatments.values().stream().noneMatch(t -> t.equals("control"))); + assertTrue(set3Treatments.values().stream().noneMatch(t -> t.equals("control"))); + assertTrue(set1Results.values().stream().noneMatch(t -> t.treatment().equals("control"))); + assertTrue(set2Results.values().stream().noneMatch(t -> t.treatment().equals("control"))); + assertTrue(set3Results.values().stream().noneMatch(t -> t.treatment().equals("control"))); + assertTrue(allTreatments.values().stream().noneMatch(t -> t.equals("control"))); + assertTrue(allTreatmentsWithConfig.values().stream().noneMatch(t -> t.treatment().equals("control"))); + } + + private SplitClient getClient( + Context mContext, + SplitRoomDatabase splitRoomDatabase, + String... sets) throws IOException, InterruptedException { + SplitClientConfig config = new TestableSplitConfigBuilder() + .ready(30000) + .trafficType("client") + .enableDebug() + .impressionsRefreshRate(1000) + .impressionsCountersRefreshRate(1000) + .syncConfig(SyncConfig.builder() + .addSplitFilter(SplitFilter.bySet(Arrays.asList(sets))) + .build()) + .featuresRefreshRate(2) + .streamingEnabled(false) + .eventFlushInterval(1000) + .build(); + + Map responses = new HashMap<>(); + responses.put("splitChanges", (uri, httpMethod, body) -> { + String since = getSinceFromUri(uri); + + if (since.equals("-1")) { + return new HttpResponseMock(200, loadSplitChangeWithSet(2)); + } else { + return new HttpResponseMock(200, IntegrationHelper.emptySplitChanges(1602796638344L, 1602796638344L)); + } + }); + + responses.put("mySegments/CUSTOMER_ID", (uri, httpMethod, body) -> new HttpResponseMock(200, IntegrationHelper.emptyMySegments())); + + HttpResponseMockDispatcher httpResponseMockDispatcher = IntegrationHelper.buildDispatcher(responses); + + SplitFactory factory = IntegrationHelper.buildFactory( + IntegrationHelper.dummyApiKey(), + IntegrationHelper.dummyUserKey(), + config, + mContext, + new HttpClientMock(httpResponseMockDispatcher), + splitRoomDatabase, null, null, null); + + CountDownLatch readyLatch = new CountDownLatch(1); + SplitClient client = factory.client(); + client.on(SplitEvent.SDK_READY, TestingHelper.testTask(readyLatch)); + + boolean readyAwait = readyLatch.await(5, TimeUnit.SECONDS); + + if (readyAwait) { + return client; + } else { + return null; + } + } + + private String loadSplitChangeWithSet(int setsCount) { + String change = fileHelper.loadFileContent(mContext, "split_changes_flag_set-" + setsCount + ".json"); + SplitChange parsedChange = Json.fromJson(change, SplitChange.class); + parsedChange.since = parsedChange.till; + + return Json.toJson(parsedChange); + } +} diff --git a/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java b/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java index dfc0a8b98..202fdf463 100644 --- a/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java +++ b/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java @@ -19,7 +19,6 @@ import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import fake.HttpClientMock; import fake.HttpResponseMock; @@ -28,14 +27,11 @@ import helper.FileHelper; import helper.IntegrationHelper; import helper.TestableSplitConfigBuilder; -import io.split.android.client.SplitClient; import io.split.android.client.SplitClientConfig; import io.split.android.client.SplitFactory; import io.split.android.client.SplitFilter; import io.split.android.client.SyncConfig; import io.split.android.client.dtos.SplitChange; -import io.split.android.client.events.SplitEvent; -import io.split.android.client.events.SplitEventTask; import io.split.android.client.storage.db.SplitEntity; import io.split.android.client.storage.db.SplitRoomDatabase; import io.split.android.client.utils.Json; @@ -77,7 +73,7 @@ public void featureFlagIsUpdatedAccordingToSetsWhenTheyAreConfigured() throws IO -> it should be removed from storage */ - createFactory(mContext, mRoomDb, false, "set_1", "set_2"); + createFactory(mContext, mRoomDb, "set_1", "set_2"); boolean awaitFirst = firstChangeLatch.await(5, TimeUnit.SECONDS); Thread.sleep(200); @@ -122,7 +118,7 @@ public void featureFlagSetsAreIgnoredWhenSetsAreNotConfigured() throws IOExcepti -> that split should be updated. */ - createFactory(mContext, mRoomDb, false); + createFactory(mContext, mRoomDb); boolean awaitFirst = firstChangeLatch.await(5, TimeUnit.SECONDS); Thread.sleep(500); @@ -174,7 +170,6 @@ public void featureFlagSetsAreIgnoredWhenSetsAreNotConfigured() throws IOExcepti private SplitFactory createFactory( Context mContext, SplitRoomDatabase splitRoomDatabase, - boolean streamingEnabled, String... sets) throws IOException { SplitClientConfig config = new TestableSplitConfigBuilder() .ready(30000) @@ -186,7 +181,7 @@ private SplitFactory createFactory( .addSplitFilter(SplitFilter.bySet(Arrays.asList(sets))) .build()) .featuresRefreshRate(2) - .streamingEnabled(streamingEnabled) + .streamingEnabled(false) .eventFlushInterval(1000) .build(); From ea49ea0d9543d44e91ac9caafcc18a84ac67606b Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 12 Sep 2023 15:49:02 -0300 Subject: [PATCH 69/89] Fix test --- .../android/client/service/splits/SplitChangeProcessorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java b/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java index d270ec649..1bddee8c2 100644 --- a/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java +++ b/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java @@ -226,7 +226,7 @@ public void creatingWithNullFilterProcessesEverything() { @Test public void creatingWithFilterWithEmptyConfiguredValuesProcessesEverything() { - List filterList = Collections.singletonList(SplitFilter.bySet(Collections.emptyList())); + Map filterList = Collections.singletonMap(SplitFilter.Type.BY_SET, SplitFilter.bySet(Collections.emptyList())); mProcessor = new SplitChangeProcessor(filterList); From 14b311688621ea34492dcb7cfd9677d6e7f5a2fe Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 12 Sep 2023 16:44:37 -0300 Subject: [PATCH 70/89] Refactor --- .../sets/FlagSetsStreamingTest.java | 160 +++++------------- 1 file changed, 47 insertions(+), 113 deletions(-) diff --git a/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java b/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java index 736cd2ad5..e4e60f0c3 100644 --- a/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java +++ b/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java @@ -69,24 +69,16 @@ public void setUp() { } @Test - public void sdkWithoutSetsConfiguredDoesExcludeUpdates() throws IOException, InterruptedException { - /* - * Initialize a factory with streaming enabled and no sets. - * - * Receive notification with new feature flag with no sets. - * - * Verify that the feature flag is added. - */ + public void sdkWithoutSetsConfiguredDoesNotExcludeUpdates() throws IOException, InterruptedException { + // 1. Initialize a factory with streaming enabled and no sets. LinkedBlockingDeque mStreamingData = new LinkedBlockingDeque<>(); SplitClient readyClient = getReadyClient(mContext, mRoomDb, mStreamingData); int initialSplitsSize = mRoomDb.splitDao().getAll().size(); - - // set up update listener CountDownLatch updateLatch = new CountDownLatch(1); readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(updateLatch)); - // push change + // 2. Receive notification with new feature flag with no sets. pushToStreaming(mStreamingData, noSetsSplitChange); boolean updateAwait = updateLatch.await(5, TimeUnit.SECONDS); @@ -97,128 +89,54 @@ public void sdkWithoutSetsConfiguredDoesExcludeUpdates() throws IOException, Int @Test public void sdkWithSetsConfiguredDeletedDueToEmptySets() throws IOException, InterruptedException { - /* - * Initialize a factory with set_1 & set_2 sets configured. - * - * 1. Receive a SPLIT_UPDATE with {name:"test", "sets":["set_1", "set_2"]}. It should process it since is part of the config.Sets - * - * 2. Receive a SPLIT_UPDATE with {name:"test", "sets":["set_1"]}. It should process it since is still part of the config.Sets - * - * 3. Receive a SPLIT_UPDATE with {name:"test", "sets":[]}. The featureFlag should be removed. - * - */ LinkedBlockingDeque streamingData = new LinkedBlockingDeque<>(); SplitClient readyClient = getReadyClient(mContext, mRoomDb, streamingData, "set_1", "set_2"); - // 1. Receive a SPLIT_UPDATE with {name:"test", "sets":["set_1", "set_2"]} - CountDownLatch firstUpdate = new CountDownLatch(1); - readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(firstUpdate)); - pushToStreaming(streamingData, splitChange2); - boolean firstUpdateAwait = firstUpdate.await(5, TimeUnit.SECONDS); - List entities = mRoomDb.splitDao().getAll(); - boolean firstUpdateStored = entities.size() == 1 && entities.get(0).getBody().contains("\"sets\":[\"set_1\",\"set_2\"]") && - entities.get(0).getBody().contains("\"name\":\"workm\""); + // 1. Receive a SPLIT_UPDATE with "sets":["set_1", "set_2"] + boolean firstChange = processUpdate(readyClient, streamingData, splitChange2, "\"sets\":[\"set_1\",\"set_2\"]", "\"name\":\"workm\""); - // 2. Receive a SPLIT_UPDATE with {name:"test", "sets":["set_1"]} - CountDownLatch secondUpdate = new CountDownLatch(1); - readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(secondUpdate)); - pushToStreaming(streamingData, splitChange3); - boolean secondUpdateAwait = secondUpdate.await(5, TimeUnit.SECONDS); - entities = mRoomDb.splitDao().getAll(); - boolean secondUpdateStored = entities.size() == 1 && entities.get(0).getBody().contains("\"sets\":[\"set_1\"]") && - entities.get(0).getBody().contains("\"name\":\"workm\""); + // 2. Receive a SPLIT_UPDATE with "sets":["set_1"] + boolean secondChange = processUpdate(readyClient, streamingData, splitChange3, "\"sets\":[\"set_1\"]", "\"name\":\"workm\""); - // 3. Receive a SPLIT_UPDATE with {name:"test", "sets":[]} - CountDownLatch thirdUpdate = new CountDownLatch(1); - readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(thirdUpdate)); - pushToStreaming(streamingData, splitChange4None); - boolean thirdUpdateAwait = thirdUpdate.await(5, TimeUnit.SECONDS); - entities = mRoomDb.splitDao().getAll(); - boolean thirdUpdateStored = entities.isEmpty(); + // 3. Receive a SPLIT_UPDATE with "sets":[] + boolean thirdChange = processUpdate(readyClient, streamingData, splitChange4None); - assertTrue(firstUpdateAwait); - assertTrue(firstUpdateStored); - assertTrue(secondUpdateAwait); - assertTrue(secondUpdateStored); - assertTrue(thirdUpdateAwait); - assertTrue(thirdUpdateStored); + assertTrue(firstChange); + assertTrue(secondChange); + assertTrue(thirdChange); } @Test public void sdkWithSetsConfiguredDeletedDueToNonMatchingSets() throws IOException, InterruptedException { - /* - * Initialize a factory with set_1 & set_2 sets configured. - * - * 1. Receive a SPLIT_UPDATE with {name:"test", "sets":["set_1", "set_2"]}. It should process it since is part of the config.Sets - * - * 2. Receive a SPLIT_UPDATE with "sets":["set_1"]. The feature flag should be updated. - * - * 3. Receive a SPLIT_UPDATE with "sets":["set_3"]. The feature flag should be removed. - * - * 4. Receive a SPLIT_UPDATE with "sets":["set_3", "set_4"] No changes in storage. - */ - LinkedBlockingDeque streamingData = new LinkedBlockingDeque<>(); SplitClient readyClient = getReadyClient(mContext, mRoomDb, streamingData, "set_1", "set_2"); // 1. Receive a SPLIT_UPDATE with "sets":["set_1", "set_2"] - CountDownLatch firstUpdate = new CountDownLatch(1); - readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(firstUpdate)); - pushToStreaming(streamingData, splitChange2); - boolean firstUpdateAwait = firstUpdate.await(5, TimeUnit.SECONDS); - List entities = mRoomDb.splitDao().getAll(); - boolean firstUpdateStored = entities.size() == 1 && entities.get(0).getBody().contains("\"sets\":[\"set_1\",\"set_2\"]") && - entities.get(0).getBody().contains("\"name\":\"workm\""); + boolean firstChange = processUpdate(readyClient, streamingData, splitChange2, "\"sets\":[\"set_1\",\"set_2\"]", "\"name\":\"workm\""); // 2. Receive a SPLIT_UPDATE with "sets":["set_1"] - CountDownLatch secondUpdate = new CountDownLatch(1); - readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(secondUpdate)); - pushToStreaming(streamingData, splitChange3); - boolean secondUpdateAwait = secondUpdate.await(5, TimeUnit.SECONDS); - entities = mRoomDb.splitDao().getAll(); - boolean secondUpdateStored = entities.size() == 1 && entities.get(0).getBody().contains("\"sets\":[\"set_1\"]") && - entities.get(0).getBody().contains("\"name\":\"workm\""); + boolean secondChange = processUpdate(readyClient, streamingData, splitChange3, "\"sets\":[\"set_1\"]", "\"name\":\"workm\""); // 3. Receive a SPLIT_UPDATE with "sets":["set_3"] - CountDownLatch thirdUpdate = new CountDownLatch(1); - readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(thirdUpdate)); - pushToStreaming(streamingData, splitChange4); - boolean thirdUpdateAwait = thirdUpdate.await(5, TimeUnit.SECONDS); - entities = mRoomDb.splitDao().getAll(); - boolean thirdUpdateStored = entities.size() == 0; + boolean thirdChange = processUpdate(readyClient, streamingData, splitChange4); // 4. Receive a SPLIT_UPDATE with "sets":["set_3", "set_4"] - CountDownLatch fourthUpdate = new CountDownLatch(1); - readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(fourthUpdate)); - pushToStreaming(streamingData, splitChange5); - boolean fourthUpdateAwait = fourthUpdate.await(5, TimeUnit.SECONDS); - entities = mRoomDb.splitDao().getAll(); - boolean fourthUpdateStored = entities.size() == 0; + boolean fourthChange = processUpdate(readyClient, streamingData, splitChange5); - assertTrue(firstUpdateAwait); - assertTrue(firstUpdateStored); - assertTrue(secondUpdateAwait); - assertTrue(secondUpdateStored); - assertTrue(thirdUpdateAwait); - assertTrue(thirdUpdateStored); - assertTrue(fourthUpdateAwait); - assertTrue(fourthUpdateStored); + assertTrue(firstChange); + assertTrue(secondChange); + assertTrue(thirdChange); + assertTrue(fourthChange); } @Test public void sdkWithSetsReceivesSplitKill() throws IOException, InterruptedException { - /* - * Initialize a factory with set_1 & set_2 sets configured. - * - * 1. Receive a SPLIT_UPDATE with {name:"test", "sets":["set_1", "set_2"]}. It should process it since is part of the config.Sets - * - * 2. Receive a SPLIT_KILL with {cn:2, name:"workm", "defaultTreatment":"off" }. The featureFlag should be killed and a fetch should be performed. - */ + // 1. Initialize a factory with set_1 & set_2 sets configured. LinkedBlockingDeque streamingData = new LinkedBlockingDeque<>(); SplitClient readyClient = getReadyClient(mContext, mRoomDb, streamingData, "set_1", "set_2"); - // 1. Receive a SPLIT_UPDATE with {name:"test", "sets":["set_1", "set_2"]} + // 2. Receive a SPLIT_UPDATE with "sets":["set_1", "set_2"] CountDownLatch firstUpdate = new CountDownLatch(1); readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(firstUpdate)); pushToStreaming(streamingData, splitChange2); @@ -228,7 +146,7 @@ public void sdkWithSetsReceivesSplitKill() throws IOException, InterruptedExcept entities.get(0).getBody().contains("\"killed\":false") && entities.get(0).getBody().contains("\"name\":\"workm\""); - // 2. Receive a SPLIT_KILL with {cn:2, name:"test", "defaultTreatment":"off" } + // 3. Receive a SPLIT_KILL for workm CountDownLatch secondUpdate = new CountDownLatch(1); readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(secondUpdate)); pushToStreaming(streamingData, IntegrationHelper.splitKill("5", "workm")); @@ -237,7 +155,7 @@ public void sdkWithSetsReceivesSplitKill() throws IOException, InterruptedExcept boolean secondUpdateStored = entities.size() == 1 && entities.get(0).getBody().contains("\"killed\":true") && entities.get(0).getBody().contains("\"name\":\"workm\""); - // 3. A fetch is triggered due to the SPLIT_KILL + // 4. A fetch is triggered due to the SPLIT_KILL boolean correctAmountOfChanges = mSplitChangesHits.get() == 3; assertTrue(firstUpdateAwait); @@ -249,17 +167,14 @@ public void sdkWithSetsReceivesSplitKill() throws IOException, InterruptedExcept @Test public void sdkWithSetsReceivesSplitKillForNonExistingFeatureFlag() throws IOException, InterruptedException { - /* - * Initialize a factory with set_1 & set_2 sets configured. - * - * 1. Receive a SPLIT_KILL with {cn:2, name:"workm", "defaultTreatment":"off" }. No changes in storage, a fetch should be performed. - */ + + // 1. Initialize a factory with set_1 & set_2 sets configured. LinkedBlockingDeque streamingData = new LinkedBlockingDeque<>(); SplitClient readyClient = getReadyClient(mContext, mRoomDb, streamingData, "set_1", "set_2"); int initialEntities = mRoomDb.splitDao().getAll().size(); - // 1. Receive a SPLIT_KILL with {cn:2, name:"test", "defaultTreatment":"off" } + // 2. Receive a SPLIT_KILL; storage is not modified since flag is not present. CountDownLatch firstUpdate = new CountDownLatch(1); readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(firstUpdate)); int initialChangesHits = mSplitChangesHits.get(); @@ -268,7 +183,7 @@ public void sdkWithSetsReceivesSplitKillForNonExistingFeatureFlag() throws IOExc List entities = mRoomDb.splitDao().getAll(); boolean firstUpdateStored = entities.isEmpty(); - // 2. A fetch is triggered due to the SPLIT_KILL + // 3. A fetch is triggered due to the SPLIT_KILL int finalChangesHits = mSplitChangesHits.get(); assertFalse(firstUpdateAwait); @@ -342,4 +257,23 @@ private static void pushToStreaming(LinkedBlockingDeque streamingData, S } catch (InterruptedException ignored) { } } + + private boolean processUpdate(SplitClient client, LinkedBlockingDeque streamingData, String splitChange, String... expectedContents) throws InterruptedException { + CountDownLatch updateLatch = new CountDownLatch(1); + client.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(updateLatch)); + pushToStreaming(streamingData, splitChange); + boolean updateAwaited = updateLatch.await(5, TimeUnit.SECONDS); + List entities = mRoomDb.splitDao().getAll(); + + if (expectedContents == null || expectedContents.length == 0) { + return updateAwaited && entities.isEmpty(); + } + + boolean contentMatches = true; + for (String expected : expectedContents) { + contentMatches = contentMatches && entities.size() == 1 && entities.get(0).getBody().contains(expected); + } + + return updateAwaited && contentMatches; + } } From b9c31b15e49d4a669dfc2e7a45e304fe41c05cba Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 12 Sep 2023 18:06:05 -0300 Subject: [PATCH 71/89] Better comments --- .../integration/sets/FlagSetsPollingTest.java | 40 +++++++------------ .../sets/FlagSetsStreamingTest.java | 12 +++++- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java b/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java index dfc0a8b98..1458a4016 100644 --- a/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java +++ b/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java @@ -64,31 +64,25 @@ public void setUp() throws Exception { @Test public void featureFlagIsUpdatedAccordingToSetsWhenTheyAreConfigured() throws IOException, InterruptedException { - /* - This test creates a factory with 2 configured sets. - - The first split change will have 2 splits (workm and workm_set_3), one that belongs to set_1 and set_2 and one that belongs to set_3; - -> it should be added to storage - - The second change will have 1 split (workm) that belongs to set_1 only. - -> it should remain in storage and be updated - - The third change will have 1 split (workm) that belongs to set_3 only. - -> it should be removed from storage - */ - + // 1. Initialize a factory with polling and sets set_1 & set_2 configured. createFactory(mContext, mRoomDb, false, "set_1", "set_2"); + // 2. Receive split change with 1 split belonging to set_1 & set_2 and one belonging to set_3 + // -> only one feature flag should be added boolean awaitFirst = firstChangeLatch.await(5, TimeUnit.SECONDS); Thread.sleep(200); int firstSize = mRoomDb.splitDao().getAll().size(); boolean firstSetsCorrect = mRoomDb.splitDao().getAll().get(0).getBody().contains("[\"set_1\",\"set_2\"]"); + // 3. Receive split change with 1 split belonging to set_1 only + // -> the feature flag should be updated boolean awaitSecond = secondChangeLatch.await(5, TimeUnit.SECONDS); Thread.sleep(200); int secondSize = mRoomDb.splitDao().getAll().size(); boolean secondSetsCorrect = mRoomDb.splitDao().getAll().get(0).getBody().contains("[\"set_1\"]"); + // 4. Receive split change with 1 split belonging to set_3 only + // -> the feature flag should be removed boolean awaitThird = thirdChangeLatch.await(5, TimeUnit.SECONDS); Thread.sleep(200); int thirdSize = mRoomDb.splitDao().getAll().size(); @@ -109,21 +103,11 @@ public void featureFlagIsUpdatedAccordingToSetsWhenTheyAreConfigured() throws IO @Test public void featureFlagSetsAreIgnoredWhenSetsAreNotConfigured() throws IOException, InterruptedException { - /* - This test creates a factory with no sets configured. - - The first split change will have 2 splits (workm and workm_set_3), one that belongs to set_1 and set_2 and one that belongs to set_3; - -> both should be added to storage. - - The second change will have 1 split (workm) that belongs to set_1 only. - -> that split should be updated. - - The third change will have 1 split (workm) that belongs to set_3 only. - -> that split should be updated. - */ - + // 1. Initialize a factory with polling and sets set_1 & set_2 configured. createFactory(mContext, mRoomDb, false); + // 2. Receive split change with 1 split belonging to set_1 & set_2 and one belonging to set_3 + // -> only one feature flag should be added boolean awaitFirst = firstChangeLatch.await(5, TimeUnit.SECONDS); Thread.sleep(500); int firstSize = mRoomDb.splitDao().getAll().size(); @@ -131,6 +115,8 @@ public void featureFlagSetsAreIgnoredWhenSetsAreNotConfigured() throws IOExcepti boolean firstSetsCorrect = firstEntities.get(0).getBody().contains("[\"set_1\",\"set_2\"]") && firstEntities.get(1).getBody().contains("[\"set_3\"]"); + // 3. Receive split change with 1 split belonging to set_1 only + // -> the feature flag should be updated boolean awaitSecond = secondChangeLatch.await(5, TimeUnit.SECONDS); Thread.sleep(500); int secondSize = mRoomDb.splitDao().getAll().size(); @@ -145,6 +131,8 @@ public void featureFlagSetsAreIgnoredWhenSetsAreNotConfigured() throws IOExcepti Logger.w("body0: " + body0); Logger.w("body1: " + body1); + // 4. Receive split change with 1 split belonging to set_3 only + // -> the feature flag should be removed boolean awaitThird = thirdChangeLatch.await(5, TimeUnit.SECONDS); Thread.sleep(500); List thirdEntities = mRoomDb.splitDao().getAll(); diff --git a/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java b/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java index e4e60f0c3..d61a46e06 100644 --- a/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java +++ b/src/androidTest/java/tests/integration/sets/FlagSetsStreamingTest.java @@ -79,6 +79,7 @@ public void sdkWithoutSetsConfiguredDoesNotExcludeUpdates() throws IOException, readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(updateLatch)); // 2. Receive notification with new feature flag with no sets. + // 3. Assert that the update is processed and the split is stored. pushToStreaming(mStreamingData, noSetsSplitChange); boolean updateAwait = updateLatch.await(5, TimeUnit.SECONDS); @@ -93,12 +94,15 @@ public void sdkWithSetsConfiguredDeletedDueToEmptySets() throws IOException, Int SplitClient readyClient = getReadyClient(mContext, mRoomDb, streamingData, "set_1", "set_2"); // 1. Receive a SPLIT_UPDATE with "sets":["set_1", "set_2"] + // -> flag is added to the storage boolean firstChange = processUpdate(readyClient, streamingData, splitChange2, "\"sets\":[\"set_1\",\"set_2\"]", "\"name\":\"workm\""); // 2. Receive a SPLIT_UPDATE with "sets":["set_1"] + // -> flag is updated in storage boolean secondChange = processUpdate(readyClient, streamingData, splitChange3, "\"sets\":[\"set_1\"]", "\"name\":\"workm\""); // 3. Receive a SPLIT_UPDATE with "sets":[] + // -> flag is removed from storage boolean thirdChange = processUpdate(readyClient, streamingData, splitChange4None); assertTrue(firstChange); @@ -112,15 +116,19 @@ public void sdkWithSetsConfiguredDeletedDueToNonMatchingSets() throws IOExceptio SplitClient readyClient = getReadyClient(mContext, mRoomDb, streamingData, "set_1", "set_2"); // 1. Receive a SPLIT_UPDATE with "sets":["set_1", "set_2"] + // -> workm is added to the storage boolean firstChange = processUpdate(readyClient, streamingData, splitChange2, "\"sets\":[\"set_1\",\"set_2\"]", "\"name\":\"workm\""); // 2. Receive a SPLIT_UPDATE with "sets":["set_1"] + // -> workm sets are updated to set_1 only boolean secondChange = processUpdate(readyClient, streamingData, splitChange3, "\"sets\":[\"set_1\"]", "\"name\":\"workm\""); // 3. Receive a SPLIT_UPDATE with "sets":["set_3"] + // -> workm is removed from the storage boolean thirdChange = processUpdate(readyClient, streamingData, splitChange4); // 4. Receive a SPLIT_UPDATE with "sets":["set_3", "set_4"] + // -> workm is not added to the storage boolean fourthChange = processUpdate(readyClient, streamingData, splitChange5); assertTrue(firstChange); @@ -137,6 +145,7 @@ public void sdkWithSetsReceivesSplitKill() throws IOException, InterruptedExcept SplitClient readyClient = getReadyClient(mContext, mRoomDb, streamingData, "set_1", "set_2"); // 2. Receive a SPLIT_UPDATE with "sets":["set_1", "set_2"] + // -> flag is added to the storage CountDownLatch firstUpdate = new CountDownLatch(1); readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(firstUpdate)); pushToStreaming(streamingData, splitChange2); @@ -146,7 +155,8 @@ public void sdkWithSetsReceivesSplitKill() throws IOException, InterruptedExcept entities.get(0).getBody().contains("\"killed\":false") && entities.get(0).getBody().contains("\"name\":\"workm\""); - // 3. Receive a SPLIT_KILL for workm + // 3. Receive a SPLIT_KILL for flag + // -> flag is updated in storage CountDownLatch secondUpdate = new CountDownLatch(1); readyClient.on(SplitEvent.SDK_UPDATE, TestingHelper.testTask(secondUpdate)); pushToStreaming(streamingData, IntegrationHelper.splitKill("5", "workm")); From 3168519712016d513c83c2416a9d56545ae55401 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 13 Sep 2023 10:09:51 -0300 Subject: [PATCH 72/89] Minify json --- .../assets/split_changes_flag_set-0.json | 109 +-------- .../assets/split_changes_flag_set-1.json | 109 +-------- .../assets/split_changes_flag_set-2.json | 211 +----------------- 3 files changed, 3 insertions(+), 426 deletions(-) diff --git a/src/androidTest/assets/split_changes_flag_set-0.json b/src/androidTest/assets/split_changes_flag_set-0.json index b3c0556d1..93be5fda4 100644 --- a/src/androidTest/assets/split_changes_flag_set-0.json +++ b/src/androidTest/assets/split_changes_flag_set-0.json @@ -1,108 +1 @@ -{ - "splits": [ - { - "trafficTypeName": "client", - "name": "workm", - "trafficAllocation": 100, - "trafficAllocationSeed": 147392224, - "seed": 524417105, - "status": "ACTIVE", - "killed": false, - "defaultTreatment": "on", - "changeNumber": 1602798638344, - "algo": 2, - "configurations": {}, - "sets": ["set_3"], - "conditions": [ - { - "conditionType": "ROLLOUT", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": { - "trafficType": "client", - "attribute": null - }, - "matcherType": "IN_SEGMENT", - "negate": false, - "userDefinedSegmentMatcherData": { - "segmentName": "new_segment" - }, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": null - } - ] - }, - "partitions": [ - { - "treatment": "on", - "size": 0 - }, - { - "treatment": "off", - "size": 0 - }, - { - "treatment": "free", - "size": 100 - }, - { - "treatment": "conta", - "size": 0 - } - ], - "label": "in segment new_segment" - }, - { - "conditionType": "ROLLOUT", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": { - "trafficType": "client", - "attribute": null - }, - "matcherType": "ALL_KEYS", - "negate": false, - "userDefinedSegmentMatcherData": null, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": null - } - ] - }, - "partitions": [ - { - "treatment": "on", - "size": 100 - }, - { - "treatment": "off", - "size": 0 - }, - { - "treatment": "free", - "size": 0 - }, - { - "treatment": "conta", - "size": 0 - } - ], - "label": "default rule" - } - ] - } - ], - "since": 1602797638344, - "till": 1602798638344 -} +{"splits":[{"trafficTypeName":"client","name":"workm","trafficAllocation":100,"trafficAllocationSeed":147392224,"seed":524417105,"status":"ACTIVE","killed":false,"defaultTreatment":"on","changeNumber":1602798638344,"algo":2,"configurations":{},"sets":["set_3"],"conditions":[{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"client","attribute":null},"matcherType":"IN_SEGMENT","negate":false,"userDefinedSegmentMatcherData":{"segmentName":"new_segment"},"whitelistMatcherData":null,"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"on","size":0},{"treatment":"off","size":0},{"treatment":"free","size":100},{"treatment":"conta","size":0}],"label":"in segment new_segment"},{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"client","attribute":null},"matcherType":"ALL_KEYS","negate":false,"userDefinedSegmentMatcherData":null,"whitelistMatcherData":null,"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"on","size":100},{"treatment":"off","size":0},{"treatment":"free","size":0},{"treatment":"conta","size":0}],"label":"default rule"}]}],"since":1602797638344,"till":1602798638344} diff --git a/src/androidTest/assets/split_changes_flag_set-1.json b/src/androidTest/assets/split_changes_flag_set-1.json index 6ed91ced6..67f617712 100644 --- a/src/androidTest/assets/split_changes_flag_set-1.json +++ b/src/androidTest/assets/split_changes_flag_set-1.json @@ -1,108 +1 @@ -{ - "splits": [ - { - "trafficTypeName": "client", - "name": "workm", - "trafficAllocation": 100, - "trafficAllocationSeed": 147392224, - "seed": 524417105, - "status": "ACTIVE", - "killed": false, - "defaultTreatment": "on", - "changeNumber": 1602797638344, - "algo": 2, - "configurations": {}, - "sets": ["set_1"], - "conditions": [ - { - "conditionType": "ROLLOUT", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": { - "trafficType": "client", - "attribute": null - }, - "matcherType": "IN_SEGMENT", - "negate": false, - "userDefinedSegmentMatcherData": { - "segmentName": "new_segment" - }, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": null - } - ] - }, - "partitions": [ - { - "treatment": "on", - "size": 0 - }, - { - "treatment": "off", - "size": 0 - }, - { - "treatment": "free", - "size": 100 - }, - { - "treatment": "conta", - "size": 0 - } - ], - "label": "in segment new_segment" - }, - { - "conditionType": "ROLLOUT", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": { - "trafficType": "client", - "attribute": null - }, - "matcherType": "ALL_KEYS", - "negate": false, - "userDefinedSegmentMatcherData": null, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": null - } - ] - }, - "partitions": [ - { - "treatment": "on", - "size": 100 - }, - { - "treatment": "off", - "size": 0 - }, - { - "treatment": "free", - "size": 0 - }, - { - "treatment": "conta", - "size": 0 - } - ], - "label": "default rule" - } - ] - } - ], - "since": 1602796638344, - "till": 1602797638344 -} +{"splits":[{"trafficTypeName":"client","name":"workm","trafficAllocation":100,"trafficAllocationSeed":147392224,"seed":524417105,"status":"ACTIVE","killed":false,"defaultTreatment":"on","changeNumber":1602797638344,"algo":2,"configurations":{},"sets":["set_1"],"conditions":[{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"client","attribute":null},"matcherType":"IN_SEGMENT","negate":false,"userDefinedSegmentMatcherData":{"segmentName":"new_segment"},"whitelistMatcherData":null,"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"on","size":0},{"treatment":"off","size":0},{"treatment":"free","size":100},{"treatment":"conta","size":0}],"label":"in segment new_segment"},{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"client","attribute":null},"matcherType":"ALL_KEYS","negate":false,"userDefinedSegmentMatcherData":null,"whitelistMatcherData":null,"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"on","size":100},{"treatment":"off","size":0},{"treatment":"free","size":0},{"treatment":"conta","size":0}],"label":"default rule"}]}],"since":1602796638344,"till":1602797638344} diff --git a/src/androidTest/assets/split_changes_flag_set-2.json b/src/androidTest/assets/split_changes_flag_set-2.json index 2d28b7582..a96e3e209 100644 --- a/src/androidTest/assets/split_changes_flag_set-2.json +++ b/src/androidTest/assets/split_changes_flag_set-2.json @@ -1,210 +1 @@ -{ - "splits": [ - { - "trafficTypeName": "client", - "name": "workm", - "trafficAllocation": 100, - "trafficAllocationSeed": 147392224, - "seed": 524417105, - "status": "ACTIVE", - "killed": false, - "defaultTreatment": "on", - "changeNumber": 1602796638344, - "algo": 2, - "configurations": {}, - "sets": ["set_1", "set_2"], - "conditions": [ - { - "conditionType": "ROLLOUT", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": { - "trafficType": "client", - "attribute": null - }, - "matcherType": "IN_SEGMENT", - "negate": false, - "userDefinedSegmentMatcherData": { - "segmentName": "new_segment" - }, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": null - } - ] - }, - "partitions": [ - { - "treatment": "on", - "size": 0 - }, - { - "treatment": "off", - "size": 0 - }, - { - "treatment": "free", - "size": 100 - }, - { - "treatment": "conta", - "size": 0 - } - ], - "label": "in segment new_segment" - }, - { - "conditionType": "ROLLOUT", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": { - "trafficType": "client", - "attribute": null - }, - "matcherType": "ALL_KEYS", - "negate": false, - "userDefinedSegmentMatcherData": null, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": null - } - ] - }, - "partitions": [ - { - "treatment": "on", - "size": 100 - }, - { - "treatment": "off", - "size": 0 - }, - { - "treatment": "free", - "size": 0 - }, - { - "treatment": "conta", - "size": 0 - } - ], - "label": "default rule" - } - ] - }, - { - "trafficTypeName": "client", - "name": "workm_set_3", - "trafficAllocation": 100, - "trafficAllocationSeed": 147392224, - "seed": 524417105, - "status": "ACTIVE", - "killed": false, - "defaultTreatment": "on", - "changeNumber": 1602796638344, - "algo": 2, - "configurations": {}, - "sets": ["set_3"], - "conditions": [ - { - "conditionType": "ROLLOUT", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": { - "trafficType": "client", - "attribute": null - }, - "matcherType": "IN_SEGMENT", - "negate": false, - "userDefinedSegmentMatcherData": { - "segmentName": "new_segment" - }, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": null - } - ] - }, - "partitions": [ - { - "treatment": "on", - "size": 0 - }, - { - "treatment": "off", - "size": 0 - }, - { - "treatment": "free", - "size": 100 - }, - { - "treatment": "conta", - "size": 0 - } - ], - "label": "in segment new_segment" - }, - { - "conditionType": "ROLLOUT", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": { - "trafficType": "client", - "attribute": null - }, - "matcherType": "ALL_KEYS", - "negate": false, - "userDefinedSegmentMatcherData": null, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": null - } - ] - }, - "partitions": [ - { - "treatment": "on", - "size": 100 - }, - { - "treatment": "off", - "size": 0 - }, - { - "treatment": "free", - "size": 0 - }, - { - "treatment": "conta", - "size": 0 - } - ], - "label": "default rule" - } - ] - } - ], - "since": -1, - "till": 1602796638344 -} +{"splits":[{"trafficTypeName":"client","name":"workm","trafficAllocation":100,"trafficAllocationSeed":147392224,"seed":524417105,"status":"ACTIVE","killed":false,"defaultTreatment":"on","changeNumber":1602796638344,"algo":2,"configurations":{},"sets":["set_1","set_2"],"conditions":[{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"client","attribute":null},"matcherType":"IN_SEGMENT","negate":false,"userDefinedSegmentMatcherData":{"segmentName":"new_segment"},"whitelistMatcherData":null,"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"on","size":0},{"treatment":"off","size":0},{"treatment":"free","size":100},{"treatment":"conta","size":0}],"label":"in segment new_segment"},{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"client","attribute":null},"matcherType":"ALL_KEYS","negate":false,"userDefinedSegmentMatcherData":null,"whitelistMatcherData":null,"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"on","size":100},{"treatment":"off","size":0},{"treatment":"free","size":0},{"treatment":"conta","size":0}],"label":"default rule"}]},{"trafficTypeName":"client","name":"workm_set_3","trafficAllocation":100,"trafficAllocationSeed":147392224,"seed":524417105,"status":"ACTIVE","killed":false,"defaultTreatment":"on","changeNumber":1602796638344,"algo":2,"configurations":{},"sets":["set_3"],"conditions":[{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"client","attribute":null},"matcherType":"IN_SEGMENT","negate":false,"userDefinedSegmentMatcherData":{"segmentName":"new_segment"},"whitelistMatcherData":null,"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"on","size":0},{"treatment":"off","size":0},{"treatment":"free","size":100},{"treatment":"conta","size":0}],"label":"in segment new_segment"},{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"client","attribute":null},"matcherType":"ALL_KEYS","negate":false,"userDefinedSegmentMatcherData":null,"whitelistMatcherData":null,"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"on","size":100},{"treatment":"off","size":0},{"treatment":"free","size":0},{"treatment":"conta","size":0}],"label":"default rule"}]}],"since":-1,"till":1602796638344} From 95c88ee4f3a99876463bd3f6acd787c847f99875 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 13 Sep 2023 11:34:34 -0300 Subject: [PATCH 73/89] Fix name --- .../client/service/splits/FeatureFlagProcessStrategy.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java b/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java index fafc48806..2f50f6bbb 100644 --- a/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java +++ b/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java @@ -6,7 +6,6 @@ import io.split.android.client.dtos.Split; import io.split.android.client.dtos.Status; -import io.split.android.client.utils.logger.Logger; interface FeatureFlagProcessStrategy { @@ -37,7 +36,6 @@ class NamesProcessStrategy implements FeatureFlagProcessStrategy { @Override public void process(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag) { - Logger.v("Processing with names"); // If the feature flag name is in the filter, we process it according to its status. Otherwise it is ignored if (mConfiguredValues.contains(featureFlag.name)) { mStatusProcessStrategy.process(activeFeatureFlags, archivedFeatureFlags, featureFlag); @@ -57,7 +55,6 @@ class SetsProcessStrategy implements FeatureFlagProcessStrategy { @Override public void process(List activeFeatureFlags, List archivedFeatureFlags, Split featureFlag) { - Logger.v("Processing with sets"); if (featureFlag.sets == null || featureFlag.sets.isEmpty()) { archivedFeatureFlags.add(featureFlag); return; From d2d8800e284c52d6d2945bc70af6375cbbb29362 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 13 Sep 2023 15:30:50 -0300 Subject: [PATCH 74/89] Track invalid values in SyncConfig --- .../java/io/split/android/client/SyncConfig.java | 12 ++++++++++-- .../service/executor/SplitTaskFactoryImpl.java | 2 +- .../java/io/split/android/client/SyncConfigTest.java | 12 ++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/split/android/client/SyncConfig.java b/src/main/java/io/split/android/client/SyncConfig.java index c0bd4e5fe..9311d757a 100644 --- a/src/main/java/io/split/android/client/SyncConfig.java +++ b/src/main/java/io/split/android/client/SyncConfig.java @@ -13,21 +13,28 @@ public class SyncConfig { private final List mFilters; + private int mInvalidValueCount = 0; - private SyncConfig(List filters) { + private SyncConfig(List filters, int invalidValueCount) { mFilters = filters; + mInvalidValueCount = invalidValueCount; } public List getFilters() { return mFilters; } + public int getInvalidValueCount() { + return mInvalidValueCount; + } + public static Builder builder() { return new Builder(); } public static class Builder { private List mBuilderFilters = new ArrayList<>(); + private int mInvalidValueCount = 0; private final SplitValidator mSplitValidator = new SplitValidatorImpl(); public SyncConfig build() { @@ -46,7 +53,7 @@ public SyncConfig build() { validatedFilters.add(new SplitFilter(filter.getType(), validatedValues)); } } - return new SyncConfig(validatedFilters); + return new SyncConfig(validatedFilters, mInvalidValueCount); } public Builder addSplitFilter(@NonNull SplitFilter filter) { @@ -54,6 +61,7 @@ public Builder addSplitFilter(@NonNull SplitFilter filter) { throw new IllegalArgumentException("Filter can't be null"); } mBuilderFilters.add(filter); + mInvalidValueCount += filter.getInvalidValueCount(); return this; } } diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java index e91cd8c3a..d1b5a5382 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java @@ -204,7 +204,7 @@ private TelemetryTaskFactory initializeTelemetryTaskFactory(@NonNull SplitClient SplitFilter bySetFilter = filters.get(SplitFilter.Type.BY_SET); if (bySetFilter != null) { flagSetCount = bySetFilter.getValues().size(); - invalidFlagSetCount = bySetFilter.getInvalidValueCount(); + invalidFlagSetCount = splitClientConfig.syncConfig().getInvalidValueCount(); } } diff --git a/src/test/java/io/split/android/client/SyncConfigTest.java b/src/test/java/io/split/android/client/SyncConfigTest.java index d46b094c4..2356aeb4a 100644 --- a/src/test/java/io/split/android/client/SyncConfigTest.java +++ b/src/test/java/io/split/android/client/SyncConfigTest.java @@ -162,4 +162,16 @@ public void addingNullFilterToConfig() { Assert.assertTrue(exceptionThrown); Assert.assertNull(config); } + + @Test + public void invalidValuesAreTracked() { + // Currently only invalid values for {@link SplitFilter#BY_SET} are tracked, for telemetry + + SyncConfig config = SyncConfig.builder() + .addSplitFilter(SplitFilter.bySet(Arrays.asList("_f1", "f2", "f3"))) + .addSplitFilter(SplitFilter.bySet(Arrays.asList("f4", "_f5", "_f6", "_f6"))) + .build(); + + Assert.assertEquals(4, config.getInvalidValueCount()); + } } From 67f14d65f14d01a2c53b1ac94b397eefbb93f727 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 13 Sep 2023 15:33:26 -0300 Subject: [PATCH 75/89] Sets in telemetry config test --- .../telemetry/TelemetryIntegrationTest.java | 65 +++++++++++++++++-- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/src/androidTest/java/tests/integration/telemetry/TelemetryIntegrationTest.java b/src/androidTest/java/tests/integration/telemetry/TelemetryIntegrationTest.java index 414c3df83..1f9a132e7 100644 --- a/src/androidTest/java/tests/integration/telemetry/TelemetryIntegrationTest.java +++ b/src/androidTest/java/tests/integration/telemetry/TelemetryIntegrationTest.java @@ -11,6 +11,7 @@ import org.junit.Before; import org.junit.Test; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -28,6 +29,8 @@ import io.split.android.client.SplitClient; import io.split.android.client.SplitClientConfig; import io.split.android.client.SplitFactory; +import io.split.android.client.SplitFilter; +import io.split.android.client.SyncConfig; import io.split.android.client.api.Key; import io.split.android.client.dtos.Split; import io.split.android.client.dtos.SplitChange; @@ -195,11 +198,54 @@ public void recordSessionLength() throws InterruptedException { assertTrue(sessionLength > 0); } - private void initializeClient(boolean streamingEnabled) { + @Test + public void flagSetsAreIncludedInPayload() throws InterruptedException { + CountDownLatch sseLatch = new CountDownLatch(1); + CountDownLatch metricsLatch = new CountDownLatch(2); + AtomicReference metricsPayload = new AtomicReference<>(); + final Dispatcher dispatcher = new Dispatcher() { + + @Override + public MockResponse dispatch(RecordedRequest request) { + String path = request.getPath(); + if (path.contains("/mySegments")) { + return new MockResponse().setResponseCode(200).setBody("{\"mySegments\":[{ \"id\":\"id1\", \"name\":\"segment1\"}, { \"id\":\"id1\", \"name\":\"segment2\"}]}"); + } else if (path.contains("/splitChanges")) { + long changeNumber = -1; + return new MockResponse().setResponseCode(200) + .setBody("{\"splits\":[], \"since\":" + changeNumber + ", \"till\":" + (changeNumber + 1000) + "}"); + } else if (path.contains("/events/bulk")) { + return new MockResponse().setResponseCode(200); + } else if (path.contains("metrics/usage")) { + metricsLatch.countDown(); + return new MockResponse().setResponseCode(200); + } else if (path.contains("metrics")) { + metricsPayload.set(request.getBody().readUtf8()); + return new MockResponse().setResponseCode(200); + } else if (path.contains("auth")) { + sseLatch.countDown(); + return new MockResponse().setResponseCode(401); + } else { + return new MockResponse().setResponseCode(404); + } + } + }; + + mWebServer.setDispatcher(dispatcher); + + initializeClient(false, "a", "_b", "a", "a", "c", "d", "_d"); + metricsLatch.await(20, TimeUnit.SECONDS); + String s = metricsPayload.get(); + Logger.w("Metrics payload: " + s); + assertTrue(s.contains("\"fsT\":5")); + assertTrue(s.contains("\"fsI\":2")); + } + + private void initializeClient(boolean streamingEnabled, String ... sets) { insertSplitsFromFileIntoDB(); CountDownLatch countDownLatch = new CountDownLatch(1); - client = getTelemetrySplitFactory(mWebServer, streamingEnabled).client(); + client = getTelemetrySplitFactory(mWebServer, streamingEnabled, sets).client(); TestingHelper.TestEventTask readyFromCacheTask = new TestingHelper.TestEventTask(countDownLatch); client.on(SplitEvent.SDK_READY, readyFromCacheTask); @@ -211,7 +257,7 @@ private void initializeClient(boolean streamingEnabled) { } } - private SplitFactory getTelemetrySplitFactory(MockWebServer webServer, boolean streamingEnabled) { + private SplitFactory getTelemetrySplitFactory(MockWebServer webServer, boolean streamingEnabled, String... sets) { final String url = webServer.url("/").url().toString(); ServiceEndpoints endpoints = ServiceEndpoints.builder() .eventsEndpoint(url) @@ -219,7 +265,7 @@ private SplitFactory getTelemetrySplitFactory(MockWebServer webServer, boolean s .sseAuthServiceEndpoint(url) .apiEndpoint(url).eventsEndpoint(url).build(); - SplitClientConfig config = new TestableSplitConfigBuilder() + TestableSplitConfigBuilder builder = new TestableSplitConfigBuilder() .serviceEndpoints(endpoints) .enableDebug() .telemetryRefreshRate(10) @@ -228,8 +274,15 @@ private SplitFactory getTelemetrySplitFactory(MockWebServer webServer, boolean s .impressionsRefreshRate(9999) .readTimeout(3000) .streamingEnabled(streamingEnabled) - .shouldRecordTelemetry(true) - .build(); + .shouldRecordTelemetry(true); + + if (sets != null && sets.length > 0) { + builder.syncConfig(SyncConfig.builder() + .addSplitFilter(SplitFilter.bySet(Arrays.asList(sets))) + .build()); + } + + SplitClientConfig config = builder.build(); mTelemetryStorage = StorageFactory.getTelemetryStorage(true); return IntegrationHelper.buildFactory( From 3b3caa69e429ce75bebabfe201876d644fd94834 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 13 Sep 2023 16:10:03 -0300 Subject: [PATCH 76/89] Evaluation with flags tests in telemetry; --- .../telemetry/TelemetryIntegrationTest.java | 62 ++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/src/androidTest/java/tests/integration/telemetry/TelemetryIntegrationTest.java b/src/androidTest/java/tests/integration/telemetry/TelemetryIntegrationTest.java index 1f9a132e7..62cb20e86 100644 --- a/src/androidTest/java/tests/integration/telemetry/TelemetryIntegrationTest.java +++ b/src/androidTest/java/tests/integration/telemetry/TelemetryIntegrationTest.java @@ -2,6 +2,7 @@ import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertNotNull; import static junit.framework.TestCase.assertTrue; import android.content.Context; @@ -82,12 +83,16 @@ public void telemetryInitTest() { @Test public void telemetryEvaluationLatencyTest() { - initializeClient(false); + initializeClient(false, "a", "b"); client.getTreatment("test_split"); client.getTreatments(Arrays.asList("test_split", "test_split_2"), null); client.getTreatmentWithConfig("test_split", null); client.getTreatmentsWithConfig(Arrays.asList("test_split", "test_split_2"), null); client.track("test_traffic_type", "test_split"); + client.getTreatmentsByFlagSet("a", null); + client.getTreatmentsByFlagSets(Arrays.asList("a", "b"), null); + client.getTreatmentsWithConfigByFlagSet("a", null); + client.getTreatmentsWithConfigByFlagSets(Arrays.asList("a", "b"), null); MethodLatencies methodLatencies = mTelemetryStorage.popLatencies(); assertFalse(methodLatencies.getTreatment().stream().allMatch(aLong -> aLong == 0L)); @@ -95,6 +100,60 @@ public void telemetryEvaluationLatencyTest() { assertFalse(methodLatencies.getTreatmentWithConfig().stream().allMatch(aLong -> aLong == 0L)); assertFalse(methodLatencies.getTreatmentsWithConfig().stream().allMatch(aLong -> aLong == 0L)); assertFalse(methodLatencies.getTrack().stream().allMatch(aLong -> aLong == 0L)); + assertFalse(methodLatencies.getTreatmentsByFlagSet().stream().allMatch(aLong -> aLong == 0L)); + assertFalse(methodLatencies.getTreatmentsByFlagSets().stream().allMatch(aLong -> aLong == 0L)); + assertFalse(methodLatencies.getTreatmentsWithConfigByFlagSet().stream().allMatch(aLong -> aLong == 0L)); + assertFalse(methodLatencies.getTreatmentsWithConfigByFlagSets().stream().allMatch(aLong -> aLong == 0L)); + } + + @Test + public void evaluationByFlagsInfoIsInPayload() throws InterruptedException { + CountDownLatch metricsLatch = new CountDownLatch(1); + AtomicReference metricsPayload = new AtomicReference<>(); + final Dispatcher dispatcher = new Dispatcher() { + + @Override + public MockResponse dispatch(RecordedRequest request) { + String path = request.getPath(); + if (path.contains("/mySegments")) { + return new MockResponse().setResponseCode(200).setBody("{\"mySegments\":[{ \"id\":\"id1\", \"name\":\"segment1\"}, { \"id\":\"id1\", \"name\":\"segment2\"}]}"); + } else if (path.contains("/splitChanges")) { + long changeNumber = -1; + return new MockResponse().setResponseCode(200) + .setBody("{\"splits\":[], \"since\":" + changeNumber + ", \"till\":" + (changeNumber + 1000) + "}"); + } else if (path.contains("/events/bulk")) { + return new MockResponse().setResponseCode(200); + } else if (path.contains("metrics/usage")) { + metricsPayload.set(request.getBody().readUtf8()); + metricsLatch.countDown(); + return new MockResponse().setResponseCode(200); + } else if (path.contains("metrics")) { + return new MockResponse().setResponseCode(200); + } else if (path.contains("auth")) { + return new MockResponse().setResponseCode(401); + } else { + return new MockResponse().setResponseCode(404); + } + } + }; + + mWebServer.setDispatcher(dispatcher); + + initializeClient(false, "a", "b"); + client.getTreatmentsByFlagSet("a", null); + client.getTreatmentsByFlagSets(Arrays.asList("a", "b"), null); + client.getTreatmentsWithConfigByFlagSet("a", null); + client.getTreatmentsWithConfigByFlagSets(Arrays.asList("a", "b"), null); + + boolean await = metricsLatch.await(10, TimeUnit.SECONDS); + + assertTrue(await); + assertTrue(metricsPayload.get().contains("\"tf\":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]")); + assertTrue(metricsPayload.get().contains("\"tfs\":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]")); + assertTrue(metricsPayload.get().contains("\"tcf\":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]")); + assertTrue(metricsPayload.get().contains("\"tcfs\":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]")); + assertTrue(metricsPayload.get().contains("\"tcf\":0,\"tcfs\":0")); + assertTrue(metricsPayload.get().contains("\"tf\":0,\"tfs\":0")); } @Test @@ -236,7 +295,6 @@ public MockResponse dispatch(RecordedRequest request) { initializeClient(false, "a", "_b", "a", "a", "c", "d", "_d"); metricsLatch.await(20, TimeUnit.SECONDS); String s = metricsPayload.get(); - Logger.w("Metrics payload: " + s); assertTrue(s.contains("\"fsT\":5")); assertTrue(s.contains("\"fsI\":2")); } From bf8bca2e93cacaf71d62414e636c69e21ebf9bc7 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 13 Sep 2023 16:17:40 -0300 Subject: [PATCH 77/89] Extra safety check --- .../android/client/service/executor/SplitTaskFactoryImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java index d1b5a5382..1341f4d5f 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java @@ -204,7 +204,8 @@ private TelemetryTaskFactory initializeTelemetryTaskFactory(@NonNull SplitClient SplitFilter bySetFilter = filters.get(SplitFilter.Type.BY_SET); if (bySetFilter != null) { flagSetCount = bySetFilter.getValues().size(); - invalidFlagSetCount = splitClientConfig.syncConfig().getInvalidValueCount(); + invalidFlagSetCount = (splitClientConfig.syncConfig() != null) ? + splitClientConfig.syncConfig().getInvalidValueCount() : 0; } } From ba26ccd90fee4873f360898167096cdf9beb5d0d Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Sep 2023 10:10:56 -0300 Subject: [PATCH 78/89] Additional query string test --- .../integration/sets/FlagSetsPollingTest.java | 20 ++++++++++++++++++- .../service/splits/SplitChangeProcessor.java | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java b/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java index 3a549259d..8c3c095f7 100644 --- a/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java +++ b/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java @@ -14,11 +14,13 @@ import java.io.IOException; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import fake.HttpClientMock; import fake.HttpResponseMock; @@ -46,6 +48,7 @@ public class FlagSetsPollingTest { private CountDownLatch secondChangeLatch; private CountDownLatch thirdChangeLatch; private SplitRoomDatabase mRoomDb; + private volatile String mSplitChangesUri; @Before public void setUp() throws Exception { @@ -157,6 +160,19 @@ public void featureFlagSetsAreIgnoredWhenSetsAreNotConfigured() throws IOExcepti assertTrue(awaitHits); } + @Test + public void queryStringIsBuiltCorrectlyWhenSetsAreConfigured() throws IOException, InterruptedException { + // 1. Initialize a factory with polling and sets set_1 & set_2 configured. + createFactory(mContext, mRoomDb, "set_x", "set_x", "set_3", "set_2", "set_3", "set_ww", "invalid+"); + + boolean awaitFirst = firstChangeLatch.await(5, TimeUnit.SECONDS); + + String uri = mSplitChangesUri; + + assertTrue(awaitFirst); + assertEquals("https://sdk.split.io/api/splitChanges?since=-1&sets=set_2,set_3,set_ww,set_x", uri); + } + private SplitFactory createFactory( Context mContext, SplitRoomDatabase splitRoomDatabase, @@ -169,6 +185,8 @@ private SplitFactory createFactory( .impressionsCountersRefreshRate(1000) .syncConfig(SyncConfig.builder() .addSplitFilter(SplitFilter.bySet(Arrays.asList(sets))) + .addSplitFilter(SplitFilter.byName(Arrays.asList("workm", "workm_set_3"))) // added to test that this filter is ignored + .addSplitFilter(SplitFilter.byPrefix(Collections.singletonList("pref"))) // added to test that this filter is ignored .build()) .featuresRefreshRate(2) .streamingEnabled(false) @@ -177,8 +195,8 @@ private SplitFactory createFactory( Map responses = new HashMap<>(); responses.put("splitChanges", (uri, httpMethod, body) -> { + mSplitChangesUri = uri.toString(); String since = getSinceFromUri(uri); - hitsLatch.countDown(); if (since.equals("-1")) { firstChangeLatch.countDown(); diff --git a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java index a24848d42..88abaf09d 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java +++ b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java @@ -61,7 +61,7 @@ private ProcessedSplitChange buildProcessedSplitChange(List featureFlags, FeatureFlagProcessStrategy processStrategy = getProcessStrategy(mSplitFilter); for (Split featureFlag : featureFlags) { - if (featureFlag.name == null) { + if (featureFlag == null || featureFlag.name == null) { continue; } From 74d4d2a4c292160d73ebd24ea23eb9cdcd2c7941 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Sep 2023 10:14:28 -0300 Subject: [PATCH 79/89] FlagSetsFilter --- .../split/android/client/FlagSetsFilter.java | 10 ++++ .../android/client/FlagSetsFilterImpl.java | 47 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/main/java/io/split/android/client/FlagSetsFilter.java create mode 100644 src/main/java/io/split/android/client/FlagSetsFilterImpl.java diff --git a/src/main/java/io/split/android/client/FlagSetsFilter.java b/src/main/java/io/split/android/client/FlagSetsFilter.java new file mode 100644 index 000000000..06850ca11 --- /dev/null +++ b/src/main/java/io/split/android/client/FlagSetsFilter.java @@ -0,0 +1,10 @@ +package io.split.android.client; + +import java.util.Set; + +interface FlagSetsFilter { + + boolean intersect(Set sets); + + boolean intersect(String set); +} diff --git a/src/main/java/io/split/android/client/FlagSetsFilterImpl.java b/src/main/java/io/split/android/client/FlagSetsFilterImpl.java new file mode 100644 index 000000000..3464ff70d --- /dev/null +++ b/src/main/java/io/split/android/client/FlagSetsFilterImpl.java @@ -0,0 +1,47 @@ +package io.split.android.client; + +import java.util.HashSet; +import java.util.Set; + +class FlagSetsFilterImpl implements FlagSetsFilter { + + private final boolean mShouldFilter; + private final Set mFlagSets; + + FlagSetsFilterImpl(Set flagSets) { + mFlagSets = new HashSet<>(flagSets); + mShouldFilter = !mFlagSets.isEmpty(); + } + + @Override + public boolean intersect(Set sets) { + if (!mShouldFilter) { + return true; + } + + if (sets == null) { + return false; + } + + for (String set : sets) { + if (mFlagSets.contains(set)) { + return true; + } + } + + return false; + } + + @Override + public boolean intersect(String set) { + if (!mShouldFilter) { + return true; + } + + if (set == null) { + return false; + } + + return mFlagSets.contains(set); + } +} From 0bedb89f03b1b286fc38cdd30c3df5816174a088 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Sep 2023 15:42:22 -0300 Subject: [PATCH 80/89] Flag sets filter initial integration --- .../android/client/FeatureFlagFilter.java | 10 +++ .../split/android/client/FlagSetsFilter.java | 6 +- .../android/client/FlagSetsFilterImpl.java | 4 +- .../client/SplitClientFactoryImpl.java | 5 +- .../android/client/SplitFactoryHelper.java | 14 ---- .../android/client/SplitFactoryImpl.java | 9 ++- .../localhost/LocalhostSplitClient.java | 5 +- .../localhost/LocalhostSplitFactory.java | 8 +- .../LocalhostSplitClientContainerImpl.java | 9 ++- .../executor/SplitTaskFactoryImpl.java | 4 +- .../splits/FeatureFlagProcessStrategy.java | 20 +++-- .../service/splits/SplitChangeProcessor.java | 16 ++-- .../service/workmanager/SplitsSyncWorker.java | 9 ++- .../shared/SplitClientContainerImpl.java | 5 +- .../TreatmentManagerFactoryImpl.java | 10 ++- .../validators/TreatmentManagerImpl.java | 9 ++- .../client/FlagSetsFilterImplTest.java | 73 +++++++++++++++++++ .../splits/SplitChangeProcessorTest.java | 15 ++-- 18 files changed, 163 insertions(+), 68 deletions(-) create mode 100644 src/main/java/io/split/android/client/FeatureFlagFilter.java create mode 100644 src/test/java/io/split/android/client/FlagSetsFilterImplTest.java diff --git a/src/main/java/io/split/android/client/FeatureFlagFilter.java b/src/main/java/io/split/android/client/FeatureFlagFilter.java new file mode 100644 index 000000000..fb4dc7764 --- /dev/null +++ b/src/main/java/io/split/android/client/FeatureFlagFilter.java @@ -0,0 +1,10 @@ +package io.split.android.client; + +import java.util.Set; + +interface FeatureFlagFilter { + + boolean intersect(Set values); + + boolean intersect(String values); +} diff --git a/src/main/java/io/split/android/client/FlagSetsFilter.java b/src/main/java/io/split/android/client/FlagSetsFilter.java index 06850ca11..4c982e365 100644 --- a/src/main/java/io/split/android/client/FlagSetsFilter.java +++ b/src/main/java/io/split/android/client/FlagSetsFilter.java @@ -2,9 +2,5 @@ import java.util.Set; -interface FlagSetsFilter { - - boolean intersect(Set sets); - - boolean intersect(String set); +public interface FlagSetsFilter extends FeatureFlagFilter { } diff --git a/src/main/java/io/split/android/client/FlagSetsFilterImpl.java b/src/main/java/io/split/android/client/FlagSetsFilterImpl.java index 3464ff70d..84ab5c839 100644 --- a/src/main/java/io/split/android/client/FlagSetsFilterImpl.java +++ b/src/main/java/io/split/android/client/FlagSetsFilterImpl.java @@ -3,12 +3,12 @@ import java.util.HashSet; import java.util.Set; -class FlagSetsFilterImpl implements FlagSetsFilter { +public class FlagSetsFilterImpl implements FlagSetsFilter { private final boolean mShouldFilter; private final Set mFlagSets; - FlagSetsFilterImpl(Set flagSets) { + public FlagSetsFilterImpl(Set flagSets) { mFlagSets = new HashSet<>(flagSets); mShouldFilter = !mFlagSets.isEmpty(); } diff --git a/src/main/java/io/split/android/client/SplitClientFactoryImpl.java b/src/main/java/io/split/android/client/SplitClientFactoryImpl.java index 69e7dc998..e5690d3f8 100644 --- a/src/main/java/io/split/android/client/SplitClientFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitClientFactoryImpl.java @@ -3,6 +3,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.util.Set; @@ -59,7 +60,7 @@ public SplitClientFactoryImpl(@NonNull SplitFactory splitFactory, @NonNull KeyValidator keyValidator, @NonNull EventsTracker eventsTracker, @NonNull ImpressionListener customerImpressionListener, - @NonNull Set configuredFlagSets) { + @Nullable FlagSetsFilter flagSetsFilter) { mSplitFactory = checkNotNull(splitFactory); mClientContainer = checkNotNull(clientContainer); mConfig = checkNotNull(config); @@ -85,7 +86,7 @@ public SplitClientFactoryImpl(@NonNull SplitFactory splitFactory, new AttributesMergerImpl(), mStorageContainer.getTelemetryStorage(), mSplitParser, - configuredFlagSets, + flagSetsFilter, splitsStorage ); } diff --git a/src/main/java/io/split/android/client/SplitFactoryHelper.java b/src/main/java/io/split/android/client/SplitFactoryHelper.java index 399a8740a..6541a05e7 100644 --- a/src/main/java/io/split/android/client/SplitFactoryHelper.java +++ b/src/main/java/io/split/android/client/SplitFactoryHelper.java @@ -11,9 +11,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; @@ -429,18 +427,6 @@ Pair, String> getFilterConfiguration(SyncConf return new Pair<>(groupedFilters, splitsFilterQueryString); } - @NonNull - Set getConfiguredFlagSets(Map filters) { - Set configuredFlagSets; - SplitFilter flagSetSplitFilter = filters.get(SplitFilter.Type.BY_SET); - if (flagSetSplitFilter != null) { - configuredFlagSets = new HashSet<>(flagSetSplitFilter.getValues()); - } else { - configuredFlagSets = new HashSet<>(); - } - return configuredFlagSets; - } - private TelemetryStorage getTelemetryStorage(boolean shouldRecordTelemetry, TelemetryStorage telemetryStorage) { if (telemetryStorage != null) { return telemetryStorage; diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index c387d9d55..11519d18b 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -180,9 +180,14 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { SplitApiFacade splitApiFacade = factoryHelper.buildApiFacade( config, defaultHttpClient, splitsFilterQueryStringFromConfig); + FlagSetsFilter flagSetsFilter = null; + if (filters.get(SplitFilter.Type.BY_SET) != null) { + flagSetsFilter = new FlagSetsFilterImpl(new HashSet<>(filters.get(SplitFilter.Type.BY_SET).getValues())); + } + SplitTaskFactory splitTaskFactory = new SplitTaskFactoryImpl( config, splitApiFacade, mStorageContainer, splitsFilterQueryStringFromConfig, mEventsManagerCoordinator, - filters, testingConfig); + filters, flagSetsFilter, testingConfig); cleanUpDabase(splitTaskExecutor, splitTaskFactory); WorkManagerWrapper workManagerWrapper = factoryHelper.buildWorkManagerWrapper(context, config, apiToken, databaseName, filters); @@ -270,7 +275,7 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { telemetrySynchronizer, mStorageContainer, splitTaskExecutor, splitApiFacade, validationLogger, keyValidator, customerImpressionListener, streamingComponents.getPushNotificationManager(), componentsRegister, workManagerWrapper, - eventsTracker, factoryHelper.getConfiguredFlagSets(filters)); + eventsTracker, flagSetsFilter); mDestroyer = new Runnable() { public void run() { Logger.w("Shutdown called for split"); 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 b4dfbdbf3..941f3ee7a 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java @@ -14,6 +14,7 @@ import io.split.android.client.Evaluator; import io.split.android.client.EvaluatorImpl; +import io.split.android.client.FlagSetsFilter; import io.split.android.client.SplitClient; import io.split.android.client.SplitClientConfig; import io.split.android.client.SplitFactory; @@ -61,7 +62,7 @@ public LocalhostSplitClient(@NonNull LocalhostSplitFactory container, @NonNull AttributesManager attributesManager, @NonNull AttributesMerger attributesMerger, @NonNull TelemetryStorageProducer telemetryStorageProducer, - @NonNull Set configuredFlagSets) { + @Nullable FlagSetsFilter flagSetsFilter) { mFactoryRef = new WeakReference<>(checkNotNull(container)); mClientContainer = new WeakReference<>(checkNotNull(clientContainer)); @@ -72,7 +73,7 @@ public LocalhostSplitClient(@NonNull LocalhostSplitFactory container, new EvaluatorImpl(splitsStorage, splitParser), new KeyValidatorImpl(), new SplitValidatorImpl(), getImpressionsListener(splitClientConfig), splitClientConfig.labelsEnabled(), eventsManager, attributesManager, attributesMerger, - telemetryStorageProducer, configuredFlagSets, splitsStorage); + telemetryStorageProducer, flagSetsFilter, splitsStorage); } @Override diff --git a/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java b/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java index 1f79f0e8d..0948fad70 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java @@ -13,6 +13,8 @@ import java.util.Set; import io.split.android.client.FilterBuilder; +import io.split.android.client.FlagSetsFilter; +import io.split.android.client.FlagSetsFilterImpl; import io.split.android.client.SplitClient; import io.split.android.client.SplitClientConfig; import io.split.android.client.SplitFactory; @@ -75,7 +77,7 @@ public LocalhostSplitFactory(String key, Context context, mManager = new SplitManagerImpl(splitsStorage, new SplitValidatorImpl(), splitParser); - Set configuredSets = new HashSet<>(); + FlagSetsFilter flagSetsFilter = null; if (config.syncConfig() != null) { Map groupedFilters = new FilterBuilder(config.syncConfig().getFilters()) .getGroupedFilter(); @@ -83,7 +85,7 @@ public LocalhostSplitFactory(String key, Context context, if (!groupedFilters.isEmpty()) { SplitFilter bySetFilter = groupedFilters.get(SplitFilter.Type.BY_SET); if (bySetFilter != null) { - configuredSets.addAll(bySetFilter.getValues()); + flagSetsFilter = new FlagSetsFilterImpl(new HashSet<>(bySetFilter.getValues())); } } } @@ -97,7 +99,7 @@ public LocalhostSplitFactory(String key, Context context, new NoOpTelemetryStorage(), eventsManagerCoordinator, taskExecutor, - configuredSets); + flagSetsFilter); mSynchronizer = new LocalhostSynchronizer(taskExecutor, config, splitsStorage, buildQueryString(config.syncConfig())); mSynchronizer.start(); diff --git a/src/main/java/io/split/android/client/localhost/shared/LocalhostSplitClientContainerImpl.java b/src/main/java/io/split/android/client/localhost/shared/LocalhostSplitClientContainerImpl.java index e4a6d2a69..f0eb208e5 100644 --- a/src/main/java/io/split/android/client/localhost/shared/LocalhostSplitClientContainerImpl.java +++ b/src/main/java/io/split/android/client/localhost/shared/LocalhostSplitClientContainerImpl.java @@ -2,6 +2,7 @@ import java.util.Set; +import io.split.android.client.FlagSetsFilter; import io.split.android.client.SplitClient; import io.split.android.client.SplitClientConfig; import io.split.android.client.api.Key; @@ -31,7 +32,7 @@ public class LocalhostSplitClientContainerImpl extends BaseSplitClientContainer private final TelemetryStorageProducer mTelemetryStorageProducer; private final EventsManagerCoordinator mEventsManagerCoordinator; private final SplitTaskExecutor mSplitTaskExecutor; - private final Set mConfiguredSets; + private final FlagSetsFilter mFlagSetsFilter; public LocalhostSplitClientContainerImpl(LocalhostSplitFactory splitFactory, SplitClientConfig config, @@ -42,7 +43,7 @@ public LocalhostSplitClientContainerImpl(LocalhostSplitFactory splitFactory, TelemetryStorageProducer telemetryStorageProducer, EventsManagerCoordinator eventsManagerCoordinator, SplitTaskExecutor taskExecutor, - Set configuredSets) { + FlagSetsFilter flagSetsFilter) { mSplitFactory = splitFactory; mConfig = config; mSplitStorage = splitsStorage; @@ -52,7 +53,7 @@ public LocalhostSplitClientContainerImpl(LocalhostSplitFactory splitFactory, mTelemetryStorageProducer = telemetryStorageProducer; mEventsManagerCoordinator = eventsManagerCoordinator; mSplitTaskExecutor = taskExecutor; - mConfiguredSets = configuredSets; + mFlagSetsFilter = flagSetsFilter; } @Override @@ -76,7 +77,7 @@ protected void createNewClient(Key key) { attributesManager, mAttributesMerger, mTelemetryStorageProducer, - mConfiguredSets + mFlagSetsFilter ); eventsManager.getExecutorResources().setSplitClient(client); diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java index 1341f4d5f..dde6c846c 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java @@ -12,6 +12,7 @@ import java.util.Map; import java.util.Set; +import io.split.android.client.FlagSetsFilter; import io.split.android.client.SplitClientConfig; import io.split.android.client.SplitFilter; import io.split.android.client.TestingConfig; @@ -67,6 +68,7 @@ public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig, @Nullable String splitsFilterQueryString, ISplitEventsManager eventsManager, @Nullable Map filters, + @Nullable FlagSetsFilter flagSetsFilter, @Nullable TestingConfig testingConfig) { mSplitClientConfig = checkNotNull(splitClientConfig); @@ -74,7 +76,7 @@ public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig, mSplitsStorageContainer = checkNotNull(splitStorageContainer); mSplitsFilterQueryStringFromConfig = splitsFilterQueryString; mEventsManager = eventsManager; - mSplitChangeProcessor = new SplitChangeProcessor(filters); + mSplitChangeProcessor = new SplitChangeProcessor(filters, flagSetsFilter); TelemetryStorage telemetryStorage = mSplitsStorageContainer.getTelemetryStorage(); mTelemetryRuntimeProducer = telemetryStorage; diff --git a/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java b/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java index 2f50f6bbb..f61871de0 100644 --- a/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java +++ b/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java @@ -2,8 +2,11 @@ import androidx.annotation.NonNull; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import io.split.android.client.FlagSetsFilter; import io.split.android.client.dtos.Split; import io.split.android.client.dtos.Status; @@ -45,12 +48,13 @@ public void process(List activeFeatureFlags, List archivedFeatureF class SetsProcessStrategy implements FeatureFlagProcessStrategy { - private final List mConfiguredValues; + private final FlagSetsFilter mFlagSetsFilter; private final StatusProcessStrategy mStatusProcessStrategy; - SetsProcessStrategy(@NonNull List configuredValues, @NonNull StatusProcessStrategy statusProcessStrategy) { - mConfiguredValues = configuredValues; + SetsProcessStrategy(@NonNull FlagSetsFilter flagSetsFilter, @NonNull StatusProcessStrategy statusProcessStrategy) { + mStatusProcessStrategy = statusProcessStrategy; + mFlagSetsFilter = flagSetsFilter; } @Override @@ -61,19 +65,21 @@ public void process(List activeFeatureFlags, List archivedFeatureF } boolean shouldArchive = true; + Set newSets = new HashSet<>(); for (String set : featureFlag.sets) { - if (mConfiguredValues.contains(set)) { - featureFlag.sets.retainAll(mConfiguredValues); // Remove all sets that don't match the configured sets + if (mFlagSetsFilter.intersect(set)) { + newSets.add(set); // Remove all sets that don't match the configured sets // Since the feature flag has at least one set that matches the configured sets, // we process it according to its status - mStatusProcessStrategy.process(activeFeatureFlags, archivedFeatureFlags, featureFlag); shouldArchive = false; - break; } } if (shouldArchive) { archivedFeatureFlags.add(featureFlag); + } else { + featureFlag.sets = newSets; + mStatusProcessStrategy.process(activeFeatureFlags, archivedFeatureFlags, featureFlag); } } } diff --git a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java index 88abaf09d..18cd9cea2 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java +++ b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.Map; +import io.split.android.client.FlagSetsFilter; import io.split.android.client.SplitFilter; import io.split.android.client.dtos.Split; import io.split.android.client.dtos.SplitChange; @@ -20,12 +21,9 @@ public class SplitChangeProcessor { private final StatusProcessStrategy mStatusProcessStrategy; - @VisibleForTesting - SplitChangeProcessor() { - this((SplitFilter) null); - } + private final FlagSetsFilter mFlagSetsFilter; - public SplitChangeProcessor(@Nullable Map filters) { + public SplitChangeProcessor(@Nullable Map filters, FlagSetsFilter flagSetsFilter) { // We're only supporting one filter type if (filters == null || filters.isEmpty()) { mSplitFilter = null; @@ -34,10 +32,12 @@ public SplitChangeProcessor(@Nullable Map filters } mStatusProcessStrategy = new StatusProcessStrategy(); + mFlagSetsFilter = flagSetsFilter; } - public SplitChangeProcessor(@Nullable SplitFilter splitFilter) { + public SplitChangeProcessor(@Nullable SplitFilter splitFilter, @Nullable FlagSetsFilter flagSetsFilter) { mSplitFilter = splitFilter; + mFlagSetsFilter = flagSetsFilter; mStatusProcessStrategy = new StatusProcessStrategy(); } @@ -76,8 +76,8 @@ private FeatureFlagProcessStrategy getProcessStrategy(SplitFilter splitFilter) { return mStatusProcessStrategy; } - if (splitFilter.getType() == SplitFilter.Type.BY_SET) { - return new SetsProcessStrategy(splitFilter.getValues(), mStatusProcessStrategy); + if (splitFilter.getType() == SplitFilter.Type.BY_SET && mFlagSetsFilter != null) { + return new SetsProcessStrategy(mFlagSetsFilter, mStatusProcessStrategy); } else if (splitFilter.getType() == SplitFilter.Type.BY_NAME) { return new NamesProcessStrategy(splitFilter.getValues(), mStatusProcessStrategy); } else { diff --git a/src/main/java/io/split/android/client/service/workmanager/SplitsSyncWorker.java b/src/main/java/io/split/android/client/service/workmanager/SplitsSyncWorker.java index bf7633d17..0745bdb45 100644 --- a/src/main/java/io/split/android/client/service/workmanager/SplitsSyncWorker.java +++ b/src/main/java/io/split/android/client/service/workmanager/SplitsSyncWorker.java @@ -10,8 +10,11 @@ import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import io.split.android.client.FlagSetsFilter; +import io.split.android.client.FlagSetsFilterImpl; import io.split.android.client.SplitFilter; import io.split.android.client.dtos.SplitChange; import io.split.android.client.service.ServiceConstants; @@ -49,8 +52,12 @@ public SplitsSyncWorker(@NonNull Context context, TelemetryStorage telemetryStorage = StorageFactory.getTelemetryStorage(shouldRecordTelemetry); + SplitChangeProcessor splitChangeProcessor = new SplitChangeProcessor(filter, (filter != null && filter.getType() == SplitFilter.Type.BY_SET) ? + new FlagSetsFilterImpl(new HashSet<>(filter.getValues())) : null); + SplitsSyncHelper splitsSyncHelper = new SplitsSyncHelper(splitsFetcher, splitsStorage, - new SplitChangeProcessor(filter), telemetryStorage); + splitChangeProcessor, + telemetryStorage); mSplitTask = buildSplitSyncTask(splitsStorage, telemetryStorage, splitsSyncHelper); } catch (URISyntaxException e) { diff --git a/src/main/java/io/split/android/client/shared/SplitClientContainerImpl.java b/src/main/java/io/split/android/client/shared/SplitClientContainerImpl.java index a23d5e73c..007e868f6 100644 --- a/src/main/java/io/split/android/client/shared/SplitClientContainerImpl.java +++ b/src/main/java/io/split/android/client/shared/SplitClientContainerImpl.java @@ -10,6 +10,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import io.split.android.client.EventsTracker; +import io.split.android.client.FlagSetsFilter; import io.split.android.client.SplitClient; import io.split.android.client.SplitClientConfig; import io.split.android.client.SplitClientFactory; @@ -72,7 +73,7 @@ public SplitClientContainerImpl(@NonNull String defaultMatchingKey, @NonNull ClientComponentsRegister clientComponentsRegister, @NonNull MySegmentsWorkManagerWrapper workManagerWrapper, @NonNull EventsTracker eventsTracker, - @NonNull Set configuredFlagSets) { + @Nullable FlagSetsFilter flagSetsFilter) { mDefaultMatchingKey = checkNotNull(defaultMatchingKey); mPushNotificationManager = pushNotificationManager; mStreamingEnabled = config.streamingEnabled(); @@ -91,7 +92,7 @@ public SplitClientContainerImpl(@NonNull String defaultMatchingKey, keyValidator, eventsTracker, customerImpressionListener, - configuredFlagSets + flagSetsFilter ); mClientComponentsRegister = checkNotNull(clientComponentsRegister); mSplitTaskExecutor = checkNotNull(splitTaskExecutor); diff --git a/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java b/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java index 956a94b32..fd4ea363f 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java @@ -3,11 +3,13 @@ import static com.google.common.base.Preconditions.checkNotNull; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.util.Set; import io.split.android.client.Evaluator; import io.split.android.client.EvaluatorImpl; +import io.split.android.client.FlagSetsFilter; import io.split.android.client.api.Key; import io.split.android.client.attributes.AttributesManager; import io.split.android.client.attributes.AttributesMerger; @@ -27,7 +29,7 @@ public class TreatmentManagerFactoryImpl implements TreatmentManagerFactory { private final AttributesMerger mAttributesMerger; private final TelemetryStorageProducer mTelemetryStorageProducer; private final Evaluator mEvaluator; - private final Set mConfiguredFlagSets; + private final FlagSetsFilter mFlagSetsFilter; private final SplitsStorage mSplitsStorage; public TreatmentManagerFactoryImpl(@NonNull KeyValidator keyValidator, @@ -37,7 +39,7 @@ public TreatmentManagerFactoryImpl(@NonNull KeyValidator keyValidator, @NonNull AttributesMerger attributesMerger, @NonNull TelemetryStorageProducer telemetryStorageProducer, @NonNull SplitParser splitParser, - @NonNull Set configuredFlagSets, + @Nullable FlagSetsFilter flagSetsFilter, @NonNull SplitsStorage splitsStorage) { mKeyValidator = checkNotNull(keyValidator); mSplitValidator = checkNotNull(splitValidator); @@ -46,7 +48,7 @@ public TreatmentManagerFactoryImpl(@NonNull KeyValidator keyValidator, mAttributesMerger = checkNotNull(attributesMerger); mTelemetryStorageProducer = checkNotNull(telemetryStorageProducer); mEvaluator = new EvaluatorImpl(splitsStorage, splitParser); - mConfiguredFlagSets = checkNotNull(configuredFlagSets); + mFlagSetsFilter = flagSetsFilter; mSplitsStorage = checkNotNull(splitsStorage); } @@ -64,7 +66,7 @@ public TreatmentManager getTreatmentManager(Key key, ListenableEventsManager eve attributesManager, mAttributesMerger, mTelemetryStorageProducer, - mConfiguredFlagSets, + mFlagSetsFilter, mSplitsStorage ); } 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 c45f553d5..3d4be2839 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java @@ -15,6 +15,7 @@ import io.split.android.client.EvaluationResult; import io.split.android.client.Evaluator; +import io.split.android.client.FlagSetsFilter; import io.split.android.client.SplitResult; import io.split.android.client.TreatmentLabels; import io.split.android.client.attributes.AttributesManager; @@ -58,7 +59,7 @@ private static class ValidationTag { @NonNull private final AttributesMerger mAttributesMerger; private final TelemetryStorageProducer mTelemetryStorageProducer; - private final Set mConfiguredFlagSets; + private final FlagSetsFilter mFlagSetsFilter; private final SplitsStorage mSplitsStorage; private final SplitFilterValidator mFlagSetsValidator; @@ -73,7 +74,7 @@ public TreatmentManagerImpl(String matchingKey, @NonNull AttributesManager attributesManager, @NonNull AttributesMerger attributesMerger, @NonNull TelemetryStorageProducer telemetryStorageProducer, - @NonNull Set configuredFlagSets, + @Nullable FlagSetsFilter flagSetsFilter, @NonNull SplitsStorage splitsStorage) { mEvaluator = evaluator; mKeyValidator = keyValidator; @@ -87,7 +88,7 @@ public TreatmentManagerImpl(String matchingKey, mAttributesManager = checkNotNull(attributesManager); mAttributesMerger = checkNotNull(attributesMerger); mTelemetryStorageProducer = checkNotNull(telemetryStorageProducer); - mConfiguredFlagSets = checkNotNull(configuredFlagSets); + mFlagSetsFilter = flagSetsFilter; mSplitsStorage = checkNotNull(splitsStorage); mFlagSetsValidator = new FlagSetsValidatorImpl(); } @@ -415,7 +416,7 @@ private Set getNamesFromSet(String validationTag, } boolean isValid = mFlagSetsValidator.isValid(flagSet); - boolean isConfigured = mConfiguredFlagSets.isEmpty() || mConfiguredFlagSets.contains(flagSet); + boolean isConfigured = mFlagSetsFilter.intersect(flagSet); if (!isValid) { mValidationLogger.e("you passed " + flagSet + " which is not valid.", validationTag); diff --git a/src/test/java/io/split/android/client/FlagSetsFilterImplTest.java b/src/test/java/io/split/android/client/FlagSetsFilterImplTest.java new file mode 100644 index 000000000..97e68c155 --- /dev/null +++ b/src/test/java/io/split/android/client/FlagSetsFilterImplTest.java @@ -0,0 +1,73 @@ +package io.split.android.client; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +public class FlagSetsFilterImplTest { + + + @Test + public void intersectReturnsTrueWhenShouldFilterIsFalse() { + Set flagSets = new HashSet<>(); + FlagSetsFilterImpl filter = new FlagSetsFilterImpl(flagSets); + assertTrue(filter.intersect(new HashSet<>())); + } + + @Test + public void intersectReturnsTrueWhenSetsIsNull() { + Set flagSets = new HashSet<>(); + flagSets.add("test"); + FlagSetsFilterImpl filter = new FlagSetsFilterImpl(flagSets); + assertFalse(filter.intersect((Set) null)); + } + + @Test + public void intersectReturnsTrueWhenSetIsContained() { + Set flagSets = new HashSet<>(); + flagSets.add("test"); + FlagSetsFilterImpl filter = new FlagSetsFilterImpl(flagSets); + Set testSet = new HashSet<>(); + testSet.add("test"); + assertTrue(filter.intersect(testSet)); + } + + @Test + public void intersectReturnsFalseWhenSetIsNotContained() { + Set flagSets = new HashSet<>(); + flagSets.add("test"); + FlagSetsFilterImpl filter = new FlagSetsFilterImpl(flagSets); + Set testSet = new HashSet<>(); + testSet.add("other"); + assertFalse(filter.intersect(testSet)); + } + + @Test + public void intersectReturnsTrueWhenStringSetIsNull() { + Set flagSets = new HashSet<>(); + flagSets.add("test"); + FlagSetsFilterImpl filter = new FlagSetsFilterImpl(flagSets); + assertFalse(filter.intersect((String) null)); + } + + @Test + public void intersectReturnsTrueWhenStringSetIsContained() { + Set flagSets = new HashSet<>(); + flagSets.add("test"); + FlagSetsFilterImpl filter = new FlagSetsFilterImpl(flagSets); + assertTrue(filter.intersect("test")); + } + + @Test + public void intersectReturnsFalseWhenStringSetIsNotContained() { + Set flagSets = new HashSet<>(); + flagSets.add("test"); + FlagSetsFilterImpl filter = new FlagSetsFilterImpl(flagSets); + assertFalse(filter.intersect("other")); + } + +} diff --git a/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java b/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java index d0944a07b..191896074 100644 --- a/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java +++ b/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java @@ -14,6 +14,7 @@ import java.util.Map; import java.util.Set; +import io.split.android.client.FlagSetsFilterImpl; import io.split.android.client.SplitFilter; import io.split.android.client.dtos.Split; import io.split.android.client.dtos.SplitChange; @@ -26,7 +27,7 @@ public class SplitChangeProcessorTest { @Before public void setup() { - mProcessor = new SplitChangeProcessor(); + mProcessor = new SplitChangeProcessor((SplitFilter) null, null); } @Test @@ -150,7 +151,7 @@ public void processAddingWithFlagSets() { configuredSets.add("set_1"); configuredSets.add("set_2"); SplitFilter filter = SplitFilter.bySet(new ArrayList<>(configuredSets)); - mProcessor = new SplitChangeProcessor(filter); + mProcessor = new SplitChangeProcessor(filter, new FlagSetsFilterImpl(configuredSets)); Split split1 = newSplit("split_1", Status.ACTIVE, new HashSet<>(Arrays.asList("set_3", "set_1"))); Split split2 = newSplit("split_2", Status.ACTIVE, Collections.singleton("set_2")); @@ -172,7 +173,7 @@ public void featureFlagWithNoSetsIsArchivedWhenProcessingWithFlagSets() { configuredSets.add("set_2"); SplitFilter filter = SplitFilter.bySet(new ArrayList<>(configuredSets)); - mProcessor = new SplitChangeProcessor(filter); + mProcessor = new SplitChangeProcessor(filter, new FlagSetsFilterImpl(configuredSets)); Split split1 = newSplit("split_1", Status.ACTIVE, null); @@ -189,7 +190,7 @@ public void featureFlagWithNoSetsIsArchivedWhenProcessingWithFlagSets() { public void featureFlagsAreFilteredByNameWhenThereIsSplitFilterByName() { SplitFilter filter = SplitFilter.byName(Arrays.asList("split_1", "split_2")); - mProcessor = new SplitChangeProcessor(filter); + mProcessor = new SplitChangeProcessor(filter, null); Split split1 = newSplit("split_1", Status.ACTIVE); Split split2 = newSplit("split_2", Status.ARCHIVED); @@ -208,7 +209,7 @@ public void featureFlagsAreFilteredByNameWhenThereIsSplitFilterByName() { @Test public void creatingWithNullFilterProcessesEverything() { Map filterMap = null; - mProcessor = new SplitChangeProcessor(filterMap); + mProcessor = new SplitChangeProcessor(filterMap, null); Split split1 = newSplit("split_1", Status.ACTIVE); Split split2 = newSplit("split_2", Status.ARCHIVED); @@ -228,7 +229,7 @@ public void creatingWithNullFilterProcessesEverything() { public void creatingWithFilterWithEmptyConfiguredValuesProcessesEverything() { Map filterMap = Collections.singletonMap(SplitFilter.Type.BY_SET, SplitFilter.bySet(Collections.emptyList())); - mProcessor = new SplitChangeProcessor(filterMap); + mProcessor = new SplitChangeProcessor(filterMap, new FlagSetsFilterImpl(Collections.emptySet())); Split split1 = newSplit("split_1", Status.ACTIVE); Split split2 = newSplit("split_2", Status.ARCHIVED); @@ -250,7 +251,7 @@ public void nonConfiguredSetsAreRemovedFromSplit() { configuredSets.add("set_1"); configuredSets.add("set_2"); SplitFilter filter = SplitFilter.bySet(new ArrayList<>(configuredSets)); - mProcessor = new SplitChangeProcessor(filter); + mProcessor = new SplitChangeProcessor(filter, new FlagSetsFilterImpl(configuredSets)); Split split1 = newSplit("split_1", Status.ACTIVE, new HashSet<>(Arrays.asList("set_1", "set_3"))); Split split2 = newSplit("split_2", Status.ACTIVE, new HashSet<>(Arrays.asList("set_2", "set_asd"))); From bf91a01bf79a2e2caba8d0ec02926f7a5f0a838a Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Sep 2023 16:12:16 -0300 Subject: [PATCH 81/89] Update tests --- .../client/TreatmentManagerTelemetryTest.java | 7 +-- .../android/client/TreatmentManagerTest.java | 8 +-- .../TreatmentManagerWithFlagSetsTest.java | 63 +++++++++++-------- ...LocalhostSplitClientContainerImplTest.java | 9 +-- .../client/utils/SplitClientImplFactory.java | 4 +- 5 files changed, 51 insertions(+), 40 deletions(-) diff --git a/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java b/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java index feb47d7ce..74681726d 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java @@ -17,7 +17,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.Set; import io.split.android.client.attributes.AttributesManager; import io.split.android.client.attributes.AttributesMerger; @@ -52,14 +51,14 @@ public class TreatmentManagerTelemetryTest { @Mock private SplitsStorage mSplitsStorage; - private Set mConfiguredFlagSets = new HashSet<>(); + private FlagSetsFilter mFlagSetsFilter; private TreatmentManagerImpl treatmentManager; private AutoCloseable mAutoCloseable; @Before public void setUp() { mAutoCloseable = MockitoAnnotations.openMocks(this); - + mFlagSetsFilter = new FlagSetsFilterImpl(new HashSet<>()); treatmentManager = new TreatmentManagerImpl( "test_key", "test_key", @@ -72,7 +71,7 @@ public void setUp() { attributesManager, attributesMerger, telemetryStorageProducer, - mConfiguredFlagSets, + mFlagSetsFilter, mSplitsStorage); when(evaluator.getTreatment(anyString(), anyString(), anyString(), anyMap())).thenReturn(new EvaluationResult("test", "label")); diff --git a/src/test/java/io/split/android/client/TreatmentManagerTest.java b/src/test/java/io/split/android/client/TreatmentManagerTest.java index edb116fd3..cd2423bf8 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerTest.java @@ -54,13 +54,13 @@ public class TreatmentManagerTest { ListenableEventsManager eventsManagerStub; AttributesManager attributesManager = mock(AttributesManager.class); TelemetryStorageProducer telemetryStorageProducer = mock(TelemetryStorageProducer.class); - private Set mConfiguredFlagSets; + private FlagSetsFilter mFlagSetsFilter; TreatmentManagerImpl treatmentManager; private SplitsStorage mSplitsStorage; @Before public void loadSplitsFromFile() { - mConfiguredFlagSets = new HashSet<>(); + mFlagSetsFilter = new FlagSetsFilterImpl(new HashSet<>()); mSplitsStorage = mock(SplitsStorage.class); treatmentManager = initializeTreatmentManager(); if (evaluator == null) { @@ -325,7 +325,7 @@ private TreatmentManager createTreatmentManager(String matchingKey, String bucke new KeyValidatorImpl(), new SplitValidatorImpl(), new ImpressionListenerMock(), config.labelsEnabled(), eventsManagerStub, mock(AttributesManager.class), mock(AttributesMerger.class), - mock(TelemetryStorageProducer.class), mConfiguredFlagSets, mSplitsStorage); + mock(TelemetryStorageProducer.class), mFlagSetsFilter, mSplitsStorage); } private TreatmentManagerImpl initializeTreatmentManager() { @@ -353,7 +353,7 @@ private TreatmentManagerImpl initializeTreatmentManager(Evaluator evaluator) { attributesManager, mock(AttributesMerger.class), telemetryStorageProducer, - mConfiguredFlagSets, + mFlagSetsFilter, mSplitsStorage); } diff --git a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java index 42281896f..dc2cc12e8 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java @@ -55,7 +55,7 @@ public class TreatmentManagerWithFlagSetsTest { @Mock private SplitsStorage mSplitsStorage; - private Set mConfiguredFlagSets; + private FlagSetsFilter mFlagSetsFilter; private TreatmentManagerImpl mTreatmentManager; private AutoCloseable mAutoCloseable; @@ -63,24 +63,11 @@ public class TreatmentManagerWithFlagSetsTest { public void setUp() { mAutoCloseable = MockitoAnnotations.openMocks(this); - mConfiguredFlagSets = new HashSet<>(); + mFlagSetsFilter = new FlagSetsFilterImpl(new HashSet<>()); when(mEventsManager.eventAlreadyTriggered(SplitEvent.SDK_READY)).thenReturn(true); when(mEventsManager.eventAlreadyTriggered(SplitEvent.SDK_READY_FROM_CACHE)).thenReturn(true); - mTreatmentManager = new TreatmentManagerImpl( - "matching_key", - "bucketing_key", - mEvaluator, - mKeyValidator, - mSplitValidator, - mImpressionListener, - SplitClientConfig.builder().build().labelsEnabled(), - mEventsManager, - mAttributesManager, - mAttributesMerger, - mTelemetryStorageProducer, - mConfiguredFlagSets, - mSplitsStorage); + initializeTreatmentManager(); when(mEvaluator.getTreatment(anyString(), anyString(), eq("test_1"), anyMap())) .thenReturn(new EvaluationResult("result_1", "label")); @@ -129,7 +116,7 @@ public void getTreatmentsByFlagSetWithNoConfiguredSetsInvalidSetDoesNotQueryStor @Test public void getTreatmentsByFlagSetWithConfiguredSetsExistingSetQueriesStorageAndUsesEvaluator() { - mConfiguredFlagSets.add("set_1"); + mFlagSetsFilter = new FlagSetsFilterImpl(Collections.singleton("set_1")); when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); @@ -141,7 +128,8 @@ public void getTreatmentsByFlagSetWithConfiguredSetsExistingSetQueriesStorageAnd @Test public void getTreatmentsByFlagSetWithConfiguredSetsNonExistingSetDoesNotQueryStorageNorUseEvaluator() { - mConfiguredFlagSets.add("set_1"); + mFlagSetsFilter = new FlagSetsFilterImpl(Collections.singleton("set_1")); + initializeTreatmentManager(); when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); @@ -151,13 +139,30 @@ public void getTreatmentsByFlagSetWithConfiguredSetsNonExistingSetDoesNotQuerySt verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); } + private void initializeTreatmentManager() { + mTreatmentManager = new TreatmentManagerImpl( + "matching_key", + "bucketing_key", + mEvaluator, + mKeyValidator, + mSplitValidator, + mImpressionListener, + SplitClientConfig.builder().build().labelsEnabled(), + mEventsManager, + mAttributesManager, + mAttributesMerger, + mTelemetryStorageProducer, + mFlagSetsFilter, + mSplitsStorage); + } + @Test public void getTreatmentsByFlagSetReturnsCorrectFormat() { Set mockNames = new HashSet<>(); mockNames.add("test_1"); mockNames.add("test_2"); when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(mockNames); - mConfiguredFlagSets.add("set_1"); + mFlagSetsFilter = new FlagSetsFilterImpl(Collections.singleton("set_1")); Map result = mTreatmentManager.getTreatmentsByFlagSet("set_1", null, false); @@ -209,7 +214,8 @@ public void getTreatmentsByFlagSetsWithNoConfiguredSetsInvalidSetDoesNotQuerySto @Test public void getTreatmentsByFlagSetsWithConfiguredSetsExistingSetQueriesStorageForConfiguredSetOnlyAndUsesEvaluator() { - mConfiguredFlagSets.add("set_1"); + mFlagSetsFilter = new FlagSetsFilterImpl(Collections.singleton("set_1")); + initializeTreatmentManager(); when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); @@ -221,7 +227,8 @@ public void getTreatmentsByFlagSetsWithConfiguredSetsExistingSetQueriesStorageFo @Test public void getTreatmentsByFlagSetsWithConfiguredSetsNonExistingSetDoesNotQueryStorageNorUseEvaluator() { - mConfiguredFlagSets.add("set_1"); + mFlagSetsFilter = new FlagSetsFilterImpl(Collections.singleton("set_1")); + initializeTreatmentManager(); mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_2", "set_3"), null, false); @@ -301,7 +308,7 @@ public void getTreatmentsWithConfigByFlagSetWithNoConfiguredSetsInvalidSetDoesNo @Test public void getTreatmentsWithConfigByFlagSetWithConfiguredSetsExistingSetQueriesStorageAndUsesEvaluator() { - mConfiguredFlagSets.add("set_1"); + mFlagSetsFilter = new FlagSetsFilterImpl(Collections.singleton("set_1")); when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); @@ -313,7 +320,8 @@ public void getTreatmentsWithConfigByFlagSetWithConfiguredSetsExistingSetQueries @Test public void getTreatmentsWithConfigByFlagSetWithConfiguredSetsNonExistingSetDoesNotQueryStorageNorUseEvaluator() { - mConfiguredFlagSets.add("set_1"); + mFlagSetsFilter = new FlagSetsFilterImpl(Collections.singleton("set_1")); + initializeTreatmentManager(); when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); @@ -329,7 +337,7 @@ public void getTreatmentsWithConfigByFlagSetReturnsCorrectFormat() { mockNames.add("test_1"); mockNames.add("test_2"); when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(mockNames); - mConfiguredFlagSets.add("set_1"); + mFlagSetsFilter = new FlagSetsFilterImpl(Collections.singleton("set_1")); Map result = mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_1", null, false); @@ -381,7 +389,9 @@ public void getTreatmentsWithConfigByFlagSetsWithNoConfiguredSetsInvalidSetDoesN @Test public void getTreatmentsWithConfigByFlagSetsWithConfiguredSetsExistingSetQueriesStorageForConfiguredSetOnlyAndUsesEvaluator() { - mConfiguredFlagSets.add("set_1"); + mFlagSetsFilter = new FlagSetsFilterImpl(Collections.singleton("set_1")); + initializeTreatmentManager(); + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); @@ -393,7 +403,8 @@ public void getTreatmentsWithConfigByFlagSetsWithConfiguredSetsExistingSetQuerie @Test public void getTreatmentsWithConfigByFlagSetsWithConfiguredSetsNonExistingSetDoesNotQueryStorageNorUseEvaluator() { - mConfiguredFlagSets.add("set_1"); + mFlagSetsFilter = new FlagSetsFilterImpl(Collections.singleton("set_1")); + initializeTreatmentManager(); mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_2", "set_3"), null, false); diff --git a/src/test/java/io/split/android/client/localhost/shared/LocalhostSplitClientContainerImplTest.java b/src/test/java/io/split/android/client/localhost/shared/LocalhostSplitClientContainerImplTest.java index 9d855de66..2088b392a 100644 --- a/src/test/java/io/split/android/client/localhost/shared/LocalhostSplitClientContainerImplTest.java +++ b/src/test/java/io/split/android/client/localhost/shared/LocalhostSplitClientContainerImplTest.java @@ -17,8 +17,9 @@ import java.util.Collection; import java.util.HashSet; -import java.util.Set; +import io.split.android.client.FlagSetsFilter; +import io.split.android.client.FlagSetsFilterImpl; import io.split.android.client.SplitClient; import io.split.android.client.SplitClientConfig; import io.split.android.client.api.Key; @@ -52,14 +53,14 @@ public class LocalhostSplitClientContainerImplTest { private SplitClientConfig mConfig; @Mock private SplitTaskExecutor mTaskExecutor; - private Set mConfiguredSets; + private FlagSetsFilter mFlagSetsFilter; private LocalhostSplitClientContainerImpl mClientContainer; @Before public void setUp() { MockitoAnnotations.openMocks(this); when(mAttributesManagerFactory.getManager(any(), any())).thenReturn(mock(AttributesManager.class)); - mConfiguredSets = new HashSet<>(); + mFlagSetsFilter = new FlagSetsFilterImpl(new HashSet<>()); mClientContainer = getClientContainer(); } @@ -108,6 +109,6 @@ private LocalhostSplitClientContainerImpl getClientContainer() { mTelemetryStorageProducer, mEventsManagerCoordinator, mTaskExecutor, - mConfiguredSets); + mFlagSetsFilter); } } diff --git a/src/test/java/io/split/android/client/utils/SplitClientImplFactory.java b/src/test/java/io/split/android/client/utils/SplitClientImplFactory.java index 03fe34446..e20344b3e 100644 --- a/src/test/java/io/split/android/client/utils/SplitClientImplFactory.java +++ b/src/test/java/io/split/android/client/utils/SplitClientImplFactory.java @@ -4,8 +4,8 @@ import java.util.Collections; -import io.split.android.client.EvaluatorImpl; import io.split.android.client.EventsTracker; +import io.split.android.client.FlagSetsFilterImpl; import io.split.android.client.SplitClientConfig; import io.split.android.client.SplitClientImpl; import io.split.android.client.SplitFactory; @@ -39,7 +39,7 @@ public static SplitClientImpl get(Key key, SplitsStorage splitsStorage) { TreatmentManagerFactory treatmentManagerFactory = new TreatmentManagerFactoryImpl( new KeyValidatorImpl(), new SplitValidatorImpl(), new ImpressionListener.NoopImpressionListener(), false, new AttributesMergerImpl(), telemetryStorage, splitParser, - Collections.emptySet(), splitsStorage); + new FlagSetsFilterImpl(Collections.emptySet()), splitsStorage); AttributesManager attributesManager = mock(AttributesManager.class); SplitClientImpl c = new SplitClientImpl( From ef81e369d02a9a19ea5e44d702133f18963e0f7c Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Sep 2023 16:45:00 -0300 Subject: [PATCH 82/89] Fix test --- .../client/service/splits/SplitChangeProcessor.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java index 18cd9cea2..3c0534bd7 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java +++ b/src/main/java/io/split/android/client/service/splits/SplitChangeProcessor.java @@ -23,6 +23,13 @@ public class SplitChangeProcessor { private final FlagSetsFilter mFlagSetsFilter; + /** @noinspection unused*/ // Used in tests + private SplitChangeProcessor() { + mSplitFilter = null; + mStatusProcessStrategy = new StatusProcessStrategy(); + mFlagSetsFilter = null; + } + public SplitChangeProcessor(@Nullable Map filters, FlagSetsFilter flagSetsFilter) { // We're only supporting one filter type if (filters == null || filters.isEmpty()) { From 9c284d75ed8a9ff25b70e2bd2689b23dcdd5d72e Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 15 Sep 2023 12:02:47 -0300 Subject: [PATCH 83/89] Filter integration --- .../integration/sets/FlagSetsPollingTest.java | 1 - .../userconsent/UserConsentModeDebugTest.kt | 4 +- .../userconsent/UserConsentModeNoneTest.kt | 4 +- .../UserConsentModeOptimizedTest.kt | 4 +- .../android/client/FlagSetsFilterImpl.java | 3 +- .../android/client/SplitFactoryImpl.java | 2 +- .../io/split/android/client/SplitFilter.java | 2 +- .../localhost/LocalhostSplitClient.java | 1 - .../localhost/LocalhostSplitFactory.java | 2 +- .../localhost/LocalhostSplitsStorage.java | 3 +- .../splits/FeatureFlagProcessStrategy.java | 3 +- .../service/workmanager/SplitsSyncWorker.java | 2 +- .../client/storage/splits/SplitsStorage.java | 3 +- .../storage/splits/SplitsStorageImpl.java | 3 +- .../validators/FlagSetsValidatorImpl.java | 34 +++++++-- .../validators/SplitFilterValidator.java | 7 +- .../validators/TreatmentManagerImpl.java | 33 ++------- .../TreatmentManagerWithFlagSetsTest.java | 70 +++++++++---------- .../validators/FlagSetsValidatorImplTest.java | 20 +++--- 19 files changed, 107 insertions(+), 94 deletions(-) diff --git a/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java b/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java index 8c3c095f7..ccb4e94c1 100644 --- a/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java +++ b/src/androidTest/java/tests/integration/sets/FlagSetsPollingTest.java @@ -162,7 +162,6 @@ public void featureFlagSetsAreIgnoredWhenSetsAreNotConfigured() throws IOExcepti @Test public void queryStringIsBuiltCorrectlyWhenSetsAreConfigured() throws IOException, InterruptedException { - // 1. Initialize a factory with polling and sets set_1 & set_2 configured. createFactory(mContext, mRoomDb, "set_x", "set_x", "set_3", "set_2", "set_3", "set_ww", "invalid+"); boolean awaitFirst = firstChangeLatch.await(5, TimeUnit.SECONDS); diff --git a/src/androidTest/java/tests/integration/userconsent/UserConsentModeDebugTest.kt b/src/androidTest/java/tests/integration/userconsent/UserConsentModeDebugTest.kt index b162d2a54..4c0d9eb02 100644 --- a/src/androidTest/java/tests/integration/userconsent/UserConsentModeDebugTest.kt +++ b/src/androidTest/java/tests/integration/userconsent/UserConsentModeDebugTest.kt @@ -231,7 +231,7 @@ class UserConsentModeDebugTest { } else if (uri.path.contains("/splitChanges")) { if (mChangeHit == 0) { mChangeHit+=1 - return getSplitsMockResponse("", "") + return getSplitsMockResponse("") } return HttpResponseMock(200, IntegrationHelper.emptySplitChanges(99999999, 99999999)) } else if (uri.path.contains("/testImpressions/bulk")) { @@ -259,7 +259,7 @@ class UserConsentModeDebugTest { } } - private fun getSplitsMockResponse(since: String, till: String): HttpResponseMock { + private fun getSplitsMockResponse(since: String): HttpResponseMock { return HttpResponseMock(200, loadSplitChanges()) } diff --git a/src/androidTest/java/tests/integration/userconsent/UserConsentModeNoneTest.kt b/src/androidTest/java/tests/integration/userconsent/UserConsentModeNoneTest.kt index dddec5f6a..106bfd65b 100644 --- a/src/androidTest/java/tests/integration/userconsent/UserConsentModeNoneTest.kt +++ b/src/androidTest/java/tests/integration/userconsent/UserConsentModeNoneTest.kt @@ -232,7 +232,7 @@ class UserConsentModeNoneTest { } else if (uri.path.contains("/splitChanges")) { if (mChangeHit == 0) { mChangeHit+=1 - return getSplitsMockResponse("", "") + return getSplitsMockResponse("") } return HttpResponseMock(200, IntegrationHelper.emptySplitChanges(99999999, 99999999)) } else if (uri.path.contains("/testImpressions/bulk")) { @@ -260,7 +260,7 @@ class UserConsentModeNoneTest { } } - private fun getSplitsMockResponse(since: String, till: String): HttpResponseMock { + private fun getSplitsMockResponse(since: String): HttpResponseMock { return HttpResponseMock(200, loadSplitChanges()) } diff --git a/src/androidTest/java/tests/integration/userconsent/UserConsentModeOptimizedTest.kt b/src/androidTest/java/tests/integration/userconsent/UserConsentModeOptimizedTest.kt index 4a6ab033b..28e9fdb77 100644 --- a/src/androidTest/java/tests/integration/userconsent/UserConsentModeOptimizedTest.kt +++ b/src/androidTest/java/tests/integration/userconsent/UserConsentModeOptimizedTest.kt @@ -242,7 +242,7 @@ class UserConsentModeOptimizedTest { } else if (uri.path.contains("/splitChanges")) { if (mChangeHit == 0) { mChangeHit+=1 - return getSplitsMockResponse("", "") + return getSplitsMockResponse("") } return HttpResponseMock(200, IntegrationHelper.emptySplitChanges(99999999, 99999999)) } else if (uri.path.contains("/testImpressions/bulk")) { @@ -270,7 +270,7 @@ class UserConsentModeOptimizedTest { } } - private fun getSplitsMockResponse(since: String, till: String): HttpResponseMock { + private fun getSplitsMockResponse(since: String): HttpResponseMock { return HttpResponseMock(200, loadSplitChanges()) } diff --git a/src/main/java/io/split/android/client/FlagSetsFilterImpl.java b/src/main/java/io/split/android/client/FlagSetsFilterImpl.java index 84ab5c839..d8c731280 100644 --- a/src/main/java/io/split/android/client/FlagSetsFilterImpl.java +++ b/src/main/java/io/split/android/client/FlagSetsFilterImpl.java @@ -1,5 +1,6 @@ package io.split.android.client; +import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -8,7 +9,7 @@ public class FlagSetsFilterImpl implements FlagSetsFilter { private final boolean mShouldFilter; private final Set mFlagSets; - public FlagSetsFilterImpl(Set flagSets) { + public FlagSetsFilterImpl(Collection flagSets) { mFlagSets = new HashSet<>(flagSets); mShouldFilter = !mFlagSets.isEmpty(); } diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index 11519d18b..e798dcdcd 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -182,7 +182,7 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { FlagSetsFilter flagSetsFilter = null; if (filters.get(SplitFilter.Type.BY_SET) != null) { - flagSetsFilter = new FlagSetsFilterImpl(new HashSet<>(filters.get(SplitFilter.Type.BY_SET).getValues())); + flagSetsFilter = new FlagSetsFilterImpl(filters.get(SplitFilter.Type.BY_SET).getValues()); } SplitTaskFactory splitTaskFactory = new SplitTaskFactoryImpl( diff --git a/src/main/java/io/split/android/client/SplitFilter.java b/src/main/java/io/split/android/client/SplitFilter.java index 908f01c34..38b1a0721 100644 --- a/src/main/java/io/split/android/client/SplitFilter.java +++ b/src/main/java/io/split/android/client/SplitFilter.java @@ -86,7 +86,7 @@ public static SplitFilter bySet(@NonNull List values) { SplitFilter(Type type, List values, SplitFilterValidator validator) { mType = type; - SplitFilterValidator.ValidationResult validationResult = validator.cleanup(values); + SplitFilterValidator.ValidationResult validationResult = validator.cleanup("SDK config", values); mValues = validationResult.getValues(); mInvalidValueCount = validationResult.getInvalidValueCount(); } 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 941f3ee7a..c70d79420 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java @@ -12,7 +12,6 @@ import java.util.Map; import java.util.Set; -import io.split.android.client.Evaluator; import io.split.android.client.EvaluatorImpl; import io.split.android.client.FlagSetsFilter; import io.split.android.client.SplitClient; diff --git a/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java b/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java index 0948fad70..e65838367 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostSplitFactory.java @@ -85,7 +85,7 @@ public LocalhostSplitFactory(String key, Context context, if (!groupedFilters.isEmpty()) { SplitFilter bySetFilter = groupedFilters.get(SplitFilter.Type.BY_SET); if (bySetFilter != null) { - flagSetsFilter = new FlagSetsFilterImpl(new HashSet<>(bySetFilter.getValues())); + flagSetsFilter = new FlagSetsFilterImpl(bySetFilter.getValues()); } } } diff --git a/src/main/java/io/split/android/client/localhost/LocalhostSplitsStorage.java b/src/main/java/io/split/android/client/localhost/LocalhostSplitsStorage.java index 76bc60b3d..989d9a16c 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostSplitsStorage.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostSplitsStorage.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -126,7 +127,7 @@ public void clear() { @NonNull @Override - public Set getNamesByFlagSets(List sets) { + public Set getNamesByFlagSets(Collection sets) { Set namesToReturn = new HashSet<>(); if (sets == null || sets.isEmpty()) { return namesToReturn; diff --git a/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java b/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java index f61871de0..1cffb6669 100644 --- a/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java +++ b/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java @@ -68,7 +68,7 @@ public void process(List activeFeatureFlags, List archivedFeatureF Set newSets = new HashSet<>(); for (String set : featureFlag.sets) { if (mFlagSetsFilter.intersect(set)) { - newSets.add(set); // Remove all sets that don't match the configured sets + newSets.add(set); // Add the flag set to the valid group // Since the feature flag has at least one set that matches the configured sets, // we process it according to its status shouldArchive = false; @@ -78,6 +78,7 @@ public void process(List activeFeatureFlags, List archivedFeatureF if (shouldArchive) { archivedFeatureFlags.add(featureFlag); } else { + // Replace the feature flag sets with the intersection of the configured sets and the feature flag sets featureFlag.sets = newSets; mStatusProcessStrategy.process(activeFeatureFlags, archivedFeatureFlags, featureFlag); } diff --git a/src/main/java/io/split/android/client/service/workmanager/SplitsSyncWorker.java b/src/main/java/io/split/android/client/service/workmanager/SplitsSyncWorker.java index 0745bdb45..982e2fcae 100644 --- a/src/main/java/io/split/android/client/service/workmanager/SplitsSyncWorker.java +++ b/src/main/java/io/split/android/client/service/workmanager/SplitsSyncWorker.java @@ -53,7 +53,7 @@ public SplitsSyncWorker(@NonNull Context context, TelemetryStorage telemetryStorage = StorageFactory.getTelemetryStorage(shouldRecordTelemetry); SplitChangeProcessor splitChangeProcessor = new SplitChangeProcessor(filter, (filter != null && filter.getType() == SplitFilter.Type.BY_SET) ? - new FlagSetsFilterImpl(new HashSet<>(filter.getValues())) : null); + new FlagSetsFilterImpl(filter.getValues()) : null); SplitsSyncHelper splitsSyncHelper = new SplitsSyncHelper(splitsFetcher, splitsStorage, splitChangeProcessor, diff --git a/src/main/java/io/split/android/client/storage/splits/SplitsStorage.java b/src/main/java/io/split/android/client/storage/splits/SplitsStorage.java index d152243b5..62af70e7b 100644 --- a/src/main/java/io/split/android/client/storage/splits/SplitsStorage.java +++ b/src/main/java/io/split/android/client/storage/splits/SplitsStorage.java @@ -3,6 +3,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; @@ -35,5 +36,5 @@ public interface SplitsStorage { void clear(); @NonNull - Set getNamesByFlagSets(List flagSets); + Set getNamesByFlagSets(Collection flagSets); } diff --git a/src/main/java/io/split/android/client/storage/splits/SplitsStorageImpl.java b/src/main/java/io/split/android/client/storage/splits/SplitsStorageImpl.java index 2f3eb99f6..b143c209c 100644 --- a/src/main/java/io/split/android/client/storage/splits/SplitsStorageImpl.java +++ b/src/main/java/io/split/android/client/storage/splits/SplitsStorageImpl.java @@ -6,6 +6,7 @@ import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -150,7 +151,7 @@ public void clear() { @NonNull @Override - public Set getNamesByFlagSets(List sets) { + public Set getNamesByFlagSets(Collection sets) { Set namesToReturn = new HashSet<>(); if (sets == null || sets.isEmpty()) { return namesToReturn; diff --git a/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java b/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java index 5e14a45a3..a9dbcb6b5 100644 --- a/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java +++ b/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java @@ -2,9 +2,12 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.TreeSet; +import io.split.android.client.FlagSetsFilter; import io.split.android.client.utils.logger.Logger; public class FlagSetsValidatorImpl implements SplitFilterValidator { @@ -19,7 +22,7 @@ public class FlagSetsValidatorImpl implements SplitFilterValidator { * @return list of unique alphanumerically ordered valid flag sets */ @Override - public ValidationResult cleanup(List values) { + public ValidationResult cleanup(String method, List values) { if (values == null || values.isEmpty()) { return new ValidationResult(Collections.emptyList(), 0); } @@ -34,12 +37,12 @@ public ValidationResult cleanup(List values) { } if (set.trim().length() != set.length()) { - Logger.w("SDK config: Flag Set name " + set + " has extra whitespace, trimming"); + Logger.w(method + ": Flag Set name " + set + " has extra whitespace, trimming"); set = set.trim(); } if (!set.toLowerCase().equals(set)) { - Logger.w("SDK config: Flag Set name "+set+" should be all lowercase - converting string to lowercase"); + Logger.w(method + ": Flag Set name "+set+" should be all lowercase - converting string to lowercase"); set = set.toLowerCase(); } @@ -47,7 +50,7 @@ public ValidationResult cleanup(List values) { cleanedUpSets.add(set); } else { invalidValueCount++; - Logger.w("SDK config: you passed "+ set +", Flag Set must adhere to the regular expressions "+ FLAG_SET_REGEX +". This means a Flag Set must be start with a letter, be in lowercase, alphanumeric and have a max length of 50 characters. "+ set +" was discarded."); + Logger.w(method + ": you passed "+ set +", Flag Set must adhere to the regular expressions "+ FLAG_SET_REGEX +". This means a Flag Set must be start with a letter, be in lowercase, alphanumeric and have a max length of 50 characters. "+ set +" was discarded."); } } @@ -58,4 +61,27 @@ public ValidationResult cleanup(List values) { public boolean isValid(String value) { return value != null && value.trim().matches(FLAG_SET_REGEX); } + + @Override + public Set items(List values, FlagSetsFilter flagSetsFilter) { + Set setsToReturn = new HashSet<>(); + + if (values == null || values.isEmpty()) { + return setsToReturn; + } + + for (String flagSet : values) { + if (!isValid(flagSet)) { + continue; + } + + if (flagSetsFilter != null && !flagSetsFilter.intersect(flagSet)) { + continue; + } + + setsToReturn.add(flagSet); + } + + return setsToReturn; + } } diff --git a/src/main/java/io/split/android/client/validators/SplitFilterValidator.java b/src/main/java/io/split/android/client/validators/SplitFilterValidator.java index 110043d47..b5f0d1b2b 100644 --- a/src/main/java/io/split/android/client/validators/SplitFilterValidator.java +++ b/src/main/java/io/split/android/client/validators/SplitFilterValidator.java @@ -1,13 +1,18 @@ package io.split.android.client.validators; import java.util.List; +import java.util.Set; + +import io.split.android.client.FlagSetsFilter; public interface SplitFilterValidator { - ValidationResult cleanup(List values); + ValidationResult cleanup(String method, List values); boolean isValid(String value); + Set items(List values, FlagSetsFilter flagSetsFilter); + class ValidationResult { private final List mValues; 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 3d4be2839..8a5511467 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java @@ -186,7 +186,7 @@ public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Null String validationTag = ValidationTag.GET_TREATMENTS_BY_FLAG_SET; Set names = new HashSet<>(); try { - names = getNamesFromSet(validationTag, Collections.singletonList(flagSet)); + names = getNamesFromSet(Collections.singletonList(flagSet)); if (isClientDestroyed) { mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); return controlTreatmentsForSplits(new ArrayList<>(names), validationTag); @@ -211,7 +211,7 @@ public Map getTreatmentsByFlagSets(@NonNull List flagSet String validationTag = ValidationTag.GET_TREATMENTS_BY_FLAG_SETS; Set names = new HashSet<>(); try { - names = getNamesFromSet(validationTag, flagSets); + names = getNamesFromSet(flagSets); if (isClientDestroyed) { mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); return controlTreatmentsForSplits(new ArrayList<>(names), validationTag); @@ -236,7 +236,7 @@ public Map getTreatmentsWithConfigByFlagSet(@NonNull String String validationTag = ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET; Set names = new HashSet<>(); try { - names = getNamesFromSet(validationTag, Collections.singletonList(flagSet)); + names = getNamesFromSet(Collections.singletonList(flagSet)); if (isClientDestroyed) { mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); return controlTreatmentsForSplitsWithConfig(new ArrayList<>(names), validationTag); @@ -261,7 +261,7 @@ public Map getTreatmentsWithConfigByFlagSets(@NonNull List< String validationTag = ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS; Set names = new HashSet<>(); try { - names = getNamesFromSet(validationTag, flagSets); + names = getNamesFromSet(flagSets); if (isClientDestroyed) { mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); return controlTreatmentsForSplitsWithConfig(new ArrayList<>(names), validationTag); @@ -402,30 +402,9 @@ private void recordLatency(Method treatment, long startTime) { } @NonNull - private Set getNamesFromSet(String validationTag, - @NonNull List flagSets) { + private Set getNamesFromSet(@NonNull List flagSets) { - if (flagSets == null) { - return new HashSet<>(); - } - - List setsToEvaluate = new ArrayList<>(); - for (String flagSet : flagSets) { - if (setsToEvaluate.contains(flagSet)) { - continue; - } - - boolean isValid = mFlagSetsValidator.isValid(flagSet); - boolean isConfigured = mFlagSetsFilter.intersect(flagSet); - - if (!isValid) { - mValidationLogger.e("you passed " + flagSet + " which is not valid.", validationTag); - } else if (!isConfigured) { - mValidationLogger.e("you passed " + flagSet + " which is not defined in the configuration.", validationTag); - } else { - setsToEvaluate.add(flagSet); - } - } + Set setsToEvaluate = mFlagSetsValidator.items(flagSets, mFlagSetsFilter); if (setsToEvaluate.isEmpty()) { return new HashSet<>(); diff --git a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java index dc2cc12e8..dc1f4efc5 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java @@ -94,18 +94,18 @@ public void getTreatmentsByFlagSetDestroyedDoesNotUseEvaluator() { @Test public void getTreatmentsByFlagSetWithNoConfiguredSetsQueriesStorageAndUsesEvaluator() { - when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + when(mSplitsStorage.getNamesByFlagSets(Collections.singleton("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); mTreatmentManager.getTreatmentsByFlagSet("set_1", null, false); - verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mSplitsStorage).getNamesByFlagSets(Collections.singleton("set_1")); verify(mEvaluator).getTreatment(eq("matching_key"), eq("bucketing_key"), eq("test_1"), anyMap()); } @Test public void getTreatmentsByFlagSetWithNoConfiguredSetsInvalidSetDoesNotQueryStorageNorUseEvaluator() { - when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + when(mSplitsStorage.getNamesByFlagSets(Collections.singleton("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); mTreatmentManager.getTreatmentsByFlagSet("SET!", null, false); @@ -117,12 +117,12 @@ public void getTreatmentsByFlagSetWithNoConfiguredSetsInvalidSetDoesNotQueryStor @Test public void getTreatmentsByFlagSetWithConfiguredSetsExistingSetQueriesStorageAndUsesEvaluator() { mFlagSetsFilter = new FlagSetsFilterImpl(Collections.singleton("set_1")); - when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + when(mSplitsStorage.getNamesByFlagSets(Collections.singleton("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); mTreatmentManager.getTreatmentsByFlagSet("set_1", null, false); - verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mSplitsStorage).getNamesByFlagSets(Collections.singleton("set_1")); verify(mEvaluator).getTreatment(eq("matching_key"), eq("bucketing_key"), eq("test_1"), anyMap()); } @@ -130,7 +130,7 @@ public void getTreatmentsByFlagSetWithConfiguredSetsExistingSetQueriesStorageAnd public void getTreatmentsByFlagSetWithConfiguredSetsNonExistingSetDoesNotQueryStorageNorUseEvaluator() { mFlagSetsFilter = new FlagSetsFilterImpl(Collections.singleton("set_1")); initializeTreatmentManager(); - when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + when(mSplitsStorage.getNamesByFlagSets(Collections.singleton("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); mTreatmentManager.getTreatmentsByFlagSet("set_2", null, false); @@ -161,7 +161,7 @@ public void getTreatmentsByFlagSetReturnsCorrectFormat() { Set mockNames = new HashSet<>(); mockNames.add("test_1"); mockNames.add("test_2"); - when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(mockNames); + when(mSplitsStorage.getNamesByFlagSets(Collections.singleton("set_1"))).thenReturn(mockNames); mFlagSetsFilter = new FlagSetsFilterImpl(Collections.singleton("set_1")); Map result = mTreatmentManager.getTreatmentsByFlagSet("set_1", null, false); @@ -173,7 +173,7 @@ public void getTreatmentsByFlagSetReturnsCorrectFormat() { @Test public void getTreatmentsByFlagSetRecordsTelemetry() { - when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(Collections.singleton("test_1")); + when(mSplitsStorage.getNamesByFlagSets(Collections.singleton("set_1"))).thenReturn(Collections.singleton("test_1")); mTreatmentManager.getTreatmentsByFlagSet("set_1", null, false); @@ -191,24 +191,24 @@ public void getTreatmentsByFlagSetsDestroyedDoesNotUseEvaluator() { @Test public void getTreatmentsByFlagSetsWithNoConfiguredSetsQueriesStorageAndUsesEvaluator() { - when(mSplitsStorage.getNamesByFlagSets(Arrays.asList("set_1", "set_2"))) + when(mSplitsStorage.getNamesByFlagSets(new HashSet<>(Arrays.asList("set_1", "set_2")))) .thenReturn(new HashSet<>(Arrays.asList("test_1", "test_2"))); mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_1", "set_2"), null, false); - verify(mSplitsStorage).getNamesByFlagSets(Arrays.asList("set_1", "set_2")); + verify(mSplitsStorage).getNamesByFlagSets(new HashSet<>(Arrays.asList("set_1", "set_2"))); verify(mEvaluator).getTreatment(anyString(), anyString(), eq("test_1"), anyMap()); verify(mEvaluator).getTreatment(anyString(), anyString(), eq("test_2"), anyMap()); } @Test public void getTreatmentsByFlagSetsWithNoConfiguredSetsInvalidSetDoesNotQueryStorageForInvalidSet() { - when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + when(mSplitsStorage.getNamesByFlagSets(Collections.singleton("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_1", "SET!"), null, false); - verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mSplitsStorage).getNamesByFlagSets(Collections.singleton("set_1")); verify(mEvaluator).getTreatment(any(), any(), eq("test_1"), anyMap()); } @@ -216,12 +216,12 @@ public void getTreatmentsByFlagSetsWithNoConfiguredSetsInvalidSetDoesNotQuerySto public void getTreatmentsByFlagSetsWithConfiguredSetsExistingSetQueriesStorageForConfiguredSetOnlyAndUsesEvaluator() { mFlagSetsFilter = new FlagSetsFilterImpl(Collections.singleton("set_1")); initializeTreatmentManager(); - when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + when(mSplitsStorage.getNamesByFlagSets(Collections.singleton("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_1", "set_2"), null, false); - verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mSplitsStorage).getNamesByFlagSets(Collections.singleton("set_1")); verify(mEvaluator).getTreatment(anyString(), anyString(), eq("test_1"), anyMap()); } @@ -241,7 +241,7 @@ public void getTreatmentsByFlagSetsReturnsCorrectFormat() { Set mockNames = new HashSet<>(); mockNames.add("test_1"); mockNames.add("test_2"); - when(mSplitsStorage.getNamesByFlagSets(Arrays.asList("set_1", "set_2"))).thenReturn(mockNames); + when(mSplitsStorage.getNamesByFlagSets(new HashSet<>(Arrays.asList("set_1", "set_2")))).thenReturn(mockNames); Map result = mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_1", "set_2"), null, false); @@ -254,7 +254,7 @@ public void getTreatmentsByFlagSetsReturnsCorrectFormat() { public void getTreatmentsByFlagSetsWithDuplicatedSetDeduplicates() { mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_1", "set_1"), null, false); - verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mSplitsStorage).getNamesByFlagSets(Collections.singleton("set_1")); } @Test @@ -268,7 +268,7 @@ public void getTreatmentsByFlagSetsWithNullSetListReturnsEmpty() { @Test public void getTreatmentsByFlagSetsRecordsTelemetry() { - when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(Collections.singleton("test_1")); + when(mSplitsStorage.getNamesByFlagSets(Collections.singleton("set_1"))).thenReturn(Collections.singleton("test_1")); mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_1", "set_2"), null, false); @@ -286,18 +286,18 @@ public void getTreatmentsWithConfigByFlagSetDestroyedDoesNotUseEvaluator() { @Test public void getTreatmentsWithConfigByFlagSetWithNoConfiguredSetsQueriesStorageAndUsesEvaluator() { - when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) - .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); + when(mSplitsStorage.getNamesByFlagSets(Collections.singleton("set_1"))) + .thenReturn(Collections.singleton("test_1")); mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_1", null, false); - verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mSplitsStorage).getNamesByFlagSets(Collections.singleton("set_1")); verify(mEvaluator).getTreatment(eq("matching_key"), eq("bucketing_key"), eq("test_1"), anyMap()); } @Test public void getTreatmentsWithConfigByFlagSetWithNoConfiguredSetsInvalidSetDoesNotQueryStorageNorUseEvaluator() { - when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + when(mSplitsStorage.getNamesByFlagSets(Collections.singleton("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); mTreatmentManager.getTreatmentsWithConfigByFlagSet("SET!", null, false); @@ -309,12 +309,12 @@ public void getTreatmentsWithConfigByFlagSetWithNoConfiguredSetsInvalidSetDoesNo @Test public void getTreatmentsWithConfigByFlagSetWithConfiguredSetsExistingSetQueriesStorageAndUsesEvaluator() { mFlagSetsFilter = new FlagSetsFilterImpl(Collections.singleton("set_1")); - when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + when(mSplitsStorage.getNamesByFlagSets(Collections.singleton("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_1", null, false); - verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mSplitsStorage).getNamesByFlagSets(Collections.singleton("set_1")); verify(mEvaluator).getTreatment(eq("matching_key"), eq("bucketing_key"), eq("test_1"), anyMap()); } @@ -322,7 +322,7 @@ public void getTreatmentsWithConfigByFlagSetWithConfiguredSetsExistingSetQueries public void getTreatmentsWithConfigByFlagSetWithConfiguredSetsNonExistingSetDoesNotQueryStorageNorUseEvaluator() { mFlagSetsFilter = new FlagSetsFilterImpl(Collections.singleton("set_1")); initializeTreatmentManager(); - when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + when(mSplitsStorage.getNamesByFlagSets(Collections.singleton("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_2", null, false); @@ -336,7 +336,7 @@ public void getTreatmentsWithConfigByFlagSetReturnsCorrectFormat() { Set mockNames = new HashSet<>(); mockNames.add("test_1"); mockNames.add("test_2"); - when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(mockNames); + when(mSplitsStorage.getNamesByFlagSets(Collections.singleton("set_1"))).thenReturn(mockNames); mFlagSetsFilter = new FlagSetsFilterImpl(Collections.singleton("set_1")); Map result = mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_1", null, false); @@ -348,7 +348,7 @@ public void getTreatmentsWithConfigByFlagSetReturnsCorrectFormat() { @Test public void getTreatmentsWithConfigByFlagSetRecordsTelemetry() { - when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(Collections.singleton("test_1")); + when(mSplitsStorage.getNamesByFlagSets(Collections.singleton("set_1"))).thenReturn(Collections.singleton("test_1")); mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_1", null, false); @@ -366,24 +366,24 @@ public void getTreatmentsWithConfigByFlagSetsDestroyedDoesNotUseEvaluator() { @Test public void getTreatmentsWithConfigByFlagSetsWithNoConfiguredSetsQueriesStorageAndUsesEvaluator() { - when(mSplitsStorage.getNamesByFlagSets(Arrays.asList("set_1", "set_2"))) + when(mSplitsStorage.getNamesByFlagSets(new HashSet<>(Arrays.asList("set_1", "set_2")))) .thenReturn(new HashSet<>(Arrays.asList("test_1", "test_2"))); mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_2"), null, false); - verify(mSplitsStorage).getNamesByFlagSets(Arrays.asList("set_1", "set_2")); + verify(mSplitsStorage).getNamesByFlagSets(new HashSet<>(Arrays.asList("set_1", "set_2"))); verify(mEvaluator).getTreatment(anyString(), anyString(), eq("test_1"), anyMap()); verify(mEvaluator).getTreatment(anyString(), anyString(), eq("test_2"), anyMap()); } @Test public void getTreatmentsWithConfigByFlagSetsWithNoConfiguredSetsInvalidSetDoesNotQueryStorageForInvalidSet() { - when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + when(mSplitsStorage.getNamesByFlagSets(Collections.singleton("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "SET!"), null, false); - verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mSplitsStorage).getNamesByFlagSets(Collections.singleton("set_1")); verify(mEvaluator).getTreatment(any(), any(), eq("test_1"), anyMap()); } @@ -392,12 +392,12 @@ public void getTreatmentsWithConfigByFlagSetsWithConfiguredSetsExistingSetQuerie mFlagSetsFilter = new FlagSetsFilterImpl(Collections.singleton("set_1")); initializeTreatmentManager(); - when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + when(mSplitsStorage.getNamesByFlagSets(Collections.singleton("set_1"))) .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_2"), null, false); - verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mSplitsStorage).getNamesByFlagSets(Collections.singleton("set_1")); verify(mEvaluator).getTreatment(anyString(), anyString(), eq("test_1"), anyMap()); } @@ -417,7 +417,7 @@ public void getTreatmentsWithConfigByFlagSetsReturnsCorrectFormat() { Set mockNames = new HashSet<>(); mockNames.add("test_1"); mockNames.add("test_2"); - when(mSplitsStorage.getNamesByFlagSets(Arrays.asList("set_1", "set_2"))).thenReturn(mockNames); + when(mSplitsStorage.getNamesByFlagSets(new HashSet<>(Arrays.asList("set_1", "set_2")))).thenReturn(mockNames); Map result = mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_2"), null, false); @@ -430,7 +430,7 @@ public void getTreatmentsWithConfigByFlagSetsReturnsCorrectFormat() { public void getTreatmentsWithConfigByFlagSetsWithDuplicatedSetDeduplicates() { mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_1"), null, false); - verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mSplitsStorage).getNamesByFlagSets(Collections.singleton("set_1")); } @Test @@ -444,7 +444,7 @@ public void getTreatmentsWithConfigByFlagSetsWithNullSetListReturnsEmpty() { @Test public void getTreatmentsWithConfigByFlagSetsRecordsTelemetry() { - when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(Collections.singleton("test_1")); + when(mSplitsStorage.getNamesByFlagSets(Collections.singleton("set_1"))).thenReturn(Collections.singleton("test_1")); mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_2"), null, false); diff --git a/src/test/java/io/split/android/client/validators/FlagSetsValidatorImplTest.java b/src/test/java/io/split/android/client/validators/FlagSetsValidatorImplTest.java index 72e3482db..573d74a9e 100644 --- a/src/test/java/io/split/android/client/validators/FlagSetsValidatorImplTest.java +++ b/src/test/java/io/split/android/client/validators/FlagSetsValidatorImplTest.java @@ -15,21 +15,21 @@ public class FlagSetsValidatorImplTest { @Test public void nullInputReturnsEmptyList() { - SplitFilterValidator.ValidationResult result = mValidator.cleanup(null); + SplitFilterValidator.ValidationResult result = mValidator.cleanup("method", null); assertTrue(result.getValues().isEmpty()); assertEquals(0, result.getInvalidValueCount()); } @Test public void emptyInputReturnsEmptyList() { - SplitFilterValidator.ValidationResult result = mValidator.cleanup(Collections.emptyList()); + SplitFilterValidator.ValidationResult result = mValidator.cleanup("method", Collections.emptyList()); assertTrue(result.getValues().isEmpty()); assertEquals(0, result.getInvalidValueCount()); } @Test public void duplicatedInputValuesAreRemoved() { - SplitFilterValidator.ValidationResult result = mValidator.cleanup(Arrays.asList("set1", "set1")); + SplitFilterValidator.ValidationResult result = mValidator.cleanup("method", Arrays.asList("set1", "set1")); assertEquals(1, result.getValues().size()); assertTrue(result.getValues().contains("set1")); assertEquals(0, result.getInvalidValueCount()); @@ -37,7 +37,7 @@ public void duplicatedInputValuesAreRemoved() { @Test public void valuesAreSortedAlphanumerically() { - SplitFilterValidator.ValidationResult result = mValidator.cleanup(Arrays.asList("set2", "set1", "set_1", "1set")); + SplitFilterValidator.ValidationResult result = mValidator.cleanup("method", Arrays.asList("set2", "set1", "set_1", "1set")); assertEquals(4, result.getValues().size()); assertEquals("1set", result.getValues().get(0)); assertEquals("set1", result.getValues().get(1)); @@ -48,7 +48,7 @@ public void valuesAreSortedAlphanumerically() { @Test public void invalidValuesAreRemoved() { - SplitFilterValidator.ValidationResult result = mValidator.cleanup(Arrays.asList("set1", "set2", "set_1", "set-1", "set 1", "set 2")); + SplitFilterValidator.ValidationResult result = mValidator.cleanup("method", Arrays.asList("set1", "set2", "set_1", "set-1", "set 1", "set 2")); assertEquals(3, result.getValues().size()); assertEquals("set1", result.getValues().get(0)); assertEquals("set2", result.getValues().get(1)); @@ -59,7 +59,7 @@ public void invalidValuesAreRemoved() { @Test public void setWithMoreThan50CharsIsRemoved() { String longSet = "abcdfghijklmnopqrstuvwxyz1234567890abcdfghijklmnopq"; - SplitFilterValidator.ValidationResult result = mValidator.cleanup(Arrays.asList("set1", longSet)); + SplitFilterValidator.ValidationResult result = mValidator.cleanup("method", Arrays.asList("set1", longSet)); assertEquals(51, longSet.length()); assertEquals(1, result.getValues().size()); assertEquals("set1", result.getValues().get(0)); @@ -68,7 +68,7 @@ public void setWithMoreThan50CharsIsRemoved() { @Test public void setWithLessThanOneCharIsOrEmptyRemoved() { - SplitFilterValidator.ValidationResult result = mValidator.cleanup(Arrays.asList("set1", "", " ")); + SplitFilterValidator.ValidationResult result = mValidator.cleanup("method", Arrays.asList("set1", "", " ")); assertEquals(1, result.getValues().size()); assertEquals("set1", result.getValues().get(0)); assertEquals(2, result.getInvalidValueCount()); @@ -76,7 +76,7 @@ public void setWithLessThanOneCharIsOrEmptyRemoved() { @Test public void nullSetIsRemoved() { - SplitFilterValidator.ValidationResult result = mValidator.cleanup(Arrays.asList("set1", null)); + SplitFilterValidator.ValidationResult result = mValidator.cleanup("method", Arrays.asList("set1", null)); assertEquals(1, result.getValues().size()); assertEquals("set1", result.getValues().get(0)); assertEquals(1, result.getInvalidValueCount()); @@ -84,7 +84,7 @@ public void nullSetIsRemoved() { @Test public void setWithExtraWhitespaceIsTrimmed() { - SplitFilterValidator.ValidationResult result = mValidator.cleanup(Arrays.asList("set1 ", " set2\r", "set3 ", "set 4\n")); + SplitFilterValidator.ValidationResult result = mValidator.cleanup("method", Arrays.asList("set1 ", " set2\r", "set3 ", "set 4\n")); assertEquals(3, result.getValues().size()); assertEquals("set1", result.getValues().get(0)); assertEquals("set2", result.getValues().get(1)); @@ -94,7 +94,7 @@ public void setWithExtraWhitespaceIsTrimmed() { @Test public void setsAreLowercase() { - SplitFilterValidator.ValidationResult result = mValidator.cleanup(Arrays.asList("SET1", "Set2", "SET_3")); + SplitFilterValidator.ValidationResult result = mValidator.cleanup("method", Arrays.asList("SET1", "Set2", "SET_3")); assertEquals(3, result.getValues().size()); assertEquals("set1", result.getValues().get(0)); assertEquals("set2", result.getValues().get(1)); From ee1d73c57c89afdb106d66402b9d102fb1e763bc Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 15 Sep 2023 12:10:48 -0300 Subject: [PATCH 84/89] Refactor --- .../java/io/split/android/client/SplitFactoryHelper.java | 9 +++++++++ .../java/io/split/android/client/SplitFactoryImpl.java | 7 +------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/split/android/client/SplitFactoryHelper.java b/src/main/java/io/split/android/client/SplitFactoryHelper.java index 6541a05e7..4550db1ab 100644 --- a/src/main/java/io/split/android/client/SplitFactoryHelper.java +++ b/src/main/java/io/split/android/client/SplitFactoryHelper.java @@ -427,6 +427,15 @@ Pair, String> getFilterConfiguration(SyncConf return new Pair<>(groupedFilters, splitsFilterQueryString); } + @Nullable + FlagSetsFilter getFlagSetsFilter(Map filters) { + if (filters.get(SplitFilter.Type.BY_SET) != null) { + return new FlagSetsFilterImpl(filters.get(SplitFilter.Type.BY_SET).getValues()); + } + + return null; + } + private TelemetryStorage getTelemetryStorage(boolean shouldRecordTelemetry, TelemetryStorage telemetryStorage) { if (telemetryStorage != null) { return telemetryStorage; diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index e798dcdcd..2f67145d8 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -7,10 +7,8 @@ import java.net.URISyntaxException; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import io.split.android.client.api.Key; import io.split.android.client.common.CompressionUtilProvider; @@ -180,10 +178,7 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { SplitApiFacade splitApiFacade = factoryHelper.buildApiFacade( config, defaultHttpClient, splitsFilterQueryStringFromConfig); - FlagSetsFilter flagSetsFilter = null; - if (filters.get(SplitFilter.Type.BY_SET) != null) { - flagSetsFilter = new FlagSetsFilterImpl(filters.get(SplitFilter.Type.BY_SET).getValues()); - } + FlagSetsFilter flagSetsFilter = factoryHelper.getFlagSetsFilter(filters); SplitTaskFactory splitTaskFactory = new SplitTaskFactoryImpl( config, splitApiFacade, mStorageContainer, splitsFilterQueryStringFromConfig, mEventsManagerCoordinator, From f00e6b279d26429afbf011a7bb50b34f1e12076e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Thea?= Date: Wed, 27 Sep 2023 11:14:56 -0300 Subject: [PATCH 85/89] Add filter types conflict log message (#543) --- src/main/java/io/split/android/client/FilterBuilder.java | 5 +++-- src/main/java/io/split/android/client/SplitFilter.java | 2 +- src/main/java/io/split/android/client/SyncConfig.java | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/split/android/client/FilterBuilder.java b/src/main/java/io/split/android/client/FilterBuilder.java index 88d1cd5c2..e562ef75c 100644 --- a/src/main/java/io/split/android/client/FilterBuilder.java +++ b/src/main/java/io/split/android/client/FilterBuilder.java @@ -81,6 +81,7 @@ private void addFilters(List filters) { if (filter.getType() == SplitFilter.Type.BY_SET) { // BY_SET filter has precedence over other filters, so we remove all other filters // and only add BY_SET filters + Logger.w("SDK Config: The Set filter is exclusive and cannot be used simultaneously with names or prefix filters. Ignoring names and prefixes"); if (!containsSetsFilter) { mFilters.clear(); containsSetsFilter = true; @@ -96,9 +97,9 @@ private void addFilters(List filters) { private void validateFilterSize(SplitFilter.Type type, int size) { if (size > type.maxValuesCount()) { - String message = "Error: " + type.maxValuesCount() + " different split " + type.queryStringField() + + String message = "Error: " + type.maxValuesCount() + " different feature flag " + type.queryStringField() + " can be specified at most. You passed " + size - + ". Please consider reducing the amount or using prefixes to target specific groups of splits."; + + ". Please consider reducing the amount or using prefixes to target specific groups of feature flags."; throw new IllegalArgumentException(message); } } diff --git a/src/main/java/io/split/android/client/SplitFilter.java b/src/main/java/io/split/android/client/SplitFilter.java index 38b1a0721..04a137733 100644 --- a/src/main/java/io/split/android/client/SplitFilter.java +++ b/src/main/java/io/split/android/client/SplitFilter.java @@ -51,7 +51,7 @@ public int maxValuesCount() { case BY_PREFIX: return 50; case BY_SET: - return 1000; + return 999999; default: return 0; } diff --git a/src/main/java/io/split/android/client/SyncConfig.java b/src/main/java/io/split/android/client/SyncConfig.java index 9311d757a..080703ab4 100644 --- a/src/main/java/io/split/android/client/SyncConfig.java +++ b/src/main/java/io/split/android/client/SyncConfig.java @@ -33,7 +33,7 @@ public static Builder builder() { } public static class Builder { - private List mBuilderFilters = new ArrayList<>(); + private final List mBuilderFilters = new ArrayList<>(); private int mInvalidValueCount = 0; private final SplitValidator mSplitValidator = new SplitValidatorImpl(); From f3029a61384014cbde68b1feb5be7fe46104594c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Thea?= Date: Wed, 18 Oct 2023 12:17:52 -0300 Subject: [PATCH 86/89] Default treatment in SplitView (#545) --- .../android/client/SplitManagerImpl.java | 1 + .../split/android/client/api/SplitView.java | 1 + .../localhost/LocalhostSplitClient.java | 3 +- .../TreatmentManagerFactoryImpl.java | 8 ++-- .../validators/TreatmentManagerImpl.java | 11 +++--- .../android/client/SplitManagerImplTest.java | 28 ++++++++++++++ .../client/TreatmentManagerTelemetryTest.java | 3 +- .../android/client/TreatmentManagerTest.java | 37 ++++++++++++++++--- .../TreatmentManagerWithFlagSetsTest.java | 3 +- 9 files changed, 78 insertions(+), 17 deletions(-) diff --git a/src/main/java/io/split/android/client/SplitManagerImpl.java b/src/main/java/io/split/android/client/SplitManagerImpl.java index b26ab60bd..512d8aa7e 100644 --- a/src/main/java/io/split/android/client/SplitManagerImpl.java +++ b/src/main/java/io/split/android/client/SplitManagerImpl.java @@ -143,6 +143,7 @@ private SplitView toSplitView(ParsedSplit parsedSplit) { splitView.changeNumber = parsedSplit.changeNumber(); splitView.configs = parsedSplit.configurations(); splitView.sets = new ArrayList<>(parsedSplit.sets() == null ? new HashSet<>() : parsedSplit.sets()); + splitView.defaultTreatment = parsedSplit.defaultTreatment(); Set treatments = new HashSet<>(); for (ParsedCondition condition : parsedSplit.parsedConditions()) { diff --git a/src/main/java/io/split/android/client/api/SplitView.java b/src/main/java/io/split/android/client/api/SplitView.java index 4825eb33d..9cbc7192e 100644 --- a/src/main/java/io/split/android/client/api/SplitView.java +++ b/src/main/java/io/split/android/client/api/SplitView.java @@ -20,4 +20,5 @@ public class SplitView { public Map configs; @NonNull public List sets = new ArrayList<>(); + public String defaultTreatment; } 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 c70d79420..089c70075 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java @@ -33,6 +33,7 @@ import io.split.android.client.validators.SplitValidatorImpl; import io.split.android.client.validators.TreatmentManager; import io.split.android.client.validators.TreatmentManagerImpl; +import io.split.android.client.validators.ValidationMessageLoggerImpl; import io.split.android.engine.experiments.SplitParser; import io.split.android.grammar.Treatments; @@ -72,7 +73,7 @@ public LocalhostSplitClient(@NonNull LocalhostSplitFactory container, new EvaluatorImpl(splitsStorage, splitParser), new KeyValidatorImpl(), new SplitValidatorImpl(), getImpressionsListener(splitClientConfig), splitClientConfig.labelsEnabled(), eventsManager, attributesManager, attributesMerger, - telemetryStorageProducer, flagSetsFilter, splitsStorage); + telemetryStorageProducer, flagSetsFilter, splitsStorage, new ValidationMessageLoggerImpl()); } @Override diff --git a/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java b/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java index fd4ea363f..1aa809c4e 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java @@ -5,15 +5,12 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import java.util.Set; - import io.split.android.client.Evaluator; import io.split.android.client.EvaluatorImpl; import io.split.android.client.FlagSetsFilter; import io.split.android.client.api.Key; import io.split.android.client.attributes.AttributesManager; import io.split.android.client.attributes.AttributesMerger; -import io.split.android.client.events.ISplitEventsManager; import io.split.android.client.events.ListenableEventsManager; import io.split.android.client.impressions.ImpressionListener; import io.split.android.client.storage.splits.SplitsStorage; @@ -31,6 +28,7 @@ public class TreatmentManagerFactoryImpl implements TreatmentManagerFactory { private final Evaluator mEvaluator; private final FlagSetsFilter mFlagSetsFilter; private final SplitsStorage mSplitsStorage; + private final ValidationMessageLogger mValidationMessageLogger; public TreatmentManagerFactoryImpl(@NonNull KeyValidator keyValidator, @NonNull SplitValidator splitValidator, @@ -50,6 +48,7 @@ public TreatmentManagerFactoryImpl(@NonNull KeyValidator keyValidator, mEvaluator = new EvaluatorImpl(splitsStorage, splitParser); mFlagSetsFilter = flagSetsFilter; mSplitsStorage = checkNotNull(splitsStorage); + mValidationMessageLogger = new ValidationMessageLoggerImpl(); } @Override @@ -67,7 +66,8 @@ public TreatmentManager getTreatmentManager(Key key, ListenableEventsManager eve mAttributesMerger, mTelemetryStorageProducer, mFlagSetsFilter, - mSplitsStorage + mSplitsStorage, + mValidationMessageLogger ); } } 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 8a5511467..8ac4e1343 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java @@ -75,7 +75,8 @@ public TreatmentManagerImpl(String matchingKey, @NonNull AttributesMerger attributesMerger, @NonNull TelemetryStorageProducer telemetryStorageProducer, @Nullable FlagSetsFilter flagSetsFilter, - @NonNull SplitsStorage splitsStorage) { + @NonNull SplitsStorage splitsStorage, + @NonNull ValidationMessageLogger validationLogger) { mEvaluator = evaluator; mKeyValidator = keyValidator; mSplitValidator = splitValidator; @@ -84,7 +85,7 @@ public TreatmentManagerImpl(String matchingKey, mImpressionListener = impressionListener; mLabelsEnabled = labelsEnabled; mEventsManager = eventsManager; - mValidationLogger = new ValidationMessageLoggerImpl(); + mValidationLogger = checkNotNull(validationLogger); mAttributesManager = checkNotNull(attributesManager); mAttributesMerger = checkNotNull(attributesMerger); mTelemetryStorageProducer = checkNotNull(telemetryStorageProducer); @@ -385,16 +386,16 @@ private Map controlTreatmentsForSplits(List splits, Stri return TreatmentManagerHelper.controlTreatmentsForSplits(splits, mSplitValidator, validationTag, mValidationLogger); } - private EvaluationResult evaluateIfReady(String splitName, + private EvaluationResult evaluateIfReady(String featureFlagName, Map attributes, String validationTag) { if (!mEventsManager.eventAlreadyTriggered(SplitEvent.SDK_READY) && !mEventsManager.eventAlreadyTriggered(SplitEvent.SDK_READY_FROM_CACHE)) { - mValidationLogger.w("the SDK is not ready, results may be incorrect. Make sure to wait for SDK readiness before using this method", validationTag); + mValidationLogger.w("the SDK is not ready, results may be incorrect for feature flag " + featureFlagName + ". Make sure to wait for SDK readiness before using this method", validationTag); mTelemetryStorageProducer.recordNonReadyUsage(); return new EvaluationResult(Treatments.CONTROL, TreatmentLabels.NOT_READY, null, null); } - return mEvaluator.getTreatment(mMatchingKey, mBucketingKey, splitName, attributes); + return mEvaluator.getTreatment(mMatchingKey, mBucketingKey, featureFlagName, attributes); } private void recordLatency(Method treatment, long startTime) { diff --git a/src/test/java/io/split/android/client/SplitManagerImplTest.java b/src/test/java/io/split/android/client/SplitManagerImplTest.java index 2097ba00f..8fedc46ee 100644 --- a/src/test/java/io/split/android/client/SplitManagerImplTest.java +++ b/src/test/java/io/split/android/client/SplitManagerImplTest.java @@ -34,6 +34,7 @@ import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.core.IsNull.notNullValue; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.when; @@ -158,6 +159,33 @@ public void flagSets() { assertEquals(new ArrayList<>(split.sets), splitNames.get(0).sets); } + @Test + public void defaultTreatmentIsPresent() { + Split split = SplitHelper.createSplit("FeatureName", 123, true, + "some_treatment", Lists.newArrayList(getTestCondition()), + "traffic", 456L, 1, null); + when(mSplitsStorage.get("FeatureName")).thenReturn(split); + + SplitView featureFlag = mSplitManager.split("FeatureName"); + + assertEquals("some_treatment", featureFlag.defaultTreatment); + } + + @Test + public void defaultTreatmentIsPresentWhenFetchingMultipleSplits() { + Map splitsMap = new HashMap<>(); + Split split = SplitHelper.createSplit("FeatureName", 123, true, + "some_treatment", Lists.newArrayList(getTestCondition()), + "traffic", 456L, 1, null); + splitsMap.put(split.name, split); + when(mSplitsStorage.getAll()).thenReturn(splitsMap); + + List splitNames = mSplitManager.splits(); + + assertEquals(1, splitNames.size()); + assertEquals("some_treatment", splitNames.get(0).defaultTreatment); + } + private Condition getTestCondition() { return SplitHelper.createCondition(CombiningMatcher.of(new AllKeysMatcher()), Lists.newArrayList(ConditionsTestUtil.partition("off", 10))); } diff --git a/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java b/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java index 74681726d..e3eecafbe 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java @@ -29,6 +29,7 @@ import io.split.android.client.validators.KeyValidator; import io.split.android.client.validators.SplitValidator; import io.split.android.client.validators.TreatmentManagerImpl; +import io.split.android.client.validators.ValidationMessageLoggerImpl; public class TreatmentManagerTelemetryTest { @@ -72,7 +73,7 @@ public void setUp() { attributesMerger, telemetryStorageProducer, mFlagSetsFilter, - mSplitsStorage); + mSplitsStorage, new ValidationMessageLoggerImpl()); when(evaluator.getTreatment(anyString(), anyString(), anyString(), anyMap())).thenReturn(new EvaluationResult("test", "label")); } diff --git a/src/test/java/io/split/android/client/TreatmentManagerTest.java b/src/test/java/io/split/android/client/TreatmentManagerTest.java index cd2423bf8..1a5ce153c 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerTest.java @@ -40,6 +40,8 @@ import io.split.android.client.validators.SplitValidatorImpl; import io.split.android.client.validators.TreatmentManager; import io.split.android.client.validators.TreatmentManagerImpl; +import io.split.android.client.validators.ValidationMessageLogger; +import io.split.android.client.validators.ValidationMessageLoggerImpl; import io.split.android.engine.experiments.SplitParser; import io.split.android.fake.ImpressionListenerMock; import io.split.android.fake.SplitEventsManagerStub; @@ -57,11 +59,13 @@ public class TreatmentManagerTest { private FlagSetsFilter mFlagSetsFilter; TreatmentManagerImpl treatmentManager; private SplitsStorage mSplitsStorage; + private ValidationMessageLogger mValidationMessageLogger; @Before public void loadSplitsFromFile() { mFlagSetsFilter = new FlagSetsFilterImpl(new HashSet<>()); mSplitsStorage = mock(SplitsStorage.class); + mValidationMessageLogger = mock(ValidationMessageLogger.class); treatmentManager = initializeTreatmentManager(); if (evaluator == null) { FileHelper fileHelper = new FileHelper(); @@ -168,7 +172,7 @@ public void testNonExistingSplits() { } @Test - public void testEmtpySplit() { + public void testEmptySplit() { String matchingKey = "nico_test"; String splitName = ""; List splitList = new ArrayList<>(); @@ -293,6 +297,24 @@ public void getTreatmentsWithConfigTakesValuesFromAttributesManagerIntoAccount() verify(attributesManager).getAllAttributes(); } + @Test + public void evaluationWhenNotReadyLogsCorrectMessage() { + ValidationMessageLogger validationMessageLogger = mock(ValidationMessageLogger.class); + SplitValidator splitValidator = mock(SplitValidator.class); + Evaluator evaluatorMock = mock(Evaluator.class); + ListenableEventsManager eventsManager = mock(ListenableEventsManager.class); + when(evaluatorMock.getTreatment(eq("my_key"), eq(null), eq("test_split"), anyMap())) + .thenReturn(new EvaluationResult("test", "test")); + when(splitValidator.validateName(any())).thenReturn(null); + when(splitValidator.splitNotFoundMessage(any())).thenReturn(null); + when(eventsManager.eventAlreadyTriggered(SplitEvent.SDK_READY)).thenReturn(false); + when(eventsManager.eventAlreadyTriggered(SplitEvent.SDK_READY_FROM_CACHE)).thenReturn(false); + createTreatmentManager("my_key", null, validationMessageLogger, splitValidator, evaluatorMock, eventsManager) + .getTreatment("test_split", null, false); + + verify(validationMessageLogger).w(eq("the SDK is not ready, results may be incorrect for feature flag test_split. Make sure to wait for SDK readiness before using this method"), any()); + } + private void assertControl(List splitList, String treatment, Map treatmentList, SplitResult splitResult, Map splitResultList) { Assert.assertNotNull(treatment); Assert.assertEquals(Treatments.CONTROL, treatment); @@ -318,14 +340,18 @@ private void assertControl(List splitList, String treatment, Map splitsMap(List splits) { diff --git a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java index dc1f4efc5..b732f466e 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java @@ -33,6 +33,7 @@ import io.split.android.client.validators.KeyValidator; import io.split.android.client.validators.SplitValidator; import io.split.android.client.validators.TreatmentManagerImpl; +import io.split.android.client.validators.ValidationMessageLoggerImpl; public class TreatmentManagerWithFlagSetsTest { @@ -153,7 +154,7 @@ private void initializeTreatmentManager() { mAttributesMerger, mTelemetryStorageProducer, mFlagSetsFilter, - mSplitsStorage); + mSplitsStorage, new ValidationMessageLoggerImpl()); } @Test From 2d1147d8961e9e5c08604f9c2cc90d3abea5915d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Thea?= Date: Wed, 18 Oct 2023 12:18:16 -0300 Subject: [PATCH 87/89] Fix sets counters for telemetry (#546) --- .../tests/integration/streaming/ControlTest.java | 14 ++++++-------- .../telemetry/TelemetryIntegrationTest.java | 5 ++--- .../java/io/split/android/client/SplitFilter.java | 9 +++++++++ .../java/io/split/android/client/SyncConfig.java | 14 +++++++++++--- .../service/executor/SplitTaskFactoryImpl.java | 11 ++++++----- .../client/validators/FlagSetsValidatorImpl.java | 5 ++++- .../validators/FlagSetsValidatorImplTest.java | 2 +- 7 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/androidTest/java/tests/integration/streaming/ControlTest.java b/src/androidTest/java/tests/integration/streaming/ControlTest.java index b817e20ed..200fb5138 100644 --- a/src/androidTest/java/tests/integration/streaming/ControlTest.java +++ b/src/androidTest/java/tests/integration/streaming/ControlTest.java @@ -92,7 +92,8 @@ public void controlNotification() throws IOException, InterruptedException { CountDownLatch readyLatch = new CountDownLatch(1); - TestingHelper.TestEventTask updateTask = TestingHelper.testTask(new CountDownLatch(1), "CONTROL notif update task"); + CountDownLatch updateLatch = new CountDownLatch(3); + SplitEventTaskHelper updateTask = new SplitEventTaskHelper(updateLatch); HttpClientMock httpClientMock = new HttpClientMock(createBasicResponseDispatcher()); @@ -133,19 +134,17 @@ public void controlNotification() throws IOException, InterruptedException { pushControl("STREAMING_RESUMED"); synchronizerSpy.stopPeriodicFetchLatch.await(10, TimeUnit.SECONDS); - updateTask.mLatch = new CountDownLatch(1); pushMySegmentsUpdatePayload("new_segment"); - updateTask.mLatch.await(10, TimeUnit.SECONDS); + updateLatch.await(10, TimeUnit.SECONDS); String treatmentEnabled = mClient.getTreatment(splitName); //Enable streaming, push a new my segments payload update and check data again - updateTask.mLatch = new CountDownLatch(1); + updateLatch = new CountDownLatch(1); pushControl("STREAMING_DISABLED"); - updateTask.mLatch.await(5, TimeUnit.SECONDS); + updateLatch.await(5, TimeUnit.SECONDS); pushMySegmentsUpdatePayload("new_segment"); - sleep(1000); - + updateLatch.await(5, TimeUnit.SECONDS); String treatmentDisabled = mClient.getTreatment(splitName); assertTrue(telemetryStorage.popStreamingEvents().stream().anyMatch(event -> { @@ -154,7 +153,6 @@ public void controlNotification() throws IOException, InterruptedException { } return false; })); - assertEquals(1, telemetryStorage.popTokenRefreshes()); Assert.assertEquals("on", treatmentReady); Assert.assertEquals("on", treatmentPaused); Assert.assertEquals("free", treatmentEnabled); diff --git a/src/androidTest/java/tests/integration/telemetry/TelemetryIntegrationTest.java b/src/androidTest/java/tests/integration/telemetry/TelemetryIntegrationTest.java index 62cb20e86..967ca15bc 100644 --- a/src/androidTest/java/tests/integration/telemetry/TelemetryIntegrationTest.java +++ b/src/androidTest/java/tests/integration/telemetry/TelemetryIntegrationTest.java @@ -45,7 +45,6 @@ import io.split.android.client.telemetry.model.MethodLatencies; import io.split.android.client.telemetry.storage.TelemetryStorage; import io.split.android.client.utils.Json; -import io.split.android.client.utils.logger.Logger; import okhttp3.mockwebserver.Dispatcher; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; @@ -295,8 +294,8 @@ public MockResponse dispatch(RecordedRequest request) { initializeClient(false, "a", "_b", "a", "a", "c", "d", "_d"); metricsLatch.await(20, TimeUnit.SECONDS); String s = metricsPayload.get(); - assertTrue(s.contains("\"fsT\":5")); - assertTrue(s.contains("\"fsI\":2")); + assertTrue(s.contains("\"fsI\":4")); + assertTrue(s.contains("\"fsT\":7")); } private void initializeClient(boolean streamingEnabled, String ... sets) { diff --git a/src/main/java/io/split/android/client/SplitFilter.java b/src/main/java/io/split/android/client/SplitFilter.java index 04a137733..b775ff4bd 100644 --- a/src/main/java/io/split/android/client/SplitFilter.java +++ b/src/main/java/io/split/android/client/SplitFilter.java @@ -61,6 +61,7 @@ public int maxValuesCount() { private final SplitFilter.Type mType; private final List mValues; private int mInvalidValueCount; + private int mTotalValueCount; public static SplitFilter byName(@NonNull List values) { return new SplitFilter(Type.BY_NAME, values); @@ -71,6 +72,9 @@ public static SplitFilter byPrefix(@NonNull List values) { } public static SplitFilter bySet(@NonNull List values) { + if (values == null) { + values = new ArrayList<>(); + } return new SplitFilter(Type.BY_SET, values, new FlagSetsValidatorImpl()); } @@ -89,6 +93,7 @@ public static SplitFilter bySet(@NonNull List values) { SplitFilterValidator.ValidationResult validationResult = validator.cleanup("SDK config", values); mValues = validationResult.getValues(); mInvalidValueCount = validationResult.getInvalidValueCount(); + mTotalValueCount = (values != null) ? values.size() - validationResult.getInvalidValueCount() : 0; } public Type getType() { @@ -102,4 +107,8 @@ public List getValues() { public int getInvalidValueCount() { return mInvalidValueCount; } + + public int getTotalValueCount() { + return mTotalValueCount; + } } diff --git a/src/main/java/io/split/android/client/SyncConfig.java b/src/main/java/io/split/android/client/SyncConfig.java index 080703ab4..f02b62d12 100644 --- a/src/main/java/io/split/android/client/SyncConfig.java +++ b/src/main/java/io/split/android/client/SyncConfig.java @@ -13,11 +13,13 @@ public class SyncConfig { private final List mFilters; - private int mInvalidValueCount = 0; + private final int mInvalidValueCount; + private final int mTotalValueCount; - private SyncConfig(List filters, int invalidValueCount) { + private SyncConfig(List filters, int invalidValueCount, int totalValueCount) { mFilters = filters; mInvalidValueCount = invalidValueCount; + mTotalValueCount = totalValueCount; } public List getFilters() { @@ -28,6 +30,10 @@ public int getInvalidValueCount() { return mInvalidValueCount; } + public int getTotalValueCount() { + return mTotalValueCount; + } + public static Builder builder() { return new Builder(); } @@ -35,6 +41,7 @@ public static Builder builder() { public static class Builder { private final List mBuilderFilters = new ArrayList<>(); private int mInvalidValueCount = 0; + private int mTotalValueCount = 0; private final SplitValidator mSplitValidator = new SplitValidatorImpl(); public SyncConfig build() { @@ -53,7 +60,7 @@ public SyncConfig build() { validatedFilters.add(new SplitFilter(filter.getType(), validatedValues)); } } - return new SyncConfig(validatedFilters, mInvalidValueCount); + return new SyncConfig(validatedFilters, mInvalidValueCount, mTotalValueCount); } public Builder addSplitFilter(@NonNull SplitFilter filter) { @@ -62,6 +69,7 @@ public Builder addSplitFilter(@NonNull SplitFilter filter) { } mBuilderFilters.add(filter); mInvalidValueCount += filter.getInvalidValueCount(); + mTotalValueCount += filter.getTotalValueCount(); return this; } } diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java index dde6c846c..be036dd40 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java @@ -200,14 +200,15 @@ public SplitInPlaceUpdateTask createSplitsUpdateTask(Split featureFlag, long sin @NonNull private TelemetryTaskFactory initializeTelemetryTaskFactory(@NonNull SplitClientConfig splitClientConfig, @Nullable Map filters, TelemetryStorage telemetryStorage) { final TelemetryTaskFactory mTelemetryTaskFactory; - int flagSetCount = 0; int invalidFlagSetCount = 0; + int totalFlagSetCount = 0; if (filters != null && !filters.isEmpty()) { SplitFilter bySetFilter = filters.get(SplitFilter.Type.BY_SET); if (bySetFilter != null) { - flagSetCount = bySetFilter.getValues().size(); - invalidFlagSetCount = (splitClientConfig.syncConfig() != null) ? - splitClientConfig.syncConfig().getInvalidValueCount() : 0; + if (splitClientConfig.syncConfig() != null) { + invalidFlagSetCount = splitClientConfig.syncConfig().getInvalidValueCount(); + totalFlagSetCount = splitClientConfig.syncConfig().getTotalValueCount(); + } } } @@ -217,7 +218,7 @@ private TelemetryTaskFactory initializeTelemetryTaskFactory(@NonNull SplitClient splitClientConfig, mSplitsStorageContainer.getSplitsStorage(), mSplitsStorageContainer.getMySegmentsStorageContainer(), - flagSetCount, + totalFlagSetCount, invalidFlagSetCount); return mTelemetryTaskFactory; } diff --git a/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java b/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java index a9dbcb6b5..5553eaa37 100644 --- a/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java +++ b/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java @@ -47,7 +47,10 @@ public ValidationResult cleanup(String method, List values) { } if (set.matches(FLAG_SET_REGEX)) { - cleanedUpSets.add(set); + if (!cleanedUpSets.add(set)) { + Logger.w(method + ": you passed duplicated Flag Set. " + set + " was deduplicated"); + invalidValueCount++; + } } else { invalidValueCount++; Logger.w(method + ": you passed "+ set +", Flag Set must adhere to the regular expressions "+ FLAG_SET_REGEX +". This means a Flag Set must be start with a letter, be in lowercase, alphanumeric and have a max length of 50 characters. "+ set +" was discarded."); diff --git a/src/test/java/io/split/android/client/validators/FlagSetsValidatorImplTest.java b/src/test/java/io/split/android/client/validators/FlagSetsValidatorImplTest.java index 573d74a9e..075317f1d 100644 --- a/src/test/java/io/split/android/client/validators/FlagSetsValidatorImplTest.java +++ b/src/test/java/io/split/android/client/validators/FlagSetsValidatorImplTest.java @@ -32,7 +32,7 @@ public void duplicatedInputValuesAreRemoved() { SplitFilterValidator.ValidationResult result = mValidator.cleanup("method", Arrays.asList("set1", "set1")); assertEquals(1, result.getValues().size()); assertTrue(result.getValues().contains("set1")); - assertEquals(0, result.getInvalidValueCount()); + assertEquals(1, result.getInvalidValueCount()); } @Test From b67182638c5c0accf565c47925cafe555cda6ac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Thea?= Date: Fri, 27 Oct 2023 18:20:09 -0300 Subject: [PATCH 88/89] Post review changes (#548) --- .../android/client/FlagSetsFilterImpl.java | 10 +++------- .../splits/FeatureFlagProcessStrategy.java | 17 +---------------- .../validators/FlagSetsValidatorImpl.java | 14 +++++++++++++- .../client/validators/SplitFilterValidator.java | 2 +- .../client/validators/TreatmentManagerImpl.java | 12 ++++++------ .../TreatmentManagerWithFlagSetsTest.java | 6 +++--- .../splits/SplitChangeProcessorTest.java | 8 ++++---- 7 files changed, 31 insertions(+), 38 deletions(-) diff --git a/src/main/java/io/split/android/client/FlagSetsFilterImpl.java b/src/main/java/io/split/android/client/FlagSetsFilterImpl.java index d8c731280..68c8d5501 100644 --- a/src/main/java/io/split/android/client/FlagSetsFilterImpl.java +++ b/src/main/java/io/split/android/client/FlagSetsFilterImpl.java @@ -1,5 +1,7 @@ package io.split.android.client; +import com.google.common.collect.Sets; + import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -24,13 +26,7 @@ public boolean intersect(Set sets) { return false; } - for (String set : sets) { - if (mFlagSets.contains(set)) { - return true; - } - } - - return false; + return !Sets.intersection(mFlagSets, sets).isEmpty(); } @Override diff --git a/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java b/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java index 1cffb6669..8628203a2 100644 --- a/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java +++ b/src/main/java/io/split/android/client/service/splits/FeatureFlagProcessStrategy.java @@ -2,9 +2,7 @@ import androidx.annotation.NonNull; -import java.util.HashSet; import java.util.List; -import java.util.Set; import io.split.android.client.FlagSetsFilter; import io.split.android.client.dtos.Split; @@ -64,22 +62,9 @@ public void process(List activeFeatureFlags, List archivedFeatureF return; } - boolean shouldArchive = true; - Set newSets = new HashSet<>(); - for (String set : featureFlag.sets) { - if (mFlagSetsFilter.intersect(set)) { - newSets.add(set); // Add the flag set to the valid group - // Since the feature flag has at least one set that matches the configured sets, - // we process it according to its status - shouldArchive = false; - } - } - - if (shouldArchive) { + if (!mFlagSetsFilter.intersect(featureFlag.sets)) { archivedFeatureFlags.add(featureFlag); } else { - // Replace the feature flag sets with the intersection of the configured sets and the feature flag sets - featureFlag.sets = newSets; mStatusProcessStrategy.process(activeFeatureFlags, archivedFeatureFlags, featureFlag); } } diff --git a/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java b/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java index 5553eaa37..57642a6ec 100644 --- a/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java +++ b/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java @@ -66,7 +66,7 @@ public boolean isValid(String value) { } @Override - public Set items(List values, FlagSetsFilter flagSetsFilter) { + public Set items(String method, List values, FlagSetsFilter flagSetsFilter) { Set setsToReturn = new HashSet<>(); if (values == null || values.isEmpty()) { @@ -74,11 +74,23 @@ public Set items(List values, FlagSetsFilter flagSetsFilter) { } for (String flagSet : values) { + if (flagSet.trim().length() != flagSet.length()) { + Logger.w(method + ": Flag Set name " + flagSet + " has extra whitespace, trimming"); + flagSet = flagSet.trim(); + } + + if (!flagSet.toLowerCase().equals(flagSet)) { + Logger.w(method + ": Flag Set name "+flagSet+" should be all lowercase - converting string to lowercase"); + flagSet = flagSet.toLowerCase(); + } + if (!isValid(flagSet)) { + Logger.w(method + ": you passed "+ flagSet +", Flag Set must adhere to the regular expressions "+ FLAG_SET_REGEX +". This means a Flag Set must be start with a letter, be in lowercase, alphanumeric and have a max length of 50 characters. "+ flagSet +" was discarded."); continue; } if (flagSetsFilter != null && !flagSetsFilter.intersect(flagSet)) { + Logger.w(method + ": you passed Flag Set: "+ flagSet +" and is not part of the configured Flag set list, ignoring the request."); continue; } diff --git a/src/main/java/io/split/android/client/validators/SplitFilterValidator.java b/src/main/java/io/split/android/client/validators/SplitFilterValidator.java index b5f0d1b2b..1e3255b84 100644 --- a/src/main/java/io/split/android/client/validators/SplitFilterValidator.java +++ b/src/main/java/io/split/android/client/validators/SplitFilterValidator.java @@ -11,7 +11,7 @@ public interface SplitFilterValidator { boolean isValid(String value); - Set items(List values, FlagSetsFilter flagSetsFilter); + Set items(String method, List values, FlagSetsFilter flagSetsFilter); class ValidationResult { 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 8ac4e1343..05469a2bf 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java @@ -187,11 +187,11 @@ public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Null String validationTag = ValidationTag.GET_TREATMENTS_BY_FLAG_SET; Set names = new HashSet<>(); try { - names = getNamesFromSet(Collections.singletonList(flagSet)); if (isClientDestroyed) { mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); return controlTreatmentsForSplits(new ArrayList<>(names), validationTag); } + names = getNamesFromSet("getTreatmentsByFlagSet", Collections.singletonList(flagSet)); long start = System.currentTimeMillis(); try { @@ -212,11 +212,11 @@ public Map getTreatmentsByFlagSets(@NonNull List flagSet String validationTag = ValidationTag.GET_TREATMENTS_BY_FLAG_SETS; Set names = new HashSet<>(); try { - names = getNamesFromSet(flagSets); if (isClientDestroyed) { mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); return controlTreatmentsForSplits(new ArrayList<>(names), validationTag); } + names = getNamesFromSet("getTreatmentsByFlagSets", flagSets); long start = System.currentTimeMillis(); try { @@ -237,7 +237,7 @@ public Map getTreatmentsWithConfigByFlagSet(@NonNull String String validationTag = ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET; Set names = new HashSet<>(); try { - names = getNamesFromSet(Collections.singletonList(flagSet)); + names = getNamesFromSet("getTreatmentsWithConfigByFlagSet", Collections.singletonList(flagSet)); if (isClientDestroyed) { mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); return controlTreatmentsForSplitsWithConfig(new ArrayList<>(names), validationTag); @@ -262,11 +262,11 @@ public Map getTreatmentsWithConfigByFlagSets(@NonNull List< String validationTag = ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS; Set names = new HashSet<>(); try { - names = getNamesFromSet(flagSets); if (isClientDestroyed) { mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); return controlTreatmentsForSplitsWithConfig(new ArrayList<>(names), validationTag); } + names = getNamesFromSet("getTreatmentsWithConfigByFlagSets", flagSets); long start = System.currentTimeMillis(); try { @@ -403,9 +403,9 @@ private void recordLatency(Method treatment, long startTime) { } @NonNull - private Set getNamesFromSet(@NonNull List flagSets) { + private Set getNamesFromSet(@NonNull String method, @NonNull List flagSets) { - Set setsToEvaluate = mFlagSetsValidator.items(flagSets, mFlagSetsFilter); + Set setsToEvaluate = mFlagSetsValidator.items(method, flagSets, mFlagSetsFilter); if (setsToEvaluate.isEmpty()) { return new HashSet<>(); diff --git a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java index b732f466e..88d6ffbfa 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java @@ -89,7 +89,7 @@ public void tearDown() { public void getTreatmentsByFlagSetDestroyedDoesNotUseEvaluator() { mTreatmentManager.getTreatmentsByFlagSet("set_1", null, true); - verify(mSplitsStorage).getNamesByFlagSets(any()); + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); } @@ -186,7 +186,7 @@ public void getTreatmentsByFlagSetRecordsTelemetry() { public void getTreatmentsByFlagSetsDestroyedDoesNotUseEvaluator() { mTreatmentManager.getTreatmentsByFlagSets(Collections.singletonList("set_1"), null, true); - verify(mSplitsStorage).getNamesByFlagSets(any()); + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); } @@ -361,7 +361,7 @@ public void getTreatmentsWithConfigByFlagSetRecordsTelemetry() { public void getTreatmentsWithConfigByFlagSetsDestroyedDoesNotUseEvaluator() { mTreatmentManager.getTreatmentsWithConfigByFlagSets(Collections.singletonList("set_1"), null, true); - verify(mSplitsStorage).getNamesByFlagSets(any()); + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); } diff --git a/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java b/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java index 191896074..66ca13210 100644 --- a/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java +++ b/src/test/java/io/split/android/client/service/splits/SplitChangeProcessorTest.java @@ -246,7 +246,7 @@ public void creatingWithFilterWithEmptyConfiguredValuesProcessesEverything() { } @Test - public void nonConfiguredSetsAreRemovedFromSplit() { + public void nonConfiguredSetsAreNotRemovedFromSplit() { Set configuredSets = new HashSet<>(); configuredSets.add("set_1"); configuredSets.add("set_2"); @@ -270,14 +270,14 @@ public void nonConfiguredSetsAreRemovedFromSplit() { Split processedSplit1 = result.getActiveSplits().get(0); Assert.assertEquals(split1.name, processedSplit1.name); Assert.assertEquals(2, initialSplit1Sets); - Assert.assertEquals(1, processedSplit1.sets.size()); + Assert.assertEquals(2, processedSplit1.sets.size()); Assert.assertTrue(processedSplit1.sets.contains("set_1")); - Assert.assertFalse(processedSplit1.sets.contains("set_3")); + Assert.assertTrue(processedSplit1.sets.contains("set_3")); Split processedSplit2 = result.getActiveSplits().get(1); Assert.assertEquals(split2.name, processedSplit2.name); Assert.assertEquals(2, initialSplit2Sets); - Assert.assertEquals(1, processedSplit2.sets.size()); + Assert.assertEquals(2, processedSplit2.sets.size()); Assert.assertTrue(processedSplit2.sets.contains("set_2")); } From 98b7069b5782242b4f6f841b2c6601e7fa65ccde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Thea?= Date: Mon, 30 Oct 2023 11:19:53 -0300 Subject: [PATCH 89/89] Update version (#549) --- .github/workflows/deploy.yml | 1 - build.gradle | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9ffc78efc..fa1639912 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -4,7 +4,6 @@ on: push: branches: - development - - SDKS-7440 jobs: build-app: diff --git a/build.gradle b/build.gradle index a4a2c5adc..78ec8cda1 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ apply plugin: 'signing' apply plugin: 'kotlin-android' ext { - splitVersion = '3.3.1-alpha-2' + splitVersion = '3.4.0-alpha-1' } android {