• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2012 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 <memory>
6 #include <utility>
7 
8 #include "base/functional/bind.h"
9 #include "base/memory/ref_counted.h"
10 #include "base/run_loop.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/test/task_environment.h"
14 #include "base/time/time.h"
15 #include "base/timer/elapsed_timer.h"
16 #include "net/cookies/canonical_cookie.h"
17 #include "net/cookies/cookie_monster.h"
18 #include "net/cookies/cookie_monster_store_test.h"
19 #include "net/cookies/cookie_util.h"
20 #include "net/cookies/parsed_cookie.h"
21 #include "testing/gtest/include/gtest/gtest.h"
22 #include "testing/perf/perf_result_reporter.h"
23 #include "third_party/abseil-cpp/absl/types/optional.h"
24 #include "url/gurl.h"
25 
26 namespace net {
27 
28 namespace {
29 
30 const int kNumCookies = 20000;
31 const char kCookieLine[] = "A  = \"b=;\\\"\"  ;secure;;; samesite=none";
32 
33 static constexpr char kMetricPrefixParsedCookie[] = "ParsedCookie.";
34 static constexpr char kMetricPrefixCookieMonster[] = "CookieMonster.";
35 static constexpr char kMetricParseTimeMs[] = "parse_time";
36 static constexpr char kMetricAddTimeMs[] = "add_time";
37 static constexpr char kMetricQueryTimeMs[] = "query_time";
38 static constexpr char kMetricDeleteAllTimeMs[] = "delete_all_time";
39 static constexpr char kMetricQueryDomainTimeMs[] = "query_domain_time";
40 static constexpr char kMetricImportTimeMs[] = "import_time";
41 static constexpr char kMetricGetKeyTimeMs[] = "get_key_time";
42 static constexpr char kMetricGCTimeMs[] = "gc_time";
43 
SetUpParseReporter(const std::string & story)44 perf_test::PerfResultReporter SetUpParseReporter(const std::string& story) {
45   perf_test::PerfResultReporter reporter(kMetricPrefixParsedCookie, story);
46   reporter.RegisterImportantMetric(kMetricParseTimeMs, "ms");
47   return reporter;
48 }
49 
SetUpCookieMonsterReporter(const std::string & story)50 perf_test::PerfResultReporter SetUpCookieMonsterReporter(
51     const std::string& story) {
52   perf_test::PerfResultReporter reporter(kMetricPrefixCookieMonster, story);
53   reporter.RegisterImportantMetric(kMetricAddTimeMs, "ms");
54   reporter.RegisterImportantMetric(kMetricQueryTimeMs, "ms");
55   reporter.RegisterImportantMetric(kMetricDeleteAllTimeMs, "ms");
56   reporter.RegisterImportantMetric(kMetricQueryDomainTimeMs, "ms");
57   reporter.RegisterImportantMetric(kMetricImportTimeMs, "ms");
58   reporter.RegisterImportantMetric(kMetricGetKeyTimeMs, "ms");
59   reporter.RegisterImportantMetric(kMetricGCTimeMs, "ms");
60   return reporter;
61 }
62 
63 class CookieMonsterTest : public testing::Test {
64  public:
65   CookieMonsterTest() = default;
66 
67  private:
68   base::test::SingleThreadTaskEnvironment task_environment_{
69       base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
70 };
71 
72 class CookieTestCallback {
73  public:
74   CookieTestCallback() = default;
75 
76  protected:
WaitForCallback()77   void WaitForCallback() {
78     // Note that the performance tests currently all operate on a loaded cookie
79     // store (or, more precisely, one that has no backing persistent store).
80     // Therefore, callbacks will actually always complete synchronously. If the
81     // tests get more advanced we need to add other means of signaling
82     // completion.
83     base::RunLoop().RunUntilIdle();
84     EXPECT_TRUE(has_run_);
85     has_run_ = false;
86   }
87 
Run()88   void Run() { has_run_ = true; }
89 
90   bool has_run_ = false;
91 };
92 
93 class SetCookieCallback : public CookieTestCallback {
94  public:
SetCookie(CookieMonster * cm,const GURL & gurl,const std::string & cookie_line)95   void SetCookie(CookieMonster* cm,
96                  const GURL& gurl,
97                  const std::string& cookie_line) {
98     auto cookie = CanonicalCookie::Create(
99         gurl, cookie_line, base::Time::Now(), absl::nullopt /* server_time */,
100         absl::nullopt /* cookie_partition_key */);
101     cm->SetCanonicalCookieAsync(
102         std::move(cookie), gurl, options_,
103         base::BindOnce(&SetCookieCallback::Run, base::Unretained(this)));
104     WaitForCallback();
105   }
106 
107  private:
Run(CookieAccessResult result)108   void Run(CookieAccessResult result) {
109     EXPECT_TRUE(result.status.IsInclude())
110         << "result.status: " << result.status.GetDebugString();
111     CookieTestCallback::Run();
112   }
113   CookieOptions options_;
114 };
115 
116 class GetCookieListCallback : public CookieTestCallback {
117  public:
GetCookieList(CookieMonster * cm,const GURL & gurl)118   const CookieList& GetCookieList(CookieMonster* cm, const GURL& gurl) {
119     cm->GetCookieListWithOptionsAsync(
120         gurl, options_, CookiePartitionKeyCollection(),
121         base::BindOnce(&GetCookieListCallback::Run, base::Unretained(this)));
122     WaitForCallback();
123     return cookie_list_;
124   }
125 
126  private:
Run(const CookieAccessResultList & cookie_list,const CookieAccessResultList & excluded_cookies)127   void Run(const CookieAccessResultList& cookie_list,
128            const CookieAccessResultList& excluded_cookies) {
129     cookie_list_ = cookie_util::StripAccessResults(cookie_list);
130     CookieTestCallback::Run();
131   }
132   CookieList cookie_list_;
133   CookieOptions options_;
134 };
135 
136 class GetAllCookiesCallback : public CookieTestCallback {
137  public:
GetAllCookies(CookieMonster * cm)138   CookieList GetAllCookies(CookieMonster* cm) {
139     cm->GetAllCookiesAsync(
140         base::BindOnce(&GetAllCookiesCallback::Run, base::Unretained(this)));
141     WaitForCallback();
142     return cookies_;
143   }
144 
145  private:
Run(const CookieList & cookies)146   void Run(const CookieList& cookies) {
147     cookies_ = cookies;
148     CookieTestCallback::Run();
149   }
150   CookieList cookies_;
151 };
152 
153 }  // namespace
154 
TEST(ParsedCookieTest,TestParseCookies)155 TEST(ParsedCookieTest, TestParseCookies) {
156   std::string cookie(kCookieLine);
157   auto reporter = SetUpParseReporter("parse_cookies");
158   base::ElapsedTimer timer;
159   for (int i = 0; i < kNumCookies; ++i) {
160     ParsedCookie pc(cookie);
161     EXPECT_TRUE(pc.IsValid());
162   }
163   reporter.AddResult(kMetricParseTimeMs, timer.Elapsed().InMillisecondsF());
164 }
165 
TEST(ParsedCookieTest,TestParseBigCookies)166 TEST(ParsedCookieTest, TestParseBigCookies) {
167   std::string cookie(3800, 'z');
168   cookie += kCookieLine;
169   auto reporter = SetUpParseReporter("parse_big_cookies");
170   base::ElapsedTimer timer;
171   for (int i = 0; i < kNumCookies; ++i) {
172     ParsedCookie pc(cookie);
173     EXPECT_TRUE(pc.IsValid());
174   }
175   reporter.AddResult(kMetricParseTimeMs, timer.Elapsed().InMillisecondsF());
176 }
177 
TEST_F(CookieMonsterTest,TestAddCookiesOnSingleHost)178 TEST_F(CookieMonsterTest, TestAddCookiesOnSingleHost) {
179   auto cm = std::make_unique<CookieMonster>(nullptr, nullptr);
180   std::vector<std::string> cookies;
181   for (int i = 0; i < kNumCookies; i++) {
182     cookies.push_back(base::StringPrintf("a%03d=b; SameSite=None; Secure", i));
183   }
184 
185   SetCookieCallback setCookieCallback;
186 
187   // Add a bunch of cookies on a single host
188   auto reporter = SetUpCookieMonsterReporter("single_host");
189   base::ElapsedTimer add_timer;
190 
191   const GURL kGoogleURL = GURL("https://www.foo.com");
192   for (std::vector<std::string>::const_iterator it = cookies.begin();
193        it != cookies.end(); ++it) {
194     setCookieCallback.SetCookie(cm.get(), kGoogleURL, *it);
195   }
196   reporter.AddResult(kMetricAddTimeMs, add_timer.Elapsed().InMillisecondsF());
197 
198   GetCookieListCallback getCookieListCallback;
199 
200   base::ElapsedTimer query_timer;
201   for (std::vector<std::string>::const_iterator it = cookies.begin();
202        it != cookies.end(); ++it) {
203     getCookieListCallback.GetCookieList(cm.get(), kGoogleURL);
204   }
205   reporter.AddResult(kMetricQueryTimeMs,
206                      query_timer.Elapsed().InMillisecondsF());
207 
208   base::ElapsedTimer delete_all_timer;
209   cm->DeleteAllAsync(CookieMonster::DeleteCallback());
210   base::RunLoop().RunUntilIdle();
211   reporter.AddResult(kMetricDeleteAllTimeMs,
212                      delete_all_timer.Elapsed().InMillisecondsF());
213 }
214 
TEST_F(CookieMonsterTest,TestAddCookieOnManyHosts)215 TEST_F(CookieMonsterTest, TestAddCookieOnManyHosts) {
216   auto cm = std::make_unique<CookieMonster>(nullptr, nullptr);
217   std::string cookie(kCookieLine);
218   std::vector<GURL> gurls;  // just wanna have ffffuunnn
219   for (int i = 0; i < kNumCookies; ++i) {
220     gurls.emplace_back(base::StringPrintf("https://a%04d.izzle", i));
221   }
222 
223   SetCookieCallback setCookieCallback;
224 
225   // Add a cookie on a bunch of host
226   auto reporter = SetUpCookieMonsterReporter("many_hosts");
227   base::ElapsedTimer add_timer;
228   for (std::vector<GURL>::const_iterator it = gurls.begin(); it != gurls.end();
229        ++it) {
230     setCookieCallback.SetCookie(cm.get(), *it, cookie);
231   }
232   reporter.AddResult(kMetricAddTimeMs, add_timer.Elapsed().InMillisecondsF());
233 
234   GetCookieListCallback getCookieListCallback;
235 
236   base::ElapsedTimer query_timer;
237   for (std::vector<GURL>::const_iterator it = gurls.begin(); it != gurls.end();
238        ++it) {
239     getCookieListCallback.GetCookieList(cm.get(), *it);
240   }
241   reporter.AddResult(kMetricQueryTimeMs,
242                      query_timer.Elapsed().InMillisecondsF());
243 
244   base::ElapsedTimer delete_all_timer;
245   cm->DeleteAllAsync(CookieMonster::DeleteCallback());
246   base::RunLoop().RunUntilIdle();
247   reporter.AddResult(kMetricDeleteAllTimeMs,
248                      delete_all_timer.Elapsed().InMillisecondsF());
249 }
250 
TEST_F(CookieMonsterTest,TestDomainTree)251 TEST_F(CookieMonsterTest, TestDomainTree) {
252   auto cm = std::make_unique<CookieMonster>(nullptr, nullptr);
253   GetCookieListCallback getCookieListCallback;
254   SetCookieCallback setCookieCallback;
255   const char domain_cookie_format_tree[] =
256       "a=b; domain=%s; samesite=none; secure";
257   const std::string domain_base("top.com");
258 
259   std::vector<std::string> domain_list;
260 
261   // Create a balanced binary tree of domains on which the cookie is set.
262   domain_list.push_back(domain_base);
263   for (int i1 = 0; i1 < 2; i1++) {
264     std::string domain_base_1((i1 ? "a." : "b.") + domain_base);
265     EXPECT_EQ("top.com", cm->GetKey(domain_base_1));
266     domain_list.push_back(domain_base_1);
267     for (int i2 = 0; i2 < 2; i2++) {
268       std::string domain_base_2((i2 ? "a." : "b.") + domain_base_1);
269       EXPECT_EQ("top.com", cm->GetKey(domain_base_2));
270       domain_list.push_back(domain_base_2);
271       for (int i3 = 0; i3 < 2; i3++) {
272         std::string domain_base_3((i3 ? "a." : "b.") + domain_base_2);
273         EXPECT_EQ("top.com", cm->GetKey(domain_base_3));
274         domain_list.push_back(domain_base_3);
275         for (int i4 = 0; i4 < 2; i4++) {
276           std::string domain_base_4((i4 ? "a." : "b.") + domain_base_3);
277           EXPECT_EQ("top.com", cm->GetKey(domain_base_4));
278           domain_list.push_back(domain_base_4);
279         }
280       }
281     }
282   }
283 
284   EXPECT_EQ(31u, domain_list.size());
285   for (std::vector<std::string>::const_iterator it = domain_list.begin();
286        it != domain_list.end(); it++) {
287     GURL gurl("https://" + *it + "/");
288     const std::string cookie =
289         base::StringPrintf(domain_cookie_format_tree, it->c_str());
290     setCookieCallback.SetCookie(cm.get(), gurl, cookie);
291   }
292 
293   GetAllCookiesCallback getAllCookiesCallback;
294   EXPECT_EQ(31u, getAllCookiesCallback.GetAllCookies(cm.get()).size());
295 
296   GURL probe_gurl("https://b.a.b.a.top.com/");
297   const CookieList& cookie_list =
298       getCookieListCallback.GetCookieList(cm.get(), probe_gurl);
299   EXPECT_EQ(5u, cookie_list.size())
300       << CanonicalCookie::BuildCookieLine(cookie_list);
301   auto reporter = SetUpCookieMonsterReporter("tree");
302   base::ElapsedTimer query_domain_timer;
303   for (int i = 0; i < kNumCookies; i++) {
304     getCookieListCallback.GetCookieList(cm.get(), probe_gurl);
305   }
306   reporter.AddResult(kMetricQueryDomainTimeMs,
307                      query_domain_timer.Elapsed().InMillisecondsF());
308 }
309 
TEST_F(CookieMonsterTest,TestDomainLine)310 TEST_F(CookieMonsterTest, TestDomainLine) {
311   auto cm = std::make_unique<CookieMonster>(nullptr, nullptr);
312   SetCookieCallback setCookieCallback;
313   GetCookieListCallback getCookieListCallback;
314   std::vector<std::string> domain_list;
315   GURL probe_gurl("https://b.a.b.a.top.com/");
316 
317   // Create a line of 32 domain cookies such that all cookies stored
318   // by effective TLD+1 will apply to probe GURL.
319   // (TLD + 1 is the level above .com/org/net/etc, e.g. "top.com"
320   // or "google.com".  "Effective" is added to include sites like
321   // bbc.co.uk, where the effetive TLD+1 is more than one level
322   // below the top level.)
323   domain_list.push_back("a.top.com");
324   domain_list.push_back("b.a.top.com");
325   domain_list.push_back("a.b.a.top.com");
326   domain_list.push_back("b.a.b.a.top.com");
327   EXPECT_EQ(4u, domain_list.size());
328 
329   const char domain_cookie_format_line[] =
330       "a%03d=b; domain=%s; samesite=none; secure";
331   for (int i = 0; i < 8; i++) {
332     for (std::vector<std::string>::const_iterator it = domain_list.begin();
333          it != domain_list.end(); it++) {
334       GURL gurl("https://" + *it + "/");
335       const std::string cookie =
336           base::StringPrintf(domain_cookie_format_line, i, it->c_str());
337       setCookieCallback.SetCookie(cm.get(), gurl, cookie);
338     }
339   }
340 
341   const CookieList& cookie_list =
342       getCookieListCallback.GetCookieList(cm.get(), probe_gurl);
343   EXPECT_EQ(32u, cookie_list.size());
344   auto reporter = SetUpCookieMonsterReporter("line");
345   base::ElapsedTimer query_domain_timer;
346   for (int i = 0; i < kNumCookies; i++) {
347     getCookieListCallback.GetCookieList(cm.get(), probe_gurl);
348   }
349   reporter.AddResult(kMetricQueryDomainTimeMs,
350                      query_domain_timer.Elapsed().InMillisecondsF());
351 }
352 
TEST_F(CookieMonsterTest,TestImport)353 TEST_F(CookieMonsterTest, TestImport) {
354   auto store = base::MakeRefCounted<MockPersistentCookieStore>();
355   std::vector<std::unique_ptr<CanonicalCookie>> initial_cookies;
356   GetCookieListCallback getCookieListCallback;
357 
358   // We want to setup a fairly large backing store, with 300 domains of 50
359   // cookies each.  Creation times must be unique.
360   int64_t time_tick(base::Time::Now().ToInternalValue());
361 
362   for (int domain_num = 0; domain_num < 300; domain_num++) {
363     GURL gurl(base::StringPrintf("http://www.Domain_%d.com", domain_num));
364     for (int cookie_num = 0; cookie_num < 50; cookie_num++) {
365       std::string cookie_line(
366           base::StringPrintf("Cookie_%d=1; Path=/", cookie_num));
367       AddCookieToList(gurl, cookie_line,
368                       base::Time::FromInternalValue(time_tick++),
369                       &initial_cookies);
370     }
371   }
372 
373   store->SetLoadExpectation(true, std::move(initial_cookies));
374 
375   auto cm = std::make_unique<CookieMonster>(store.get(), nullptr);
376 
377   // Import will happen on first access.
378   GURL gurl("www.foo.com");
379   CookieOptions options;
380   auto reporter = SetUpCookieMonsterReporter("from_store");
381   base::ElapsedTimer import_timer;
382   getCookieListCallback.GetCookieList(cm.get(), gurl);
383   reporter.AddResult(kMetricImportTimeMs,
384                      import_timer.Elapsed().InMillisecondsF());
385 
386   // Just confirm keys were set as expected.
387   EXPECT_EQ("domain_1.com", cm->GetKey("www.Domain_1.com"));
388 }
389 
TEST_F(CookieMonsterTest,TestGetKey)390 TEST_F(CookieMonsterTest, TestGetKey) {
391   auto cm = std::make_unique<CookieMonster>(nullptr, nullptr);
392   auto reporter = SetUpCookieMonsterReporter("baseline_story");
393   base::ElapsedTimer get_key_timer;
394   for (int i = 0; i < kNumCookies; i++)
395     cm->GetKey("www.foo.com");
396   reporter.AddResult(kMetricGetKeyTimeMs,
397                      get_key_timer.Elapsed().InMillisecondsF());
398 }
399 
400 // This test is probing for whether garbage collection happens when it
401 // shouldn't.  This will not in general be visible functionally, since
402 // if GC runs twice in a row without any change to the store, the second
403 // GC run will not do anything the first one didn't.  That's why this is
404 // a performance test.  The test should be considered to pass if all the
405 // times reported are approximately the same--this indicates that no GC
406 // happened repeatedly for any case.
TEST_F(CookieMonsterTest,TestGCTimes)407 TEST_F(CookieMonsterTest, TestGCTimes) {
408   SetCookieCallback setCookieCallback;
409 
410   const struct TestCase {
411     const char* const name;
412     size_t num_cookies;
413     size_t num_old_cookies;
414   } test_cases[] = {
415       {
416        // A whole lot of recent cookies; gc shouldn't happen.
417        "all_recent",
418        CookieMonster::kMaxCookies * 2,
419        0,
420       },
421       {
422        // Some old cookies, but still overflowing max.
423        "mostly_recent",
424        CookieMonster::kMaxCookies * 2,
425        CookieMonster::kMaxCookies / 2,
426       },
427       {
428        // Old cookies enough to bring us right down to our purge line.
429        "balanced",
430        CookieMonster::kMaxCookies * 2,
431        CookieMonster::kMaxCookies + CookieMonster::kPurgeCookies + 1,
432       },
433       {
434        "mostly_old",
435        // Old cookies enough to bring below our purge line (which we
436        // shouldn't do).
437        CookieMonster::kMaxCookies * 2,
438        CookieMonster::kMaxCookies * 3 / 4,
439       },
440       {
441        "less_than_gc_thresh",
442        // Few enough cookies that gc shouldn't happen at all.
443        CookieMonster::kMaxCookies - 5,
444        0,
445       },
446   };
447   for (const auto& test_case : test_cases) {
448     std::unique_ptr<CookieMonster> cm = CreateMonsterFromStoreForGC(
449         test_case.num_cookies, test_case.num_old_cookies, 0, 0,
450         CookieMonster::kSafeFromGlobalPurgeDays * 2);
451 
452     GURL gurl("https://foo.com");
453     std::string cookie_line("z=3; samesite=none; secure");
454     // Trigger the Garbage collection we're allowed.
455     setCookieCallback.SetCookie(cm.get(), gurl, cookie_line);
456 
457     auto reporter = SetUpCookieMonsterReporter(test_case.name);
458     base::ElapsedTimer gc_timer;
459     for (int i = 0; i < kNumCookies; i++)
460       setCookieCallback.SetCookie(cm.get(), gurl, cookie_line);
461     reporter.AddResult(kMetricGCTimeMs, gc_timer.Elapsed().InMillisecondsF());
462   }
463 }
464 
465 }  // namespace net
466