clean_exit_beacon_unittest.cc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. // Copyright 2021 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/metrics/clean_exit_beacon.h"
  5. #include <memory>
  6. #include <string>
  7. #include "base/files/file_path.h"
  8. #include "base/files/file_util.h"
  9. #include "base/files/scoped_temp_dir.h"
  10. #include "base/memory/scoped_refptr.h"
  11. #include "base/metrics/field_trial.h"
  12. #include "base/test/gtest_util.h"
  13. #include "base/test/metrics/histogram_tester.h"
  14. #include "base/test/mock_entropy_provider.h"
  15. #include "base/test/task_environment.h"
  16. #include "base/time/time.h"
  17. #include "build/build_config.h"
  18. #include "components/metrics/metrics_pref_names.h"
  19. #include "components/prefs/pref_registry_simple.h"
  20. #include "components/prefs/pref_service_factory.h"
  21. #include "components/prefs/testing_pref_service.h"
  22. #include "components/prefs/testing_pref_store.h"
  23. #include "components/variations/pref_names.h"
  24. #include "components/variations/variations_test_utils.h"
  25. #include "testing/gtest/include/gtest/gtest.h"
  26. #include "third_party/abseil-cpp/absl/types/optional.h"
  27. namespace metrics {
  28. namespace {
  29. const wchar_t kDummyWindowsRegistryKey[] = L"";
  30. } // namespace
  31. class TestCleanExitBeacon : public CleanExitBeacon {
  32. public:
  33. explicit TestCleanExitBeacon(
  34. PrefService* local_state,
  35. const base::FilePath& user_data_dir = base::FilePath())
  36. : CleanExitBeacon(kDummyWindowsRegistryKey, user_data_dir, local_state) {
  37. Initialize();
  38. }
  39. ~TestCleanExitBeacon() override = default;
  40. };
  41. class CleanExitBeaconTest : public ::testing::Test {
  42. public:
  43. void SetUp() override {
  44. metrics::CleanExitBeacon::RegisterPrefs(prefs_.registry());
  45. ASSERT_TRUE(user_data_dir_.CreateUniqueTempDir());
  46. }
  47. protected:
  48. base::HistogramTester histogram_tester_;
  49. TestingPrefServiceSimple prefs_;
  50. base::ScopedTempDir user_data_dir_;
  51. private:
  52. base::test::TaskEnvironment task_environment_;
  53. };
  54. struct BadBeaconTestParams {
  55. const std::string test_name;
  56. bool beacon_file_exists;
  57. const std::string beacon_file_contents;
  58. BeaconFileState beacon_file_state;
  59. };
  60. // Used for testing beacon files that are not well-formed, do not exist, etc.
  61. class BadBeaconFileTest
  62. : public testing::WithParamInterface<BadBeaconTestParams>,
  63. public CleanExitBeaconTest {};
  64. struct BeaconConsistencyTestParams {
  65. // Inputs:
  66. const std::string test_name;
  67. absl::optional<bool> beacon_file_beacon_value;
  68. absl::optional<bool> platform_specific_beacon_value;
  69. absl::optional<bool> local_state_beacon_value;
  70. // Result:
  71. CleanExitBeaconConsistency expected_consistency;
  72. };
  73. #if BUILDFLAG(IS_IOS)
  74. // Used for testing the logic that emits to the UMA.CleanExitBeaconConsistency3
  75. // histogram.
  76. class BeaconFileAndPlatformBeaconConsistencyTest
  77. : public testing::WithParamInterface<BeaconConsistencyTestParams>,
  78. public CleanExitBeaconTest {};
  79. #endif // BUILDFLAG(IS_IOS)
  80. // Verify that the crash streak metric is 0 when default pref values are used.
  81. TEST_F(CleanExitBeaconTest, CrashStreakMetricWithDefaultPrefs) {
  82. CleanExitBeacon::ResetStabilityExitedCleanlyForTesting(&prefs_);
  83. TestCleanExitBeacon clean_exit_beacon(&prefs_);
  84. histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes", 0,
  85. 1);
  86. }
  87. // Verify that the crash streak metric is 0 when prefs are explicitly set to
  88. // their defaults.
  89. TEST_F(CleanExitBeaconTest, CrashStreakMetricWithNoCrashes) {
  90. // The default value for kStabilityExitedCleanly is true, but defaults can
  91. // change, so we explicitly set it to true here. Similarly, we explicitly set
  92. // kVariationsCrashStreak to 0.
  93. CleanExitBeacon::SetStabilityExitedCleanlyForTesting(&prefs_, true);
  94. prefs_.SetInteger(variations::prefs::kVariationsCrashStreak, 0);
  95. TestCleanExitBeacon clean_exit_beacon(&prefs_);
  96. histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes", 0,
  97. 1);
  98. }
  99. // Verify that the crash streak metric is correctly recorded when there is a
  100. // non-zero crash streak.
  101. TEST_F(CleanExitBeaconTest, CrashStreakMetricWithSomeCrashes) {
  102. // The default value for kStabilityExitedCleanly is true, but defaults can
  103. // change, so we explicitly set it to true here.
  104. CleanExitBeacon::SetStabilityExitedCleanlyForTesting(&prefs_, true);
  105. prefs_.SetInteger(variations::prefs::kVariationsCrashStreak, 1);
  106. TestCleanExitBeacon clean_exit_beacon(&prefs_);
  107. histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes", 1,
  108. 1);
  109. }
  110. // Verify that the crash streak is correctly incremented and recorded when the
  111. // last Chrome session did not exit cleanly.
  112. TEST_F(CleanExitBeaconTest, CrashIncrementsCrashStreak) {
  113. CleanExitBeacon::SetStabilityExitedCleanlyForTesting(&prefs_, false);
  114. prefs_.SetInteger(variations::prefs::kVariationsCrashStreak, 1);
  115. TestCleanExitBeacon clean_exit_beacon(&prefs_);
  116. EXPECT_EQ(prefs_.GetInteger(variations::prefs::kVariationsCrashStreak), 2);
  117. histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes", 2,
  118. 1);
  119. }
  120. // Verify that the crash streak is correctly incremented and recorded when the
  121. // last Chrome session did not exit cleanly and the default crash streak value
  122. // is used.
  123. TEST_F(CleanExitBeaconTest,
  124. CrashIncrementsCrashStreakWithDefaultCrashStreakPref) {
  125. CleanExitBeacon::SetStabilityExitedCleanlyForTesting(&prefs_, false);
  126. TestCleanExitBeacon clean_exit_beacon(&prefs_);
  127. EXPECT_EQ(prefs_.GetInteger(variations::prefs::kVariationsCrashStreak), 1);
  128. histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes", 1,
  129. 1);
  130. }
  131. // Verify that no attempt is made to read the beacon file when no user
  132. // data dir is provided.
  133. TEST_F(CleanExitBeaconTest, InitWithoutUserDataDir) {
  134. TestCleanExitBeacon beacon(&prefs_, base::FilePath());
  135. EXPECT_TRUE(beacon.GetUserDataDirForTesting().empty());
  136. EXPECT_TRUE(beacon.GetBeaconFilePathForTesting().empty());
  137. histogram_tester_.ExpectTotalCount(
  138. "Variations.ExtendedSafeMode.BeaconFileStateAtStartup", 0);
  139. }
  140. INSTANTIATE_TEST_SUITE_P(
  141. All,
  142. BadBeaconFileTest,
  143. ::testing::Values(
  144. BadBeaconTestParams{
  145. .test_name = "NoVariationsFile",
  146. .beacon_file_exists = false,
  147. .beacon_file_contents = "",
  148. .beacon_file_state = BeaconFileState::kNotDeserializable},
  149. BadBeaconTestParams{
  150. .test_name = "EmptyVariationsFile",
  151. .beacon_file_exists = true,
  152. .beacon_file_contents = "",
  153. .beacon_file_state = BeaconFileState::kNotDeserializable},
  154. BadBeaconTestParams{
  155. .test_name = "NotDictionary",
  156. .beacon_file_exists = true,
  157. .beacon_file_contents = "{abc123",
  158. .beacon_file_state = BeaconFileState::kNotDeserializable},
  159. BadBeaconTestParams{
  160. .test_name = "EmptyDictionary",
  161. .beacon_file_exists = true,
  162. .beacon_file_contents = "{}",
  163. .beacon_file_state = BeaconFileState::kMissingDictionary},
  164. BadBeaconTestParams{
  165. .test_name = "MissingCrashStreak",
  166. .beacon_file_exists = true,
  167. .beacon_file_contents =
  168. "{\"user_experience_metrics.stability.exited_cleanly\":true}",
  169. .beacon_file_state = BeaconFileState::kMissingCrashStreak},
  170. BadBeaconTestParams{
  171. .test_name = "MissingBeacon",
  172. .beacon_file_exists = true,
  173. .beacon_file_contents = "{\"variations_crash_streak\":1}",
  174. .beacon_file_state = BeaconFileState::kMissingBeacon}),
  175. [](const ::testing::TestParamInfo<BadBeaconTestParams>& params) {
  176. return params.param.test_name;
  177. });
  178. // Verify that the inability to get the beacon file's contents for a plethora of
  179. // reasons (a) doesn't crash and (b) correctly records the BeaconFileState
  180. // metric.
  181. TEST_P(BadBeaconFileTest, InitWithUnusableBeaconFile) {
  182. BadBeaconTestParams params = GetParam();
  183. const base::FilePath user_data_dir_path = user_data_dir_.GetPath();
  184. if (params.beacon_file_exists) {
  185. const base::FilePath temp_beacon_file_path =
  186. user_data_dir_path.Append(kCleanExitBeaconFilename);
  187. ASSERT_LT(0, base::WriteFile(temp_beacon_file_path,
  188. params.beacon_file_contents.data()));
  189. }
  190. TestCleanExitBeacon beacon(&prefs_, user_data_dir_path);
  191. histogram_tester_.ExpectUniqueSample(
  192. "Variations.ExtendedSafeMode.BeaconFileStateAtStartup",
  193. params.beacon_file_state, 1);
  194. }
  195. // Verify that successfully reading the beacon file's contents results in
  196. // correctly (a) setting the |did_previous_session_exit_cleanly_| field and (b)
  197. // recording metrics when the last session exited cleanly.
  198. TEST_F(CleanExitBeaconTest, InitWithBeaconFile) {
  199. const base::FilePath user_data_dir_path = user_data_dir_.GetPath();
  200. const base::FilePath temp_beacon_file_path =
  201. user_data_dir_path.Append(kCleanExitBeaconFilename);
  202. const int num_crashes = 2;
  203. ASSERT_LT(0, base::WriteFile(
  204. temp_beacon_file_path,
  205. CleanExitBeacon::CreateBeaconFileContentsForTesting(
  206. /*exited_cleanly=*/true, /*crash_streak=*/num_crashes)
  207. .data()));
  208. TestCleanExitBeacon clean_exit_beacon(&prefs_, user_data_dir_path);
  209. histogram_tester_.ExpectUniqueSample(
  210. "Variations.ExtendedSafeMode.BeaconFileStateAtStartup",
  211. BeaconFileState::kReadable, 1);
  212. EXPECT_TRUE(clean_exit_beacon.exited_cleanly());
  213. histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes",
  214. num_crashes, 1);
  215. }
  216. // Verify that successfully reading the beacon file's contents results in
  217. // correctly (a) setting the |did_previous_session_exit_cleanly_| field and (b)
  218. // recording metrics when the last session did not exit cleanly.
  219. TEST_F(CleanExitBeaconTest, InitWithCrashAndBeaconFile) {
  220. const base::FilePath user_data_dir_path = user_data_dir_.GetPath();
  221. const base::FilePath temp_beacon_file_path =
  222. user_data_dir_path.Append(kCleanExitBeaconFilename);
  223. const int last_session_num_crashes = 2;
  224. ASSERT_LT(0,
  225. base::WriteFile(temp_beacon_file_path,
  226. CleanExitBeacon::CreateBeaconFileContentsForTesting(
  227. /*exited_cleanly=*/false,
  228. /*crash_streak=*/last_session_num_crashes)
  229. .data()));
  230. const int updated_num_crashes = last_session_num_crashes + 1;
  231. TestCleanExitBeacon clean_exit_beacon(&prefs_, user_data_dir_path);
  232. histogram_tester_.ExpectUniqueSample(
  233. "Variations.ExtendedSafeMode.BeaconFileStateAtStartup",
  234. BeaconFileState::kReadable, 1);
  235. EXPECT_FALSE(clean_exit_beacon.exited_cleanly());
  236. histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes",
  237. updated_num_crashes, 1);
  238. }
  239. TEST_F(CleanExitBeaconTest, WriteBeaconValueWhenNotExitingCleanly) {
  240. const base::FilePath user_data_dir_path = user_data_dir_.GetPath();
  241. const base::FilePath beacon_file_path =
  242. user_data_dir_path.Append(kCleanExitBeaconFilename);
  243. ASSERT_FALSE(base::PathExists(beacon_file_path));
  244. TestCleanExitBeacon clean_exit_beacon(&prefs_, user_data_dir_path);
  245. clean_exit_beacon.WriteBeaconValue(/*exited_cleanly=*/false,
  246. /*is_extended_safe_mode=*/true);
  247. // Verify that the beacon file exists and has well-formed contents after
  248. // updating the beacon value.
  249. EXPECT_TRUE(base::PathExists(beacon_file_path));
  250. std::string beacon_file_contents1;
  251. ASSERT_TRUE(base::ReadFileToString(beacon_file_path, &beacon_file_contents1));
  252. EXPECT_EQ(beacon_file_contents1,
  253. "{\"user_experience_metrics.stability.exited_cleanly\":false,"
  254. "\"variations_crash_streak\":0}");
  255. // Verify that the BeaconFileWrite metric was emitted.
  256. histogram_tester_.ExpectUniqueSample(
  257. "Variations.ExtendedSafeMode.BeaconFileWrite", 1, 1);
  258. // Write the beacon value again. This is done because it is possible for
  259. // WriteBeaconValue() to be called twice during startup or shutdown with the
  260. // same value for |exited_cleanly|.
  261. clean_exit_beacon.WriteBeaconValue(/*exited_cleanly*/ false,
  262. /*is_extended_safe_mode=*/false);
  263. // Verify that the beacon file exists and has well-formed contents after
  264. // updating the beacon value.
  265. EXPECT_TRUE(base::PathExists(beacon_file_path));
  266. std::string beacon_file_contents2;
  267. ASSERT_TRUE(base::ReadFileToString(beacon_file_path, &beacon_file_contents2));
  268. EXPECT_EQ(beacon_file_contents2,
  269. "{\"user_experience_metrics.stability.exited_cleanly\":false,"
  270. "\"variations_crash_streak\":0}");
  271. // Verify that the BeaconFileWrite metric was not emitted a second time. The
  272. // beacon file should not have been written again since the beacon value did
  273. // not change.
  274. histogram_tester_.ExpectUniqueSample(
  275. "Variations.ExtendedSafeMode.BeaconFileWrite", 1, 1);
  276. }
  277. TEST_F(CleanExitBeaconTest, WriteBeaconValueWhenExitingCleanly) {
  278. const base::FilePath user_data_dir_path = user_data_dir_.GetPath();
  279. const base::FilePath beacon_file_path =
  280. user_data_dir_path.Append(kCleanExitBeaconFilename);
  281. ASSERT_FALSE(base::PathExists(beacon_file_path));
  282. TestCleanExitBeacon clean_exit_beacon(&prefs_, user_data_dir_path);
  283. clean_exit_beacon.WriteBeaconValue(/*exited_cleanly=*/true,
  284. /*is_extended_safe_mode=*/false);
  285. // Verify that the beacon file exists and has well-formed contents after
  286. // updating the beacon value.
  287. EXPECT_TRUE(base::PathExists(beacon_file_path));
  288. std::string beacon_file_contents1;
  289. ASSERT_TRUE(base::ReadFileToString(beacon_file_path, &beacon_file_contents1));
  290. EXPECT_EQ(beacon_file_contents1,
  291. "{\"user_experience_metrics.stability.exited_cleanly\":true,"
  292. "\"variations_crash_streak\":0}");
  293. // Verify that the BeaconFileWrite metric was emitted.
  294. histogram_tester_.ExpectUniqueSample(
  295. "Variations.ExtendedSafeMode.BeaconFileWrite", 1, 1);
  296. // Write the beacon value again. This is done because it is possible for
  297. // WriteBeaconValue() to be called twice during startup or shutdown with the
  298. // same value for |exited_cleanly|.
  299. clean_exit_beacon.WriteBeaconValue(/*exited_cleanly*/ true,
  300. /*is_extended_safe_mode=*/false);
  301. // Verify that the beacon file exists and has well-formed contents after
  302. // updating the beacon value.
  303. EXPECT_TRUE(base::PathExists(beacon_file_path));
  304. std::string beacon_file_contents2;
  305. ASSERT_TRUE(base::ReadFileToString(beacon_file_path, &beacon_file_contents2));
  306. EXPECT_EQ(beacon_file_contents2,
  307. "{\"user_experience_metrics.stability.exited_cleanly\":true,"
  308. "\"variations_crash_streak\":0}");
  309. // Verify that the BeaconFileWrite metric was not emitted a second time. The
  310. // beacon file should not have been written again since the beacon value did
  311. // not change.
  312. histogram_tester_.ExpectUniqueSample(
  313. "Variations.ExtendedSafeMode.BeaconFileWrite", 1, 1);
  314. }
  315. // Verify that there's a DCHECK when attempting to write a clean beacon with
  316. // |is_extended_safe_mode| set to true. When |is_extended_safe_mode| is true,
  317. // the only valid value for |exited_cleanly| is false.
  318. TEST_F(CleanExitBeaconTest, InvalidWriteBeaconValueArgsTriggerDcheck) {
  319. TestCleanExitBeacon clean_exit_beacon(&prefs_, user_data_dir_.GetPath());
  320. EXPECT_DCHECK_DEATH(
  321. clean_exit_beacon.WriteBeaconValue(/*exited_cleanly=*/true,
  322. /*is_extended_safe_mode=*/true));
  323. }
  324. #if BUILDFLAG(IS_IOS)
  325. // Verify the logic for recording UMA.CleanExitBeaconConsistency3.
  326. INSTANTIATE_TEST_SUITE_P(
  327. All,
  328. BeaconFileAndPlatformBeaconConsistencyTest,
  329. ::testing::Values(
  330. BeaconConsistencyTestParams{
  331. .test_name = "MissingMissing",
  332. .expected_consistency =
  333. CleanExitBeaconConsistency::kMissingMissing},
  334. BeaconConsistencyTestParams{
  335. .test_name = "MissingClean",
  336. .platform_specific_beacon_value = true,
  337. .expected_consistency = CleanExitBeaconConsistency::kMissingClean},
  338. BeaconConsistencyTestParams{
  339. .test_name = "MissingDirty",
  340. .platform_specific_beacon_value = false,
  341. .expected_consistency = CleanExitBeaconConsistency::kMissingDirty},
  342. BeaconConsistencyTestParams{
  343. .test_name = "CleanMissing",
  344. .beacon_file_beacon_value = true,
  345. .expected_consistency = CleanExitBeaconConsistency::kCleanMissing},
  346. BeaconConsistencyTestParams{
  347. .test_name = "DirtyMissing",
  348. .beacon_file_beacon_value = false,
  349. .expected_consistency = CleanExitBeaconConsistency::kDirtyMissing},
  350. BeaconConsistencyTestParams{
  351. .test_name = "CleanClean",
  352. .beacon_file_beacon_value = true,
  353. .platform_specific_beacon_value = true,
  354. .expected_consistency = CleanExitBeaconConsistency::kCleanClean},
  355. BeaconConsistencyTestParams{
  356. .test_name = "CleanDirty",
  357. .beacon_file_beacon_value = true,
  358. .platform_specific_beacon_value = false,
  359. .expected_consistency = CleanExitBeaconConsistency::kCleanDirty},
  360. BeaconConsistencyTestParams{
  361. .test_name = "DirtyClean",
  362. .beacon_file_beacon_value = false,
  363. .platform_specific_beacon_value = true,
  364. .expected_consistency = CleanExitBeaconConsistency::kDirtyClean},
  365. BeaconConsistencyTestParams{
  366. .test_name = "DirtyDirty",
  367. .beacon_file_beacon_value = false,
  368. .platform_specific_beacon_value = false,
  369. .expected_consistency = CleanExitBeaconConsistency::kDirtyDirty}),
  370. [](const ::testing::TestParamInfo<BeaconConsistencyTestParams>& params) {
  371. return params.param.test_name;
  372. });
  373. TEST_P(BeaconFileAndPlatformBeaconConsistencyTest, BeaconConsistency) {
  374. // Verify that the beacon file is not present. Unless set below, this beacon
  375. // is considered missing.
  376. const base::FilePath user_data_dir_path = user_data_dir_.GetPath();
  377. const base::FilePath temp_beacon_file_path =
  378. user_data_dir_path.Append(kCleanExitBeaconFilename);
  379. ASSERT_FALSE(base::PathExists(temp_beacon_file_path));
  380. // Clear the platform-specific beacon. Unless set below, this beacon is also
  381. // considered missing.
  382. CleanExitBeacon::ResetStabilityExitedCleanlyForTesting(&prefs_);
  383. BeaconConsistencyTestParams params = GetParam();
  384. if (params.beacon_file_beacon_value) {
  385. ASSERT_LT(
  386. 0, base::WriteFile(
  387. temp_beacon_file_path,
  388. CleanExitBeacon::CreateBeaconFileContentsForTesting(
  389. /*exited_cleanly=*/params.beacon_file_beacon_value.value(),
  390. /*crash_streak=*/0)
  391. .data()));
  392. }
  393. if (params.platform_specific_beacon_value) {
  394. CleanExitBeacon::SetUserDefaultsBeacon(
  395. /*exited_cleanly=*/params.platform_specific_beacon_value.value());
  396. }
  397. TestCleanExitBeacon clean_exit_beacon(&prefs_, user_data_dir_path);
  398. histogram_tester_.ExpectUniqueSample("UMA.CleanExitBeaconConsistency3",
  399. params.expected_consistency, 1);
  400. }
  401. #endif // BUILDFLAG(IS_IOS)
  402. } // namespace metrics