Skip to content
Permalink
Browse files

Issue #14: Add mechanism for overriding the local experiment configur…

…ation

Closes #14: Add mechanism for overriding the local experiment configuration
  • Loading branch information...
fercarcedo committed Jun 22, 2018
1 parent 267c633 commit 1f745bcfa8d879145f4514bf431ef9f5eb3978af
@@ -32,4 +32,18 @@ data class Experiment(
val max: Int? = null,
val min: Int? = null
)

override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other == null || other !is Experiment) {
return false
}
return other.id == id
}

override fun hashCode(): Int {
return id.hashCode()
}
}
@@ -18,10 +18,9 @@ internal class ExperimentEvaluator(private val regionProvider: RegionProvider? =
userBucket: Int = getUserBucket(context)
): Experiment? {
val experiment = getExperiment(experimentDescriptor, experiments) ?: return null
return if (isInBucket(userBucket, experiment) && matches(context, experiment)) {
experiment
} else {
null
val isEnabled = isInBucket(userBucket, experiment) && matches(context, experiment)
context.getSharedPreferences(OVERRIDES_PREF_NAME, Context.MODE_PRIVATE).let {
return if (it.getBoolean(experimentDescriptor.id, isEnabled)) experiment else null
}
}

@@ -69,7 +68,29 @@ internal class ExperimentEvaluator(private val regionProvider: RegionProvider? =
return (checksum % MAX_BUCKET).toInt()
}

fun setOverride(context: Context, descriptor: ExperimentDescriptor, active: Boolean) {
context.getSharedPreferences(OVERRIDES_PREF_NAME, Context.MODE_PRIVATE)
.edit()
.putBoolean(descriptor.id, active)
.apply()
}

fun clearOverride(context: Context, descriptor: ExperimentDescriptor) {
context.getSharedPreferences(OVERRIDES_PREF_NAME, Context.MODE_PRIVATE)
.edit()
.remove(descriptor.id)
.apply()
}

fun clearAllOverrides(context: Context) {
context.getSharedPreferences(OVERRIDES_PREF_NAME, Context.MODE_PRIVATE)
.edit()
.clear()
.apply()
}

companion object {
private const val MAX_BUCKET = 100L
private const val OVERRIDES_PREF_NAME = "mozilla.components.service.fretboard.overrides"
}
}
@@ -82,4 +82,32 @@ class Fretboard(
fun getExperiment(descriptor: ExperimentDescriptor): Experiment? {
return evaluator.getExperiment(descriptor, experiments)
}

/**
* Overrides a specified experiment
*
* @param descriptor descriptor of the experiment
* @param active overridden value for the experiment, true to activate it, false to deactivate
*/
fun setOverride(context: Context, descriptor: ExperimentDescriptor, active: Boolean) {
evaluator.setOverride(context, descriptor, active)
}

/**
* Clears an override for a specified experiment
*
* @param descriptor descriptor of the experiment
*/
fun clearOverride(context: Context, descriptor: ExperimentDescriptor) {
evaluator.clearOverride(context, descriptor)
}

/**
* Clears all experiment overrides
*
* @param context context
*/
fun clearAllOverrides(context: Context) {
evaluator.clearAllOverrides(context)
}
}
@@ -5,16 +5,20 @@
package mozilla.components.service.fretboard

