123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447 |
- // 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/metrics/clean_exit_beacon.h"
- #include <memory>
- #include <string>
- #include "base/files/file_path.h"
- #include "base/files/file_util.h"
- #include "base/files/scoped_temp_dir.h"
- #include "base/memory/scoped_refptr.h"
- #include "base/metrics/field_trial.h"
- #include "base/test/gtest_util.h"
- #include "base/test/metrics/histogram_tester.h"
- #include "base/test/mock_entropy_provider.h"
- #include "base/test/task_environment.h"
- #include "base/time/time.h"
- #include "build/build_config.h"
- #include "components/metrics/metrics_pref_names.h"
- #include "components/prefs/pref_registry_simple.h"
- #include "components/prefs/pref_service_factory.h"
- #include "components/prefs/testing_pref_service.h"
- #include "components/prefs/testing_pref_store.h"
- #include "components/variations/pref_names.h"
- #include "components/variations/variations_test_utils.h"
- #include "testing/gtest/include/gtest/gtest.h"
- #include "third_party/abseil-cpp/absl/types/optional.h"
- namespace metrics {
- namespace {
- const wchar_t kDummyWindowsRegistryKey[] = L"";
- } // namespace
- class TestCleanExitBeacon : public CleanExitBeacon {
- public:
- explicit TestCleanExitBeacon(
- PrefService* local_state,
- const base::FilePath& user_data_dir = base::FilePath())
- : CleanExitBeacon(kDummyWindowsRegistryKey, user_data_dir, local_state) {
- Initialize();
- }
- ~TestCleanExitBeacon() override = default;
- };
- class CleanExitBeaconTest : public ::testing::Test {
- public:
- void SetUp() override {
- metrics::CleanExitBeacon::RegisterPrefs(prefs_.registry());
- ASSERT_TRUE(user_data_dir_.CreateUniqueTempDir());
- }
- protected:
- base::HistogramTester histogram_tester_;
- TestingPrefServiceSimple prefs_;
- base::ScopedTempDir user_data_dir_;
- private:
- base::test::TaskEnvironment task_environment_;
- };
- struct BadBeaconTestParams {
- const std::string test_name;
- bool beacon_file_exists;
- const std::string beacon_file_contents;
- BeaconFileState beacon_file_state;
- };
- // Used for testing beacon files that are not well-formed, do not exist, etc.
- class BadBeaconFileTest
- : public testing::WithParamInterface<BadBeaconTestParams>,
- public CleanExitBeaconTest {};
- struct BeaconConsistencyTestParams {
- // Inputs:
- const std::string test_name;
- absl::optional<bool> beacon_file_beacon_value;
- absl::optional<bool> platform_specific_beacon_value;
- absl::optional<bool> local_state_beacon_value;
- // Result:
- CleanExitBeaconConsistency expected_consistency;
- };
- #if BUILDFLAG(IS_IOS)
- // Used for testing the logic that emits to the UMA.CleanExitBeaconConsistency3
- // histogram.
- class BeaconFileAndPlatformBeaconConsistencyTest
- : public testing::WithParamInterface<BeaconConsistencyTestParams>,
- public CleanExitBeaconTest {};
- #endif // BUILDFLAG(IS_IOS)
- // Verify that the crash streak metric is 0 when default pref values are used.
- TEST_F(CleanExitBeaconTest, CrashStreakMetricWithDefaultPrefs) {
- CleanExitBeacon::ResetStabilityExitedCleanlyForTesting(&prefs_);
- TestCleanExitBeacon clean_exit_beacon(&prefs_);
- histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes", 0,
- 1);
- }
- // Verify that the crash streak metric is 0 when prefs are explicitly set to
- // their defaults.
- TEST_F(CleanExitBeaconTest, CrashStreakMetricWithNoCrashes) {
- // The default value for kStabilityExitedCleanly is true, but defaults can
- // change, so we explicitly set it to true here. Similarly, we explicitly set
- // kVariationsCrashStreak to 0.
- CleanExitBeacon::SetStabilityExitedCleanlyForTesting(&prefs_, true);
- prefs_.SetInteger(variations::prefs::kVariationsCrashStreak, 0);
- TestCleanExitBeacon clean_exit_beacon(&prefs_);
- histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes", 0,
- 1);
- }
- // Verify that the crash streak metric is correctly recorded when there is a
- // non-zero crash streak.
- TEST_F(CleanExitBeaconTest, CrashStreakMetricWithSomeCrashes) {
- // The default value for kStabilityExitedCleanly is true, but defaults can
- // change, so we explicitly set it to true here.
- CleanExitBeacon::SetStabilityExitedCleanlyForTesting(&prefs_, true);
- prefs_.SetInteger(variations::prefs::kVariationsCrashStreak, 1);
- TestCleanExitBeacon clean_exit_beacon(&prefs_);
- histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes", 1,
- 1);
- }
- // Verify that the crash streak is correctly incremented and recorded when the
- // last Chrome session did not exit cleanly.
- TEST_F(CleanExitBeaconTest, CrashIncrementsCrashStreak) {
- CleanExitBeacon::SetStabilityExitedCleanlyForTesting(&prefs_, false);
- prefs_.SetInteger(variations::prefs::kVariationsCrashStreak, 1);
- TestCleanExitBeacon clean_exit_beacon(&prefs_);
- EXPECT_EQ(prefs_.GetInteger(variations::prefs::kVariationsCrashStreak), 2);
- histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes", 2,
- 1);
- }
- // Verify that the crash streak is correctly incremented and recorded when the
- // last Chrome session did not exit cleanly and the default crash streak value
- // is used.
- TEST_F(CleanExitBeaconTest,
- CrashIncrementsCrashStreakWithDefaultCrashStreakPref) {
- CleanExitBeacon::SetStabilityExitedCleanlyForTesting(&prefs_, false);
- TestCleanExitBeacon clean_exit_beacon(&prefs_);
- EXPECT_EQ(prefs_.GetInteger(variations::prefs::kVariationsCrashStreak), 1);
- histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes", 1,
- 1);
- }
- // Verify that no attempt is made to read the beacon file when no user
- // data dir is provided.
- TEST_F(CleanExitBeaconTest, InitWithoutUserDataDir) {
- TestCleanExitBeacon beacon(&prefs_, base::FilePath());
- EXPECT_TRUE(beacon.GetUserDataDirForTesting().empty());
- EXPECT_TRUE(beacon.GetBeaconFilePathForTesting().empty());
- histogram_tester_.ExpectTotalCount(
- "Variations.ExtendedSafeMode.BeaconFileStateAtStartup", 0);
- }
- INSTANTIATE_TEST_SUITE_P(
- All,
- BadBeaconFileTest,
- ::testing::Values(
- BadBeaconTestParams{
- .test_name = "NoVariationsFile",
- .beacon_file_exists = false,
- .beacon_file_contents = "",
- .beacon_file_state = BeaconFileState::kNotDeserializable},
- BadBeaconTestParams{
- .test_name = "EmptyVariationsFile",
- .beacon_file_exists = true,
- .beacon_file_contents = "",
- .beacon_file_state = BeaconFileState::kNotDeserializable},
- BadBeaconTestParams{
- .test_name = "NotDictionary",
- .beacon_file_exists = true,
- .beacon_file_contents = "{abc123",
- .beacon_file_state = BeaconFileState::kNotDeserializable},
- BadBeaconTestParams{
- .test_name = "EmptyDictionary",
- .beacon_file_exists = true,
- .beacon_file_contents = "{}",
- .beacon_file_state = BeaconFileState::kMissingDictionary},
- BadBeaconTestParams{
- .test_name = "MissingCrashStreak",
- .beacon_file_exists = true,
- .beacon_file_contents =
- "{\"user_experience_metrics.stability.exited_cleanly\":true}",
- .beacon_file_state = BeaconFileState::kMissingCrashStreak},
- BadBeaconTestParams{
- .test_name = "MissingBeacon",
- .beacon_file_exists = true,
- .beacon_file_contents = "{\"variations_crash_streak\":1}",
- .beacon_file_state = BeaconFileState::kMissingBeacon}),
- [](const ::testing::TestParamInfo<BadBeaconTestParams>& params) {
- return params.param.test_name;
- });
- // Verify that the inability to get the beacon file's contents for a plethora of
- // reasons (a) doesn't crash and (b) correctly records the BeaconFileState
- // metric.
- TEST_P(BadBeaconFileTest, InitWithUnusableBeaconFile) {
- BadBeaconTestParams params = GetParam();
- const base::FilePath user_data_dir_path = user_data_dir_.GetPath();
- if (params.beacon_file_exists) {
- const base::FilePath temp_beacon_file_path =
- user_data_dir_path.Append(kCleanExitBeaconFilename);
- ASSERT_LT(0, base::WriteFile(temp_beacon_file_path,
- params.beacon_file_contents.data()));
- }
- TestCleanExitBeacon beacon(&prefs_, user_data_dir_path);
- histogram_tester_.ExpectUniqueSample(
- "Variations.ExtendedSafeMode.BeaconFileStateAtStartup",
- params.beacon_file_state, 1);
- }
- // Verify that successfully reading the beacon file's contents results in
- // correctly (a) setting the |did_previous_session_exit_cleanly_| field and (b)
- // recording metrics when the last session exited cleanly.
- TEST_F(CleanExitBeaconTest, InitWithBeaconFile) {
- const base::FilePath user_data_dir_path = user_data_dir_.GetPath();
- const base::FilePath temp_beacon_file_path =
- user_data_dir_path.Append(kCleanExitBeaconFilename);
- const int num_crashes = 2;
- ASSERT_LT(0, base::WriteFile(
- temp_beacon_file_path,
- CleanExitBeacon::CreateBeaconFileContentsForTesting(
- /*exited_cleanly=*/true, /*crash_streak=*/num_crashes)
- .data()));
- TestCleanExitBeacon clean_exit_beacon(&prefs_, user_data_dir_path);
- histogram_tester_.ExpectUniqueSample(
- "Variations.ExtendedSafeMode.BeaconFileStateAtStartup",
- BeaconFileState::kReadable, 1);
- EXPECT_TRUE(clean_exit_beacon.exited_cleanly());
- histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes",
- num_crashes, 1);
- }
- // Verify that successfully reading the beacon file's contents results in
- // correctly (a) setting the |did_previous_session_exit_cleanly_| field and (b)
- // recording metrics when the last session did not exit cleanly.
- TEST_F(CleanExitBeaconTest, InitWithCrashAndBeaconFile) {
- const base::FilePath user_data_dir_path = user_data_dir_.GetPath();
- const base::FilePath temp_beacon_file_path =
- user_data_dir_path.Append(kCleanExitBeaconFilename);
- const int last_session_num_crashes = 2;
- ASSERT_LT(0,
- base::WriteFile(temp_beacon_file_path,
- CleanExitBeacon::CreateBeaconFileContentsForTesting(
- /*exited_cleanly=*/false,
- /*crash_streak=*/last_session_num_crashes)
- .data()));
- const int updated_num_crashes = last_session_num_crashes + 1;
- TestCleanExitBeacon clean_exit_beacon(&prefs_, user_data_dir_path);
- histogram_tester_.ExpectUniqueSample(
- "Variations.ExtendedSafeMode.BeaconFileStateAtStartup",
- BeaconFileState::kReadable, 1);
- EXPECT_FALSE(clean_exit_beacon.exited_cleanly());
- histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes",
- updated_num_crashes, 1);
- }
- TEST_F(CleanExitBeaconTest, WriteBeaconValueWhenNotExitingCleanly) {
- const base::FilePath user_data_dir_path = user_data_dir_.GetPath();
- const base::FilePath beacon_file_path =
- user_data_dir_path.Append(kCleanExitBeaconFilename);
- ASSERT_FALSE(base::PathExists(beacon_file_path));
- TestCleanExitBeacon clean_exit_beacon(&prefs_, user_data_dir_path);
- clean_exit_beacon.WriteBeaconValue(/*exited_cleanly=*/false,
- /*is_extended_safe_mode=*/true);
- // Verify that the beacon file exists and has well-formed contents after
- // updating the beacon value.
- EXPECT_TRUE(base::PathExists(beacon_file_path));
- std::string beacon_file_contents1;
- ASSERT_TRUE(base::ReadFileToString(beacon_file_path, &beacon_file_contents1));
- EXPECT_EQ(beacon_file_contents1,
- "{\"user_experience_metrics.stability.exited_cleanly\":false,"
- "\"variations_crash_streak\":0}");
- // Verify that the BeaconFileWrite metric was emitted.
- histogram_tester_.ExpectUniqueSample(
- "Variations.ExtendedSafeMode.BeaconFileWrite", 1, 1);
- // Write the beacon value again. This is done because it is possible for
- // WriteBeaconValue() to be called twice during startup or shutdown with the
- // same value for |exited_cleanly|.
- clean_exit_beacon.WriteBeaconValue(/*exited_cleanly*/ false,
- /*is_extended_safe_mode=*/false);
- // Verify that the beacon file exists and has well-formed contents after
- // updating the beacon value.
- EXPECT_TRUE(base::PathExists(beacon_file_path));
- std::string beacon_file_contents2;
- ASSERT_TRUE(base::ReadFileToString(beacon_file_path, &beacon_file_contents2));
- EXPECT_EQ(beacon_file_contents2,
- "{\"user_experience_metrics.stability.exited_cleanly\":false,"
- "\"variations_crash_streak\":0}");
- // Verify that the BeaconFileWrite metric was not emitted a second time. The
- // beacon file should not have been written again since the beacon value did
- // not change.
- histogram_tester_.ExpectUniqueSample(
- "Variations.ExtendedSafeMode.BeaconFileWrite", 1, 1);
- }
- TEST_F(CleanExitBeaconTest, WriteBeaconValueWhenExitingCleanly) {
- const base::FilePath user_data_dir_path = user_data_dir_.GetPath();
- const base::FilePath beacon_file_path =
- user_data_dir_path.Append(kCleanExitBeaconFilename);
- ASSERT_FALSE(base::PathExists(beacon_file_path));
- TestCleanExitBeacon clean_exit_beacon(&prefs_, user_data_dir_path);
- clean_exit_beacon.WriteBeaconValue(/*exited_cleanly=*/true,
- /*is_extended_safe_mode=*/false);
- // Verify that the beacon file exists and has well-formed contents after
- // updating the beacon value.
- EXPECT_TRUE(base::PathExists(beacon_file_path));
- std::string beacon_file_contents1;
- ASSERT_TRUE(base::ReadFileToString(beacon_file_path, &beacon_file_contents1));
- EXPECT_EQ(beacon_file_contents1,
- "{\"user_experience_metrics.stability.exited_cleanly\":true,"
- "\"variations_crash_streak\":0}");
- // Verify that the BeaconFileWrite metric was emitted.
- histogram_tester_.ExpectUniqueSample(
- "Variations.ExtendedSafeMode.BeaconFileWrite", 1, 1);
- // Write the beacon value again. This is done because it is possible for
- // WriteBeaconValue() to be called twice during startup or shutdown with the
- // same value for |exited_cleanly|.
- clean_exit_beacon.WriteBeaconValue(/*exited_cleanly*/ true,
- /*is_extended_safe_mode=*/false);
- // Verify that the beacon file exists and has well-formed contents after
- // updating the beacon value.
- EXPECT_TRUE(base::PathExists(beacon_file_path));
- std::string beacon_file_contents2;
- ASSERT_TRUE(base::ReadFileToString(beacon_file_path, &beacon_file_contents2));
- EXPECT_EQ(beacon_file_contents2,
- "{\"user_experience_metrics.stability.exited_cleanly\":true,"
- "\"variations_crash_streak\":0}");
- // Verify that the BeaconFileWrite metric was not emitted a second time. The
- // beacon file should not have been written again since the beacon value did
- // not change.
- histogram_tester_.ExpectUniqueSample(
- "Variations.ExtendedSafeMode.BeaconFileWrite", 1, 1);
- }
- // Verify that there's a DCHECK when attempting to write a clean beacon with
- // |is_extended_safe_mode| set to true. When |is_extended_safe_mode| is true,
- // the only valid value for |exited_cleanly| is false.
- TEST_F(CleanExitBeaconTest, InvalidWriteBeaconValueArgsTriggerDcheck) {
- TestCleanExitBeacon clean_exit_beacon(&prefs_, user_data_dir_.GetPath());
- EXPECT_DCHECK_DEATH(
- clean_exit_beacon.WriteBeaconValue(/*exited_cleanly=*/true,
- /*is_extended_safe_mode=*/true));
- }
- #if BUILDFLAG(IS_IOS)
- // Verify the logic for recording UMA.CleanExitBeaconConsistency3.
- INSTANTIATE_TEST_SUITE_P(
- All,
- BeaconFileAndPlatformBeaconConsistencyTest,
- ::testing::Values(
- BeaconConsistencyTestParams{
- .test_name = "MissingMissing",
- .expected_consistency =
- CleanExitBeaconConsistency::kMissingMissing},
- BeaconConsistencyTestParams{
- .test_name = "MissingClean",
- .platform_specific_beacon_value = true,
- .expected_consistency = CleanExitBeaconConsistency::kMissingClean},
- BeaconConsistencyTestParams{
- .test_name = "MissingDirty",
- .platform_specific_beacon_value = false,
- .expected_consistency = CleanExitBeaconConsistency::kMissingDirty},
- BeaconConsistencyTestParams{
- .test_name = "CleanMissing",
- .beacon_file_beacon_value = true,
- .expected_consistency = CleanExitBeaconConsistency::kCleanMissing},
- BeaconConsistencyTestParams{
- .test_name = "DirtyMissing",
- .beacon_file_beacon_value = false,
- .expected_consistency = CleanExitBeaconConsistency::kDirtyMissing},
- BeaconConsistencyTestParams{
- .test_name = "CleanClean",
- .beacon_file_beacon_value = true,
- .platform_specific_beacon_value = true,
- .expected_consistency = CleanExitBeaconConsistency::kCleanClean},
- BeaconConsistencyTestParams{
- .test_name = "CleanDirty",
- .beacon_file_beacon_value = true,
- .platform_specific_beacon_value = false,
- .expected_consistency = CleanExitBeaconConsistency::kCleanDirty},
- BeaconConsistencyTestParams{
- .test_name = "DirtyClean",
- .beacon_file_beacon_value = false,
- .platform_specific_beacon_value = true,
- .expected_consistency = CleanExitBeaconConsistency::kDirtyClean},
- BeaconConsistencyTestParams{
- .test_name = "DirtyDirty",
- .beacon_file_beacon_value = false,
- .platform_specific_beacon_value = false,
- .expected_consistency = CleanExitBeaconConsistency::kDirtyDirty}),
- [](const ::testing::TestParamInfo<BeaconConsistencyTestParams>& params) {
- return params.param.test_name;
- });
- TEST_P(BeaconFileAndPlatformBeaconConsistencyTest, BeaconConsistency) {
- // Verify that the beacon file is not present. Unless set below, this beacon
- // is considered missing.
- const base::FilePath user_data_dir_path = user_data_dir_.GetPath();
- const base::FilePath temp_beacon_file_path =
- user_data_dir_path.Append(kCleanExitBeaconFilename);
- ASSERT_FALSE(base::PathExists(temp_beacon_file_path));
- // Clear the platform-specific beacon. Unless set below, this beacon is also
- // considered missing.
- CleanExitBeacon::ResetStabilityExitedCleanlyForTesting(&prefs_);
- BeaconConsistencyTestParams params = GetParam();
- if (params.beacon_file_beacon_value) {
- ASSERT_LT(
- 0, base::WriteFile(
- temp_beacon_file_path,
- CleanExitBeacon::CreateBeaconFileContentsForTesting(
- /*exited_cleanly=*/params.beacon_file_beacon_value.value(),
- /*crash_streak=*/0)
- .data()));
- }
- if (params.platform_specific_beacon_value) {
- CleanExitBeacon::SetUserDefaultsBeacon(
- /*exited_cleanly=*/params.platform_specific_beacon_value.value());
- }
- TestCleanExitBeacon clean_exit_beacon(&prefs_, user_data_dir_path);
- histogram_tester_.ExpectUniqueSample("UMA.CleanExitBeaconConsistency3",
- params.expected_consistency, 1);
- }
- #endif // BUILDFLAG(IS_IOS)
- } // namespace metrics
|