Home | History | Annotate | Download | only in core
      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 <algorithm>
      6 #include <map>
      7 #include <string>
      8 #include <vector>
      9 
     10 #include "base/bind.h"
     11 #include "base/bind_helpers.h"
     12 #include "base/location.h"
     13 #include "base/memory/scoped_ptr.h"
     14 #include "base/message_loop/message_loop.h"
     15 #include "base/path_service.h"
     16 #include "base/strings/string_number_conversions.h"
     17 #include "base/values.h"
     18 #include "components/dom_distiller/core/article_distillation_update.h"
     19 #include "components/dom_distiller/core/distiller.h"
     20 #include "components/dom_distiller/core/distiller_page.h"
     21 #include "components/dom_distiller/core/fake_distiller_page.h"
     22 #include "components/dom_distiller/core/proto/distilled_article.pb.h"
     23 #include "components/dom_distiller/core/proto/distilled_page.pb.h"
     24 #include "net/url_request/url_request_context_getter.h"
     25 #include "testing/gmock/include/gmock/gmock.h"
     26 #include "testing/gtest/include/gtest/gtest.h"
     27 #include "third_party/dom_distiller_js/dom_distiller.pb.h"
     28 #include "third_party/dom_distiller_js/dom_distiller_json_converter.h"
     29 #include "ui/base/resource/resource_bundle.h"
     30 
     31 using std::vector;
     32 using std::string;
     33 using ::testing::Invoke;
     34 using ::testing::Return;
     35 using ::testing::_;
     36 
     37 using dom_distiller::proto::DomDistillerOptions;
     38 using dom_distiller::proto::DomDistillerResult;
     39 
     40 namespace {
     41 const char kTitle[] = "Title";
     42 const char kContent[] = "Content";
     43 const char kURL[] = "http://a.com/";
     44 const size_t kTotalImages = 2;
     45 const char* kImageURLs[kTotalImages] = {"http://a.com/img1.jpg",
     46                                         "http://a.com/img2.jpg"};
     47 const char* kImageData[kTotalImages] = {"abcde", "12345"};
     48 const char kDebugLog[] = "Debug Log";
     49 
     50 const string GetImageName(int page_num, int image_num) {
     51   return base::IntToString(page_num) + "_" + base::IntToString(image_num);
     52 }
     53 
     54 scoped_ptr<base::Value> CreateDistilledValueReturnedFromJS(
     55     const string& title,
     56     const string& content,
     57     const vector<int>& image_indices,
     58     const string& next_page_url,
     59     const string& prev_page_url = "") {
     60   DomDistillerResult result;
     61   result.set_title(title);
     62   result.mutable_distilled_content()->set_html(content);
     63   result.mutable_pagination_info()->set_next_page(next_page_url);
     64   result.mutable_pagination_info()->set_prev_page(prev_page_url);
     65 
     66   for (size_t i = 0; i < image_indices.size(); ++i) {
     67     result.add_image_urls(kImageURLs[image_indices[i]]);
     68   }
     69 
     70   return dom_distiller::proto::json::DomDistillerResult::WriteToValue(result);
     71 }
     72 
     73 // Return the sequence in which Distiller will distill pages.
     74 // Note: ignores any delays due to fetching images etc.
     75 vector<int> GetPagesInSequence(int start_page_num, int num_pages) {
     76   // Distiller prefers distilling past pages first. E.g. when distillation
     77   // starts on page 2 then pages are distilled in the order: 2, 1, 0, 3, 4.
     78   vector<int> page_nums;
     79   for (int page = start_page_num; page >= 0; --page)
     80     page_nums.push_back(page);
     81   for (int page = start_page_num + 1; page < num_pages; ++page)
     82     page_nums.push_back(page);
     83   return page_nums;
     84 }
     85 
     86 struct MultipageDistillerData {
     87  public:
     88   MultipageDistillerData() {}
     89   ~MultipageDistillerData() {}
     90   vector<string> page_urls;
     91   vector<string> content;
     92   vector<vector<int> > image_ids;
     93   // The Javascript values returned by mock distiller.
     94   ScopedVector<base::Value> distilled_values;
     95 
     96  private:
     97   DISALLOW_COPY_AND_ASSIGN(MultipageDistillerData);
     98 };
     99 
    100 void VerifyIncrementalUpdatesMatch(
    101     const MultipageDistillerData* distiller_data,
    102     int num_pages_in_article,
    103     const vector<dom_distiller::ArticleDistillationUpdate>& incremental_updates,
    104     int start_page_num) {
    105   vector<int> page_seq =
    106       GetPagesInSequence(start_page_num, num_pages_in_article);
    107   // Updates should contain a list of pages. Pages in an update should be in
    108   // the correct ascending page order regardless of |start_page_num|.
    109   // E.g. if distillation starts at page 2 of a 3 page article, the updates
    110   // will be [[2], [1, 2], [1, 2, 3]]. This example assumes that image fetches
    111   // do not delay distillation of a page. There can be scenarios when image
    112   // fetch delays distillation of a page (E.g. 1 is delayed due to image
    113   // fetches so updates can be in this order [[2], [2,3], [1,2,3]].
    114   for (size_t update_count = 0; update_count < incremental_updates.size();
    115        ++update_count) {
    116     const dom_distiller::ArticleDistillationUpdate& update =
    117         incremental_updates[update_count];
    118     EXPECT_EQ(update_count + 1, update.GetPagesSize());
    119 
    120     vector<int> expected_page_nums_in_update(
    121         page_seq.begin(), page_seq.begin() + update.GetPagesSize());
    122     std::sort(expected_page_nums_in_update.begin(),
    123               expected_page_nums_in_update.end());
    124 
    125     // If we already got the first page then there is no previous page.
    126     EXPECT_EQ((expected_page_nums_in_update[0] != 0), update.HasPrevPage());
    127 
    128     // if we already got the last page then there is no next page.
    129     EXPECT_EQ(
    130         (*expected_page_nums_in_update.rbegin()) != num_pages_in_article - 1,
    131         update.HasNextPage());
    132     for (size_t j = 0; j < update.GetPagesSize(); ++j) {
    133       int actual_page_num = expected_page_nums_in_update[j];
    134       EXPECT_EQ(distiller_data->page_urls[actual_page_num],
    135                 update.GetDistilledPage(j).url());
    136       EXPECT_EQ(distiller_data->content[actual_page_num],
    137                 update.GetDistilledPage(j).html());
    138     }
    139   }
    140 }
    141 
    142 scoped_ptr<MultipageDistillerData> CreateMultipageDistillerDataWithoutImages(
    143     size_t pages_size) {
    144   scoped_ptr<MultipageDistillerData> result(new MultipageDistillerData());
    145   string url_prefix = "http://a.com/";
    146   for (size_t page_num = 0; page_num < pages_size; ++page_num) {
    147     result->page_urls.push_back(url_prefix + base::IntToString(page_num));
    148     result->content.push_back("Content for page:" +
    149                               base::IntToString(page_num));
    150     result->image_ids.push_back(vector<int>());
    151     string next_page_url = (page_num + 1 < pages_size)
    152                                ? url_prefix + base::IntToString(page_num + 1)
    153                                : "";
    154     string prev_page_url =
    155         (page_num > 0) ? result->page_urls[page_num - 1] : "";
    156     scoped_ptr<base::Value> distilled_value =
    157         CreateDistilledValueReturnedFromJS(kTitle,
    158                                            result->content[page_num],
    159                                            result->image_ids[page_num],
    160                                            next_page_url,
    161                                            prev_page_url);
    162     result->distilled_values.push_back(distilled_value.release());
    163   }
    164   return result.Pass();
    165 }
    166 
    167 void VerifyArticleProtoMatchesMultipageData(
    168     const dom_distiller::DistilledArticleProto* article_proto,
    169     const MultipageDistillerData* distiller_data,
    170     size_t pages_size) {
    171   ASSERT_EQ(pages_size, static_cast<size_t>(article_proto->pages_size()));
    172   EXPECT_EQ(kTitle, article_proto->title());
    173   for (size_t page_num = 0; page_num < pages_size; ++page_num) {
    174     const dom_distiller::DistilledPageProto& page =
    175         article_proto->pages(page_num);
    176     EXPECT_EQ(distiller_data->content[page_num], page.html());
    177     EXPECT_EQ(distiller_data->page_urls[page_num], page.url());
    178     EXPECT_EQ(distiller_data->image_ids[page_num].size(),
    179               static_cast<size_t>(page.image_size()));
    180     const vector<int>& image_ids_for_page = distiller_data->image_ids[page_num];
    181     for (size_t img_num = 0; img_num < image_ids_for_page.size(); ++img_num) {
    182       EXPECT_EQ(kImageData[image_ids_for_page[img_num]],
    183                 page.image(img_num).data());
    184       EXPECT_EQ(GetImageName(page_num + 1, img_num),
    185                 page.image(img_num).name());
    186     }
    187   }
    188 }
    189 
    190 void AddComponentsResources() {
    191   base::FilePath pak_file;
    192   base::FilePath pak_dir;
    193   PathService::Get(base::DIR_MODULE, &pak_dir);
    194   pak_file = pak_dir.Append(FILE_PATH_LITERAL("components_resources.pak"));
    195   ui::ResourceBundle::GetSharedInstance().AddDataPackFromPath(
    196       pak_file, ui::SCALE_FACTOR_NONE);
    197 }
    198 
    199 }  // namespace
    200 
    201 namespace dom_distiller {
    202 
    203 using test::MockDistillerPage;
    204 using test::MockDistillerPageFactory;
    205 
    206 class TestDistillerURLFetcher : public DistillerURLFetcher {
    207  public:
    208   explicit TestDistillerURLFetcher(bool delay_fetch)
    209       : DistillerURLFetcher(NULL), delay_fetch_(delay_fetch) {
    210     responses_[kImageURLs[0]] = string(kImageData[0]);
    211     responses_[kImageURLs[1]] = string(kImageData[1]);
    212   }
    213 
    214   virtual void FetchURL(const string& url,
    215                         const URLFetcherCallback& callback) OVERRIDE {
    216     ASSERT_FALSE(callback.is_null());
    217     url_ = url;
    218     callback_ = callback;
    219     if (!delay_fetch_) {
    220       PostCallbackTask();
    221     }
    222   }
    223 
    224   void PostCallbackTask() {
    225     ASSERT_TRUE(base::MessageLoop::current());
    226     ASSERT_FALSE(callback_.is_null());
    227     base::MessageLoop::current()->PostTask(
    228         FROM_HERE, base::Bind(callback_, responses_[url_]));
    229   }
    230 
    231  private:
    232   std::map<string, string> responses_;
    233   string url_;
    234   URLFetcherCallback callback_;
    235   bool delay_fetch_;
    236 };
    237 
    238 class TestDistillerURLFetcherFactory : public DistillerURLFetcherFactory {
    239  public:
    240   TestDistillerURLFetcherFactory() : DistillerURLFetcherFactory(NULL) {}
    241 
    242   virtual ~TestDistillerURLFetcherFactory() {}
    243   virtual DistillerURLFetcher* CreateDistillerURLFetcher() const OVERRIDE {
    244     return new TestDistillerURLFetcher(false);
    245   }
    246 };
    247 
    248 class MockDistillerURLFetcherFactory : public DistillerURLFetcherFactory {
    249  public:
    250   MockDistillerURLFetcherFactory() : DistillerURLFetcherFactory(NULL) {}
    251   virtual ~MockDistillerURLFetcherFactory() {}
    252 
    253   MOCK_CONST_METHOD0(CreateDistillerURLFetcher, DistillerURLFetcher*());
    254 };
    255 
    256 class DistillerTest : public testing::Test {
    257  public:
    258   virtual ~DistillerTest() {}
    259 
    260   virtual void SetUp() OVERRIDE {
    261     AddComponentsResources();
    262   }
    263 
    264   void OnDistillArticleDone(scoped_ptr<DistilledArticleProto> proto) {
    265     article_proto_ = proto.Pass();
    266   }
    267 
    268   void OnDistillArticleUpdate(const ArticleDistillationUpdate& article_update) {
    269     in_sequence_updates_.push_back(article_update);
    270   }
    271 
    272   void DistillPage(const std::string& url,
    273                    scoped_ptr<DistillerPage> distiller_page) {
    274     distiller_->DistillPage(GURL(url),
    275                             distiller_page.Pass(),
    276                             base::Bind(&DistillerTest::OnDistillArticleDone,
    277                                        base::Unretained(this)),
    278                             base::Bind(&DistillerTest::OnDistillArticleUpdate,
    279                                        base::Unretained(this)));
    280   }
    281 
    282  protected:
    283   scoped_ptr<DistillerImpl> distiller_;
    284   scoped_ptr<DistilledArticleProto> article_proto_;
    285   std::vector<ArticleDistillationUpdate> in_sequence_updates_;
    286   MockDistillerPageFactory page_factory_;
    287   TestDistillerURLFetcherFactory url_fetcher_factory_;
    288 };
    289 
    290 ACTION_P3(DistillerPageOnDistillationDone, distiller_page, url, result) {
    291   distiller_page->OnDistillationDone(url, result);
    292 }
    293 
    294 scoped_ptr<DistillerPage> CreateMockDistillerPage(const base::Value* result,
    295                                                   const GURL& url) {
    296   MockDistillerPage* distiller_page = new MockDistillerPage();
    297   EXPECT_CALL(*distiller_page, DistillPageImpl(url, _))
    298       .WillOnce(DistillerPageOnDistillationDone(distiller_page, url, result));
    299   return scoped_ptr<DistillerPage>(distiller_page).Pass();
    300 }
    301 
    302 scoped_ptr<DistillerPage> CreateMockDistillerPageWithPendingJSCallback(
    303     MockDistillerPage** distiller_page_ptr,
    304     const GURL& url) {
    305   MockDistillerPage* distiller_page = new MockDistillerPage();
    306   *distiller_page_ptr = distiller_page;
    307   EXPECT_CALL(*distiller_page, DistillPageImpl(url, _));
    308   return scoped_ptr<DistillerPage>(distiller_page).Pass();
    309 }
    310 
    311 scoped_ptr<DistillerPage> CreateMockDistillerPages(
    312     MultipageDistillerData* distiller_data,
    313     size_t pages_size,
    314     int start_page_num) {
    315   MockDistillerPage* distiller_page = new MockDistillerPage();
    316   {
    317     testing::InSequence s;
    318     vector<int> page_nums = GetPagesInSequence(start_page_num, pages_size);
    319     for (size_t page_num = 0; page_num < pages_size; ++page_num) {
    320       int page = page_nums[page_num];
    321       GURL url = GURL(distiller_data->page_urls[page]);
    322       EXPECT_CALL(*distiller_page, DistillPageImpl(url, _))
    323           .WillOnce(DistillerPageOnDistillationDone(
    324               distiller_page, url, distiller_data->distilled_values[page]));
    325     }
    326   }
    327   return scoped_ptr<DistillerPage>(distiller_page).Pass();
    328 }
    329 
    330 TEST_F(DistillerTest, DistillPage) {
    331   base::MessageLoopForUI loop;
    332   scoped_ptr<base::Value> result =
    333       CreateDistilledValueReturnedFromJS(kTitle, kContent, vector<int>(), "");
    334   distiller_.reset(
    335       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
    336   DistillPage(kURL, CreateMockDistillerPage(result.get(), GURL(kURL)).Pass());
    337   base::MessageLoop::current()->RunUntilIdle();
    338   EXPECT_EQ(kTitle, article_proto_->title());
    339   ASSERT_EQ(article_proto_->pages_size(), 1);
    340   const DistilledPageProto& first_page = article_proto_->pages(0);
    341   EXPECT_EQ(kContent, first_page.html());
    342   EXPECT_EQ(kURL, first_page.url());
    343 }
    344 
    345 TEST_F(DistillerTest, DistillPageWithDebugInfo) {
    346   base::MessageLoopForUI loop;
    347   DomDistillerResult dd_result;
    348   dd_result.mutable_debug_info()->set_log(kDebugLog);
    349   scoped_ptr<base::Value> result =
    350       dom_distiller::proto::json::DomDistillerResult::WriteToValue(dd_result);
    351   distiller_.reset(
    352       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
    353   DistillPage(kURL, CreateMockDistillerPage(result.get(), GURL(kURL)).Pass());
    354   base::MessageLoop::current()->RunUntilIdle();
    355   const DistilledPageProto& first_page = article_proto_->pages(0);
    356   EXPECT_EQ(kDebugLog, first_page.debug_info().log());
    357 }
    358 
    359 TEST_F(DistillerTest, DistillPageWithImages) {
    360   base::MessageLoopForUI loop;
    361   vector<int> image_indices;
    362   image_indices.push_back(0);
    363   image_indices.push_back(1);
    364   scoped_ptr<base::Value> result =
    365       CreateDistilledValueReturnedFromJS(kTitle, kContent, image_indices, "");
    366   distiller_.reset(
    367       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
    368   DistillPage(kURL, CreateMockDistillerPage(result.get(), GURL(kURL)).Pass());
    369   base::MessageLoop::current()->RunUntilIdle();
    370   EXPECT_EQ(kTitle, article_proto_->title());
    371   ASSERT_EQ(article_proto_->pages_size(), 1);
    372   const DistilledPageProto& first_page = article_proto_->pages(0);
    373   EXPECT_EQ(kContent, first_page.html());
    374   EXPECT_EQ(kURL, first_page.url());
    375   ASSERT_EQ(2, first_page.image_size());
    376   EXPECT_EQ(kImageData[0], first_page.image(0).data());
    377   EXPECT_EQ(GetImageName(1, 0), first_page.image(0).name());
    378   EXPECT_EQ(kImageData[1], first_page.image(1).data());
    379   EXPECT_EQ(GetImageName(1, 1), first_page.image(1).name());
    380 }
    381 
    382 TEST_F(DistillerTest, DistillMultiplePages) {
    383   base::MessageLoopForUI loop;
    384   const size_t kNumPages = 8;
    385   scoped_ptr<MultipageDistillerData> distiller_data =
    386       CreateMultipageDistillerDataWithoutImages(kNumPages);
    387 
    388   // Add images.
    389   int next_image_number = 0;
    390   for (size_t page_num = 0; page_num < kNumPages; ++page_num) {
    391     // Each page has different number of images.
    392     size_t tot_images = (page_num + kTotalImages) % (kTotalImages + 1);
    393     vector<int> image_indices;
    394     for (size_t img_num = 0; img_num < tot_images; img_num++) {
    395       image_indices.push_back(next_image_number);
    396       next_image_number = (next_image_number + 1) % kTotalImages;
    397     }
    398     distiller_data->image_ids.push_back(image_indices);
    399   }
    400 
    401   distiller_.reset(
    402       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
    403   DistillPage(
    404       distiller_data->page_urls[0],
    405       CreateMockDistillerPages(distiller_data.get(), kNumPages, 0).Pass());
    406   base::MessageLoop::current()->RunUntilIdle();
    407   VerifyArticleProtoMatchesMultipageData(
    408       article_proto_.get(), distiller_data.get(), kNumPages);
    409 }
    410 
    411 TEST_F(DistillerTest, DistillLinkLoop) {
    412   base::MessageLoopForUI loop;
    413   // Create a loop, the next page is same as the current page. This could
    414   // happen if javascript misparses a next page link.
    415   scoped_ptr<base::Value> result =
    416       CreateDistilledValueReturnedFromJS(kTitle, kContent, vector<int>(), kURL);
    417   distiller_.reset(
    418       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
    419   DistillPage(kURL, CreateMockDistillerPage(result.get(), GURL(kURL)).Pass());
    420   base::MessageLoop::current()->RunUntilIdle();
    421   EXPECT_EQ(kTitle, article_proto_->title());
    422   EXPECT_EQ(article_proto_->pages_size(), 1);
    423 }
    424 
    425 TEST_F(DistillerTest, CheckMaxPageLimitExtraPage) {
    426   base::MessageLoopForUI loop;
    427   const size_t kMaxPagesInArticle = 10;
    428   scoped_ptr<MultipageDistillerData> distiller_data =
    429       CreateMultipageDistillerDataWithoutImages(kMaxPagesInArticle);
    430 
    431   // Note: Next page url of the last page of article is set. So distiller will
    432   // try to do kMaxPagesInArticle + 1 calls if the max article limit does not
    433   // work.
    434   scoped_ptr<base::Value> last_page_data =
    435       CreateDistilledValueReturnedFromJS(
    436           kTitle,
    437           distiller_data->content[kMaxPagesInArticle - 1],
    438           vector<int>(),
    439           "",
    440           distiller_data->page_urls[kMaxPagesInArticle - 2]);
    441 
    442   distiller_data->distilled_values.pop_back();
    443   distiller_data->distilled_values.push_back(last_page_data.release());
    444 
    445   distiller_.reset(
    446       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
    447 
    448   distiller_->SetMaxNumPagesInArticle(kMaxPagesInArticle);
    449 
    450   DistillPage(distiller_data->page_urls[0],
    451               CreateMockDistillerPages(
    452                   distiller_data.get(), kMaxPagesInArticle, 0).Pass());
    453   base::MessageLoop::current()->RunUntilIdle();
    454   EXPECT_EQ(kTitle, article_proto_->title());
    455   EXPECT_EQ(kMaxPagesInArticle,
    456             static_cast<size_t>(article_proto_->pages_size()));
    457 }
    458 
    459 TEST_F(DistillerTest, CheckMaxPageLimitExactLimit) {
    460   base::MessageLoopForUI loop;
    461   const size_t kMaxPagesInArticle = 10;
    462   scoped_ptr<MultipageDistillerData> distiller_data =
    463       CreateMultipageDistillerDataWithoutImages(kMaxPagesInArticle);
    464 
    465   distiller_.reset(
    466       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
    467 
    468   // Check if distilling an article with exactly the page limit works.
    469   distiller_->SetMaxNumPagesInArticle(kMaxPagesInArticle);
    470 
    471   DistillPage(distiller_data->page_urls[0],
    472               CreateMockDistillerPages(
    473                   distiller_data.get(), kMaxPagesInArticle, 0).Pass());
    474   base::MessageLoop::current()->RunUntilIdle();
    475   EXPECT_EQ(kTitle, article_proto_->title());
    476   EXPECT_EQ(kMaxPagesInArticle,
    477             static_cast<size_t>(article_proto_->pages_size()));
    478 }
    479 
    480 TEST_F(DistillerTest, SinglePageDistillationFailure) {
    481   base::MessageLoopForUI loop;
    482   // To simulate failure return a null value.
    483   scoped_ptr<base::Value> nullValue(base::Value::CreateNullValue());
    484   distiller_.reset(
    485       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
    486   DistillPage(kURL,
    487               CreateMockDistillerPage(nullValue.get(), GURL(kURL)).Pass());
    488   base::MessageLoop::current()->RunUntilIdle();
    489   EXPECT_EQ("", article_proto_->title());
    490   EXPECT_EQ(0, article_proto_->pages_size());
    491 }
    492 
    493 TEST_F(DistillerTest, MultiplePagesDistillationFailure) {
    494   base::MessageLoopForUI loop;
    495   const size_t kNumPages = 8;
    496   scoped_ptr<MultipageDistillerData> distiller_data =
    497       CreateMultipageDistillerDataWithoutImages(kNumPages);
    498 
    499   // The page number of the failed page.
    500   size_t failed_page_num = 3;
    501   // reset distilled data of the failed page.
    502   distiller_data->distilled_values.erase(
    503       distiller_data->distilled_values.begin() + failed_page_num);
    504   distiller_data->distilled_values.insert(
    505       distiller_data->distilled_values.begin() + failed_page_num,
    506       base::Value::CreateNullValue());
    507   // Expect only calls till the failed page number.
    508   distiller_.reset(
    509       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
    510   DistillPage(distiller_data->page_urls[0],
    511               CreateMockDistillerPages(
    512                   distiller_data.get(), failed_page_num + 1, 0).Pass());
    513   base::MessageLoop::current()->RunUntilIdle();
    514   EXPECT_EQ(kTitle, article_proto_->title());
    515   VerifyArticleProtoMatchesMultipageData(
    516       article_proto_.get(), distiller_data.get(), failed_page_num);
    517 }
    518 
    519 TEST_F(DistillerTest, DistillPreviousPage) {
    520   base::MessageLoopForUI loop;
    521   const size_t kNumPages = 8;
    522 
    523   // The page number of the article on which distillation starts.
    524   int start_page_num = 3;
    525   scoped_ptr<MultipageDistillerData> distiller_data =
    526       CreateMultipageDistillerDataWithoutImages(kNumPages);
    527 
    528   distiller_.reset(
    529       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
    530   DistillPage(distiller_data->page_urls[start_page_num],
    531               CreateMockDistillerPages(
    532                   distiller_data.get(), kNumPages, start_page_num).Pass());
    533   base::MessageLoop::current()->RunUntilIdle();
    534   VerifyArticleProtoMatchesMultipageData(
    535       article_proto_.get(), distiller_data.get(), kNumPages);
    536 }
    537 
    538 TEST_F(DistillerTest, IncrementalUpdates) {
    539   base::MessageLoopForUI loop;
    540   const size_t kNumPages = 8;
    541 
    542   // The page number of the article on which distillation starts.
    543   int start_page_num = 3;
    544   scoped_ptr<MultipageDistillerData> distiller_data =
    545       CreateMultipageDistillerDataWithoutImages(kNumPages);
    546 
    547   distiller_.reset(
    548       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
    549   DistillPage(distiller_data->page_urls[start_page_num],
    550               CreateMockDistillerPages(
    551                   distiller_data.get(), kNumPages, start_page_num).Pass());
    552   base::MessageLoop::current()->RunUntilIdle();
    553   EXPECT_EQ(kTitle, article_proto_->title());
    554   ASSERT_EQ(kNumPages, static_cast<size_t>(article_proto_->pages_size()));
    555   EXPECT_EQ(kNumPages, in_sequence_updates_.size());
    556 
    557   VerifyIncrementalUpdatesMatch(
    558       distiller_data.get(), kNumPages, in_sequence_updates_, start_page_num);
    559 }
    560 
    561 TEST_F(DistillerTest, IncrementalUpdatesDoNotDeleteFinalArticle) {
    562   base::MessageLoopForUI loop;
    563   const size_t kNumPages = 8;
    564   int start_page_num = 3;
    565   scoped_ptr<MultipageDistillerData> distiller_data =
    566       CreateMultipageDistillerDataWithoutImages(kNumPages);
    567 
    568   distiller_.reset(
    569       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
    570   DistillPage(distiller_data->page_urls[start_page_num],
    571               CreateMockDistillerPages(
    572                   distiller_data.get(), kNumPages, start_page_num).Pass());
    573   base::MessageLoop::current()->RunUntilIdle();
    574   EXPECT_EQ(kNumPages, in_sequence_updates_.size());
    575 
    576   in_sequence_updates_.clear();
    577 
    578   // Should still be able to access article and pages.
    579   VerifyArticleProtoMatchesMultipageData(
    580       article_proto_.get(), distiller_data.get(), kNumPages);
    581 }
    582 
    583 TEST_F(DistillerTest, DeletingArticleDoesNotInterfereWithUpdates) {
    584   base::MessageLoopForUI loop;
    585   const size_t kNumPages = 8;
    586   scoped_ptr<MultipageDistillerData> distiller_data =
    587       CreateMultipageDistillerDataWithoutImages(kNumPages);
    588   // The page number of the article on which distillation starts.
    589   int start_page_num = 3;
    590 
    591   distiller_.reset(
    592       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
    593   DistillPage(distiller_data->page_urls[start_page_num],
    594               CreateMockDistillerPages(
    595                   distiller_data.get(), kNumPages, start_page_num).Pass());
    596   base::MessageLoop::current()->RunUntilIdle();
    597   EXPECT_EQ(kNumPages, in_sequence_updates_.size());
    598   EXPECT_EQ(kTitle, article_proto_->title());
    599   ASSERT_EQ(kNumPages, static_cast<size_t>(article_proto_->pages_size()));
    600 
    601   // Delete the article.
    602   article_proto_.reset();
    603   VerifyIncrementalUpdatesMatch(
    604       distiller_data.get(), kNumPages, in_sequence_updates_, start_page_num);
    605 }
    606 
    607 TEST_F(DistillerTest, CancelWithDelayedImageFetchCallback) {
    608   base::MessageLoopForUI loop;
    609   vector<int> image_indices;
    610   image_indices.push_back(0);
    611   scoped_ptr<base::Value> distilled_value =
    612       CreateDistilledValueReturnedFromJS(kTitle, kContent, image_indices, "");
    613   TestDistillerURLFetcher* delayed_fetcher = new TestDistillerURLFetcher(true);
    614   MockDistillerURLFetcherFactory mock_url_fetcher_factory;
    615   EXPECT_CALL(mock_url_fetcher_factory, CreateDistillerURLFetcher())
    616       .WillOnce(Return(delayed_fetcher));
    617   distiller_.reset(
    618       new DistillerImpl(mock_url_fetcher_factory, DomDistillerOptions()));
    619   DistillPage(
    620       kURL, CreateMockDistillerPage(distilled_value.get(), GURL(kURL)).Pass());
    621   base::MessageLoop::current()->RunUntilIdle();
    622 
    623   // Post callback from the url fetcher and then delete the distiller.
    624   delayed_fetcher->PostCallbackTask();
    625   distiller_.reset();
    626 
    627   base::MessageLoop::current()->RunUntilIdle();
    628 }
    629 
    630 TEST_F(DistillerTest, CancelWithDelayedJSCallback) {
    631   base::MessageLoopForUI loop;
    632   scoped_ptr<base::Value> distilled_value =
    633       CreateDistilledValueReturnedFromJS(kTitle, kContent, vector<int>(), "");
    634   MockDistillerPage* distiller_page = NULL;
    635   distiller_.reset(
    636       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
    637   DistillPage(kURL,
    638               CreateMockDistillerPageWithPendingJSCallback(&distiller_page,
    639                                                            GURL(kURL)));
    640   base::MessageLoop::current()->RunUntilIdle();
    641 
    642   ASSERT_TRUE(distiller_page);
    643   // Post the task to execute javascript and then delete the distiller.
    644   distiller_page->OnDistillationDone(GURL(kURL), distilled_value.get());
    645   distiller_.reset();
    646 
    647   base::MessageLoop::current()->RunUntilIdle();
    648 }
    649 
    650 }  // namespace dom_distiller
    651