| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2017 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 |
| 5 #include "components/ntp_snippets/user_classifier.h" |
| 6 |
| 7 #include <memory> |
| 8 #include <string> |
| 9 #include <utility> |
| 10 |
| 11 #include "base/memory/ptr_util.h" |
| 12 #include "base/test/histogram_tester.h" |
| 13 #include "base/test/simple_test_clock.h" |
| 14 #include "base/time/time.h" |
| 15 #include "components/ntp_snippets/features.h" |
| 16 #include "components/ntp_snippets/ntp_snippets_constants.h" |
| 17 #include "components/prefs/pref_registry_simple.h" |
| 18 #include "components/prefs/testing_pref_service.h" |
| 19 #include "components/variations/variations_params_manager.h" |
| 20 #include "testing/gmock/include/gmock/gmock.h" |
| 21 #include "testing/gtest/include/gtest/gtest.h" |
| 22 |
| 23 using testing::DoubleNear; |
| 24 using testing::Eq; |
| 25 using testing::Gt; |
| 26 using testing::Lt; |
| 27 using testing::SizeIs; |
| 28 |
| 29 namespace ntp_snippets { |
| 30 namespace { |
| 31 |
| 32 char kNowString[] = "2017-03-01 10:45"; |
| 33 |
| 34 class UserClassifierTest : public testing::Test { |
| 35 public: |
| 36 UserClassifierTest() { |
| 37 UserClassifier::RegisterProfilePrefs(test_prefs_.registry()); |
| 38 } |
| 39 |
| 40 UserClassifier* CreateUserClassifier() { |
| 41 auto test_clock = base::MakeUnique<base::SimpleTestClock>(); |
| 42 test_clock_ = test_clock.get(); |
| 43 |
| 44 base::Time now; |
| 45 CHECK(base::Time::FromUTCString(kNowString, &now)); |
| 46 test_clock_->SetNow(now); |
| 47 |
| 48 user_classifier_ = |
| 49 base::MakeUnique<UserClassifier>(&test_prefs_, std::move(test_clock)); |
| 50 return user_classifier_.get(); |
| 51 } |
| 52 |
| 53 base::SimpleTestClock* test_clock() { return test_clock_; } |
| 54 |
| 55 private: |
| 56 TestingPrefServiceSimple test_prefs_; |
| 57 std::unique_ptr<UserClassifier> user_classifier_; |
| 58 |
| 59 // Owned by the UserClassifier. |
| 60 base::SimpleTestClock* test_clock_; |
| 61 |
| 62 DISALLOW_COPY_AND_ASSIGN(UserClassifierTest); |
| 63 }; |
| 64 |
| 65 TEST_F(UserClassifierTest, ShouldBeActiveNtpUserInitially) { |
| 66 UserClassifier* user_classifier = CreateUserClassifier(); |
| 67 EXPECT_THAT(user_classifier->GetUserClass(), |
| 68 Eq(UserClassifier::UserClass::ACTIVE_NTP_USER)); |
| 69 } |
| 70 |
| 71 TEST_F(UserClassifierTest, |
| 72 ShouldBecomeActiveSuggestionsConsumerByClickingOften) { |
| 73 UserClassifier* user_classifier = CreateUserClassifier(); |
| 74 |
| 75 // After one click still only an active user. |
| 76 user_classifier->OnEvent(UserClassifier::Metric::SUGGESTIONS_USED); |
| 77 EXPECT_THAT(user_classifier->GetUserClass(), |
| 78 Eq(UserClassifier::UserClass::ACTIVE_NTP_USER)); |
| 79 |
| 80 // After a few more clicks, become an active consumer. |
| 81 for (int i = 0; i < 5; i++) { |
| 82 test_clock()->Advance(base::TimeDelta::FromHours(1)); |
| 83 user_classifier->OnEvent(UserClassifier::Metric::SUGGESTIONS_USED); |
| 84 } |
| 85 EXPECT_THAT(user_classifier->GetUserClass(), |
| 86 Eq(UserClassifier::UserClass::ACTIVE_SUGGESTIONS_CONSUMER)); |
| 87 } |
| 88 |
| 89 TEST_F(UserClassifierTest, |
| 90 ShouldBecomeActiveSuggestionsConsumerByClickingOftenWithDecreasedParam) { |
| 91 // Increase the param to one half. |
| 92 variations::testing::VariationParamsManager variation_params( |
| 93 kStudyName, |
| 94 {{"user_classifier_active_consumer_clicks_at_least_once_per_hours", |
| 95 "36"}}, |
| 96 {kArticleSuggestionsFeature.name}); |
| 97 UserClassifier* user_classifier = CreateUserClassifier(); |
| 98 |
| 99 // After two clicks still only an active user. |
| 100 user_classifier->OnEvent(UserClassifier::Metric::SUGGESTIONS_USED); |
| 101 test_clock()->Advance(base::TimeDelta::FromHours(1)); |
| 102 user_classifier->OnEvent(UserClassifier::Metric::SUGGESTIONS_USED); |
| 103 EXPECT_THAT(user_classifier->GetUserClass(), |
| 104 Eq(UserClassifier::UserClass::ACTIVE_NTP_USER)); |
| 105 |
| 106 // One more click to become an active consumer. |
| 107 test_clock()->Advance(base::TimeDelta::FromHours(1)); |
| 108 user_classifier->OnEvent(UserClassifier::Metric::SUGGESTIONS_USED); |
| 109 EXPECT_THAT(user_classifier->GetUserClass(), |
| 110 Eq(UserClassifier::UserClass::ACTIVE_SUGGESTIONS_CONSUMER)); |
| 111 } |
| 112 |
| 113 TEST_F(UserClassifierTest, ShouldBecomeRareNtpUserByNoActivity) { |
| 114 UserClassifier* user_classifier = CreateUserClassifier(); |
| 115 |
| 116 // After two days of waiting still an active user. |
| 117 test_clock()->Advance(base::TimeDelta::FromDays(2)); |
| 118 EXPECT_THAT(user_classifier->GetUserClass(), |
| 119 Eq(UserClassifier::UserClass::ACTIVE_NTP_USER)); |
| 120 |
| 121 // Two more days to become a rare user. |
| 122 test_clock()->Advance(base::TimeDelta::FromDays(2)); |
| 123 EXPECT_THAT(user_classifier->GetUserClass(), |
| 124 Eq(UserClassifier::UserClass::RARE_NTP_USER)); |
| 125 } |
| 126 |
| 127 TEST_F(UserClassifierTest, |
| 128 ShouldBecomeRareNtpUserByNoActivityWithDecreasedParam) { |
| 129 // Decrease the param to one half. |
| 130 variations::testing::VariationParamsManager variation_params( |
| 131 kStudyName, |
| 132 {{"user_classifier_rare_user_opens_ntp_at_most_once_per_hours", "48"}}, |
| 133 {kArticleSuggestionsFeature.name}); |
| 134 UserClassifier* user_classifier = CreateUserClassifier(); |
| 135 |
| 136 // After one days of waiting still an active user. |
| 137 test_clock()->Advance(base::TimeDelta::FromDays(1)); |
| 138 EXPECT_THAT(user_classifier->GetUserClass(), |
| 139 Eq(UserClassifier::UserClass::ACTIVE_NTP_USER)); |
| 140 |
| 141 // One more day to become a rare user. |
| 142 test_clock()->Advance(base::TimeDelta::FromDays(1)); |
| 143 EXPECT_THAT(user_classifier->GetUserClass(), |
| 144 Eq(UserClassifier::UserClass::RARE_NTP_USER)); |
| 145 } |
| 146 |
| 147 class UserClassifierMetricTest |
| 148 : public UserClassifierTest, |
| 149 public ::testing::WithParamInterface< |
| 150 std::pair<UserClassifier::Metric, std::string>> { |
| 151 public: |
| 152 UserClassifierMetricTest() : UserClassifierTest() {} |
| 153 |
| 154 private: |
| 155 DISALLOW_COPY_AND_ASSIGN(UserClassifierMetricTest); |
| 156 }; |
| 157 |
| 158 TEST_P(UserClassifierMetricTest, ShouldDecreaseEstimateAfterEvent) { |
| 159 UserClassifier::Metric metric = GetParam().first; |
| 160 UserClassifier* user_classifier = CreateUserClassifier(); |
| 161 |
| 162 // The initial event does not decrease the estimate. |
| 163 user_classifier->OnEvent(metric); |
| 164 |
| 165 for (int i = 0; i < 10; i++) { |
| 166 test_clock()->Advance(base::TimeDelta::FromHours(1)); |
| 167 double old_metric = user_classifier->GetEstimatedAvgTime(metric); |
| 168 user_classifier->OnEvent(metric); |
| 169 EXPECT_THAT(user_classifier->GetEstimatedAvgTime(metric), Lt(old_metric)); |
| 170 } |
| 171 } |
| 172 |
| 173 TEST_P(UserClassifierMetricTest, ShouldReportToUmaOnEvent) { |
| 174 UserClassifier::Metric metric = GetParam().first; |
| 175 const std::string& histogram_name = GetParam().second; |
| 176 base::HistogramTester histogram_tester; |
| 177 UserClassifier* user_classifier = CreateUserClassifier(); |
| 178 |
| 179 user_classifier->OnEvent(metric); |
| 180 EXPECT_THAT(histogram_tester.GetAllSamples(histogram_name), SizeIs(1)); |
| 181 } |
| 182 |
| 183 TEST_P(UserClassifierMetricTest, ShouldConvergeTowardsPattern) { |
| 184 UserClassifier::Metric metric = GetParam().first; |
| 185 UserClassifier* user_classifier = CreateUserClassifier(); |
| 186 |
| 187 // Have the pattern of an event every five hours and start changing it towards |
| 188 // an event every 10 hours. |
| 189 for (int i = 0; i < 100; i++) { |
| 190 test_clock()->Advance(base::TimeDelta::FromHours(5)); |
| 191 user_classifier->OnEvent(metric); |
| 192 } |
| 193 EXPECT_THAT(user_classifier->GetEstimatedAvgTime(metric), |
| 194 DoubleNear(5.0, 0.1)); |
| 195 for (int i = 0; i < 3; i++) { |
| 196 test_clock()->Advance(base::TimeDelta::FromHours(10)); |
| 197 user_classifier->OnEvent(metric); |
| 198 } |
| 199 EXPECT_THAT(user_classifier->GetEstimatedAvgTime(metric), Gt(5.5)); |
| 200 for (int i = 0; i < 100; i++) { |
| 201 test_clock()->Advance(base::TimeDelta::FromHours(10)); |
| 202 user_classifier->OnEvent(metric); |
| 203 } |
| 204 EXPECT_THAT(user_classifier->GetEstimatedAvgTime(metric), |
| 205 DoubleNear(10.0, 0.1)); |
| 206 } |
| 207 |
| 208 TEST_P(UserClassifierMetricTest, ShouldIgnoreSubsequentEventsForHalfAnHour) { |
| 209 UserClassifier::Metric metric = GetParam().first; |
| 210 UserClassifier* user_classifier = CreateUserClassifier(); |
| 211 |
| 212 // The initial event |
| 213 user_classifier->OnEvent(metric); |
| 214 // Subsequent events get ignored for the next 30 minutes. |
| 215 for (int i = 0; i < 5; i++) { |
| 216 test_clock()->Advance(base::TimeDelta::FromMinutes(5)); |
| 217 double old_metric = user_classifier->GetEstimatedAvgTime(metric); |
| 218 user_classifier->OnEvent(metric); |
| 219 EXPECT_THAT(user_classifier->GetEstimatedAvgTime(metric), Eq(old_metric)); |
| 220 } |
| 221 // An event 30 minutes after the initial event is finally not ignored. |
| 222 test_clock()->Advance(base::TimeDelta::FromMinutes(5)); |
| 223 double old_metric = user_classifier->GetEstimatedAvgTime(metric); |
| 224 user_classifier->OnEvent(metric); |
| 225 EXPECT_THAT(user_classifier->GetEstimatedAvgTime(metric), Lt(old_metric)); |
| 226 } |
| 227 |
| 228 TEST_P(UserClassifierMetricTest, |
| 229 ShouldIgnoreSubsequentEventsWithIncreasedLimit) { |
| 230 UserClassifier::Metric metric = GetParam().first; |
| 231 // Increase the min_hours to 1.0, i.e. 60 minutes. |
| 232 variations::testing::VariationParamsManager variation_params( |
| 233 kStudyName, {{"user_classifier_min_hours", "1.0"}}, |
| 234 {kArticleSuggestionsFeature.name}); |
| 235 UserClassifier* user_classifier = CreateUserClassifier(); |
| 236 |
| 237 // The initial event |
| 238 user_classifier->OnEvent(metric); |
| 239 // Subsequent events get ignored for the next 60 minutes. |
| 240 for (int i = 0; i < 11; i++) { |
| 241 test_clock()->Advance(base::TimeDelta::FromMinutes(5)); |
| 242 double old_metric = user_classifier->GetEstimatedAvgTime(metric); |
| 243 user_classifier->OnEvent(metric); |
| 244 EXPECT_THAT(user_classifier->GetEstimatedAvgTime(metric), Eq(old_metric)); |
| 245 } |
| 246 // An event 60 minutes after the initial event is finally not ignored. |
| 247 test_clock()->Advance(base::TimeDelta::FromMinutes(5)); |
| 248 double old_metric = user_classifier->GetEstimatedAvgTime(metric); |
| 249 user_classifier->OnEvent(metric); |
| 250 EXPECT_THAT(user_classifier->GetEstimatedAvgTime(metric), Lt(old_metric)); |
| 251 } |
| 252 |
| 253 TEST_P(UserClassifierMetricTest, ShouldCapDelayBetweenEvents) { |
| 254 UserClassifier::Metric metric = GetParam().first; |
| 255 UserClassifier* user_classifier = CreateUserClassifier(); |
| 256 |
| 257 // The initial event |
| 258 user_classifier->OnEvent(metric); |
| 259 // Wait for an insane amount of time |
| 260 test_clock()->Advance(base::TimeDelta::FromDays(365)); |
| 261 user_classifier->OnEvent(metric); |
| 262 double metric_after_a_year = user_classifier->GetEstimatedAvgTime(metric); |
| 263 |
| 264 // Now repeat the same with s/one year/one week. |
| 265 user_classifier->ClearClassificationForDebugging(); |
| 266 user_classifier->OnEvent(metric); |
| 267 test_clock()->Advance(base::TimeDelta::FromDays(7)); |
| 268 user_classifier->OnEvent(metric); |
| 269 |
| 270 // The results should be the same. |
| 271 EXPECT_THAT(user_classifier->GetEstimatedAvgTime(metric), |
| 272 Eq(metric_after_a_year)); |
| 273 } |
| 274 |
| 275 TEST_P(UserClassifierMetricTest, |
| 276 ShouldCapDelayBetweenEventsWithDecreasedLimit) { |
| 277 UserClassifier::Metric metric = GetParam().first; |
| 278 // Decrease the max_hours to 72, i.e. 3 days. |
| 279 variations::testing::VariationParamsManager variation_params( |
| 280 kStudyName, {{"user_classifier_max_hours", "72"}}, |
| 281 {kArticleSuggestionsFeature.name}); |
| 282 UserClassifier* user_classifier = CreateUserClassifier(); |
| 283 |
| 284 // The initial event |
| 285 user_classifier->OnEvent(metric); |
| 286 // Wait for an insane amount of time |
| 287 test_clock()->Advance(base::TimeDelta::FromDays(365)); |
| 288 user_classifier->OnEvent(metric); |
| 289 double metric_after_a_year = user_classifier->GetEstimatedAvgTime(metric); |
| 290 |
| 291 // Now repeat the same with s/one year/two days. |
| 292 user_classifier->ClearClassificationForDebugging(); |
| 293 user_classifier->OnEvent(metric); |
| 294 test_clock()->Advance(base::TimeDelta::FromDays(3)); |
| 295 user_classifier->OnEvent(metric); |
| 296 |
| 297 // The results should be the same. |
| 298 EXPECT_THAT(user_classifier->GetEstimatedAvgTime(metric), |
| 299 Eq(metric_after_a_year)); |
| 300 } |
| 301 |
| 302 INSTANTIATE_TEST_CASE_P( |
| 303 , // An empty prefix for the parametrized tests names (no need to |
| 304 // distinguish the only instance we make here). |
| 305 UserClassifierMetricTest, |
| 306 testing::Values( |
| 307 std::make_pair(UserClassifier::Metric::NTP_OPENED, |
| 308 "NewTabPage.UserClassifier.AverageHoursToOpenNTP"), |
| 309 std::make_pair( |
| 310 UserClassifier::Metric::SUGGESTIONS_SHOWN, |
| 311 "NewTabPage.UserClassifier.AverageHoursToShowSuggestions"), |
| 312 std::make_pair( |
| 313 UserClassifier::Metric::SUGGESTIONS_USED, |
| 314 "NewTabPage.UserClassifier.AverageHoursToUseSuggestions"))); |
| 315 |
| 316 } // namespace |
| 317 } // namespace ntp_snippets |
| OLD | NEW |