Bug 1744953 - Add support for rollouts in the Nimbus platform API r=barret

Differential Revision: https://phabricator.services.mozilla.com/D135495
This commit is contained in:
Andrei Oprea 2022-01-18 14:03:48 +00:00
parent 636619b737
commit f76baf9a72
3 changed files with 152 additions and 26 deletions

View file

@ -16,44 +16,75 @@ namespace mozilla {
static nsTHashSet<nsCString> sExposureFeatureSet;
void NimbusFeatures::GetPrefName(const nsACString& aFeatureId,
void NimbusFeatures::GetPrefName(const nsACString& branchPrefix,
const nsACString& aFeatureId,
const nsACString& aVariable,
nsACString& aPref) {
// This branch is used to store experiment data
constexpr auto kSyncDataPrefBranch = "nimbus.syncdatastore."_ns;
aPref.Truncate();
aPref.Append(kSyncDataPrefBranch);
aPref.Append(aFeatureId);
nsAutoCString featureAndVariable;
featureAndVariable.Append(aFeatureId);
if (!aVariable.IsEmpty()) {
aPref.Append(".");
aPref.Append(aVariable);
featureAndVariable.Append(".");
featureAndVariable.Append(aVariable);
}
aPref.Truncate();
aPref.Append(branchPrefix);
aPref.Append(featureAndVariable);
}
/**
* Returns the variable value configured via experiment or rollout.
* If a fallback pref is defined in the FeatureManifest and it
* has a user value set this takes precedence over remote configurations.
*/
bool NimbusFeatures::GetBool(const nsACString& aFeatureId,
const nsACString& aVariable, bool aDefault) {
nsAutoCString pref;
GetPrefName(aFeatureId, aVariable, pref);
if (Preferences::HasUserValue(pref.get())) {
return Preferences::GetBool(pref.get(), aDefault);
auto prefName = GetNimbusFallbackPrefName(aFeatureId, aVariable);
if (prefName.isSome() && Preferences::HasUserValue(prefName->get())) {
return Preferences::GetBool(prefName->get(), aDefault);
}
nsAutoCString experimentPref;
GetPrefName(kSyncDataPrefBranch, aFeatureId, aVariable, experimentPref);
if (Preferences::HasUserValue(experimentPref.get())) {
return Preferences::GetBool(experimentPref.get(), aDefault);
}
nsAutoCString rolloutPref;
GetPrefName(kSyncRolloutsPrefBranch, aFeatureId, aVariable, rolloutPref);
if (Preferences::HasUserValue(rolloutPref.get())) {
return Preferences::GetBool(rolloutPref.get(), aDefault);
}
auto prefName = GetNimbusFallbackPrefName(aFeatureId, aVariable);
if (prefName.isSome()) {
return Preferences::GetBool(prefName->get(), aDefault);
}
return aDefault;
}
/**
* Returns the variable value configured via experiment or rollout.
* If a fallback pref is defined in the FeatureManifest and it
* has a user value set this takes precedence over remote configurations.
*/
int NimbusFeatures::GetInt(const nsACString& aFeatureId,
const nsACString& aVariable, int aDefault) {
nsAutoCString pref;
GetPrefName(aFeatureId, aVariable, pref);
if (Preferences::HasUserValue(pref.get())) {
return Preferences::GetInt(pref.get(), aDefault);
auto prefName = GetNimbusFallbackPrefName(aFeatureId, aVariable);
if (prefName.isSome() && Preferences::HasUserValue(prefName->get())) {
return Preferences::GetInt(prefName->get(), aDefault);
}
nsAutoCString experimentPref;
GetPrefName(kSyncDataPrefBranch, aFeatureId, aVariable, experimentPref);
if (Preferences::HasUserValue(experimentPref.get())) {
return Preferences::GetInt(experimentPref.get(), aDefault);
}
nsAutoCString rolloutPref;
GetPrefName(kSyncRolloutsPrefBranch, aFeatureId, aVariable, rolloutPref);
if (Preferences::HasUserValue(rolloutPref.get())) {
return Preferences::GetInt(rolloutPref.get(), aDefault);
}
auto prefName = GetNimbusFallbackPrefName(aFeatureId, aVariable);
if (prefName.isSome()) {
return Preferences::GetInt(prefName->get(), aDefault);
}
@ -64,18 +95,34 @@ nsresult NimbusFeatures::OnUpdate(const nsACString& aFeatureId,
const nsACString& aVariable,
PrefChangedFunc aUserCallback,
void* aUserData) {
nsAutoCString pref;
GetPrefName(aFeatureId, aVariable, pref);
return Preferences::RegisterCallback(aUserCallback, pref, aUserData);
nsAutoCString experimentPref;
nsAutoCString rolloutPref;
GetPrefName(kSyncDataPrefBranch, aFeatureId, aVariable, experimentPref);
GetPrefName(kSyncRolloutsPrefBranch, aFeatureId, aVariable, rolloutPref);
nsresult rv =
Preferences::RegisterCallback(aUserCallback, experimentPref, aUserData);
NS_ENSURE_SUCCESS(rv, rv);
rv = Preferences::RegisterCallback(aUserCallback, rolloutPref, aUserData);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult NimbusFeatures::OffUpdate(const nsACString& aFeatureId,
const nsACString& aVariable,
PrefChangedFunc aUserCallback,
void* aUserData) {
nsAutoCString pref;
GetPrefName(aFeatureId, aVariable, pref);
return Preferences::UnregisterCallback(aUserCallback, pref, aUserData);
nsAutoCString experimentPref;
nsAutoCString rolloutPref;
GetPrefName(kSyncDataPrefBranch, aFeatureId, aVariable, experimentPref);
GetPrefName(kSyncRolloutsPrefBranch, aFeatureId, aVariable, rolloutPref);
nsresult rv =
Preferences::UnregisterCallback(aUserCallback, experimentPref, aUserData);
NS_ENSURE_SUCCESS(rv, rv);
rv = Preferences::UnregisterCallback(aUserCallback, rolloutPref, aUserData);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
/**
@ -99,7 +146,7 @@ nsresult NimbusFeatures::GetExperimentSlug(const nsACString& aFeatureId,
aExperimentSlug.Truncate();
aBranchSlug.Truncate();
GetPrefName(aFeatureId, ""_ns, prefName);
GetPrefName(kSyncDataPrefBranch, aFeatureId, EmptyCString(), prefName);
MOZ_TRY(Preferences::GetString(prefName.get(), prefValue));
if (prefValue.IsEmpty()) {
return NS_ERROR_UNEXPECTED;

View file

@ -14,7 +14,13 @@ namespace mozilla {
class NimbusFeatures {
private:
static void GetPrefName(const nsACString& aFeatureId,
// This branch is used to store experiment data
static constexpr auto kSyncDataPrefBranch = "nimbus.syncdatastore."_ns;
// This branch is used to store rollouts data
static constexpr auto kSyncRolloutsPrefBranch =
"nimbus.syncdefaultsstore."_ns;
static void GetPrefName(const nsACString& branchPrefix,
const nsACString& aFeatureId,
const nsACString& aVariable, nsACString& aPref);
static nsresult GetExperimentSlug(const nsACString& aFeatureId,

View file

@ -24,6 +24,62 @@ TEST(NimbusFeaturesGet, Errors)
ASSERT_TRUE(NimbusFeatures::GetBool("foo"_ns, "enabled"_ns, false));
}
TEST(NimbusFeaturesGetRollout, Errors)
{
ASSERT_EQ(Preferences::SetInt("nimbus.syncdefaultsstore.rollout.value", 7,
PrefValueKind::User),
NS_OK);
ASSERT_EQ(NimbusFeatures::GetInt("rollout"_ns, "value"_ns, 0), 7);
ASSERT_EQ(Preferences::SetBool("nimbus.syncdefaultsstore.rollout.enabled",
true, PrefValueKind::User),
NS_OK);
ASSERT_TRUE(NimbusFeatures::GetBool("rollout"_ns, "enabled"_ns, false));
}
TEST(NimbusFeaturesExperimentPriorityOverRollouts, Errors)
{
ASSERT_EQ(Preferences::SetInt("nimbus.syncdatastore.feature.value", 12,
PrefValueKind::User),
NS_OK);
ASSERT_EQ(Preferences::SetInt("nimbus.syncdefaultsstore.feature.value", 22,
PrefValueKind::User),
NS_OK);
ASSERT_EQ(NimbusFeatures::GetInt("feature"_ns, "value"_ns, 0), 12);
ASSERT_EQ(Preferences::SetBool("nimbus.syncdatastore.feature.enabled", true,
PrefValueKind::User),
NS_OK);
ASSERT_EQ(Preferences::SetBool("nimbus.syncdefaultsstore.feature.enabled",
false, PrefValueKind::User),
NS_OK);
ASSERT_TRUE(NimbusFeatures::GetBool("feature"_ns, "enabled"_ns, false));
}
// Make sure user prefs take predence over experiments and rollouts
TEST(NimbusFeaturesDataSourcePrecedence, Errors)
{
ASSERT_EQ(
Preferences::SetInt("nimbus.testing.testInt", 29, PrefValueKind::User),
NS_OK);
ASSERT_EQ(NimbusFeatures::GetInt("testFeature"_ns, "testInt"_ns, 0), 29);
ASSERT_EQ(Preferences::SetInt("nimbus.syncdatastore.testFeature.testInt", 12,
PrefValueKind::User),
NS_OK);
ASSERT_EQ(Preferences::SetInt("nimbus.syncdefaultsstore.testFeature.testInt",
13, PrefValueKind::User),
NS_OK);
// Still return user pref
ASSERT_EQ(NimbusFeatures::GetInt("testFeature"_ns, "testInt"_ns, 0), 29);
// After user prefs it should default to experiment value
Preferences::ClearUser("nimbus.testing.testInt");
ASSERT_EQ(NimbusFeatures::GetInt("testFeature"_ns, "testInt"_ns, 0), 12);
Preferences::ClearUser("nimbus.syncdatastore.testFeature.testInt");
// After experiments it should default to rollouts
ASSERT_EQ(NimbusFeatures::GetInt("testFeature"_ns, "testInt"_ns, 0), 13);
// Cleanup
Preferences::ClearUser("nimbus.syncdefaultsstore.testFeature.testInt");
}
static void FooValueUpdated(const char* aPref, void* aUserData) {
ASSERT_STREQ(aPref, "nimbus.syncdatastore.foo.value");
ASSERT_EQ(aUserData, reinterpret_cast<void*>(13));
@ -34,6 +90,13 @@ static void FooValueUpdated(const char* aPref, void* aUserData) {
ASSERT_EQ(NimbusFeatures::GetInt("foo"_ns, "value"_ns, 0), 24);
}
static void BarRolloutValueUpdated(const char* aPref, void* aUserData) {
ASSERT_STREQ(aPref, "nimbus.syncdefaultsstore.bar.value");
ASSERT_FALSE(gPrefUpdate);
gPrefUpdate = true;
}
TEST(NimbusFeaturesGetFallback, Errors)
{
// No experiment is set and we expect to return fallback pref values
@ -60,6 +123,10 @@ TEST(NimbusFeaturesUpdate, Errors)
ASSERT_EQ(NimbusFeatures::OnUpdate("foo"_ns, "value"_ns, FooValueUpdated,
reinterpret_cast<void*>(13)),
NS_OK);
ASSERT_EQ(
NimbusFeatures::OnUpdate("bar"_ns, "value"_ns, BarRolloutValueUpdated,
reinterpret_cast<void*>(13)),
NS_OK);
ASSERT_EQ(Preferences::SetInt("nimbus.syncdatastore.foo.value", 24,
PrefValueKind::User),
NS_OK);
@ -74,6 +141,12 @@ TEST(NimbusFeaturesUpdate, Errors)
ASSERT_FALSE(NimbusFeatures::GetBool("foo"_ns, "enabled"_ns, true));
gPrefUpdate = false;
ASSERT_EQ(Preferences::SetInt("nimbus.syncdefaultsstore.bar.value", 25,
PrefValueKind::User),
NS_OK);
ASSERT_TRUE(gPrefUpdate);
gPrefUpdate = false;
// Verify OffUpdate requires a matching user data pointer to unregister.
ASSERT_EQ(NimbusFeatures::OffUpdate("foo"_ns, "value"_ns, FooValueUpdated,
reinterpret_cast<void*>(14)),