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