import android.content.Context
import android.content.SharedPreferences
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
@@ -42,6 +46,9 @@ class ExperimentEvaluatorTest {

val context = mock(Context::class.java)
`when`(context.packageName).thenReturn("test.appId")
val sharedPreferences = mock(SharedPreferences::class.java)
`when`(sharedPreferences.getBoolean(eq("testid"), anyBoolean())).thenAnswer { invocation -> invocation.arguments[1] as Boolean }
`when`(context.getSharedPreferences(anyString(), eq(Context.MODE_PRIVATE))).thenReturn(sharedPreferences)
val packageManager = mock(PackageManager::class.java)
val packageInfo = PackageInfo()
packageInfo.versionName = "test.version"
@@ -80,6 +87,9 @@ class ExperimentEvaluatorTest {

val context = mock(Context::class.java)
`when`(context.packageName).thenReturn("other.appId")
val sharedPreferences = mock(SharedPreferences::class.java)
`when`(sharedPreferences.getBoolean(eq("testid"), anyBoolean())).thenAnswer { invocation -> invocation.arguments[1] as Boolean }
`when`(context.getSharedPreferences(anyString(), eq(Context.MODE_PRIVATE))).thenReturn(sharedPreferences)
val packageManager = mock(PackageManager::class.java)
val packageInfo = PackageInfo()
packageInfo.versionName = "test.version"
@@ -115,6 +125,9 @@ class ExperimentEvaluatorTest {

val context = mock(Context::class.java)
`when`(context.packageName).thenReturn("test.appId")
val sharedPreferences = mock(SharedPreferences::class.java)
`when`(sharedPreferences.getBoolean(eq("testid"), anyBoolean())).thenAnswer { invocation -> invocation.arguments[1] as Boolean }
`when`(context.getSharedPreferences(anyString(), eq(Context.MODE_PRIVATE))).thenReturn(sharedPreferences)
val packageManager = mock(PackageManager::class.java)
val packageInfo = PackageInfo()
packageInfo.versionName = "test.version"
@@ -167,6 +180,9 @@ class ExperimentEvaluatorTest {

val context = mock(Context::class.java)
`when`(context.packageName).thenReturn("test.appId")
val sharedPreferences = mock(SharedPreferences::class.java)
`when`(sharedPreferences.getBoolean(eq("testid"), anyBoolean())).thenAnswer { invocation -> invocation.arguments[1] as Boolean }
`when`(context.getSharedPreferences(anyString(), eq(Context.MODE_PRIVATE))).thenReturn(sharedPreferences)
val packageManager = mock(PackageManager::class.java)
val packageInfo = PackageInfo()
packageInfo.versionName = "test.version"
@@ -221,6 +237,9 @@ class ExperimentEvaluatorTest {

val context = mock(Context::class.java)
`when`(context.packageName).thenReturn("test.appId")
val sharedPreferences = mock(SharedPreferences::class.java)
`when`(sharedPreferences.getBoolean(eq("testid"), anyBoolean())).thenAnswer { invocation -> invocation.arguments[1] as Boolean }
`when`(context.getSharedPreferences(anyString(), eq(Context.MODE_PRIVATE))).thenReturn(sharedPreferences)
val packageManager = mock(PackageManager::class.java)
val packageInfo = PackageInfo()
packageInfo.versionName = "test.version"
@@ -257,6 +276,9 @@ class ExperimentEvaluatorTest {

val context = mock(Context::class.java)
`when`(context.packageName).thenReturn("test.appId")
val sharedPreferences = mock(SharedPreferences::class.java)
`when`(sharedPreferences.getBoolean(eq("testid"), anyBoolean())).thenAnswer { invocation -> invocation.arguments[1] as Boolean }
`when`(context.getSharedPreferences(anyString(), eq(Context.MODE_PRIVATE))).thenReturn(sharedPreferences)
val packageManager = mock(PackageManager::class.java)
val packageInfo = PackageInfo()
packageInfo.versionName = "test.version"
@@ -311,6 +333,9 @@ class ExperimentEvaluatorTest {

val context = mock(Context::class.java)
`when`(context.packageName).thenReturn("test.appId")
val sharedPreferences = mock(SharedPreferences::class.java)
`when`(sharedPreferences.getBoolean(eq("testid"), anyBoolean())).thenAnswer { invocation -> invocation.arguments[1] as Boolean }
`when`(context.getSharedPreferences(anyString(), eq(Context.MODE_PRIVATE))).thenReturn(sharedPreferences)
val packageManager = mock(PackageManager::class.java)
val packageInfo = PackageInfo()
packageInfo.versionName = "test.version"
@@ -344,7 +369,7 @@ class ExperimentEvaluatorTest {

@Test
fun testEvaluateRegion() {
var experiment = Experiment(
val experiment = Experiment(
"testid",
"testexperiment",
"testdesc",
@@ -365,6 +390,9 @@ class ExperimentEvaluatorTest {

val context = mock(Context::class.java)
`when`(context.packageName).thenReturn("test.appId")
val sharedPreferences = mock(SharedPreferences::class.java)
`when`(sharedPreferences.getBoolean(eq("testid"), anyBoolean())).thenAnswer { invocation -> invocation.arguments[1] as Boolean }
`when`(context.getSharedPreferences(anyString(), eq(Context.MODE_PRIVATE))).thenReturn(sharedPreferences)
val packageManager = mock(PackageManager::class.java)
val packageInfo = PackageInfo()
packageInfo.versionName = "test.version"
@@ -388,11 +416,89 @@ class ExperimentEvaluatorTest {
assertNull(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
}

@Test
fun testEvaluateActivateOverride() {
val context = mock(Context::class.java)
val sharedPreferences = mock(SharedPreferences::class.java)
`when`(sharedPreferences.getBoolean(eq("id"), anyBoolean())).thenAnswer { invocation -> invocation.arguments[1] as Boolean }
`when`(context.getSharedPreferences(anyString(), eq(Context.MODE_PRIVATE))).thenReturn(sharedPreferences)
val evaluator = ExperimentEvaluator()
val experiment = Experiment("id", bucket = Experiment.Bucket(100, 0))
assertNull(evaluator.evaluate(context, ExperimentDescriptor("id"), listOf(experiment), -1))
`when`(sharedPreferences.getBoolean(eq("id"), anyBoolean())).thenReturn(true)
assertNotNull(evaluator.evaluate(context, ExperimentDescriptor("id"), listOf(experiment), -1))
}

@Test
fun testEvaluateDeactivateOverride() {
val context = mock(Context::class.java)
val sharedPreferences = mock(SharedPreferences::class.java)
`when`(sharedPreferences.getBoolean(eq("id"), anyBoolean())).thenAnswer { invocation -> invocation.arguments[1] as Boolean }
`when`(context.getSharedPreferences(anyString(), eq(Context.MODE_PRIVATE))).thenReturn(sharedPreferences)
val evaluator = ExperimentEvaluator()
val experiment = Experiment("id", bucket = Experiment.Bucket(100, 0))
assertNotNull(evaluator.evaluate(context, ExperimentDescriptor("id"), listOf(experiment), 50))
`when`(sharedPreferences.getBoolean(eq("id"), anyBoolean())).thenReturn(false)
assertNull(evaluator.evaluate(context, ExperimentDescriptor("id"), listOf(experiment), 50))
}

@Test
fun testEvaluateNoExperimentSameAsDescriptor() {
val savedExperiment = Experiment("wrongid")
val descriptor = ExperimentDescriptor("testid")
val context = mock(Context::class.java)
assertNull(ExperimentEvaluator().evaluate(context, descriptor, listOf(savedExperiment), 20))
}

@Test
fun testSetOverrideActivate() {
val context = mock(Context::class.java)
val sharedPreferences = mock(SharedPreferences::class.java)
val sharedPreferencesEditor = mock(SharedPreferences.Editor::class.java)
`when`(sharedPreferencesEditor.putBoolean(anyString(), anyBoolean())).thenReturn(sharedPreferencesEditor)
`when`(sharedPreferences.edit()).thenReturn(sharedPreferencesEditor)
`when`(context.getSharedPreferences(anyString(), eq(Context.MODE_PRIVATE))).thenReturn(sharedPreferences)
val evaluator = ExperimentEvaluator()
evaluator.setOverride(context, ExperimentDescriptor("exp-id"), true)
verify(sharedPreferencesEditor).putBoolean("exp-id", true)
}

@Test
fun testSetOverrideDeactivate() {
val context = mock(Context::class.java)
val sharedPreferences = mock(SharedPreferences::class.java)
val sharedPreferencesEditor = mock(SharedPreferences.Editor::class.java)
`when`(sharedPreferencesEditor.putBoolean(eq("exp-2-id"), anyBoolean())).thenReturn(sharedPreferencesEditor)
`when`(sharedPreferences.edit()).thenReturn(sharedPreferencesEditor)
`when`(context.getSharedPreferences(anyString(), eq(Context.MODE_PRIVATE))).thenReturn(sharedPreferences)
val evaluator = ExperimentEvaluator()
evaluator.setOverride(context, ExperimentDescriptor("exp-2-id"), false)
verify(sharedPreferencesEditor).putBoolean("exp-2-id", false)
}

@Test
fun testClearOverride() {
val context = mock(Context::class.java)
val sharedPreferences = mock(SharedPreferences::class.java)
val sharedPreferencesEditor = mock(SharedPreferences.Editor::class.java)
`when`(sharedPreferencesEditor.remove(anyString())).thenReturn(sharedPreferencesEditor)
`when`(sharedPreferences.edit()).thenReturn(sharedPreferencesEditor)
`when`(context.getSharedPreferences(anyString(), eq(Context.MODE_PRIVATE))).thenReturn(sharedPreferences)
val evaluator = ExperimentEvaluator()
evaluator.clearOverride(context, ExperimentDescriptor("exp-id"))
verify(sharedPreferencesEditor).remove("exp-id")
}

@Test
fun testClearAllOverrides() {
val context = mock(Context::class.java)
val sharedPreferences = mock(SharedPreferences::class.java)
val sharedPreferencesEditor = mock(SharedPreferences.Editor::class.java)
`when`(sharedPreferencesEditor.clear()).thenReturn(sharedPreferencesEditor)
`when`(sharedPreferences.edit()).thenReturn(sharedPreferencesEditor)
`when`(context.getSharedPreferences(anyString(), eq(Context.MODE_PRIVATE))).thenReturn(sharedPreferences)
val evaluator = ExperimentEvaluator()
evaluator.clearAllOverrides(context)
verify(sharedPreferencesEditor).clear()
}
}
@@ -0,0 +1,46 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.service.fretboard

import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class ExperimentTest {
@Test
fun testEquals() {
val experiment = Experiment(
"id",
"name",
"description",
null,
null,
12345,
null)
assertTrue(experiment == experiment)
assertFalse(experiment.equals(null))
assertFalse(experiment.equals(3))
val secondExperiment = Experiment(
"id",
"name2",
"description2",
Experiment.Matcher("eng"),
Experiment.Bucket(100, 0),
null,
null
)
assertTrue(secondExperiment == experiment)
}

@Test
fun testHashCode() {
val experiment = Experiment("id")
assertEquals(experiment.id.hashCode(), experiment.hashCode())
}
}

0 comments on commit 1f745bc

Please sign in to comment.
You can’t perform that action at this time.