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