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