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