1// Copyright (c) 2010 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 <vector>
6#include <string>
7#include <cstdio>
8
9#include "base/message_loop.h"
10#include "base/file_util.h"
11#include "base/path_service.h"
12#include "base/process_util.h"
13#include "base/shared_memory.h"
14#include "base/string_util.h"
15#include "chrome/browser/visitedlink/visitedlink_master.h"
16#include "chrome/browser/visitedlink/visitedlink_event_listener.h"
17#include "chrome/common/render_messages.h"
18#include "chrome/renderer/visitedlink_slave.h"
19#include "chrome/test/testing_profile.h"
20#include "content/browser/browser_thread.h"
21#include "content/browser/renderer_host/browser_render_process_host.h"
22#include "content/browser/renderer_host/test_render_view_host.h"
23#include "googleurl/src/gurl.h"
24#include "testing/gtest/include/gtest/gtest.h"
25
26namespace {
27
28// a nice long URL that we can append numbers to to get new URLs
29const char g_test_prefix[] =
30  "http://www.google.com/products/foo/index.html?id=45028640526508376&seq=";
31const int g_test_count = 1000;
32
33// Returns a test URL for index |i|
34GURL TestURL(int i) {
35  return GURL(StringPrintf("%s%d", g_test_prefix, i));
36}
37
38std::vector<VisitedLinkSlave*> g_slaves;
39
40}  // namespace
41
42class TrackingVisitedLinkEventListener : public VisitedLinkMaster::Listener {
43 public:
44  TrackingVisitedLinkEventListener()
45      : reset_count_(0),
46        add_count_(0) {}
47
48  virtual void NewTable(base::SharedMemory* table) {
49    if (table) {
50      for (std::vector<VisitedLinkSlave>::size_type i = 0;
51           i < g_slaves.size(); i++) {
52        base::SharedMemoryHandle new_handle = base::SharedMemory::NULLHandle();
53        table->ShareToProcess(base::GetCurrentProcessHandle(), &new_handle);
54        g_slaves[i]->OnUpdateVisitedLinks(new_handle);
55      }
56    }
57  }
58  virtual void Add(VisitedLinkCommon::Fingerprint) { add_count_++; }
59  virtual void Reset() { reset_count_++; }
60
61  void SetUp() {
62    reset_count_ = 0;
63    add_count_ = 0;
64  }
65
66  int reset_count() const { return reset_count_; }
67  int add_count() const { return add_count_; }
68
69 private:
70  int reset_count_;
71  int add_count_;
72};
73
74class VisitedLinkTest : public testing::Test {
75 protected:
76  VisitedLinkTest()
77      : ui_thread_(BrowserThread::UI, &message_loop_),
78        file_thread_(BrowserThread::FILE, &message_loop_) {}
79  // Initialize the history system. This should be called before InitVisited().
80  bool InitHistory() {
81    history_service_ = new HistoryService;
82    return history_service_->Init(history_dir_, NULL);
83  }
84
85  // Initializes the visited link objects. Pass in the size that you want a
86  // freshly created table to be. 0 means use the default.
87  //
88  // |suppress_rebuild| is set when we're not testing rebuilding, see
89  // the VisitedLinkMaster constructor.
90  bool InitVisited(int initial_size, bool suppress_rebuild) {
91    // Initialize the visited link system.
92    master_.reset(new VisitedLinkMaster(&listener_, history_service_,
93                                        suppress_rebuild, visited_file_,
94                                        initial_size));
95    return master_->Init();
96  }
97
98  // May be called multiple times (some tests will do this to clear things,
99  // and TearDown will do this to make sure eveything is shiny before quitting.
100  void ClearDB() {
101    if (master_.get())
102      master_.reset(NULL);
103
104    if (history_service_.get()) {
105      history_service_->SetOnBackendDestroyTask(new MessageLoop::QuitTask);
106      history_service_->Cleanup();
107      history_service_ = NULL;
108
109      // Wait for the backend class to terminate before deleting the files and
110      // moving to the next test. Note: if this never terminates, somebody is
111      // probably leaking a reference to the history backend, so it never calls
112      // our destroy task.
113      MessageLoop::current()->Run();
114    }
115  }
116
117  // Loads the database from disk and makes sure that the same URLs are present
118  // as were generated by TestIO_Create(). This also checks the URLs with a
119  // slave to make sure it reads the data properly.
120  void Reload() {
121    // Clean up after our caller, who may have left the database open.
122    ClearDB();
123
124    ASSERT_TRUE(InitHistory());
125    ASSERT_TRUE(InitVisited(0, true));
126    master_->DebugValidate();
127
128    // check that the table has the proper number of entries
129    int used_count = master_->GetUsedCount();
130    ASSERT_EQ(used_count, g_test_count);
131
132    // Create a slave database.
133    VisitedLinkSlave slave;
134    base::SharedMemoryHandle new_handle = base::SharedMemory::NULLHandle();
135    master_->shared_memory()->ShareToProcess(
136        base::GetCurrentProcessHandle(), &new_handle);
137    slave.OnUpdateVisitedLinks(new_handle);
138    g_slaves.push_back(&slave);
139
140    bool found;
141    for (int i = 0; i < g_test_count; i++) {
142      GURL cur = TestURL(i);
143      found = master_->IsVisited(cur);
144      EXPECT_TRUE(found) << "URL " << i << "not found in master.";
145
146      found = slave.IsVisited(cur);
147      EXPECT_TRUE(found) << "URL " << i << "not found in slave.";
148    }
149
150    // test some random URL so we know that it returns false sometimes too
151    found = master_->IsVisited(GURL("http://unfound.site/"));
152    ASSERT_FALSE(found);
153    found = slave.IsVisited(GURL("http://unfound.site/"));
154    ASSERT_FALSE(found);
155
156    master_->DebugValidate();
157
158    g_slaves.clear();
159  }
160
161  // testing::Test
162  virtual void SetUp() {
163    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
164
165    history_dir_ = temp_dir_.path().AppendASCII("VisitedLinkTest");
166    ASSERT_TRUE(file_util::CreateDirectory(history_dir_));
167
168    visited_file_ = history_dir_.Append(FILE_PATH_LITERAL("VisitedLinks"));
169    listener_.SetUp();
170  }
171
172  virtual void TearDown() {
173    ClearDB();
174  }
175
176  ScopedTempDir temp_dir_;
177
178  MessageLoop message_loop_;
179  BrowserThread ui_thread_;
180  BrowserThread file_thread_;
181
182  // Filenames for the services;
183  FilePath history_dir_;
184  FilePath visited_file_;
185
186  scoped_ptr<VisitedLinkMaster> master_;
187  scoped_refptr<HistoryService> history_service_;
188  TrackingVisitedLinkEventListener listener_;
189};
190
191// This test creates and reads some databases to make sure the data is
192// preserved throughout those operations.
193TEST_F(VisitedLinkTest, DatabaseIO) {
194  ASSERT_TRUE(InitHistory());
195  ASSERT_TRUE(InitVisited(0, true));
196
197  for (int i = 0; i < g_test_count; i++)
198    master_->AddURL(TestURL(i));
199
200  // Test that the database was written properly
201  Reload();
202}
203
204// Checks that we can delete things properly when there are collisions.
205TEST_F(VisitedLinkTest, Delete) {
206  static const int32 kInitialSize = 17;
207  ASSERT_TRUE(InitHistory());
208  ASSERT_TRUE(InitVisited(kInitialSize, true));
209
210  // Add a cluster from 14-17 wrapping around to 0. These will all hash to the
211  // same value.
212  const VisitedLinkCommon::Fingerprint kFingerprint0 = kInitialSize * 0 + 14;
213  const VisitedLinkCommon::Fingerprint kFingerprint1 = kInitialSize * 1 + 14;
214  const VisitedLinkCommon::Fingerprint kFingerprint2 = kInitialSize * 2 + 14;
215  const VisitedLinkCommon::Fingerprint kFingerprint3 = kInitialSize * 3 + 14;
216  const VisitedLinkCommon::Fingerprint kFingerprint4 = kInitialSize * 4 + 14;
217  master_->AddFingerprint(kFingerprint0, false);  // @14
218  master_->AddFingerprint(kFingerprint1, false);  // @15
219  master_->AddFingerprint(kFingerprint2, false);  // @16
220  master_->AddFingerprint(kFingerprint3, false);  // @0
221  master_->AddFingerprint(kFingerprint4, false);  // @1
222
223  // Deleting 14 should move the next value up one slot (we do not specify an
224  // order).
225  EXPECT_EQ(kFingerprint3, master_->hash_table_[0]);
226  master_->DeleteFingerprint(kFingerprint3, false);
227  VisitedLinkCommon::Fingerprint zero_fingerprint = 0;
228  EXPECT_EQ(zero_fingerprint, master_->hash_table_[1]);
229  EXPECT_NE(zero_fingerprint, master_->hash_table_[0]);
230
231  // Deleting the other four should leave the table empty.
232  master_->DeleteFingerprint(kFingerprint0, false);
233  master_->DeleteFingerprint(kFingerprint1, false);
234  master_->DeleteFingerprint(kFingerprint2, false);
235  master_->DeleteFingerprint(kFingerprint4, false);
236
237  EXPECT_EQ(0, master_->used_items_);
238  for (int i = 0; i < kInitialSize; i++)
239    EXPECT_EQ(zero_fingerprint, master_->hash_table_[i]) <<
240        "Hash table has values in it.";
241}
242
243// When we delete more than kBigDeleteThreshold we trigger different behavior
244// where the entire file is rewritten.
245TEST_F(VisitedLinkTest, BigDelete) {
246  ASSERT_TRUE(InitHistory());
247  ASSERT_TRUE(InitVisited(16381, true));
248
249  // Add the base set of URLs that won't be deleted.
250  // Reload() will test for these.
251  for (int32 i = 0; i < g_test_count; i++)
252    master_->AddURL(TestURL(i));
253
254  // Add more URLs than necessary to trigger this case.
255  const int kTestDeleteCount = VisitedLinkMaster::kBigDeleteThreshold + 2;
256  std::set<GURL> urls_to_delete;
257  for (int32 i = g_test_count; i < g_test_count + kTestDeleteCount; i++) {
258    GURL url(TestURL(i));
259    master_->AddURL(url);
260    urls_to_delete.insert(url);
261  }
262
263  master_->DeleteURLs(urls_to_delete);
264  master_->DebugValidate();
265
266  Reload();
267}
268
269TEST_F(VisitedLinkTest, DeleteAll) {
270  ASSERT_TRUE(InitHistory());
271  ASSERT_TRUE(InitVisited(0, true));
272
273  {
274    VisitedLinkSlave slave;
275    base::SharedMemoryHandle new_handle = base::SharedMemory::NULLHandle();
276    master_->shared_memory()->ShareToProcess(
277        base::GetCurrentProcessHandle(), &new_handle);
278    slave.OnUpdateVisitedLinks(new_handle);
279    g_slaves.push_back(&slave);
280
281    // Add the test URLs.
282    for (int i = 0; i < g_test_count; i++) {
283      master_->AddURL(TestURL(i));
284      ASSERT_EQ(i + 1, master_->GetUsedCount());
285    }
286    master_->DebugValidate();
287
288    // Make sure the slave picked up the adds.
289    for (int i = 0; i < g_test_count; i++)
290      EXPECT_TRUE(slave.IsVisited(TestURL(i)));
291
292    // Clear the table and make sure the slave picked it up.
293    master_->DeleteAllURLs();
294    EXPECT_EQ(0, master_->GetUsedCount());
295    for (int i = 0; i < g_test_count; i++) {
296      EXPECT_FALSE(master_->IsVisited(TestURL(i)));
297      EXPECT_FALSE(slave.IsVisited(TestURL(i)));
298    }
299
300    // Close the database.
301    g_slaves.clear();
302    ClearDB();
303  }
304
305  // Reopen and validate.
306  ASSERT_TRUE(InitHistory());
307  ASSERT_TRUE(InitVisited(0, true));
308  master_->DebugValidate();
309  EXPECT_EQ(0, master_->GetUsedCount());
310  for (int i = 0; i < g_test_count; i++)
311    EXPECT_FALSE(master_->IsVisited(TestURL(i)));
312}
313
314// This tests that the master correctly resizes its tables when it gets too
315// full, notifies its slaves of the change, and updates the disk.
316TEST_F(VisitedLinkTest, Resizing) {
317  // Create a very small database.
318  const int32 initial_size = 17;
319  ASSERT_TRUE(InitHistory());
320  ASSERT_TRUE(InitVisited(initial_size, true));
321
322  // ...and a slave
323  VisitedLinkSlave slave;
324  base::SharedMemoryHandle new_handle = base::SharedMemory::NULLHandle();
325  master_->shared_memory()->ShareToProcess(
326      base::GetCurrentProcessHandle(), &new_handle);
327  slave.OnUpdateVisitedLinks(new_handle);
328  g_slaves.push_back(&slave);
329
330  int32 used_count = master_->GetUsedCount();
331  ASSERT_EQ(used_count, 0);
332
333  for (int i = 0; i < g_test_count; i++) {
334    master_->AddURL(TestURL(i));
335    used_count = master_->GetUsedCount();
336    ASSERT_EQ(i + 1, used_count);
337  }
338
339  // Verify that the table got resized sufficiently.
340  int32 table_size;
341  VisitedLinkCommon::Fingerprint* table;
342  master_->GetUsageStatistics(&table_size, &table);
343  used_count = master_->GetUsedCount();
344  ASSERT_GT(table_size, used_count);
345  ASSERT_EQ(used_count, g_test_count) <<
346                "table count doesn't match the # of things we added";
347
348  // Verify that the slave got the resize message and has the same
349  // table information.
350  int32 child_table_size;
351  VisitedLinkCommon::Fingerprint* child_table;
352  slave.GetUsageStatistics(&child_table_size, &child_table);
353  ASSERT_EQ(table_size, child_table_size);
354  for (int32 i = 0; i < table_size; i++) {
355    ASSERT_EQ(table[i], child_table[i]);
356  }
357
358  master_->DebugValidate();
359  g_slaves.clear();
360
361  // This tests that the file is written correctly by reading it in using
362  // a new database.
363  Reload();
364}
365
366// Tests that if the database doesn't exist, it will be rebuilt from history.
367TEST_F(VisitedLinkTest, Rebuild) {
368  ASSERT_TRUE(InitHistory());
369
370  // Add half of our URLs to history. This needs to be done before we
371  // initialize the visited link DB.
372  int history_count = g_test_count / 2;
373  for (int i = 0; i < history_count; i++)
374    history_service_->AddPage(TestURL(i), history::SOURCE_BROWSED);
375
376  // Initialize the visited link DB. Since the visited links file doesn't exist
377  // and we don't suppress history rebuilding, this will load from history.
378  ASSERT_TRUE(InitVisited(0, false));
379
380  // While the table is rebuilding, add the rest of the URLs to the visited
381  // link system. This isn't guaranteed to happen during the rebuild, so we
382  // can't be 100% sure we're testing the right thing, but in practice is.
383  // All the adds above will generally take some time queuing up on the
384  // history thread, and it will take a while to catch up to actually
385  // processing the rebuild that has queued behind it. We will generally
386  // finish adding all of the URLs before it has even found the first URL.
387  for (int i = history_count; i < g_test_count; i++)
388    master_->AddURL(TestURL(i));
389
390  // Add one more and then delete it.
391  master_->AddURL(TestURL(g_test_count));
392  std::set<GURL> deleted_urls;
393  deleted_urls.insert(TestURL(g_test_count));
394  master_->DeleteURLs(deleted_urls);
395
396  // Wait for the rebuild to complete. The task will terminate the message
397  // loop when the rebuild is done. There's no chance that the rebuild will
398  // complete before we set the task because the rebuild completion message
399  // is posted to the message loop; until we Run() it, rebuild can not
400  // complete.
401  master_->set_rebuild_complete_task(new MessageLoop::QuitTask);
402  MessageLoop::current()->Run();
403
404  // Test that all URLs were written to the database properly.
405  Reload();
406
407  // Make sure the extra one was *not* written (Reload won't test this).
408  EXPECT_FALSE(master_->IsVisited(TestURL(g_test_count)));
409}
410
411// Test that importing a large number of URLs will work
412TEST_F(VisitedLinkTest, BigImport) {
413  ASSERT_TRUE(InitHistory());
414  ASSERT_TRUE(InitVisited(0, false));
415
416  // Before the table rebuilds, add a large number of URLs
417  int total_count = VisitedLinkMaster::kDefaultTableSize + 10;
418  for (int i = 0; i < total_count; i++)
419    master_->AddURL(TestURL(i));
420
421  // Wait for the rebuild to complete.
422  master_->set_rebuild_complete_task(new MessageLoop::QuitTask);
423  MessageLoop::current()->Run();
424
425  // Ensure that the right number of URLs are present
426  int used_count = master_->GetUsedCount();
427  ASSERT_EQ(used_count, total_count);
428}
429
430TEST_F(VisitedLinkTest, Listener) {
431  ASSERT_TRUE(InitHistory());
432  ASSERT_TRUE(InitVisited(0, true));
433
434  // Add test URLs.
435  for (int i = 0; i < g_test_count; i++) {
436    master_->AddURL(TestURL(i));
437    ASSERT_EQ(i + 1, master_->GetUsedCount());
438  }
439
440  std::set<GURL> deleted_urls;
441  deleted_urls.insert(TestURL(0));
442  // Delete an URL.
443  master_->DeleteURLs(deleted_urls);
444  // ... and all of the remaining ones.
445  master_->DeleteAllURLs();
446
447  // Verify that VisitedLinkMaster::Listener::Add was called for each added URL.
448  EXPECT_EQ(g_test_count, listener_.add_count());
449  // Verify that VisitedLinkMaster::Listener::Reset was called both when one and
450  // all URLs are deleted.
451  EXPECT_EQ(2, listener_.reset_count());
452}
453
454class VisitCountingProfile : public TestingProfile {
455 public:
456  explicit VisitCountingProfile(VisitedLinkEventListener* event_listener)
457      : add_count_(0),
458        add_event_count_(0),
459        reset_event_count_(0),
460        event_listener_(event_listener) {}
461
462  virtual VisitedLinkMaster* GetVisitedLinkMaster() {
463    if (!visited_link_master_.get()) {
464      visited_link_master_.reset(new VisitedLinkMaster(event_listener_, this));
465      visited_link_master_->Init();
466    }
467    return visited_link_master_.get();
468  }
469
470  void CountAddEvent(int by) {
471    add_count_ += by;
472    add_event_count_++;
473  }
474
475  void CountResetEvent() {
476    reset_event_count_++;
477  }
478
479  VisitedLinkMaster* master() const { return visited_link_master_.get(); }
480  int add_count() const { return add_count_; }
481  int add_event_count() const { return add_event_count_; }
482  int reset_event_count() const { return reset_event_count_; }
483
484 private:
485  int add_count_;
486  int add_event_count_;
487  int reset_event_count_;
488  VisitedLinkEventListener* event_listener_;
489  scoped_ptr<VisitedLinkMaster> visited_link_master_;
490};
491
492class VisitCountingRenderProcessHost : public MockRenderProcessHost {
493 public:
494  explicit VisitCountingRenderProcessHost(Profile* profile)
495      : MockRenderProcessHost(profile) {}
496
497  virtual void AddVisitedLinks(
498      const VisitedLinkCommon::Fingerprints& visited_links) {
499    VisitCountingProfile* counting_profile =
500        static_cast<VisitCountingProfile*>(profile());
501    counting_profile->CountAddEvent(visited_links.size());
502  }
503  virtual void ResetVisitedLinks() {
504    VisitCountingProfile* counting_profile =
505        static_cast<VisitCountingProfile*>(profile());
506    counting_profile->CountResetEvent();
507  }
508
509 private:
510  DISALLOW_COPY_AND_ASSIGN(VisitCountingRenderProcessHost);
511};
512
513// Stub out as little as possible, borrowing from MockRenderProcessHost.
514class VisitRelayingRenderProcessHost : public BrowserRenderProcessHost {
515 public:
516  explicit VisitRelayingRenderProcessHost(Profile* profile)
517      : BrowserRenderProcessHost(profile) {
518  }
519  virtual ~VisitRelayingRenderProcessHost() {
520  }
521
522  virtual bool Init(bool is_accessibility_enabled, bool is_extension_process) {
523    return true;
524  }
525
526  virtual void CancelResourceRequests(int render_widget_id) {
527  }
528
529  virtual void CrossSiteClosePageACK(const ViewMsg_ClosePage_Params& params) {
530  }
531
532  virtual bool WaitForPaintMsg(int render_widget_id,
533                               const base::TimeDelta& max_delay,
534                               IPC::Message* msg) {
535    return false;
536  }
537
538  virtual bool Send(IPC::Message* msg) {
539    VisitCountingProfile* counting_profile =
540        static_cast<VisitCountingProfile*>(profile());
541
542    if (msg->type() == ViewMsg_VisitedLink_Add::ID)
543      counting_profile->CountAddEvent(1);
544    else if (msg->type() == ViewMsg_VisitedLink_Reset::ID)
545      counting_profile->CountResetEvent();
546
547    delete msg;
548    return true;
549  }
550
551  virtual void SetBackgrounded(bool backgrounded) {
552    backgrounded_ = backgrounded;
553  }
554
555 private:
556  DISALLOW_COPY_AND_ASSIGN(VisitRelayingRenderProcessHost);
557};
558
559class VisitedLinkRenderProcessHostFactory
560    : public MockRenderProcessHostFactory {
561 public:
562  VisitedLinkRenderProcessHostFactory()
563      : MockRenderProcessHostFactory(),
564        relay_mode_(false) {}
565  virtual RenderProcessHost* CreateRenderProcessHost(Profile* profile) const {
566    if (relay_mode_)
567      return new VisitRelayingRenderProcessHost(profile);
568    else
569      return new VisitCountingRenderProcessHost(profile);
570  }
571
572  void set_relay_mode(bool mode) { relay_mode_ = mode; }
573
574 private:
575  bool relay_mode_;
576
577  DISALLOW_COPY_AND_ASSIGN(VisitedLinkRenderProcessHostFactory);
578};
579
580class VisitedLinkEventsTest : public RenderViewHostTestHarness {
581 public:
582  VisitedLinkEventsTest()
583      : RenderViewHostTestHarness(),
584        ui_thread_(BrowserThread::UI, &message_loop_),
585        file_thread_(BrowserThread::FILE, &message_loop_) {}
586  ~VisitedLinkEventsTest() {
587    // This ends up using the file thread to schedule the delete.
588    profile_.reset();
589    message_loop_.RunAllPending();
590  }
591  virtual void SetFactoryMode() {}
592  virtual void SetUp() {
593    SetFactoryMode();
594    event_listener_.reset(new VisitedLinkEventListener());
595    rvh_factory_.set_render_process_host_factory(&vc_rph_factory_);
596    profile_.reset(new VisitCountingProfile(event_listener_.get()));
597    RenderViewHostTestHarness::SetUp();
598  }
599
600  VisitCountingProfile* profile() const {
601    return static_cast<VisitCountingProfile*>(profile_.get());
602  }
603
604  void WaitForCoalescense() {
605    // Let the timer fire.
606    MessageLoop::current()->PostDelayedTask(FROM_HERE,
607                                            new MessageLoop::QuitTask(), 110);
608    MessageLoop::current()->Run();
609  }
610
611 protected:
612  VisitedLinkRenderProcessHostFactory vc_rph_factory_;
613
614 private:
615  scoped_ptr<VisitedLinkEventListener> event_listener_;
616  BrowserThread ui_thread_;
617  BrowserThread file_thread_;
618
619  DISALLOW_COPY_AND_ASSIGN(VisitedLinkEventsTest);
620};
621
622class VisitedLinkRelayTest : public VisitedLinkEventsTest {
623 public:
624  virtual void SetFactoryMode() { vc_rph_factory_.set_relay_mode(true); }
625};
626
627TEST_F(VisitedLinkEventsTest, Coalescense) {
628  // add some URLs to master.
629  VisitedLinkMaster* master = profile_->GetVisitedLinkMaster();
630  // Add a few URLs.
631  master->AddURL(GURL("http://acidtests.org/"));
632  master->AddURL(GURL("http://google.com/"));
633  master->AddURL(GURL("http://chromium.org/"));
634  // Just for kicks, add a duplicate URL. This shouldn't increase the resulting
635  master->AddURL(GURL("http://acidtests.org/"));
636
637  // Make sure that coalescing actually occurs. There should be no links or
638  // events received by the renderer.
639  EXPECT_EQ(0, profile()->add_count());
640  EXPECT_EQ(0, profile()->add_event_count());
641
642  WaitForCoalescense();
643
644  // We now should have 3 entries added in 1 event.
645  EXPECT_EQ(3, profile()->add_count());
646  EXPECT_EQ(1, profile()->add_event_count());
647
648  // Test whether the coalescing continues by adding a few more URLs.
649  master->AddURL(GURL("http://google.com/chrome/"));
650  master->AddURL(GURL("http://webkit.org/"));
651  master->AddURL(GURL("http://acid3.acidtests.org/"));
652
653  WaitForCoalescense();
654
655  // We should have 6 entries added in 2 events.
656  EXPECT_EQ(6, profile()->add_count());
657  EXPECT_EQ(2, profile()->add_event_count());
658
659  // Test whether duplicate entries produce add events.
660  master->AddURL(GURL("http://acidtests.org/"));
661
662  WaitForCoalescense();
663
664  // We should have no change in results.
665  EXPECT_EQ(6, profile()->add_count());
666  EXPECT_EQ(2, profile()->add_event_count());
667
668  // Ensure that the coalescing does not resume after resetting.
669  master->AddURL(GURL("http://build.chromium.org/"));
670  master->DeleteAllURLs();
671
672  WaitForCoalescense();
673
674  // We should have no change in results except for one new reset event.
675  EXPECT_EQ(6, profile()->add_count());
676  EXPECT_EQ(2, profile()->add_event_count());
677  EXPECT_EQ(1, profile()->reset_event_count());
678}
679
680TEST_F(VisitedLinkRelayTest, Basics) {
681  VisitedLinkMaster* master = profile_->GetVisitedLinkMaster();
682  rvh()->CreateRenderView(string16());
683
684  // Add a few URLs.
685  master->AddURL(GURL("http://acidtests.org/"));
686  master->AddURL(GURL("http://google.com/"));
687  master->AddURL(GURL("http://chromium.org/"));
688
689  WaitForCoalescense();
690
691  // We now should have 1 add event.
692  EXPECT_EQ(1, profile()->add_event_count());
693  EXPECT_EQ(0, profile()->reset_event_count());
694
695  master->DeleteAllURLs();
696
697  WaitForCoalescense();
698
699  // We should have no change in add results, plus one new reset event.
700  EXPECT_EQ(1, profile()->add_event_count());
701  EXPECT_EQ(1, profile()->reset_event_count());
702}
703
704TEST_F(VisitedLinkRelayTest, TabVisibility) {
705  VisitedLinkMaster* master = profile_->GetVisitedLinkMaster();
706  rvh()->CreateRenderView(string16());
707
708  // Simulate tab becoming inactive.
709  rvh()->WasHidden();
710
711  // Add a few URLs.
712  master->AddURL(GURL("http://acidtests.org/"));
713  master->AddURL(GURL("http://google.com/"));
714  master->AddURL(GURL("http://chromium.org/"));
715
716  WaitForCoalescense();
717
718  // We shouldn't have any events.
719  EXPECT_EQ(0, profile()->add_event_count());
720  EXPECT_EQ(0, profile()->reset_event_count());
721
722  // Simulate the tab becoming active.
723  rvh()->WasRestored();
724
725  // We should now have 3 add events, still no reset events.
726  EXPECT_EQ(1, profile()->add_event_count());
727  EXPECT_EQ(0, profile()->reset_event_count());
728
729  // Deactivate the tab again.
730  rvh()->WasHidden();
731
732  // Add a bunch of URLs (over 50) to exhaust the link event buffer.
733  for (int i = 0; i < 100; i++)
734    master->AddURL(TestURL(i));
735
736  WaitForCoalescense();
737
738  // Again, no change in events until tab is active.
739  EXPECT_EQ(1, profile()->add_event_count());
740  EXPECT_EQ(0, profile()->reset_event_count());
741
742  // Activate the tab.
743  rvh()->WasRestored();
744
745  // We should have only one more reset event.
746  EXPECT_EQ(1, profile()->add_event_count());
747  EXPECT_EQ(1, profile()->reset_event_count());
748}
749
750TEST_F(VisitedLinkRelayTest, WebViewReadiness) {
751  VisitedLinkMaster* master = profile_->GetVisitedLinkMaster();
752
753  // Add a few URLs.
754  master->AddURL(GURL("http://acidtests.org/"));
755  master->AddURL(GURL("http://google.com/"));
756  master->AddURL(GURL("http://chromium.org/"));
757
758  WaitForCoalescense();
759
760  std::set<GURL> deleted_urls;
761  deleted_urls.insert(GURL("http://acidtests.org/"));
762  master->DeleteURLs(deleted_urls);
763
764  // We shouldn't have any events, because RenderView hasn't been created,
765  // and we ensure that updates are sent until it is.
766  EXPECT_EQ(0, profile()->add_event_count());
767  EXPECT_EQ(0, profile()->reset_event_count());
768
769  rvh()->CreateRenderView(string16());
770
771  // We should now have just a reset event: adds are eaten up by a reset
772  // that followed.
773  EXPECT_EQ(0, profile()->add_event_count());
774  EXPECT_EQ(1, profile()->reset_event_count());
775}
776