blob: 26c90714f4d0f01f1fc49c305badaefcfcd4e9f8 [file] [log] [blame]
// 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.
// The tests in this file verify the behavior of variations safe mode. The tests
// should be kept in sync with those in ios/chrome/browser/variations/
// variations_safe_mode_egtest.mm.
#include <string>
#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"
#include "base/test/test_switches.h"
#include "chrome/browser/browser_process.h"
#include "chrome/common/chrome_paths.h"
#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_service.h"
#include "components/prefs/json_pref_store.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/pref_service_factory.h"
#include "components/variations/metrics.h"
#include "components/variations/pref_names.h"
#include "components/variations/service/variations_field_trial_creator.h"
#include "components/variations/service/variations_safe_mode_constants.h"
#include "components/variations/service/variations_service.h"
#include "components/variations/variations_switches.h"
#include "components/variations/variations_test_utils.h"
#include "components/version_info/channel.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace variations {
class VariationsSafeModeBrowserTest : public InProcessBrowserTest {
public:
VariationsSafeModeBrowserTest() { DisableTestingConfig(); }
~VariationsSafeModeBrowserTest() override = default;
protected:
base::HistogramTester histogram_tester_;
};
IN_PROC_BROWSER_TEST_F(VariationsSafeModeBrowserTest,
PRE_PRE_PRE_ThreeCrashesTriggerSafeMode) {
// The PRE test mechanism is used to set prefs in the local state file before
// the next browser test runs. No InProcessBrowserTest functions allow this
// 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();
WriteSeedData(local_state, kTestSeedData, kSafeSeedPrefKeys);
SimulateCrash(local_state);
}
IN_PROC_BROWSER_TEST_F(VariationsSafeModeBrowserTest,
PRE_PRE_ThreeCrashesTriggerSafeMode) {
SimulateCrash(g_browser_process->local_state());
}
IN_PROC_BROWSER_TEST_F(VariationsSafeModeBrowserTest,
PRE_ThreeCrashesTriggerSafeMode) {
SimulateCrash(g_browser_process->local_state());
}
IN_PROC_BROWSER_TEST_F(VariationsSafeModeBrowserTest,
ThreeCrashesTriggerSafeMode) {
EXPECT_EQ(g_browser_process->local_state()->GetInteger(
prefs::kVariationsCrashStreak),
3);
histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes", 3,
1);
// Verify that Chrome fell back to a safe seed, which happens during browser
// test setup.
histogram_tester_.ExpectUniqueSample(
"Variations.SafeMode.LoadSafeSeed.Result", LoadSeedResult::kSuccess, 1);
histogram_tester_.ExpectUniqueSample("Variations.SeedUsage",
SeedUsage::kSafeSeedUsed, 1);
// Verify that |kTestSeedData| has been applied.
EXPECT_TRUE(FieldTrialListHasAllStudiesFrom(kTestSeedData));
}
IN_PROC_BROWSER_TEST_F(VariationsSafeModeBrowserTest,
PRE_FetchFailuresTriggerSafeMode) {
// The PRE test mechanism is used to set prefs in the local state file before
// the next browser test runs. No InProcessBrowserTest functions allow this
// 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();
local_state->SetInteger(prefs::kVariationsFailedToFetchSeedStreak, 25);
WriteSeedData(local_state, kTestSeedData, kSafeSeedPrefKeys);
}
IN_PROC_BROWSER_TEST_F(VariationsSafeModeBrowserTest,
FetchFailuresTriggerSafeMode) {
histogram_tester_.ExpectUniqueSample(
"Variations.SafeMode.Streak.FetchFailures", 25, 1);
// Verify that Chrome fell back to a safe seed, which happens during browser
// test setup.
histogram_tester_.ExpectUniqueSample(
"Variations.SafeMode.LoadSafeSeed.Result", LoadSeedResult::kSuccess, 1);
histogram_tester_.ExpectUniqueSample("Variations.SeedUsage",
SeedUsage::kSafeSeedUsed, 1);
// Verify that |kTestSeedData| has been applied.
EXPECT_TRUE(FieldTrialListHasAllStudiesFrom(kTestSeedData));
}
IN_PROC_BROWSER_TEST_F(VariationsSafeModeBrowserTest,
PRE_DoNotTriggerSafeMode) {
// The PRE test mechanism is used to set prefs in the local state file before
// the next browser test runs. No InProcessBrowserTest functions allow this
// 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();
local_state->SetInteger(prefs::kVariationsCrashStreak, 2);
local_state->SetInteger(prefs::kVariationsFailedToFetchSeedStreak, 24);
WriteSeedData(local_state, kTestSeedData, kRegularSeedPrefKeys);
}
IN_PROC_BROWSER_TEST_F(VariationsSafeModeBrowserTest, DoNotTriggerSafeMode) {
histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes", 2,
1);
histogram_tester_.ExpectUniqueSample(
"Variations.SafeMode.Streak.FetchFailures", 24, 1);
// Verify that Chrome applied the regular seed.
histogram_tester_.ExpectUniqueSample("Variations.SeedLoadResult",
LoadSeedResult::kSuccess, 1);
histogram_tester_.ExpectUniqueSample("Variations.SeedUsage",
SeedUsage::kRegularSeedUsed, 1);
}
// This test code is programmatically launched by the SafeModeEndToEnd
// test below. Its primary purpose is to provide an entry-point by
// which the SafeModeEndToEnd test can cause the Field Trial Setup
// code to be exercised. For some launches, the setup code is expected
// to crash before reaching the test body; the test body simply verifies
// that the test is using the user-data-dir configured on the command-line.
//
// The MANUAL_ prefix prevents the test from running unless explicitly
// invoked.
IN_PROC_BROWSER_TEST_F(VariationsSafeModeBrowserTest, MANUAL_SubTest) {
// Validate that Chrome is running with the user-data-dir specified on the
// command-line.
base::FilePath expected_user_data_dir =
base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
::switches::kUserDataDir);
base::FilePath actual_user_data_dir;
ASSERT_TRUE(
base::PathService::Get(chrome::DIR_USER_DATA, &actual_user_data_dir));
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 {
class FieldTrialTest : public ::testing::Test {
public:
void SetUp() override {
::testing::Test::SetUp();
metrics::CleanExitBeacon::SkipCleanShutdownStepsForTesting();
pref_registry_ = base::MakeRefCounted<PrefRegistrySimple>();
metrics::MetricsService::RegisterPrefs(pref_registry_.get());
variations::VariationsService::RegisterPrefs(pref_registry_.get());
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
user_data_dir_ = temp_dir_.GetPath().AppendASCII("user-data-dir");
local_state_file_ = user_data_dir_.AppendASCII("Local State");
}
protected:
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[] = {
"Running 1 test from 1 test suite",
"OK ] VariationsSafeModeBrowserTest.MANUAL_SubTest",
"1 test from VariationsSafeModeBrowserTest",
"1 test from 1 test suite ran",
};
auto is_in_output = [&](const char* s) {
return base::Contains(output, s);
};
return base::ranges::all_of(kSubTestSuccessStrings, is_in_output);
}
bool IsCrashingSubTestOutput(const std::string& output) {
const char* const kSubTestCrashStrings[] = {
"Running 1 test from 1 test suite",
"VariationsSafeModeBrowserTest.MANUAL_SubTest",
"crash_for_testing",
};
auto is_in_output = [&](const char* s) {
return base::Contains(output, s);
};
return base::ranges::all_of(kSubTestCrashStrings, is_in_output);
}
void RunAndExpectSuccessfulSubTest(
const base::CommandLine& sub_test_command) {
std::string output;
base::GetAppOutputAndError(sub_test_command, &output);
EXPECT_TRUE(IsSuccessfulSubTestOutput(output))
<< "Did not find success signals in output:\n"
<< output;
}
void RunAndExpectCrashingSubTest(const base::CommandLine& sub_test_command) {
std::string output;
base::GetAppOutputAndError(sub_test_command, &output);
EXPECT_FALSE(IsSuccessfulSubTestOutput(output))
<< "Expected crash but found success signals in output:\n"
<< output;
EXPECT_TRUE(IsCrashingSubTestOutput(output))
<< "Did not find crash signals in output:\n"
<< output;
}
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(
PrefService* pref_service) {
static constexpr wchar_t kDummyWindowsRegistryKey[] = L"";
auto clean_exit_beacon = std::make_unique<metrics::CleanExitBeacon>(
kDummyWindowsRegistryKey, user_data_dir(), pref_service,
version_info::Channel::UNKNOWN);
clean_exit_beacon->Initialize();
return clean_exit_beacon;
}
private:
base::test::TaskEnvironment task_environment_;
scoped_refptr<PrefRegistrySimple> pref_registry_;
base::ScopedTempDir temp_dir_;
base::FilePath user_data_dir_;
base::FilePath local_state_file_;
};
} // namespace
TEST_F(FieldTrialTest, ExtendedSafeModeEndToEnd) {
// Reuse the browser_tests binary (i.e., that this test code is in), to
// manually run the sub-test.
base::CommandLine sub_test =
base::CommandLine(base::CommandLine::ForCurrentProcess()->GetProgram());
// 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);
sub_test.AppendSwitch(::switches::kSingleProcessTests);
sub_test.AppendSwitchPath(::switches::kUserDataDir, user_data_dir());
const std::string group_name = kSignalAndWriteViaFileUtilGroup;
// Select the extended variations safe mode field trial group. The "*"
// prefix forces the experiment/trial state to "active" at startup.
sub_test.AppendSwitchASCII(
::switches::kForceFieldTrials,
base::StrCat({"*", kExtendedSafeModeTrial, "/", group_name, "/"}));
// 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);
// 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);
}
SetUpExtendedSafeModeExperiment(group_name);
// 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());
ASSERT_EQ(run_count,
local_state->GetInteger(prefs::kVariationsCrashStreak));
}
// Do another run and verify that safe mode kicks in, preventing the crash.
RunAndExpectSuccessfulSubTest(sub_test);
}
} // namespace variations