Revision control

Copy as Markdown

/* 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/. */
#![cfg(feature = "rkv-safe-mode")]
mod common;
#[cfg(test)]
mod test {
use std::collections::HashSet;
use super::common::new_test_client_with_db;
#[cfg(feature = "rkv-safe-mode")]
use nimbus::error::Result;
use serde_json::json;
fn experiment_target_false() -> serde_json::Value {
json!({
"schemaVersion": "1.0.0",
"slug": "experiment_target_false",
"endDate": null,
"featureIds": ["some-feature"],
"branches": [
{
"slug": "control",
"ratio": 1
},
{
"slug": "treatment",
"ratio": 1
}
],
"channel": "nightly",
"probeSets": [],
"startDate": null,
"appName": "fenix",
"appId": "org.mozilla.fenix",
"bucketConfig": {
"count": 10000,
"start": 0,
"total": 10000,
"namespace": "experiment_target_false",
"randomizationUnit": "nimbus_id"
},
"targeting": "false",
"userFacingName": "Diagnostic test experiment",
"referenceBranch": "control",
"isEnrollmentPaused": false,
"proposedEnrollment": 7,
"userFacingDescription": "This is a test experiment for diagnostic purposes.",
"id": "secure-copper",
"last_modified": 1_602_197_324_372i64,
})
}
fn experiment_zero_buckets() -> serde_json::Value {
json!({
"schemaVersion": "1.0.0",
"slug": "experiment_zero_buckets",
"endDate": null,
"featureIds": ["some-feature"],
"branches": [
{
"slug": "control",
"ratio": 1
},
{
"slug": "treatment",
"ratio": 1
}
],
"channel": "nightly",
"probeSets": [],
"startDate": null,
"appName": "fenix",
"appId": "org.mozilla.fenix",
"bucketConfig": {
"count": 0,
"start": 0,
"total": 10000,
"namespace": "experiment_zero_buckets",
"randomizationUnit": "nimbus_id"
},
"userFacingName": "Diagnostic test experiment",
"referenceBranch": "control",
"isEnrollmentPaused": false,
"proposedEnrollment": 7,
"userFacingDescription": "This is a test experiment for diagnostic purposes.",
"id": "secure-copper",
"last_modified": 1_602_197_324_372i64,
})
}
fn experiment_always_enroll() -> serde_json::Value {
json!({
"schemaVersion": "1.0.0",
"slug": "experiment_always_enroll",
"endDate": null,
"featureIds": ["some-feature"],
"branches": [
{
"slug": "treatment",
"ratio": 1
}
],
"channel": "nightly",
"probeSets": [],
"startDate": null,
"appName": "fenix",
"appId": "org.mozilla.fenix",
"bucketConfig": {
"count": 10000,
"start": 0,
"total": 10000,
"namespace": "experiment_always_enroll",
"randomizationUnit": "nimbus_id"
},
"userFacingName": "Diagnostic test experiment",
"referenceBranch": "control",
"isEnrollmentPaused": false,
"proposedEnrollment": 7,
"userFacingDescription": "This is a test experiment for diagnostic purposes.",
"id": "secure-copper",
"last_modified": 1_602_197_324_372i64,
"targeting": "true",
})
}
#[test]
fn test_restart_opt_in() -> Result<()> {
let temp_dir = tempfile::tempdir()?;
let client = new_test_client_with_db(&temp_dir)?;
client.initialize()?;
let experiment_json = serde_json::to_string(&json!({
"data": [
experiment_target_false(),
experiment_zero_buckets(),
]
}))?;
client.set_experiments_locally(experiment_json.clone())?;
client.apply_pending_experiments()?;
// the experiment_target_false experiment has a 'targeting' of "false", we test to ensure that
// restarting the app preserves the fact that we opt-ed in, even though we were not
// targeted
client.opt_in_with_branch("experiment_target_false".into(), "treatment".into())?;
// the experiment_zero_buckets experiment has a bucket configuration of 0%, meaning we will always not
// be enrolled, we test to ensure that is overridden when we opt-in
client.opt_in_with_branch("experiment_zero_buckets".into(), "treatment".into())?;
let before_restart_experiments = client.get_active_experiments()?;
assert_eq!(before_restart_experiments.len(), 2);
assert_eq!(before_restart_experiments[0].branch_slug, "treatment");
assert_eq!(before_restart_experiments[1].branch_slug, "treatment");
// we drop the NimbusClient to terminate the underlying database connection
drop(client);
let client = new_test_client_with_db(&temp_dir)?;
client.initialize()?;
client.set_experiments_locally(experiment_json)?;
client.apply_pending_experiments()?;
let after_restart_experiments = client.get_active_experiments()?;
assert_eq!(
before_restart_experiments.len(),
after_restart_experiments.len()
);
assert_eq!(after_restart_experiments[0].branch_slug, "treatment");
assert_eq!(after_restart_experiments[1].branch_slug, "treatment");
Ok(())
}
#[test]
fn test_targeting_attributes_active_experiments() -> Result<()> {
let temp_dir = tempfile::tempdir()?;
let client = new_test_client_with_db(&temp_dir)?;
// On construction, the active_experiments in targeting attributes is empty.
let expected = HashSet::new();
let ta = client.get_targeting_attributes();
assert_eq!(ta.active_experiments, expected);
let experiment_json = serde_json::to_string(&json!({
"data": [
experiment_target_false(),
experiment_zero_buckets(),
experiment_always_enroll(),
]
}))?;
client.set_experiments_locally(experiment_json)?;
client.apply_pending_experiments()?;
let expected = ["experiment_always_enroll"]
.iter()
.map(|s| s.to_string())
.collect::<HashSet<_>>();
let ta = client.get_targeting_attributes();
assert_eq!(ta.active_experiments, expected);
// Opting in or out should keep the targeting attributes up to date.
client.opt_in_with_branch("experiment_target_false".into(), "treatment".into())?;
let expected = ["experiment_always_enroll", "experiment_target_false"]
.iter()
.map(|s| s.to_string())
.collect::<HashSet<_>>();
let ta = client.get_targeting_attributes();
assert_eq!(ta.active_experiments, expected);
let eval = client.create_targeting_helper(None)?;
assert!(eval.eval_jexl("'experiment_always_enroll' in active_experiments".to_string())?);
assert!(eval.eval_jexl("'experiment_target_false' in active_experiments".to_string())?);
assert!(!eval.eval_jexl("'experiment_zero_buckets' in active_experiments".to_string())?);
drop(client);
// On restart, we might only do an initialize
let client = new_test_client_with_db(&temp_dir)?;
client.initialize()?;
let ta = client.get_targeting_attributes();
assert_eq!(ta.active_experiments, expected);
let eval = client.create_targeting_helper(None)?;
assert!(eval.eval_jexl("'experiment_always_enroll' in active_experiments".to_string())?);
assert!(eval.eval_jexl("'experiment_target_false' in active_experiments".to_string())?);
assert!(!eval.eval_jexl("'experiment_zero_buckets' in active_experiments".to_string())?);
drop(client);
// On another restart, we might do an apply_pending_experiments, with nothing pending.
let client = new_test_client_with_db(&temp_dir)?;
client.apply_pending_experiments()?;
let ta = client.get_targeting_attributes();
assert_eq!(ta.active_experiments, expected);
let eval = client.create_targeting_helper(None)?;
assert!(eval.eval_jexl("'experiment_always_enroll' in active_experiments".to_string())?);
assert!(eval.eval_jexl("'experiment_target_false' in active_experiments".to_string())?);
assert!(!eval.eval_jexl("'experiment_zero_buckets' in active_experiments".to_string())?);
Ok(())
}
}