Refactor FeatureFlags.

This commit is contained in:
Greyson Parrelli
2024-06-12 13:57:07 -04:00
parent 39cb1c638e
commit 13f7a64139
76 changed files with 1059 additions and 1053 deletions

View File

@@ -1,80 +0,0 @@
package org.thoughtcrime.securesms.util;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
import org.junit.Test;
import org.signal.core.util.SetUtil;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public final class FeatureFlags_ConsistencyTest {
/**
* Ensures developer makes decision on whether a flag should or should not be remote capable.
*/
@Test
public void no_flags_are_in_both_lists() {
Set<String> intersection = SetUtil.intersection(FeatureFlags.REMOTE_CAPABLE,
FeatureFlags.NOT_REMOTE_CAPABLE);
assertTrue(intersection.isEmpty());
}
/**
* Ensures developer makes decision on whether a flag should or should not be remote capable.
*/
@Test
public void all_flags_are_in_one_list_or_another() {
Set<String> flagsByReflection = Stream.of(FeatureFlags.class.getDeclaredFields())
.filter(f -> f.getType() == String.class)
.filter(f -> !f.getName().equals("TAG"))
.map(f -> {
try {
f.setAccessible(true);
return (String) f.get(null);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
})
.collect(Collectors.toSet());
Set<String> flagsInBothSets = SetUtil.union(FeatureFlags.REMOTE_CAPABLE,
FeatureFlags.NOT_REMOTE_CAPABLE);
assertEquals(flagsInBothSets, flagsByReflection);
}
/**
* Ensures we don't leave old feature flag values in the hot swap list.
*/
@Test
public void all_hot_swap_values_are_defined_capable_or_not() {
Set<String> flagsInBothSets = SetUtil.union(FeatureFlags.REMOTE_CAPABLE,
FeatureFlags.NOT_REMOTE_CAPABLE);
assertTrue(flagsInBothSets.containsAll(FeatureFlags.HOT_SWAPPABLE));
}
/**
* Ensures we don't leave old feature flag values in the sticky list.
*/
@Test
public void all_sticky_values_are_defined_capable_or_not() {
Set<String> flagsInBothSets = SetUtil.union(FeatureFlags.REMOTE_CAPABLE,
FeatureFlags.NOT_REMOTE_CAPABLE);
assertTrue(flagsInBothSets.containsAll(FeatureFlags.STICKY));
}
/**
* Ensures we don't release with forced values which is intended for local development only.
*/
@Test
public void no_values_are_forced() {
assertTrue(FeatureFlags.FORCED_VALUES.isEmpty());
}
}

View File

@@ -0,0 +1,79 @@
package org.thoughtcrime.securesms.util
import org.junit.Test
import kotlin.reflect.KProperty1
import kotlin.reflect.KVisibility
import kotlin.reflect.full.memberProperties
/**
* Ensures we don't release with forced values which is intended for local development only.
*/
class FeatureFlags_StaticValuesTest {
/**
* This test cycles the REMOTE_VALUES through a bunch of different inputs, then looks at all of the public getters and checks to see if they return different
* values when the inputs change. If they don't, then it's likely that the getter is returning a static value, which was likely introduced during testing
* and not something we actually want to commit.
*/
@Test
fun `Ensure there's no static values`() {
// A list of inputs we'll cycle the remote config values to in order to see if it changes the outputs of the getters
val remoteTestInputs = listOf(
true,
false,
"true",
"false",
"cat",
"dog",
"1",
"100",
"12345678910111213141516",
"*"
)
val configKeys = FeatureFlags.configsByKey.keys
val ignoreList = setOf(
"REMOTE_VALUES",
"configsByKey",
"debugMemoryValues",
"debugDiskValues",
"debugPendingDiskValues",
"CRASH_PROMPT_CONFIG",
"PROMPT_BATTERY_SAVER",
"PROMPT_FOR_NOTIFICATION_LOGS"
)
val publicVals: List<KProperty1<*, *>> = FeatureFlags::class.memberProperties
.filter { it.visibility == KVisibility.PUBLIC }
.filterNot { ignoreList.contains(it.name) }
val publicValOutputs: MutableMap<String, MutableSet<Any?>> = mutableMapOf()
for (input in remoteTestInputs) {
for (key in configKeys) {
FeatureFlags.REMOTE_VALUES[key] = input
}
for (publicVal in publicVals) {
val output: Any? = publicVal.getter.call(FeatureFlags)
val existingOutputs: MutableSet<Any?> = publicValOutputs.getOrDefault(publicVal.name, mutableSetOf())
existingOutputs.add(output)
publicValOutputs[publicVal.name] = existingOutputs
}
}
for (entry in publicValOutputs) {
val getter = entry.key
val outputs = entry.value
if (outputs.size == 0) {
throw AssertionError("Getter $getter has no outputs! Something is wrong.")
}
if (outputs.size == 1) {
throw AssertionError("Getter '$getter' had the same output every time (value = ${outputs.first()})! Did you accidentally set it to a constant? Or, if you think this is a mistake, add a value to the inputs of this test that would case the value to change.")
}
}
}
}