// Copyright 2020 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/variations/variations_test_utils.h" #include "base/base64.h" #include "base/command_line.h" #include "base/feature_list.h" #include "components/metrics/clean_exit_beacon.h" #include "components/metrics/metrics_pref_names.h" #include "components/prefs/pref_service.h" #include "components/variations/client_filterable_state.h" #include "components/variations/field_trial_config/fieldtrial_testing_config.h" #include "components/variations/pref_names.h" #include "components/variations/proto/client_variations.pb.h" #include "components/variations/variations_associated_data.h" #include "components/variations/variations_switches.h" #include "third_party/zlib/google/compression_utils.h" namespace variations { namespace { // kTestSeed is a simple VariationsSeed containing: // serial_number: "test" // study: { // name: "UMA-Uniformity-Trial-50-Percent" // consistency: PERMANENT // experiment: { // name: "default" // probability_weight: 1 // } // experiment: { // name: "group_01" // probability_weight: 1 // } // } const char* kTestSeed_StudyNames[] = {"UMA-Uniformity-Trial-50-Percent"}; const char kTestSeed_Base64UncompressedData[] = "CgR0ZXN0Ej4KH1VNQS1Vbmlmb3JtaXR5LVRyaWFsLTUwLVBlcmNlbnQ4AUoLCgdkZWZhdWx0EA" "FKDAoIZ3JvdXBfMDEQAQ=="; const char kTestSeed_Base64CompressedData[] = "H4sIAAAAAAAA/+JiKUktLhGy45IP9XXUDc3LTMsvys0sqdQNKcpMzNE1NdANSC1KTs0rsWD04u" "ZiT0lNSyzNKRFg9OLh4kgvyi8tiDcwFGAEBAAA//90/JgERgAAAA=="; const char kTestSeed_Base64Signature[] = "MEUCIQD5AEAzk5qEuE3xOZl+xSZR15Ac1RJpsXMiou7i5W0sMAIgRn++ngh03HaMGC+Pjl9NOu" "Doxf83qsSwycF2PSS1nYQ="; const char* kCrashingSeed_StudyNames[] = {"CrashingStudy"}; // kCrashingSeed is a VariationsSeed that triggers a crash for testing: // serial_number: "35ed2d9e354b414befdf930a734094019c0162f1" // study: { // name: "CrashingStudy" // consistency: PERMANENT // experiment: { // name: "EnabledLaunch" // probability_weight: 100 // feature_association: { // enable_feature: "ForceFieldTrialSetupCrashForTesting" // } // } // experiment: { // name: "ForcedOn_ForceFieldTrialSetupCrashForTesting" // probability_weight: 0 // feature_association: { // forcing_feature_on: "ForceFieldTrialSetupCrashForTesting" // } // } // experiment: { // name: "ForcedOff_ForceFieldTrialSetupCrashForTesting" // probability_weight: 0 // feature_association: { // forcing_feature_off: "ForceFieldTrialSetupCrashForTesting" // } // } // filter: { // min_version: "91.*" // channel: CANARY // channel: DEV // channel: BETA // channel: STABLE // platform: PLATFORM_ANDROID // platform: PLATFORM_IOS // platform: PLATFORM_ANDROID_WEBVIEW // platform: PLATFORM_WINDOWS // platform: PLATFORM_MAC // platform: PLATFORM_LINUX // platform: PLATFORM_CHROMEOS // platform: PLATFORM_CHROMEOS_LACROS // } // } // version: "hash/4aa56a1dc30dfc767615248d6fee29830198b276" const char kCrashingSeed_Base64UncompressedData[] = "CigzNWVkMmQ5ZTM1NGI0MTRiZWZkZjkzMGE3MzQwOTQwMTljMDE2MmYxEp4CCg1DcmFzaGluZ1" "N0dWR5OAFKOAoNRW5hYmxlZExhdW5jaBBkYiUKI0ZvcmNlRmllbGRUcmlhbFNldHVwQ3Jhc2hG" "b3JUZXN0aW5nSlcKLEZvcmNlZE9uX0ZvcmNlRmllbGRUcmlhbFNldHVwQ3Jhc2hGb3JUZXN0aW" "5nEABiJRojRm9yY2VGaWVsZFRyaWFsU2V0dXBDcmFzaEZvclRlc3RpbmdKWAotRm9yY2VkT2Zm" "X0ZvcmNlRmllbGRUcmlhbFNldHVwQ3Jhc2hGb3JUZXN0aW5nEABiJSIjRm9yY2VGaWVsZFRyaW" "FsU2V0dXBDcmFzaEZvclRlc3RpbmdSHhIEOTEuKiAAIAEgAiADKAQoBSgGKAAoASgCKAMoCSIt" "aGFzaC80YWE1NmExZGMzMGRmYzc2NzYxNTI0OGQ2ZmVlMjk4MzAxOThiMjc2"; const char kCrashingSeed_Base64CompressedData[] = "H4sIAAAAAAAAAI3QwUvDMBTH8babwgKDsaMHKZNBEKdJk6bJWbbDEAQ30JskeS+2UKp07cF/" "Zn+rZfgH9Py+73P4ESpyhAwMilw6yaXDAMEIZgshmZGMG8+4ygJfnhMyf27tqayar0PXw6+" "O95rMt411NcKL7RtfLsCtyd3uu/W4q7CGY1vZ+oBd/" "3P5HA5HPHUDsH8nD5cMXpvPEf0icuubUfAH2fzDIYyVV2Pkt9vl1PDH+zRK4zRJJ3RKr+" "g1jWhMEzqhs9WmHPonaW2uLAcvGARfqELxPJMaVEDMjBbDotplhfoDs9NLbnoBAAA="; const char kCrashingSeed_Base64Signature[] = "MEQCIEn1+VsBfNA93dxzpk+BLhdO91kMQnofxfTK5Uo8vDi8AiAnTCFCIPgEGWNOKzuKfNWn6" "emB6pnGWjSTbI/pvfxHnw=="; // Create mock testing config equivalent to: // { // "UnitTest": [ // { // "platforms": [ // "android", // "android_weblayer", // "android_webview", // "chromeos", // "chromeos_lacros", // "fuchsia", // "ios", // "linux", // "mac", // "windows" // ], // "experiments": [ // { // "name": "Enabled", // "params": { // "x": "1" // }, // "enable_features": [ // "UnitTestEnabled" // ] // } // ] // } // ] // } const Study::Platform array_kFieldTrialConfig_platforms_0[] = { Study::PLATFORM_ANDROID, Study::PLATFORM_ANDROID_WEBLAYER, Study::PLATFORM_ANDROID_WEBVIEW, Study::PLATFORM_CHROMEOS, Study::PLATFORM_CHROMEOS_LACROS, Study::PLATFORM_FUCHSIA, Study::PLATFORM_IOS, Study::PLATFORM_LINUX, Study::PLATFORM_MAC, Study::PLATFORM_WINDOWS, }; const char* enable_features_0[] = {"UnitTestEnabled"}; const FieldTrialTestingExperimentParams array_kFieldTrialConfig_params_0[] = { { "x", "1", }, }; const FieldTrialTestingExperiment array_kFieldTrialConfig_experiments_0[] = { {/*name=*/"Enabled", /*platforms=*/array_kFieldTrialConfig_platforms_0, /*platforms_size=*/10, /*form_factors=*/{}, /*form_factors_size=*/0, /*is_low_end_device=*/absl::nullopt, /*min_os_version=*/nullptr, /*params=*/array_kFieldTrialConfig_params_0, /*params_size=*/1, /*enable_features=*/enable_features_0, /*enable_features_size=*/1, /*disable_features=*/nullptr, /*disable_features_size=*/0, /*forcing_flag=*/nullptr, /*override_ui_string=*/nullptr, /*override_ui_string_size=*/0}, }; const FieldTrialTestingStudy array_kFieldTrialConfig_studies[] = { {/*name=*/"UnitTest", /*experiments=*/array_kFieldTrialConfig_experiments_0, /*experiments_size=*/1}, }; } // namespace const SignedSeedData kTestSeedData{ kTestSeed_StudyNames, kTestSeed_Base64UncompressedData, kTestSeed_Base64CompressedData, kTestSeed_Base64Signature}; const SignedSeedData kCrashingSeedData{ kCrashingSeed_StudyNames, kCrashingSeed_Base64UncompressedData, kCrashingSeed_Base64CompressedData, kCrashingSeed_Base64Signature}; const SignedSeedPrefKeys kSafeSeedPrefKeys{prefs::kVariationsSafeCompressedSeed, prefs::kVariationsSafeSeedSignature}; const SignedSeedPrefKeys kRegularSeedPrefKeys{prefs::kVariationsCompressedSeed, prefs::kVariationsSeedSignature}; SignedSeedData::SignedSeedData(base::span in_study_names, const char* in_base64_uncompressed_data, const char* in_base64_compressed_data, const char* in_base64_signature) : study_names(std::move(in_study_names)), base64_uncompressed_data(in_base64_uncompressed_data), base64_compressed_data(in_base64_compressed_data), base64_signature(in_base64_signature) {} SignedSeedData::~SignedSeedData() = default; SignedSeedData::SignedSeedData(const SignedSeedData&) = default; SignedSeedData::SignedSeedData(SignedSeedData&&) = default; SignedSeedData& SignedSeedData::operator=(const SignedSeedData&) = default; SignedSeedData& SignedSeedData::operator=(SignedSeedData&&) = default; void DisableTestingConfig() { base::CommandLine::ForCurrentProcess()->AppendSwitch( switches::kDisableFieldTrialTestingConfig); } void EnableTestingConfig() { base::CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableFieldTrialTestingConfig); } bool ExtractVariationIds(const std::string& variations, std::set* variation_ids, std::set* trigger_ids) { std::string serialized_proto; if (!base::Base64Decode(variations, &serialized_proto)) return false; ClientVariations proto; if (!proto.ParseFromString(serialized_proto)) return false; for (int i = 0; i < proto.variation_id_size(); ++i) variation_ids->insert(proto.variation_id(i)); for (int i = 0; i < proto.trigger_variation_id_size(); ++i) trigger_ids->insert(proto.trigger_variation_id(i)); return true; } scoped_refptr CreateTrialAndAssociateId( const std::string& trial_name, const std::string& default_group_name, IDCollectionKey key, VariationID id) { AssociateGoogleVariationID(key, trial_name, default_group_name, id); scoped_refptr trial( base::FieldTrialList::CreateFieldTrial(trial_name, default_group_name)); DCHECK(trial); if (trial) { // Ensure the trial is registered under the correct key so we can look it // up. trial->Activate(); } return trial; } void SimulateCrash(PrefService* local_state) { local_state->SetBoolean(metrics::prefs::kStabilityExitedCleanly, false); metrics::CleanExitBeacon::SkipCleanShutdownStepsForTesting(); } void WriteSeedData(PrefService* local_state, const SignedSeedData& seed_data, const SignedSeedPrefKeys& pref_keys) { local_state->SetString(pref_keys.base64_compressed_data_key, seed_data.base64_compressed_data); local_state->SetString(pref_keys.base64_signature_key, seed_data.base64_signature); local_state->CommitPendingWrite(); } bool FieldTrialListHasAllStudiesFrom(const SignedSeedData& seed_data) { return base::ranges::all_of(seed_data.study_names, [](const char* study) { return base::FieldTrialList::TrialExists(study); }); } void ResetVariations() { testing::ClearAllVariationIDs(); testing::ClearAllVariationParams(); } const FieldTrialTestingConfig kTestingConfig = { array_kFieldTrialConfig_studies, 1, }; std::unique_ptr CreateDummyClientFilterableState() { auto client_state = std::make_unique( base::BindOnce([] { return false; })); client_state->locale = "en-CA"; client_state->reference_date = base::Time::Now(); client_state->version = base::Version("20.0.0.0"); client_state->channel = Study::STABLE; client_state->form_factor = Study::PHONE; client_state->platform = Study::PLATFORM_ANDROID; return client_state; } MockEntropyProviders::MockEntropyProviders( MockEntropyProviders::Results results, size_t low_entropy_domain) : EntropyProviders(results.high_entropy.has_value() ? "client_id" : "", 0, low_entropy_domain), low_provider_(results.low_entropy), high_provider_(results.high_entropy.value_or(0)) {} MockEntropyProviders::~MockEntropyProviders() = default; const base::FieldTrial::EntropyProvider& MockEntropyProviders::low_entropy() const { return low_provider_; } const base::FieldTrial::EntropyProvider& MockEntropyProviders::default_entropy() const { if (default_entropy_is_high_entropy()) { return high_provider_; } return low_provider_; } } // namespace variations