blob: 882e7e6682b36acec6d780f6918fcafa1c6e7d08 [file] [log] [blame]
jkrcalafbbf48b2017-03-22 09:46:561// 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
23using testing::DoubleNear;
24using testing::Eq;
25using testing::Gt;
26using testing::Lt;
27using testing::SizeIs;
28
29namespace ntp_snippets {
30namespace {
31
32char kNowString[] = "2017-03-01 10:45";
33
34class 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
65TEST_F(UserClassifierTest, ShouldBeActiveNtpUserInitially) {
66 UserClassifier* user_classifier = CreateUserClassifier();
67 EXPECT_THAT(user_classifier->GetUserClass(),
68 Eq(UserClassifier::UserClass::ACTIVE_NTP_USER));
69}
70
71TEST_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
89TEST_F(UserClassifierTest,
90 ShouldBecomeActiveSuggestionsConsumerByClickingOftenWithDecreasedParam) {
91 // Increase the param to one half.
92 variations::testing::VariationParamsManager variation_params(
sfiera0500d4a2017-03-30 11:58:4493 kArticleSuggestionsFeature.name,
jkrcalafbbf48b2017-03-22 09:46:5694 {{"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
113TEST_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
127TEST_F(UserClassifierTest,
128 ShouldBecomeRareNtpUserByNoActivityWithDecreasedParam) {
129 // Decrease the param to one half.
130 variations::testing::VariationParamsManager variation_params(
sfiera0500d4a2017-03-30 11:58:44131 kArticleSuggestionsFeature.name,
jkrcalafbbf48b2017-03-22 09:46:56132 {{"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
147class 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
158TEST_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
173TEST_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
183TEST_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
208TEST_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
228TEST_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(
sfiera0500d4a2017-03-30 11:58:44233 kArticleSuggestionsFeature.name, {{"user_classifier_min_hours", "1.0"}},
jkrcalafbbf48b2017-03-22 09:46:56234 {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
253TEST_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
275TEST_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(
sfiera0500d4a2017-03-30 11:58:44280 kArticleSuggestionsFeature.name, {{"user_classifier_max_hours", "72"}},
jkrcalafbbf48b2017-03-22 09:46:56281 {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
302INSTANTIATE_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