1// Copyright 2013 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 <string>
6#include <vector>
7
8#include "base/logging.h"
9#include "base/memory/scoped_ptr.h"
10#include "base/memory/scoped_vector.h"
11#include "base/strings/string_number_conversions.h"
12#include "base/time/time.h"
13#include "chrome/browser/net/evicted_domain_cookie_counter.h"
14#include "net/cookies/canonical_cookie.h"
15#include "net/cookies/cookie_monster.h"
16#include "testing/gtest/include/gtest/gtest.h"
17#include "url/gurl.h"
18
19namespace chrome_browser_net {
20
21using base::Time;
22using base::TimeDelta;
23
24namespace {
25
26const char* google_url1 = "http://www.google.com";
27const char* google_url2 = "http://mail.google.com";
28const char* other_url1 = "http://www.example.com";
29const char* other_url2 = "http://www.example.co.uk";
30
31class EvictedDomainCookieCounterTest : public testing::Test {
32 protected:
33  class MockDelegate : public EvictedDomainCookieCounter::Delegate {
34   public:
35    explicit MockDelegate(EvictedDomainCookieCounterTest* tester);
36
37    // EvictedDomainCookieCounter::Delegate implementation.
38    virtual void Report(
39        const EvictedDomainCookieCounter::EvictedCookie& evicted_cookie,
40        const Time& reinstatement_time) OVERRIDE;
41    virtual Time CurrentTime() const OVERRIDE;
42
43   private:
44    EvictedDomainCookieCounterTest* tester_;
45  };
46
47  EvictedDomainCookieCounterTest();
48  virtual ~EvictedDomainCookieCounterTest();
49
50  // testing::Test implementation.
51  virtual void SetUp() OVERRIDE;
52  virtual void TearDown() OVERRIDE;
53
54  // Initialization that allows parameters to be specified.
55  void InitCounter(size_t max_size, size_t purge_count);
56
57  // Wrapper to allocate new cookie and store it in |cookies_|.
58  // If |max_age| == 0, then the cookie does not expire.
59  void CreateNewCookie(
60      const char* url, const std::string& cookie_line, int64 max_age);
61
62  // Clears |cookies_| and creates common cookies for multiple tests.
63  void InitStockCookies();
64
65  // Sets simulation time to |rel_time|.
66  void GotoTime(int64 rel_time);
67
68  // Simulates time-passage by |delta_second|.
69  void StepTime(int64 delta_second);
70
71  // Simulates cookie addition or update.
72  void Add(net::CanonicalCookie* cookie);
73
74  // Simulates cookie removal.
75  void Remove(net::CanonicalCookie* cookie);
76
77  // Simulates cookie eviction.
78  void Evict(net::CanonicalCookie* cookie);
79
80  // For semi-realism, time considered are relative to |mock_time_base_|.
81  Time mock_time_base_;
82  Time mock_time_;
83
84  // To store allocated cookies for reuse.
85  ScopedVector<net::CanonicalCookie> cookies_;
86
87  scoped_refptr<EvictedDomainCookieCounter> cookie_counter_;
88
89  // Statistics as comma-separated string of duration (in seconds) between
90  // eviction and reinstatement for each cookie, in the order of eviction.
91  std::string google_stat_;
92  std::string other_stat_;
93};
94
95EvictedDomainCookieCounterTest::MockDelegate::MockDelegate(
96    EvictedDomainCookieCounterTest* tester)
97    : tester_(tester) {}
98
99void EvictedDomainCookieCounterTest::MockDelegate::Report(
100    const EvictedDomainCookieCounter::EvictedCookie& evicted_cookie,
101    const Time& reinstatement_time) {
102  std::string& dest = evicted_cookie.is_google ?
103      tester_->google_stat_ : tester_->other_stat_;
104  if (!dest.empty())
105    dest.append(",");
106  TimeDelta delta(reinstatement_time - evicted_cookie.eviction_time);
107  dest.append(base::Int64ToString(delta.InSeconds()));
108}
109
110Time EvictedDomainCookieCounterTest::MockDelegate::CurrentTime() const {
111  return tester_->mock_time_;
112}
113
114EvictedDomainCookieCounterTest::EvictedDomainCookieCounterTest() {}
115
116EvictedDomainCookieCounterTest::~EvictedDomainCookieCounterTest() {}
117
118void EvictedDomainCookieCounterTest::SetUp() {
119  mock_time_base_ = Time::Now() - TimeDelta::FromHours(1);
120  mock_time_ = mock_time_base_;
121}
122
123void EvictedDomainCookieCounterTest::TearDown() {
124}
125
126void EvictedDomainCookieCounterTest::InitCounter(size_t max_size,
127                                                 size_t purge_count) {
128  scoped_ptr<MockDelegate> cookie_counter_delegate(new MockDelegate(this));
129  cookie_counter_ = new EvictedDomainCookieCounter(
130      NULL,
131      cookie_counter_delegate.PassAs<EvictedDomainCookieCounter::Delegate>(),
132      max_size,
133      purge_count);
134}
135
136void EvictedDomainCookieCounterTest::CreateNewCookie(
137    const char* url, const std::string& cookie_line, int64 max_age) {
138  std::string line(cookie_line);
139  if (max_age)
140    line.append(";max-age=" + base::Int64ToString(max_age));
141  net::CanonicalCookie* cookie = net::CanonicalCookie::Create(
142      GURL(url), line, mock_time_, net::CookieOptions());
143  DCHECK(cookie);
144  cookies_.push_back(cookie);
145}
146
147void EvictedDomainCookieCounterTest::InitStockCookies() {
148  cookies_.clear();
149  CreateNewCookie(google_url1, "a1=1", 3000);           // cookies_[0].
150  CreateNewCookie(google_url2, "a2=1", 2000);           // cookies_[1].
151  CreateNewCookie(other_url1, "a1=1", 1000);            // cookies_[2].
152  CreateNewCookie(other_url1, "a2=1", 1001);            // cookies_[3].
153  CreateNewCookie(google_url1, "a1=1;Path=/sub", 999);  // cookies_[4].
154  CreateNewCookie(other_url2, "a2=1", 0);               // cookies_[5].
155}
156
157void EvictedDomainCookieCounterTest::GotoTime(int64 rel_time) {
158  mock_time_ = mock_time_base_ + TimeDelta::FromSeconds(rel_time);
159}
160
161void EvictedDomainCookieCounterTest::StepTime(int64 delta_second) {
162  mock_time_ += TimeDelta::FromSeconds(delta_second);
163}
164
165void EvictedDomainCookieCounterTest::Add(net::CanonicalCookie* cookie) {
166  cookie_counter_->OnCookieChanged(
167      *cookie, false, net::CookieMonster::Delegate::CHANGE_COOKIE_EXPLICIT);
168}
169
170void EvictedDomainCookieCounterTest::Remove(net::CanonicalCookie* cookie) {
171  cookie_counter_->OnCookieChanged(
172      *cookie, true, net::CookieMonster::Delegate::CHANGE_COOKIE_EXPLICIT);
173}
174
175void EvictedDomainCookieCounterTest::Evict(net::CanonicalCookie* cookie) {
176  cookie_counter_->OnCookieChanged(
177      *cookie, true, net::CookieMonster::Delegate::CHANGE_COOKIE_EVICTED);
178}
179
180// EvictedDomainCookieCounter takes (and owns) a CookieMonster::Delegate for
181// chaining. To ensure that the chaining indeed occurs, we implement a
182// dummy CookieMonster::Delegate to increment an integer.
183TEST_F(EvictedDomainCookieCounterTest, TestChain) {
184  int result = 0;
185
186  class ChangedDelegateDummy : public net::CookieMonster::Delegate {
187   public:
188    explicit ChangedDelegateDummy(int* result) : result_(result) {}
189
190    virtual void OnCookieChanged(const net::CanonicalCookie& cookie,
191                                 bool removed,
192                                 ChangeCause cause) OVERRIDE {
193      ++(*result_);
194    }
195
196    virtual void OnLoaded() OVERRIDE {}
197
198   private:
199    virtual ~ChangedDelegateDummy() {}
200
201    int* result_;
202  };
203
204  scoped_ptr<MockDelegate> cookie_counter_delegate(new MockDelegate(this));
205  cookie_counter_ = new EvictedDomainCookieCounter(
206      new ChangedDelegateDummy(&result),
207      cookie_counter_delegate.PassAs<EvictedDomainCookieCounter::Delegate>(),
208      10,
209      5);
210  InitStockCookies();
211  // Perform 6 cookie transactions.
212  for (int i = 0; i < 6; ++i) {
213    Add(cookies_[i]);
214    StepTime(1);
215    Evict(cookies_[i]);
216    StepTime(1);
217    Remove(cookies_[i]);
218  }
219  EXPECT_EQ(18, result);  // 6 cookies x 3 operations each.
220}
221
222// Basic flow: add cookies, evict, then reinstate.
223TEST_F(EvictedDomainCookieCounterTest, TestBasicFlow) {
224  InitCounter(10, 4);
225  InitStockCookies();
226  // Add all cookies at (relative time) t = 0.
227  for (int i = 0; i < 6; ++i)
228    Add(cookies_[i]);
229  EXPECT_EQ(0u, cookie_counter_->GetStorageSize());  // No activities on add.
230  EXPECT_EQ(";", google_stat_ + ";" + other_stat_);
231  // Evict cookies at t = [1,3,6,10,15,21].
232  for (int i = 0; i < 6; ++i) {
233    StepTime(i + 1);
234    Evict(cookies_[i]);
235  }
236  EXPECT_EQ(6u, cookie_counter_->GetStorageSize());  // Storing all evictions.
237  EXPECT_EQ(";", google_stat_ + ";" + other_stat_);
238  // Reinstate cookies at t = [22,23,24,25,26,27].
239  for (int i = 0; i < 6; ++i) {
240    StepTime(1);
241    Add(cookies_[i]);
242  }
243  EXPECT_EQ(0u, cookie_counter_->GetStorageSize());  // Everything is removed.
244  // Expected reinstatement delays: [21,20,18,15,11,6].
245  EXPECT_EQ("21,20,11;18,15,6", google_stat_ + ";" + other_stat_);
246}
247
248// Removed cookies are ignored by EvictedDomainCookieCounter.
249TEST_F(EvictedDomainCookieCounterTest, TestRemove) {
250  InitCounter(10, 4);
251  InitStockCookies();
252  // Add all cookies at (relative time) t = 0.
253  for (int i = 0; i < 6; ++i)
254    Add(cookies_[i]);
255  // Remove cookies at t = [1,3,6,10,15,21].
256  for (int i = 0; i < 6; ++i) {
257    StepTime(i + 1);
258    Remove(cookies_[i]);
259  }
260  EXPECT_EQ(0u, cookie_counter_->GetStorageSize());
261  // Add cookies again at t = [22,23,24,25,26,27].
262  for (int i = 0; i < 5; ++i) {
263    StepTime(1);
264    Add(cookies_[i]);
265  }
266  EXPECT_EQ(0u, cookie_counter_->GetStorageSize());
267  // No cookies were evicted, so no reinstatement take place.
268  EXPECT_EQ(";", google_stat_ + ";" + other_stat_);
269}
270
271// Expired cookies should not be counted by EvictedDomainCookieCounter.
272TEST_F(EvictedDomainCookieCounterTest, TestExpired) {
273  InitCounter(10, 4);
274  InitStockCookies();
275  // Add all cookies at (relative time) t = 0.
276  for (int i = 0; i < 6; ++i)
277    Add(cookies_[i]);
278  // Evict cookies at t = [1,3,6,10,15,21].
279  for (int i = 0; i < 6; ++i) {
280    StepTime(i + 1);
281    Evict(cookies_[i]);
282  }
283  EXPECT_EQ(6u, cookie_counter_->GetStorageSize());
284  GotoTime(1000);  // t = 1000, so cookies_[2,4] expire.
285
286  // Reinstate cookies at t = [1000,1000,(1000),1000,(1000),1000].
287  InitStockCookies();  // Refresh cookies, so new cookies expire in the future.
288  for (int i = 0; i < 6; ++i)
289    Add(cookies_[i]);
290  EXPECT_EQ(0u, cookie_counter_->GetStorageSize());
291  // Reinstatement delays: [999,997,(994),990,(985),979].
292  EXPECT_EQ("999,997;990,979", google_stat_ + ";" + other_stat_);
293}
294
295// Garbage collection should remove the oldest evicted cookies.
296TEST_F(EvictedDomainCookieCounterTest, TestGarbageCollection) {
297  InitCounter(4, 2);  // Reduced capacity.
298  InitStockCookies();
299  // Add all cookies at (relative time) t = 0.
300  for (int i = 0; i < 6; ++i)
301    Add(cookies_[i]);
302  // Evict cookies at t = [1,3,6,10].
303  for (int i = 0; i < 4; ++i) {
304    StepTime(i + 1);
305    Evict(cookies_[i]);
306  }
307  EXPECT_EQ(4u, cookie_counter_->GetStorageSize());  // Reached capacity.
308  StepTime(5);
309  Evict(cookies_[4]);  // Evict at t = 15, garbage collection takes place.
310  EXPECT_EQ(2u, cookie_counter_->GetStorageSize());
311  StepTime(6);
312  Evict(cookies_[5]);  // Evict at t = 21.
313  EXPECT_EQ(3u, cookie_counter_->GetStorageSize());
314  EXPECT_EQ(";", google_stat_ + ";" + other_stat_);
315  // Reinstate cookies at t = [(100),(100),(100),100,100,100].
316  GotoTime(100);
317  for (int i = 0; i < 6; ++i)
318    Add(cookies_[i]);
319  // Expected reinstatement delays: [(99),(97),(94),90,85,79]
320  EXPECT_EQ("85;90,79", google_stat_ + ";" + other_stat_);
321}
322
323// Garbage collection should remove the specified number of evicted cookies
324// even when there are ties amongst oldest evicted cookies.
325TEST_F(EvictedDomainCookieCounterTest, TestGarbageCollectionTie) {
326  InitCounter(9, 3);
327  // Add 10 cookies at time [0,1,3,6,...,45]
328  for (int i = 0; i < 10; ++i) {
329    StepTime(i);
330    CreateNewCookie(google_url1, "a" + base::IntToString(i) + "=1", 3000);
331    Add(cookies_[i]);
332  }
333  // Evict 6 cookies at t = [100,...,100].
334  GotoTime(100);
335  for (int i = 0; i < 6; ++i)
336    Evict(cookies_[i]);
337  EXPECT_EQ(6u, cookie_counter_->GetStorageSize());
338  // Evict 3 cookies at t = [210,220,230].
339  GotoTime(200);
340  for (int i = 6; i < 9; ++i) {
341    StepTime(10);
342    Evict(cookies_[i]);
343  }
344  EXPECT_EQ(9u, cookie_counter_->GetStorageSize());  // Reached capacity.
345  // Evict 1 cookie at t = 300, and garbage collection takes place.
346  GotoTime(300);
347  Evict(cookies_[9]);
348  // Some arbitrary 4 out of 6 cookies evicted at t = 100 are gone from storage.
349  EXPECT_EQ(6u, cookie_counter_->GetStorageSize());  // 10 - 4.
350  // Reinstate cookies at t = [400,...,400].
351  GotoTime(400);
352  for (int i = 0; i < 10; ++i)
353    Add(cookies_[i]);
354  EXPECT_EQ(0u, cookie_counter_->GetStorageSize());
355  // Expected reinstatement delays:
356  // [300,300,300,300,300,300 <= keeping 2 only,190,180,170,100].
357  EXPECT_EQ("300,300,190,180,170,100;", google_stat_ + ";" + other_stat_);
358}
359
360// Garbage collection prioritize removal of expired cookies.
361TEST_F(EvictedDomainCookieCounterTest, TestGarbageCollectionWithExpiry) {
362  InitCounter(5, 1);
363  InitStockCookies();
364  // Add all cookies at (relative time) t = 0.
365  for (int i = 0; i < 6; ++i)
366    Add(cookies_[i]);
367  // Evict cookies at t = [1,3,6,10,15].
368  for (int i = 0; i < 5; ++i) {
369    StepTime(i + 1);
370    Evict(cookies_[i]);
371  }
372  EXPECT_EQ(5u, cookie_counter_->GetStorageSize());  // Reached capacity.
373  GotoTime(1200);  // t = 1200, so cookies_[2,3,4] expire.
374  // Evict cookies_[5] (not expired) at t = 1200.
375  Evict(cookies_[5]);
376  // Garbage collection would have taken place, removing 3 expired cookies,
377  // so that there's no need to remove more.
378  EXPECT_EQ(3u, cookie_counter_->GetStorageSize());
379  // Reinstate cookies at t = [1500,1500,(1500),(1500),(1500),1500].
380  GotoTime(1500);
381  InitStockCookies();  // Refresh cookies, so new cookies expire in the future.
382  for (int i = 0; i < 6; ++i)
383    Add(cookies_[i]);
384  EXPECT_EQ(0u, cookie_counter_->GetStorageSize());
385  // Reinstatement delays: [1499,1497,(1494),(1490),(1485),300].
386  EXPECT_EQ("1499,1497;300", google_stat_ + ";" + other_stat_);
387}
388
389}  // namespace
390
391}  // namespace chrome_browser_net
392