1 // Copyright 2019 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/demographics/user_demographics.h"
6
7 #include <utility>
8
9 #include "base/time/time.h"
10 #include "base/values.h"
11 #include "components/sync_preferences/testing_pref_service_syncable.h"
12 #include "testing/gtest/include/gtest/gtest.h"
13 #include "third_party/metrics_proto/user_demographics.pb.h"
14
15 namespace metrics {
16
17 namespace {
18
19 // Gets the now time used for testing demographics.
GetNowTime()20 base::Time GetNowTime() {
21 constexpr char kNowTimeInStringFormat[] = "22 Jul 2019 00:00:00 UDT";
22
23 base::Time now;
24 bool result = base::Time::FromString(kNowTimeInStringFormat, &now);
25 DCHECK(result);
26 return now;
27 }
28
29 } // namespace
30
TEST(UserDemographicsTest,UserDemographicsResult_ForValue)31 TEST(UserDemographicsTest, UserDemographicsResult_ForValue) {
32 int user_birth_year = 1982;
33 UserDemographicsProto_Gender user_gender = UserDemographicsProto::GENDER_MALE;
34
35 UserDemographics user_demographics;
36 user_demographics.birth_year = user_birth_year;
37 user_demographics.gender = user_gender;
38 UserDemographicsResult user_demographics_result =
39 UserDemographicsResult::ForValue(std::move(user_demographics));
40
41 EXPECT_TRUE(user_demographics_result.IsSuccess());
42 EXPECT_EQ(UserDemographicsStatus::kSuccess,
43 user_demographics_result.status());
44 EXPECT_EQ(user_birth_year, user_demographics_result.value().birth_year);
45 EXPECT_EQ(user_gender, user_demographics_result.value().gender);
46 }
47
TEST(UserDemographicsTest,UserDemographicsResult_ForStatus)48 TEST(UserDemographicsTest, UserDemographicsResult_ForStatus) {
49 UserDemographicsStatus error_status =
50 UserDemographicsStatus::kIneligibleDemographicsData;
51 UserDemographicsResult user_demographics_result =
52 UserDemographicsResult::ForStatus(error_status);
53
54 EXPECT_FALSE(user_demographics_result.IsSuccess());
55 EXPECT_EQ(error_status, user_demographics_result.status());
56 }
57
58 class UserDemographicsPrefsTest : public testing::Test {
59 protected:
UserDemographicsPrefsTest()60 UserDemographicsPrefsTest() {
61 RegisterDemographicsLocalStatePrefs(pref_service_.registry());
62 RegisterDemographicsProfilePrefs(pref_service_.registry());
63 }
64
SetDemographics(int birth_year,UserDemographicsProto::Gender gender)65 void SetDemographics(int birth_year, UserDemographicsProto::Gender gender) {
66 SetDemographicsImpl(kSyncDemographicsPrefName, birth_year, gender);
67 }
68
69 #if BUILDFLAG(IS_CHROMEOS_ASH)
SetOsDemographics(int birth_year,UserDemographicsProto::Gender gender)70 void SetOsDemographics(int birth_year, UserDemographicsProto::Gender gender) {
71 SetDemographicsImpl(kSyncOsDemographicsPrefName, birth_year, gender);
72 }
73 #endif
74
GetLocalState()75 PrefService* GetLocalState() { return &pref_service_; }
GetProfilePrefs()76 PrefService* GetProfilePrefs() { return &pref_service_; }
77
78 private:
SetDemographicsImpl(const std::string & pref_name,int birth_year,UserDemographicsProto::Gender gender)79 void SetDemographicsImpl(const std::string& pref_name,
80 int birth_year,
81 UserDemographicsProto::Gender gender) {
82 base::Value::Dict dict;
83 dict.Set(kSyncDemographicsBirthYearPath, birth_year);
84 dict.Set(kSyncDemographicsGenderPath, static_cast<int>(gender));
85 GetProfilePrefs()->SetDict(pref_name, std::move(dict));
86 }
87
88 sync_preferences::TestingPrefServiceSyncable pref_service_;
89 };
90
TEST_F(UserDemographicsPrefsTest,ReadDemographicsWithRandomOffset)91 TEST_F(UserDemographicsPrefsTest, ReadDemographicsWithRandomOffset) {
92 int user_demographics_birth_year = 1983;
93 UserDemographicsProto_Gender user_demographics_gender =
94 UserDemographicsProto::GENDER_MALE;
95
96 // Set user demographic prefs.
97 SetDemographics(user_demographics_birth_year, user_demographics_gender);
98
99 int provided_birth_year;
100 {
101 UserDemographicsResult demographics_result =
102 GetUserNoisedBirthYearAndGenderFromPrefs(GetNowTime(), GetLocalState(),
103 GetProfilePrefs());
104 ASSERT_TRUE(demographics_result.IsSuccess());
105 EXPECT_EQ(user_demographics_gender, demographics_result.value().gender);
106 // Verify that the provided birth year is within the range.
107 provided_birth_year = demographics_result.value().birth_year;
108 int delta = provided_birth_year - user_demographics_birth_year;
109 EXPECT_LE(delta, kUserDemographicsBirthYearNoiseOffsetRange);
110 EXPECT_GE(delta, -kUserDemographicsBirthYearNoiseOffsetRange);
111 }
112
113 // Verify that the offset is cached and that the randomized birth year is the
114 // same when doing more that one read of the birth year.
115 {
116 ASSERT_TRUE(
117 GetLocalState()->HasPrefPath(kUserDemographicsBirthYearOffsetPrefName));
118 UserDemographicsResult demographics_result =
119 GetUserNoisedBirthYearAndGenderFromPrefs(GetNowTime(), GetLocalState(),
120 GetProfilePrefs());
121 ASSERT_TRUE(demographics_result.IsSuccess());
122 EXPECT_EQ(provided_birth_year, demographics_result.value().birth_year);
123 }
124 }
125
126 #if BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(UserDemographicsPrefsTest,ReadOsDemographicsWithRandomOffset)127 TEST_F(UserDemographicsPrefsTest, ReadOsDemographicsWithRandomOffset) {
128 int user_demographics_birth_year = 1983;
129 UserDemographicsProto_Gender user_demographics_gender =
130 UserDemographicsProto::GENDER_MALE;
131
132 // Set user demographic prefs.
133 SetOsDemographics(user_demographics_birth_year, user_demographics_gender);
134
135 int provided_birth_year;
136 {
137 UserDemographicsResult demographics_result =
138 GetUserNoisedBirthYearAndGenderFromPrefs(GetNowTime(), GetLocalState(),
139 GetProfilePrefs());
140 ASSERT_TRUE(demographics_result.IsSuccess());
141 EXPECT_EQ(user_demographics_gender, demographics_result.value().gender);
142 // Verify that the provided birth year is within the range.
143 provided_birth_year = demographics_result.value().birth_year;
144 int delta = provided_birth_year - user_demographics_birth_year;
145 EXPECT_LE(delta, kUserDemographicsBirthYearNoiseOffsetRange);
146 EXPECT_GE(delta, -kUserDemographicsBirthYearNoiseOffsetRange);
147 }
148
149 // Verify that the offset is cached and that the randomized birth year is the
150 // same when doing more that one read of the birth year.
151 {
152 ASSERT_TRUE(
153 GetLocalState()->HasPrefPath(kUserDemographicsBirthYearOffsetPrefName));
154 UserDemographicsResult demographics_result =
155 GetUserNoisedBirthYearAndGenderFromPrefs(GetNowTime(), GetLocalState(),
156 GetProfilePrefs());
157 ASSERT_TRUE(demographics_result.IsSuccess());
158 EXPECT_EQ(provided_birth_year, demographics_result.value().birth_year);
159 }
160 }
161 #endif // BUILDFLAG(IS_CHROMEOS_ASH)
162
TEST_F(UserDemographicsPrefsTest,ReadAndClearUserDemographicPreferences)163 TEST_F(UserDemographicsPrefsTest, ReadAndClearUserDemographicPreferences) {
164 // Verify demographic prefs are not available when there is nothing set.
165 ASSERT_FALSE(GetUserNoisedBirthYearAndGenderFromPrefs(
166 GetNowTime(), GetLocalState(), GetProfilePrefs())
167 .IsSuccess());
168
169 // Set demographic prefs directly from the pref service interface because
170 // demographic prefs will only be set on the server-side. The SyncPrefs
171 // interface cannot set demographic prefs.
172 SetDemographics(1983, UserDemographicsProto::GENDER_FEMALE);
173
174 // Set birth year noise offset to not have it randomized.
175 GetProfilePrefs()->SetInteger(kUserDemographicsBirthYearOffsetPrefName, 2);
176
177 // Verify that demographics are provided.
178 {
179 UserDemographicsResult demographics_result =
180 GetUserNoisedBirthYearAndGenderFromPrefs(GetNowTime(), GetLocalState(),
181 GetProfilePrefs());
182 ASSERT_TRUE(demographics_result.IsSuccess());
183 }
184
185 ClearDemographicsPrefs(GetProfilePrefs());
186
187 // Verify that demographics are not provided and kSyncDemographics is cleared.
188 // Note that we retain k*DemographicsBirthYearOffset. If the user resumes
189 // syncing, causing these prefs to be recreated, we don't want them to start
190 // reporting a different randomized birth year as this could narrow down or
191 // even reveal their true birth year.
192 EXPECT_FALSE(GetUserNoisedBirthYearAndGenderFromPrefs(
193 GetNowTime(), GetLocalState(), GetProfilePrefs())
194 .IsSuccess());
195 EXPECT_FALSE(GetProfilePrefs()->HasPrefPath(kSyncDemographicsPrefName));
196 #if BUILDFLAG(IS_CHROMEOS_ASH)
197 EXPECT_FALSE(GetProfilePrefs()->HasPrefPath(kSyncOsDemographicsPrefName));
198 #endif
199 EXPECT_TRUE(
200 GetLocalState()->HasPrefPath(kUserDemographicsBirthYearOffsetPrefName));
201 // Deprecated offset is not created if it does not already exist.
202 EXPECT_FALSE(GetProfilePrefs()->HasPrefPath(
203 kDeprecatedDemographicsBirthYearOffsetPrefName));
204 }
205
TEST_F(UserDemographicsPrefsTest,ReadAndClearDeprecatedOffsetPref)206 TEST_F(UserDemographicsPrefsTest, ReadAndClearDeprecatedOffsetPref) {
207 // Verify demographic prefs are not available when there is nothing set.
208 ASSERT_FALSE(GetUserNoisedBirthYearAndGenderFromPrefs(
209 GetNowTime(), GetLocalState(), GetProfilePrefs())
210 .IsSuccess());
211
212 // Set demographic prefs directly from the pref service interface because
213 // demographic prefs will only be set on the server-side. The SyncPrefs
214 // interface cannot set demographic prefs.
215 SetDemographics(1983, UserDemographicsProto::GENDER_FEMALE);
216
217 // Set deprecated birth year noise offset in the UserPrefs
218 GetProfilePrefs()->SetInteger(kDeprecatedDemographicsBirthYearOffsetPrefName,
219 2);
220
221 // Verify that demographics are provided.
222 {
223 UserDemographicsResult demographics_result =
224 GetUserNoisedBirthYearAndGenderFromPrefs(GetNowTime(), GetLocalState(),
225 GetProfilePrefs());
226 ASSERT_TRUE(demographics_result.IsSuccess());
227 }
228
229 // Offset if migrated to new pref.
230 EXPECT_EQ(
231 2, GetLocalState()->GetInteger(kUserDemographicsBirthYearOffsetPrefName));
232 // TODO(crbug/1367338): clear/remove deprecated pref after 2023/09
233 EXPECT_TRUE(GetProfilePrefs()->HasPrefPath(
234 kDeprecatedDemographicsBirthYearOffsetPrefName));
235 }
236
237 #if BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(UserDemographicsPrefsTest,ChromeOsAsh)238 TEST_F(UserDemographicsPrefsTest, ChromeOsAsh) {
239 // Verify demographic prefs are not available when there is nothing set.
240 ASSERT_FALSE(GetUserNoisedBirthYearAndGenderFromPrefs(
241 GetNowTime(), GetLocalState(), GetProfilePrefs())
242 .IsSuccess());
243
244 // Set OS demographic prefs directly within the pref service interface.
245 SetOsDemographics(1983, UserDemographicsProto::GENDER_FEMALE);
246
247 // Set birth year noise offset in the UserPrefs
248 GetLocalState()->SetInteger(kUserDemographicsBirthYearOffsetPrefName, 2);
249
250 // Verify that demographics are provided.
251 {
252 UserDemographicsResult demographics_result =
253 GetUserNoisedBirthYearAndGenderFromPrefs(GetNowTime(), GetLocalState(),
254 GetProfilePrefs());
255 ASSERT_TRUE(demographics_result.IsSuccess());
256 }
257
258 EXPECT_FALSE(GetProfilePrefs()->HasPrefPath(kSyncDemographicsPrefName));
259 EXPECT_TRUE(GetProfilePrefs()->HasPrefPath(kSyncOsDemographicsPrefName));
260 EXPECT_TRUE(
261 GetLocalState()->HasPrefPath(kUserDemographicsBirthYearOffsetPrefName));
262 }
263 #endif // BUILDFLAG(IS_CHROMEOS_ASH)
264
265 struct DemographicsTestParam {
266 // Birth year of the user.
267 int birth_year = kUserDemographicsBirthYearDefaultValue;
268
269 // Non-random offset to apply to |birth_year| as noise.
270 int birth_year_offset = kUserDemographicsBirthYearNoiseOffsetDefaultValue;
271
272 // Gender of the user.
273 UserDemographicsProto_Gender gender = kUserDemographicGenderDefaultEnumValue;
274
275 // Status of the retrieval of demographics.
276 UserDemographicsStatus status = UserDemographicsStatus::kMaxValue;
277 };
278
279 // Extend UserDemographicsPrefsTest fixture for parameterized tests on
280 // demographics.
281 class UserDemographicsPrefsTestWithParam
282 : public UserDemographicsPrefsTest,
283 public testing::WithParamInterface<DemographicsTestParam> {};
284
TEST_P(UserDemographicsPrefsTestWithParam,ReadDemographics_OffsetIsNotRandom)285 TEST_P(UserDemographicsPrefsTestWithParam, ReadDemographics_OffsetIsNotRandom) {
286 DemographicsTestParam param = GetParam();
287
288 // Set user demographic prefs.
289 SetDemographics(param.birth_year, param.gender);
290
291 // Set birth year noise offset to not have it randomized.
292 GetLocalState()->SetInteger(kUserDemographicsBirthYearOffsetPrefName,
293 param.birth_year_offset);
294
295 // Verify provided demographics for the different parameterized test cases.
296 UserDemographicsResult demographics_result =
297 GetUserNoisedBirthYearAndGenderFromPrefs(GetNowTime(), GetLocalState(),
298 GetProfilePrefs());
299 if (param.status == UserDemographicsStatus::kSuccess) {
300 ASSERT_TRUE(demographics_result.IsSuccess());
301 EXPECT_EQ(param.birth_year + param.birth_year_offset,
302 demographics_result.value().birth_year);
303 EXPECT_EQ(param.gender, demographics_result.value().gender);
304 } else {
305 ASSERT_FALSE(demographics_result.IsSuccess());
306 EXPECT_EQ(param.status, demographics_result.status());
307 }
308 }
309
310 // Test suite composed of different test cases of getting user demographics.
311 // The now time in each test case is "22 Jul 2019 00:00:00 UDT" which falls into
312 // the year bucket of 2018. Users need at most a |birth_year| +
313 // |birth_year_offset| of 1998 to be able to provide demographics.
314 INSTANTIATE_TEST_SUITE_P(
315 All,
316 UserDemographicsPrefsTestWithParam,
317 ::testing::Values(
318 // Test where birth year should not be provided because |birth_year| + 2
319 // > 1998.
320 DemographicsTestParam{
321 /*birth_year=*/1997,
322 /*birth_year_offset=*/2,
323 /*gender=*/UserDemographicsProto::GENDER_FEMALE,
324 /*status=*/UserDemographicsStatus::kIneligibleDemographicsData},
325 // Test where birth year should not be provided because |birth_year| - 2
326 // > 1998.
327 DemographicsTestParam{
328 /*birth_year=*/2001,
329 /*birth_year_offset=*/-2,
330 /*gender=*/UserDemographicsProto::GENDER_FEMALE,
331 /*status=*/UserDemographicsStatus::kIneligibleDemographicsData},
332 // Test where birth year should not be provided because age of user is
333 // |kUserDemographicsMaxAge| + 1, which is over the max age.
334 DemographicsTestParam{
335 /*birth_year=*/1933,
336 /*birth_year_offset=*/0,
337 /*gender=*/UserDemographicsProto::GENDER_FEMALE,
338 /*status=*/UserDemographicsStatus::kIneligibleDemographicsData},
339 // Test where gender should not be provided because it has a low
340 // population that can have their privacy compromised because of high
341 // entropy.
342 DemographicsTestParam{
343 /*birth_year=*/1986,
344 /*birth_year_offset=*/0,
345 /*gender=*/UserDemographicsProto::GENDER_CUSTOM_OR_OTHER,
346 /*status=*/UserDemographicsStatus::kIneligibleDemographicsData},
347 // Test where birth year can be provided because |birth_year| + 2 ==
348 // 1998.
349 DemographicsTestParam{/*birth_year=*/1996,
350 /*birth_year_offset=*/2,
351 /*gender=*/UserDemographicsProto::GENDER_FEMALE,
352 /*status=*/UserDemographicsStatus::kSuccess},
353 // Test where birth year can be provided because |birth_year| - 2 ==
354 // 1998.
355 DemographicsTestParam{/*birth_year=*/2000,
356 /*birth_year_offset=*/-2,
357 /*gender=*/UserDemographicsProto::GENDER_MALE,
358 /*status=*/UserDemographicsStatus::kSuccess},
359 // Test where birth year can be provided because |birth_year| + 2 <
360 // 1998.
361 DemographicsTestParam{/*birth_year=*/1995,
362 /*birth_year_offset=*/2,
363 /*gender=*/UserDemographicsProto::GENDER_FEMALE,
364 /*status=*/UserDemographicsStatus::kSuccess},
365 // Test where birth year can be provided because |birth_year| - 2 <
366 // 1998.
367 DemographicsTestParam{/*birth_year=*/1999,
368 /*birth_year_offset=*/-2,
369 /*gender=*/UserDemographicsProto::GENDER_MALE,
370 /*status=*/UserDemographicsStatus::kSuccess},
371 // Test where gender can be provided because it is part of a large
372 // population with a low entropy.
373 DemographicsTestParam{/*birth_year=*/1986,
374 /*birth_year_offset=*/0,
375 /*gender=*/UserDemographicsProto::GENDER_FEMALE,
376 /*status=*/UserDemographicsStatus::kSuccess},
377 // Test where gender can be provided because it is part of a large
378 // population with a low entropy.
379 DemographicsTestParam{/*birth_year=*/1986,
380 /*birth_year_offset=*/0,
381 /*gender=*/UserDemographicsProto::GENDER_MALE,
382 /*status=*/UserDemographicsStatus::kSuccess}));
383
384 } // namespace metrics
385