444 lines
19 KiB
C++
444 lines
19 KiB
C++
// Copyright 2021 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "components/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_TRUE(
|
|
base::WriteFile(temp_beacon_file_path, params.beacon_file_contents));
|
|
}
|
|
|
|
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_TRUE(base::WriteFile(
|
|
temp_beacon_file_path,
|
|
CleanExitBeacon::CreateBeaconFileContentsForTesting(
|
|
/*exited_cleanly=*/true, /*crash_streak=*/num_crashes)));
|
|
|
|
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_TRUE(
|
|
base::WriteFile(temp_beacon_file_path,
|
|
CleanExitBeacon::CreateBeaconFileContentsForTesting(
|
|
/*exited_cleanly=*/false,
|
|
/*crash_streak=*/last_session_num_crashes)));
|
|
|
|
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_TRUE(base::WriteFile(
|
|
temp_beacon_file_path,
|
|
CleanExitBeacon::CreateBeaconFileContentsForTesting(
|
|
/*exited_cleanly=*/params.beacon_file_beacon_value.value(),
|
|
/*crash_streak=*/0)));
|
|
}
|
|
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
|