Use seeds to drive variations extended safe mode end-to-end test
This CL updates the variations extended safe mode end-to-end tests
[see TEST_P(FieldTrialTest, ExtendedSafeModeEndToEnd)] to inject a
safe seed into the test environment and use a candidate seed to
enable a crashing feature.
Bug: 1249256
Change-Id: Ibeb5131750188b0b99ebf9d7eed4acb16fd0b193
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3175920
Commit-Queue: Roger McFarlane <[email protected]>
Reviewed-by: Rohit Rao <[email protected]>
Reviewed-by: Alexei Svitkine <[email protected]>
Reviewed-by: Caitlin Fischer <[email protected]>
Cr-Commit-Position: refs/heads/main@{#928645}
diff --git a/chrome/browser/metrics/variations/variations_safe_mode_browsertest.cc b/chrome/browser/metrics/variations/variations_safe_mode_browsertest.cc
index 0510087..9e200818 100644
--- a/chrome/browser/metrics/variations/variations_safe_mode_browsertest.cc
+++ b/chrome/browser/metrics/variations/variations_safe_mode_browsertest.cc
@@ -10,10 +10,12 @@
#include "base/base_switches.h"
#include "base/containers/contains.h"
+#include "base/files/file_util.h"
#include "base/metrics/field_trial.h"
#include "base/path_service.h"
#include "base/ranges/ranges.h"
#include "base/strings/strcat.h"
+#include "base/strings/stringprintf.h"
#include "base/test/launcher/test_launcher.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
@@ -23,7 +25,6 @@
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/metrics/clean_exit_beacon.h"
-#include "components/metrics/metrics_pref_names.h"
#include "components/metrics/metrics_service.h"
#include "components/prefs/json_pref_store.h"
#include "components/prefs/pref_service.h"
@@ -40,30 +41,6 @@
#include "testing/gtest/include/gtest/gtest.h"
namespace variations {
-namespace {
-
-// Sets |local_state|'s seed and seed signature prefs to a valid seed-signature
-// pair. If |use_safe_seed_prefs| is true, then uses the safe seed prefs.
-void StoreTestSeedAndSignature(PrefService* local_state,
- bool use_safe_seed_prefs) {
- const std::string seed_pref = use_safe_seed_prefs
- ? prefs::kVariationsSafeCompressedSeed
- : prefs::kVariationsCompressedSeed;
- local_state->SetString(seed_pref, kCompressedBase64TestSeedData);
-
- const std::string signature_pref = use_safe_seed_prefs
- ? prefs::kVariationsSafeSeedSignature
- : prefs::kVariationsSeedSignature;
- local_state->SetString(signature_pref, kBase64TestSeedSignature);
-}
-
-// Simulates a crash by forcing Chrome to fail to exit cleanly.
-void SimulateCrash(PrefService* local_state) {
- local_state->SetBoolean(metrics::prefs::kStabilityExitedCleanly, false);
- metrics::CleanExitBeacon::SkipCleanShutdownStepsForTesting();
-}
-
-} // namespace
class VariationsSafeModeBrowserTest : public InProcessBrowserTest {
public:
@@ -81,7 +58,7 @@
// pref to be set early enough to be read by the variations code, which runs
// very early during startup.
PrefService* local_state = g_browser_process->local_state();
- StoreTestSeedAndSignature(local_state, /*use_safe_seed_prefs=*/true);
+ WriteSeedData(local_state, kTestSeedData, kSafeSeedPrefKeys);
SimulateCrash(local_state);
}
@@ -110,9 +87,8 @@
histogram_tester_.ExpectUniqueSample("Variations.SeedUsage",
SeedUsage::kSafeSeedUsed, 1);
- // Verify that there is a field trial associated with the sole test seed
- // study, |variations::kTestSeedStudyName|.
- EXPECT_TRUE(base::FieldTrialList::TrialExists(kTestSeedStudyName));
+ // Verify that |kTestSeedData| has been applied.
+ EXPECT_TRUE(FieldTrialListHasAllStudiesFrom(kTestSeedData));
}
IN_PROC_BROWSER_TEST_F(VariationsSafeModeBrowserTest,
@@ -123,7 +99,7 @@
// very early during startup.
PrefService* local_state = g_browser_process->local_state();
local_state->SetInteger(prefs::kVariationsFailedToFetchSeedStreak, 25);
- StoreTestSeedAndSignature(local_state, /*use_safe_seed_prefs=*/true);
+ WriteSeedData(local_state, kTestSeedData, kSafeSeedPrefKeys);
}
IN_PROC_BROWSER_TEST_F(VariationsSafeModeBrowserTest,
@@ -138,9 +114,8 @@
histogram_tester_.ExpectUniqueSample("Variations.SeedUsage",
SeedUsage::kSafeSeedUsed, 1);
- // Verify that there is a field trial associated with the sole test seed
- // study, |kTestSeedStudyName|.
- EXPECT_TRUE(base::FieldTrialList::TrialExists(kTestSeedStudyName));
+ // Verify that |kTestSeedData| has been applied.
+ EXPECT_TRUE(FieldTrialListHasAllStudiesFrom(kTestSeedData));
}
IN_PROC_BROWSER_TEST_F(VariationsSafeModeBrowserTest,
@@ -152,7 +127,7 @@
PrefService* local_state = g_browser_process->local_state();
local_state->SetInteger(prefs::kVariationsCrashStreak, 2);
local_state->SetInteger(prefs::kVariationsFailedToFetchSeedStreak, 24);
- StoreTestSeedAndSignature(local_state, /*use_safe_seed_prefs=*/false);
+ WriteSeedData(local_state, kTestSeedData, kRegularSeedPrefKeys);
}
IN_PROC_BROWSER_TEST_F(VariationsSafeModeBrowserTest, DoNotTriggerSafeMode) {
@@ -189,6 +164,16 @@
ASSERT_FALSE(expected_user_data_dir.empty());
ASSERT_FALSE(actual_user_data_dir.empty());
ASSERT_EQ(actual_user_data_dir, expected_user_data_dir);
+
+ // If the test makes it this far, then either it's the first run of the
+ // test, or the safe seed was used.
+ const int crash_streak = g_browser_process->local_state()->GetInteger(
+ prefs::kVariationsCrashStreak);
+ const bool is_first_run = (crash_streak == 0);
+ const bool safe_seed_was_used =
+ FieldTrialListHasAllStudiesFrom(kTestSeedData);
+ EXPECT_NE(is_first_run, safe_seed_was_used) // ==> XOR
+ << "crash_streak=" << crash_streak;
}
namespace {
@@ -206,13 +191,20 @@
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
user_data_dir_ = temp_dir_.GetPath().AppendASCII("user-data-dir");
- pref_service_factory_.set_user_prefs(base::MakeRefCounted<JsonPrefStore>(
- user_data_dir_.AppendASCII("Local State")));
+ local_state_file_ = user_data_dir_.AppendASCII("Local State");
}
protected:
const std::string& field_trial_group() const { return GetParam(); }
const base::FilePath& user_data_dir() const { return user_data_dir_; }
+ const base::FilePath& local_state_file() const { return local_state_file_; }
+
+ const base::FilePath CopyOfLocalStateFile(int suffix) const {
+ base::FilePath copy_of_local_state_file = temp_dir_.GetPath().AppendASCII(
+ base::StringPrintf("local-state-copy-%d.json", suffix));
+ base::CopyFile(local_state_file(), copy_of_local_state_file);
+ return copy_of_local_state_file;
+ }
bool IsSuccessfulSubTestOutput(const std::string& output) {
static const char* const kSubTestSuccessStrings[] = {
@@ -259,8 +251,12 @@
<< output;
}
- std::unique_ptr<PrefService> LoadLocalState() {
- return pref_service_factory_.Create(pref_registry_);
+ std::unique_ptr<PrefService> LoadLocalState(const base::FilePath& path) {
+ PrefServiceFactory pref_service_factory;
+ pref_service_factory.set_async(false);
+ pref_service_factory.set_user_prefs(
+ base::MakeRefCounted<JsonPrefStore>(path));
+ return pref_service_factory.Create(pref_registry_);
}
std::unique_ptr<metrics::CleanExitBeacon> LoadCleanExitBeacon(
@@ -275,15 +271,14 @@
private:
base::test::TaskEnvironment task_environment_;
scoped_refptr<PrefRegistrySimple> pref_registry_;
- PrefServiceFactory pref_service_factory_;
base::ScopedTempDir temp_dir_;
base::FilePath user_data_dir_;
+ base::FilePath local_state_file_;
};
} // namespace
-// crbug.com/1252344 - Failing on some windows builders; disable temporarily.
-TEST_P(FieldTrialTest, DISABLED_ExtendedSafeModeEndToEnd) {
+TEST_P(FieldTrialTest, ExtendedSafeModeEndToEnd) {
SCOPED_TRACE(field_trial_group());
// Reuse the browser_tests binary (i.e., that this test code is in), to
@@ -291,7 +286,7 @@
base::CommandLine sub_test =
base::CommandLine(base::CommandLine::ForCurrentProcess()->GetProgram());
- // Run the sub-test in the |user_data_dir()| allocated for the test case.
+ // Run the manual sub-test in the |user_data_dir()| allocated for this test.
sub_test.AppendSwitchASCII(base::kGTestFilterFlag,
"VariationsSafeModeBrowserTest.MANUAL_SubTest");
sub_test.AppendSwitch(::switches::kRunManualTestsFlag);
@@ -304,35 +299,41 @@
base::StrCat({"*", kExtendedSafeModeTrial, "/",
field_trial_group(), "/"}));
+ // Assign the test environment to be on the "Dev" channel. This ensures
+ // compatibility with both the extended safe mode trial and the crashing
+ // study in the seed.
+ sub_test.AppendSwitchASCII(switches::kFakeVariationsChannel, "dev");
+
// Explicitly avoid any terminal control characters in the output.
sub_test.AppendSwitchASCII("gtest_color", "no");
// Initial sub-test run should be successful.
RunAndExpectSuccessfulSubTest(sub_test);
- // Add command-line switch to force crash during metric initialization.
- // TODO(crbug/1249256): inject variations seed into user-data-dir that
- // enables this feature instead of using altered command line.
- base::CommandLine crashing_sub_test = sub_test;
- crashing_sub_test.AppendSwitchASCII(
- ::switches::kEnableFeatures, kForceFieldTrialSetupCrashForTesting.name);
+ // Inject the safe and crashing seeds into the Local State of |sub_test|.
+ {
+ auto local_state = LoadLocalState(local_state_file());
+ WriteSeedData(local_state.get(), kTestSeedData, kSafeSeedPrefKeys);
+ WriteSeedData(local_state.get(), kCrashingSeedData, kRegularSeedPrefKeys);
+ }
+ // Enable the field trial for extended safe mode behavior.
+ // TODO(crbug.com/1241702): Remove this once extended safe mode is launched.
SetUpExtendedSafeModeExperiment(field_trial_group());
- // The next three runs of the sub-test should crash...
- for (int expected_crash_streak = 1;
- expected_crash_streak <= kCrashStreakThreshold;
- ++expected_crash_streak) {
- RunAndExpectCrashingSubTest(crashing_sub_test);
- auto local_state = LoadLocalState();
+ // The next |kCrashStreakThreshold| runs of the sub-test should crash...
+ for (int run_count = 1; run_count <= kCrashStreakThreshold; ++run_count) {
+ SCOPED_TRACE(base::StringPrintf("Run #%d with crashing seed", run_count));
+ RunAndExpectCrashingSubTest(sub_test);
+ auto local_state = LoadLocalState(CopyOfLocalStateFile(run_count));
auto clean_exit_beacon = LoadCleanExitBeacon(local_state.get());
ASSERT_TRUE(clean_exit_beacon != nullptr);
ASSERT_FALSE(clean_exit_beacon->exited_cleanly());
- EXPECT_EQ(expected_crash_streak,
+ ASSERT_EQ(run_count,
local_state->GetInteger(prefs::kVariationsCrashStreak));
}
- // Until safe mode kicks in.
+ // Do another run and verify that safe mode kicks in, preventing the crash.
RunAndExpectSuccessfulSubTest(sub_test);
}
diff --git a/components/variations/BUILD.gn b/components/variations/BUILD.gn
index ee47994..e289f6b3 100644
--- a/components/variations/BUILD.gn
+++ b/components/variations/BUILD.gn
@@ -188,6 +188,8 @@
"field_trial_config:field_trial_config",
"proto",
"//base/test:test_support",
+ "//components/metrics",
+ "//components/prefs:prefs",
"//components/variations/service:constants",
"//third_party/zlib/google:compression_utils",
]
@@ -213,6 +215,7 @@
"variations_seed_processor_unittest.cc",
"variations_seed_simulator_unittest.cc",
"variations_seed_store_unittest.cc",
+ "variations_test_utils_unittest.cc",
]
if (is_android || is_ios) {
diff --git a/components/variations/DEPS b/components/variations/DEPS
index 2e7e5d4..6cbcd541 100644
--- a/components/variations/DEPS
+++ b/components/variations/DEPS
@@ -1,9 +1,11 @@
# This component is shared with the Chrome OS build, so it's important to limit
# dependencies to a minimal set.
+# TODO(crbug.com/1256807): Remove components/metrics, it's only used for tests.
include_rules = [
"-components",
"+components/compression",
"+components/crash/core/common",
+ "+components/metrics",
"+components/prefs",
"+components/variations",
"+crypto",
diff --git a/components/variations/service/safe_seed_manager.cc b/components/variations/service/safe_seed_manager.cc
index d8ad349..641db31 100644
--- a/components/variations/service/safe_seed_manager.cc
+++ b/components/variations/service/safe_seed_manager.cc
@@ -15,6 +15,7 @@
#include "components/variations/client_filterable_state.h"
#include "components/variations/pref_names.h"
#include "components/variations/variations_seed_store.h"
+#include "components/variations/variations_switches.h"
namespace variations {
@@ -71,14 +72,12 @@
}
bool SafeSeedManager::ShouldRunInSafeMode() const {
- // Ignore any number of failures if the --force-fieldtrials flag is set. This
- // flag is only used by developers, and there's no need to make the
- // development process flakier.
+ // Ignore any number of failures if the --disable-variations-safe-mode flag is
+ // set.
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
- ::switches::kForceFieldTrials)) {
+ switches::kDisableVariationsSafeMode)) {
return false;
}
-
int num_crashes = local_state_->GetInteger(prefs::kVariationsCrashStreak);
int num_failed_fetches =
local_state_->GetInteger(prefs::kVariationsFailedToFetchSeedStreak);
diff --git a/components/variations/service/safe_seed_manager_unittest.cc b/components/variations/service/safe_seed_manager_unittest.cc
index bb2c5b3..94ecf9a4 100644
--- a/components/variations/service/safe_seed_manager_unittest.cc
+++ b/components/variations/service/safe_seed_manager_unittest.cc
@@ -17,6 +17,7 @@
#include "components/variations/client_filterable_state.h"
#include "components/variations/pref_names.h"
#include "components/variations/variations_seed_store.h"
+#include "components/variations/variations_switches.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace variations {
@@ -191,8 +192,8 @@
// So many failures.
prefs_.SetInteger(prefs::kVariationsCrashStreak, 100);
prefs_.SetInteger(prefs::kVariationsFailedToFetchSeedStreak, 100);
- base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
- ::switches::kForceFieldTrials, "SomeFieldTrial");
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kDisableVariationsSafeMode);
SafeSeedManager safe_seed_manager(&prefs_);
EXPECT_FALSE(safe_seed_manager.ShouldRunInSafeMode());
diff --git a/components/variations/service/variations_field_trial_creator_unittest.cc b/components/variations/service/variations_field_trial_creator_unittest.cc
index 068c332..6c73d85 100644
--- a/components/variations/service/variations_field_trial_creator_unittest.cc
+++ b/components/variations/service/variations_field_trial_creator_unittest.cc
@@ -61,13 +61,14 @@
using ::testing::Return;
// Constants used to create the test seeds.
+const char kTestSeedStudyName[] = "test";
const char kTestSeedExperimentName[] = "abc";
const char kTestSafeSeedExperimentName[] = "abc.safe";
const int kTestSeedExperimentProbability = 100;
const char kTestSeedSerialNumber[] = "123";
// Constants used to mock the serialized seed state.
-const char kTestSeedData[] = "a serialized seed, 100% realistic";
+const char kTestSeedSerializedData[] = "a serialized seed, 100% realistic";
const char kTestSeedSignature[] = "a totally valid signature, I swear!";
#if !defined(OS_ANDROID)
@@ -237,7 +238,7 @@
std::string* seed_data,
std::string* base64_signature) override {
*seed = CreateTestSeed();
- *seed_data = kTestSeedData;
+ *seed_data = kTestSeedSerializedData;
*base64_signature = kTestSeedSignature;
return true;
}
@@ -463,8 +464,8 @@
ON_CALL(safe_seed_manager, ShouldRunInSafeMode())
.WillByDefault(Return(false));
EXPECT_CALL(safe_seed_manager,
- DoSetActiveSeedState(kTestSeedData, kTestSeedSignature, _,
- seed_fetch_time))
+ DoSetActiveSeedState(kTestSeedSerializedData,
+ kTestSeedSignature, _, seed_fetch_time))
.Times(1);
TestVariationsServiceClient variations_service_client;
@@ -507,8 +508,8 @@
.WillByDefault(Return(false));
const base::Time start_time = base::Time::Now();
EXPECT_CALL(safe_seed_manager,
- DoSetActiveSeedState(kTestSeedData, kTestSeedSignature, _,
- Ge(start_time)))
+ DoSetActiveSeedState(kTestSeedSerializedData, kTestSeedSignature,
+ _, Ge(start_time)))
.Times(1);
TestVariationsServiceClient variations_service_client;
@@ -639,9 +640,9 @@
prefs_.SetTime(prefs::kVariationsLastFetchTime, recent_time);
// When using the regular seed, the safe seed manager should be informed of
// the active seed state.
- EXPECT_CALL(
- safe_seed_manager,
- DoSetActiveSeedState(kTestSeedData, kTestSeedSignature, _, recent_time))
+ EXPECT_CALL(safe_seed_manager,
+ DoSetActiveSeedState(kTestSeedSerializedData, kTestSeedSignature,
+ _, recent_time))
.Times(1);
TestVariationsServiceClient variations_service_client;
diff --git a/components/variations/variations_seed_store.cc b/components/variations/variations_seed_store.cc
index 26c9a119..cd43ff3 100644
--- a/components/variations/variations_seed_store.cc
+++ b/components/variations/variations_seed_store.cc
@@ -424,6 +424,13 @@
std::string());
}
+// static
+VerifySignatureResult VariationsSeedStore::VerifySeedSignatureForTesting(
+ const std::string& seed_bytes,
+ const std::string& base64_seed_signature) {
+ return VerifySeedSignature(seed_bytes, base64_seed_signature);
+}
+
void VariationsSeedStore::ClearPrefs(SeedType seed_type) {
if (seed_type == SeedType::LATEST) {
local_state_->ClearPref(prefs::kVariationsCompressedSeed);
diff --git a/components/variations/variations_seed_store.h b/components/variations/variations_seed_store.h
index 7c61b9b..7dfa9d5 100644
--- a/components/variations/variations_seed_store.h
+++ b/components/variations/variations_seed_store.h
@@ -143,6 +143,10 @@
PrefService* local_state() { return local_state_; }
const PrefService* local_state() const { return local_state_; }
+ static VerifySignatureResult VerifySeedSignatureForTesting(
+ const std::string& seed_bytes,
+ const std::string& base64_seed_signature);
+
private:
FRIEND_TEST_ALL_PREFIXES(VariationsSeedStoreTest, VerifySeedSignature);
FRIEND_TEST_ALL_PREFIXES(VariationsSeedStoreTest, ApplyDeltaPatch);
diff --git a/components/variations/variations_seed_store_unittest.cc b/components/variations/variations_seed_store_unittest.cc
index 2957be8..b7e35f9 100644
--- a/components/variations/variations_seed_store_unittest.cc
+++ b/components/variations/variations_seed_store_unittest.cc
@@ -736,7 +736,7 @@
const VariationsSeed seed = CreateTestSeed();
const std::string serialized_seed = SerializeSeed(seed);
// A valid signature, but for a different seed.
- const std::string signature = kBase64TestSeedSignature;
+ const std::string signature = kTestSeedData.base64_signature;
ClientFilterableState client_state(base::BindOnce([] { return false; }));
client_state.locale = "en-US";
client_state.reference_date = WrapTime(12345);
@@ -785,9 +785,9 @@
TEST(VariationsSeedStoreTest, StoreSafeSeed_ValidSignature) {
std::string serialized_seed;
- ASSERT_TRUE(
- base::Base64Decode(kUncompressedBase64TestSeedData, &serialized_seed));
- const std::string signature = kBase64TestSeedSignature;
+ ASSERT_TRUE(base::Base64Decode(kTestSeedData.base64_uncompressed_data,
+ &serialized_seed));
+ const std::string signature = kTestSeedData.base64_signature;
ClientFilterableState client_state(base::BindOnce([] { return false; }));
client_state.locale = "en-US";
client_state.reference_date = WrapTime(12345);
@@ -953,8 +953,8 @@
TEST(VariationsSeedStoreTest, VerifySeedSignature) {
// A valid seed and signature pair generated using the server's private key.
const std::string uncompressed_base64_seed_data =
- kUncompressedBase64TestSeedData;
- const std::string base64_seed_signature = kBase64TestSeedSignature;
+ kTestSeedData.base64_uncompressed_data;
+ const std::string base64_seed_signature = kTestSeedData.base64_signature;
std::string base64_seed_data;
{
diff --git a/components/variations/variations_switches.cc b/components/variations/variations_switches.cc
index 7a05457..62e91ac 100644
--- a/components/variations/variations_switches.cc
+++ b/components/variations/variations_switches.cc
@@ -10,6 +10,9 @@
// Disable field trial tests configured in fieldtrial_testing_config.json.
const char kDisableFieldTrialTestingConfig[] = "disable-field-trial-config";
+// Disable variations safe mode.
+const char kDisableVariationsSafeMode[] = "disable-variations-safe-mode";
+
// TODO(asvitkine): Consider removing or renaming this functionality.
// Enables the benchmarking extensions.
const char kEnableBenchmarking[] = "enable-benchmarking";
diff --git a/components/variations/variations_switches.h b/components/variations/variations_switches.h
index 32bb2e8..8bef579 100644
--- a/components/variations/variations_switches.h
+++ b/components/variations/variations_switches.h
@@ -16,6 +16,8 @@
COMPONENT_EXPORT(VARIATIONS)
extern const char kDisableFieldTrialTestingConfig[];
COMPONENT_EXPORT(VARIATIONS)
+extern const char kDisableVariationsSafeMode[];
+COMPONENT_EXPORT(VARIATIONS)
extern const char kEnableBenchmarking[];
COMPONENT_EXPORT(VARIATIONS)
extern const char kFakeVariationsChannel[];
diff --git a/components/variations/variations_test_utils.cc b/components/variations/variations_test_utils.cc
index 559f7835..78dc6ef 100644
--- a/components/variations/variations_test_utils.cc
+++ b/components/variations/variations_test_utils.cc
@@ -7,6 +7,10 @@
#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/pref_names.h"
#include "components/variations/proto/client_variations.pb.h"
#include "components/variations/service/variations_safe_mode_constants.h"
#include "components/variations/variations_associated_data.h"
@@ -14,24 +18,79 @@
#include "third_party/zlib/google/compression_utils.h"
namespace variations {
+namespace {
-const char kUncompressedBase64TestSeedData[] =
+const char* kTestSeed_StudyNames[] = {"UMA-Uniformity-Trial-10-Percent"};
+
+const char kTestSeed_Base64UncompressedData[] =
"CigxZDI5NDY0ZmIzZDc4ZmYxNTU2ZTViNTUxYzY0NDdjYmM3NGU1ZmQwEr0BCh9VTUEtVW5pZm"
"9ybWl0eS1UcmlhbC0xMC1QZXJjZW50GICckqUFOAFCB2RlZmF1bHRKCwoHZGVmYXVsdBABSgwK"
"CGdyb3VwXzAxEAFKDAoIZ3JvdXBfMDIQAUoMCghncm91cF8wMxABSgwKCGdyb3VwXzA0EAFKDA"
"oIZ3JvdXBfMDUQAUoMCghncm91cF8wNhABSgwKCGdyb3VwXzA3EAFKDAoIZ3JvdXBfMDgQAUoM"
"Cghncm91cF8wORAB";
-const char kCompressedBase64TestSeedData[] =
+const char kTestSeed_Base64CompressedData[] =
"H4sIAAAAAAAAAOPSMEwxsjQxM0lLMk4xt0hLMzQ1NUs1TTI1NUw2MzExT05KNjdJNU1LMRDay8"
"glH+rrqBual5mWX5SbWVKpG1KUmZija2igG5BalJyaVyLRMGfSUlYLRif2lNS0xNKcEi9uLhhT"
"gNGLh4sjvSi/tCDewBCFZ4TCM0bhmaDwTFF4Zig8cxSeBQrPUoARAEVeJPrqAAAA";
-const char kBase64TestSeedSignature[] =
+const char kTestSeed_Base64Signature[] =
"MEQCIDD1IVxjzWYncun+9IGzqYjZvqxxujQEayJULTlbTGA/AiAr0oVmEgVUQZBYq5VLOSvy96"
"JkMYgzTkHPwbv7K/CmgA==";
-const char kTestSeedStudyName[] = "UMA-Uniformity-Trial-10-Percent";
+const char* kCrashingSeed_StudyNames[] = {"CrashingStudy"};
+
+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==";
+
+} // 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<const char*> 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(
@@ -87,4 +146,25 @@
return active_group;
}
+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);
+ });
+}
+
} // namespace variations
diff --git a/components/variations/variations_test_utils.h b/components/variations/variations_test_utils.h
index 76be121..f5892ab5 100644
--- a/components/variations/variations_test_utils.h
+++ b/components/variations/variations_test_utils.h
@@ -8,22 +8,61 @@
#include <set>
#include <string>
+#include "base/containers/span.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/field_trial.h"
#include "components/variations/variations_associated_data.h"
+class PrefService;
+
namespace variations {
+// Packages signed variations seed data into a tuple for use with
+// WriteSeedData(). This allows for encapsulated seed information to be created
+// below for generic test seeds as well as seeds which cause crashes.
+struct SignedSeedData {
+ base::span<const char*> study_names; // Names of all studies in the seed.
+ const char* base64_uncompressed_data;
+ const char* base64_compressed_data;
+ const char* base64_signature;
+
+ // Out-of-line constructor/destructor/copy/move required for 'complex'
+ // classes.
+ SignedSeedData(base::span<const char*> in_study_names,
+ const char* in_base64_uncompressed_data,
+ const char* in_base64_compressed_data,
+ const char* in_base64_signature);
+ ~SignedSeedData();
+ SignedSeedData(const SignedSeedData&);
+ SignedSeedData(SignedSeedData&&);
+ SignedSeedData& operator=(const SignedSeedData&);
+ SignedSeedData& operator=(SignedSeedData&&);
+};
+
+// Packages variations seed pref keys into a tuple for use with StoreSeedInfo().
+// This allow easily writing signed seed data into either the safe seed or
+// regular seed locations in Local State.
+struct SignedSeedPrefKeys {
+ const char* base64_compressed_data_key;
+ const char* base64_signature_key;
+};
+
// The test seed data is associated with a VariationsSeed with one study,
// "UMA-Uniformity-Trial-10-Percent", and ten equally weighted groups: "default"
// and "group_01" through "group_09". The study is not associated with channels,
// platforms, or features.
-//
-// The seed and signature pair were generated using the server's private key.
-extern const char kUncompressedBase64TestSeedData[];
-extern const char kCompressedBase64TestSeedData[];
-extern const char kBase64TestSeedSignature[];
-extern const char kTestSeedStudyName[];
+extern const SignedSeedData kTestSeedData;
+
+// The crashing seed data contains a CrashingStudy that enables the
+// variations::kForceFieldTrialSetupCrashForTesting feature at 100% on all
+// platforms and channels.
+extern const SignedSeedData kCrashingSeedData;
+
+// The pref keys used to store safe signed variations seed data.
+extern const SignedSeedPrefKeys kSafeSeedPrefKeys;
+
+// The pref keys used to store regular signed variations seed data.
+extern const SignedSeedPrefKeys kRegularSeedPrefKeys;
// Disables the use of the field trial testing config to exercise
// VariationsFieldTrialCreator::CreateTrialsFromSeed().
@@ -45,6 +84,19 @@
// active group. Returns the numeric value that denotes the active group.
int SetUpExtendedSafeModeExperiment(const std::string& group_name);
+// Simulates a crash by setting the clean exit pref to false and disabling
+// the steps to update the pref on clean shutdown.
+void SimulateCrash(PrefService* local_state);
+
+// Writes |seed_info| into |local_state| using the given seed |pref_keys|.
+void WriteSeedData(PrefService* local_state,
+ const SignedSeedData& seed_data,
+ const SignedSeedPrefKeys& pref_keys);
+
+// Returns true if all of the study_names listed in |seed_data| exist in the
+// (global) field trial list.
+bool FieldTrialListHasAllStudiesFrom(const SignedSeedData& seed_data);
+
} // namespace variations
#endif // COMPONENTS_VARIATIONS_VARIATIONS_TEST_UTILS_H_
diff --git a/components/variations/variations_test_utils_unittest.cc b/components/variations/variations_test_utils_unittest.cc
new file mode 100644
index 0000000..b717044
--- /dev/null
+++ b/components/variations/variations_test_utils_unittest.cc
@@ -0,0 +1,74 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// 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/ranges/algorithm.h"
+#include "components/variations/metrics.h"
+#include "components/variations/proto/variations_seed.pb.h"
+#include "components/variations/variations_seed_store.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+namespace variations {
+
+using SignedSeedDataTest = ::testing::TestWithParam<SignedSeedData>;
+
+TEST_P(SignedSeedDataTest, HasValidBase64Data) {
+ const auto& signed_seed_data = GetParam();
+
+ std::string decoded_compressed_data;
+ ASSERT_TRUE(base::Base64Decode(signed_seed_data.base64_compressed_data,
+ &decoded_compressed_data));
+
+ std::string actual_uncompressed_data;
+ ASSERT_TRUE(compression::GzipUncompress(decoded_compressed_data,
+ &actual_uncompressed_data));
+
+ std::string actual_encoded_uncompressed_data;
+ base::Base64Encode(actual_uncompressed_data,
+ &actual_encoded_uncompressed_data);
+ EXPECT_EQ(actual_encoded_uncompressed_data,
+ signed_seed_data.base64_uncompressed_data);
+
+ std::string decoded_uncompressed_data;
+ ASSERT_TRUE(base::Base64Decode(signed_seed_data.base64_uncompressed_data,
+ &decoded_uncompressed_data));
+
+ EXPECT_EQ(decoded_uncompressed_data, actual_uncompressed_data);
+}
+
+TEST_P(SignedSeedDataTest, HasValidSignature) {
+ const auto& signed_seed_data = GetParam();
+ std::string decoded_uncompressed_data;
+ ASSERT_TRUE(base::Base64Decode(signed_seed_data.base64_uncompressed_data,
+ &decoded_uncompressed_data));
+
+ const auto verify_signature_result =
+ VariationsSeedStore::VerifySeedSignatureForTesting(
+ decoded_uncompressed_data, signed_seed_data.base64_signature);
+ EXPECT_EQ(VerifySignatureResult::VALID_SIGNATURE, verify_signature_result);
+}
+
+TEST_P(SignedSeedDataTest, HasStudyNames) {
+ const auto& signed_seed_data = GetParam();
+ std::string decoded_uncompressed_data;
+ ASSERT_TRUE(base::Base64Decode(signed_seed_data.base64_uncompressed_data,
+ &decoded_uncompressed_data));
+ VariationsSeed seed;
+ ASSERT_TRUE(seed.ParseFromString(decoded_uncompressed_data));
+ std::vector<std::string> parsed_study_names;
+ base::ranges::transform(seed.study(), std::back_inserter(parsed_study_names),
+ [](const Study& s) { return s.name(); });
+ EXPECT_THAT(parsed_study_names, ::testing::UnorderedElementsAreArray(
+ signed_seed_data.study_names));
+}
+
+INSTANTIATE_TEST_CASE_P(VariationsTestUtils,
+ SignedSeedDataTest,
+ ::testing::Values(kTestSeedData, kCrashingSeedData));
+
+} // namespace variations
\ No newline at end of file
diff --git a/ios/chrome/browser/variations/variations_app_interface.mm b/ios/chrome/browser/variations/variations_app_interface.mm
index 716dc9a..9cf8e0d6 100644
--- a/ios/chrome/browser/variations/variations_app_interface.mm
+++ b/ios/chrome/browser/variations/variations_app_interface.mm
@@ -54,7 +54,7 @@
}
+ (BOOL)fieldTrialExistsForTestSeed {
- return base::FieldTrialList::TrialExists(variations::kTestSeedStudyName);
+ return variations::FieldTrialListHasAllStudiesFrom(variations::kTestSeedData);
}
+ (BOOL)hasSafeSeed {
@@ -66,10 +66,8 @@
+ (void)setTestSafeSeedAndSignature {
PrefService* prefService = GetApplicationContext()->GetLocalState();
- prefService->SetString(variations::prefs::kVariationsSafeCompressedSeed,
- variations::kCompressedBase64TestSeedData);
- prefService->SetString(variations::prefs::kVariationsSafeSeedSignature,
- variations::kBase64TestSeedSignature);
+ variations::WriteSeedData(prefService, variations::kTestSeedData,
+ variations::kSafeSeedPrefKeys);
}
+ (int)crashStreak {
diff --git a/ios/chrome/browser/variations/variations_safe_mode_egtest.mm b/ios/chrome/browser/variations/variations_safe_mode_egtest.mm
index 73f3dbd..997c4662 100644
--- a/ios/chrome/browser/variations/variations_safe_mode_egtest.mm
+++ b/ios/chrome/browser/variations/variations_safe_mode_egtest.mm
@@ -114,7 +114,7 @@
GREYAssertTrue([VariationsAppInterface hasSafeSeed],
@"The variations safe seed pref should be set.");
GREYAssertFalse([VariationsAppInterface fieldTrialExistsForTestSeed],
- @"There should be no field trial for |kTestSeedStudyName|.");
+ @"There should be no field trials from |kTestSeedData|.");
// Crash the app three times since a crash streak of three or more triggers
// variations safe mode. Also, verify the crash streak and the field trial
@@ -124,12 +124,12 @@
[[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];
[self checkCrashStreakValue:1];
GREYAssertFalse([VariationsAppInterface fieldTrialExistsForTestSeed],
- @"There should be no field trial for |kTestSeedStudyName|.");
+ @"There should be no field trials from |kTestSeedData|.");
// Second crash.
[[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];
[self checkCrashStreakValue:2];
GREYAssertFalse([VariationsAppInterface fieldTrialExistsForTestSeed],
- @"There should be no field trial for |kTestSeedStudyName|.");
+ @"There should be no field trials from |kTestSeedData|.");
// Third crash.
[[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];
[self checkCrashStreakValue:3];
@@ -138,7 +138,7 @@
// Verify that Chrome fell back to variations safe mode by checking that there
// is a field trial for the test safe seed's study.
GREYAssertTrue([VariationsAppInterface fieldTrialExistsForTestSeed],
- @"There should be a field trial for |kTestSeedStudyName|.");
+ @"There should be field trials from |kTestSeedData|.");
}
// Tests that variations seed fetch failures trigger variations safe mode.
@@ -153,7 +153,7 @@
// Verify that there is no field trial associated with the test safe seed's
// sole study.
GREYAssertFalse([VariationsAppInterface fieldTrialExistsForTestSeed],
- @"There should be no field trial for |kTestSeedStudyName|.");
+ @"There should be no field trials from |kTestSeedData|.");
// Persist the local state pref changes made above and in setUp().
[[AppLaunchManager sharedManager]
@@ -168,7 +168,7 @@
// Verify that Chrome fell back to variations safe mode by checking that there
// is a field trial for the test safe seed's study.
GREYAssertTrue([VariationsAppInterface fieldTrialExistsForTestSeed],
- @"There should be a field trial for |kTestSeedStudyName|.");
+ @"There should be field trials from |kTestSeedData|.");
}
// Tests that variations safe mode is not triggered.
@@ -187,7 +187,7 @@
// Verify that there is no field trial associated with the test safe seed's
// sole study.
GREYAssertFalse([VariationsAppInterface fieldTrialExistsForTestSeed],
- @"There should be no field trial for |kTestSeedStudyName|.");
+ @"There should be no field trials from |kTestSeedData|.");
// Persist the local state pref changes made above and in setUp().
[[AppLaunchManager sharedManager]
@@ -202,7 +202,7 @@
// Verify that Chrome did not fall back to variations safe mode by checking
// that there isn't a field trial for the test safe seed's study.
GREYAssertFalse([VariationsAppInterface fieldTrialExistsForTestSeed],
- @"There should be no field trial for |kTestSeedStudyName|.");
+ @"There should be no field trials from |kTestSeedData|.");
}
@end