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 "components/dom_distiller/core/task_tracker.h"
6
7#include "base/run_loop.h"
8#include "components/dom_distiller/core/article_distillation_update.h"
9#include "components/dom_distiller/core/article_entry.h"
10#include "components/dom_distiller/core/distilled_content_store.h"
11#include "components/dom_distiller/core/fake_distiller.h"
12#include "testing/gtest/include/gtest/gtest.h"
13
14using testing::Return;
15using testing::_;
16
17namespace dom_distiller {
18namespace test {
19
20class FakeViewRequestDelegate : public ViewRequestDelegate {
21 public:
22  virtual ~FakeViewRequestDelegate() {}
23  MOCK_METHOD1(OnArticleReady,
24               void(const DistilledArticleProto* article_proto));
25  MOCK_METHOD1(OnArticleUpdated,
26               void(ArticleDistillationUpdate article_update));
27};
28
29class MockContentStore : public DistilledContentStore {
30 public:
31  MOCK_METHOD2(LoadContent,
32               void(const ArticleEntry& entry, LoadCallback callback));
33  MOCK_METHOD3(SaveContent,
34               void(const ArticleEntry& entry,
35                    const DistilledArticleProto& proto,
36                    SaveCallback callback));
37};
38
39class TestCancelCallback {
40 public:
41  TestCancelCallback() : cancelled_(false) {}
42  TaskTracker::CancelCallback GetCallback() {
43    return base::Bind(&TestCancelCallback::Cancel, base::Unretained(this));
44  }
45  void Cancel(TaskTracker*) { cancelled_ = true; }
46  bool Cancelled() { return cancelled_; }
47
48 private:
49  bool cancelled_;
50};
51
52class MockSaveCallback {
53 public:
54  MOCK_METHOD3(Save,
55               void(const ArticleEntry&, const DistilledArticleProto*, bool));
56};
57
58class DomDistillerTaskTrackerTest : public testing::Test {
59 public:
60  virtual void SetUp() OVERRIDE {
61    message_loop_.reset(new base::MessageLoop());
62    entry_id_ = "id0";
63    page_0_url_ = GURL("http://www.example.com/1");
64    page_1_url_ = GURL("http://www.example.com/2");
65  }
66
67  ArticleEntry GetDefaultEntry() {
68    ArticleEntry entry;
69    entry.set_entry_id(entry_id_);
70    ArticleEntryPage* page0 = entry.add_pages();
71    ArticleEntryPage* page1 = entry.add_pages();
72    page0->set_url(page_0_url_.spec());
73    page1->set_url(page_1_url_.spec());
74    return entry;
75  }
76
77 protected:
78  scoped_ptr<base::MessageLoop> message_loop_;
79  std::string entry_id_;
80  GURL page_0_url_;
81  GURL page_1_url_;
82};
83
84TEST_F(DomDistillerTaskTrackerTest, TestHasEntryId) {
85  MockDistillerFactory distiller_factory;
86  TestCancelCallback cancel_callback;
87  TaskTracker task_tracker(
88      GetDefaultEntry(), cancel_callback.GetCallback(), NULL);
89  EXPECT_TRUE(task_tracker.HasEntryId(entry_id_));
90  EXPECT_FALSE(task_tracker.HasEntryId("other_id"));
91}
92
93TEST_F(DomDistillerTaskTrackerTest, TestHasUrl) {
94  MockDistillerFactory distiller_factory;
95  TestCancelCallback cancel_callback;
96  TaskTracker task_tracker(
97      GetDefaultEntry(), cancel_callback.GetCallback(), NULL);
98  EXPECT_TRUE(task_tracker.HasUrl(page_0_url_));
99  EXPECT_TRUE(task_tracker.HasUrl(page_1_url_));
100  EXPECT_FALSE(task_tracker.HasUrl(GURL("http://other.url/")));
101}
102
103TEST_F(DomDistillerTaskTrackerTest, TestViewerCancelled) {
104  MockDistillerFactory distiller_factory;
105  TestCancelCallback cancel_callback;
106  TaskTracker task_tracker(
107      GetDefaultEntry(), cancel_callback.GetCallback(), NULL);
108
109  FakeViewRequestDelegate viewer_delegate;
110  FakeViewRequestDelegate viewer_delegate2;
111  scoped_ptr<ViewerHandle> handle(task_tracker.AddViewer(&viewer_delegate));
112  scoped_ptr<ViewerHandle> handle2(task_tracker.AddViewer(&viewer_delegate2));
113
114  EXPECT_FALSE(cancel_callback.Cancelled());
115  handle.reset();
116  EXPECT_FALSE(cancel_callback.Cancelled());
117  handle2.reset();
118  EXPECT_TRUE(cancel_callback.Cancelled());
119}
120
121TEST_F(DomDistillerTaskTrackerTest, TestViewerCancelledWithSaveRequest) {
122  MockDistillerFactory distiller_factory;
123  TestCancelCallback cancel_callback;
124  TaskTracker task_tracker(
125      GetDefaultEntry(), cancel_callback.GetCallback(), NULL);
126
127  FakeViewRequestDelegate viewer_delegate;
128  scoped_ptr<ViewerHandle> handle(task_tracker.AddViewer(&viewer_delegate));
129  EXPECT_FALSE(cancel_callback.Cancelled());
130
131  MockSaveCallback save_callback;
132  task_tracker.AddSaveCallback(
133      base::Bind(&MockSaveCallback::Save, base::Unretained(&save_callback)));
134  handle.reset();
135
136  // Since there is a pending save request, the task shouldn't be cancelled.
137  EXPECT_FALSE(cancel_callback.Cancelled());
138}
139
140TEST_F(DomDistillerTaskTrackerTest, TestViewerNotifiedOnDistillationComplete) {
141  MockDistillerFactory distiller_factory;
142  FakeDistiller* distiller = new FakeDistiller(true);
143  EXPECT_CALL(distiller_factory, CreateDistillerImpl())
144      .WillOnce(Return(distiller));
145  TestCancelCallback cancel_callback;
146  TaskTracker task_tracker(
147      GetDefaultEntry(), cancel_callback.GetCallback(), NULL);
148
149  FakeViewRequestDelegate viewer_delegate;
150  scoped_ptr<ViewerHandle> handle(task_tracker.AddViewer(&viewer_delegate));
151  base::RunLoop().RunUntilIdle();
152
153  EXPECT_CALL(viewer_delegate, OnArticleReady(_));
154
155  task_tracker.StartDistiller(&distiller_factory,
156                              scoped_ptr<DistillerPage>().Pass());
157  base::RunLoop().RunUntilIdle();
158
159  EXPECT_FALSE(cancel_callback.Cancelled());
160}
161
162TEST_F(DomDistillerTaskTrackerTest,
163       TestSaveCallbackCalledOnDistillationComplete) {
164  MockDistillerFactory distiller_factory;
165  FakeDistiller* distiller = new FakeDistiller(true);
166  EXPECT_CALL(distiller_factory, CreateDistillerImpl())
167      .WillOnce(Return(distiller));
168  TestCancelCallback cancel_callback;
169  TaskTracker task_tracker(
170      GetDefaultEntry(), cancel_callback.GetCallback(), NULL);
171
172  MockSaveCallback save_callback;
173  task_tracker.AddSaveCallback(
174      base::Bind(&MockSaveCallback::Save, base::Unretained(&save_callback)));
175  base::RunLoop().RunUntilIdle();
176
177  EXPECT_CALL(save_callback, Save(_, _, _));
178
179  task_tracker.StartDistiller(&distiller_factory,
180                              scoped_ptr<DistillerPage>().Pass());
181  base::RunLoop().RunUntilIdle();
182
183  EXPECT_TRUE(cancel_callback.Cancelled());
184}
185
186DistilledArticleProto CreateDistilledArticleForEntry(
187    const ArticleEntry& entry) {
188  DistilledArticleProto article;
189  for (int i = 0; i < entry.pages_size(); ++i) {
190    DistilledPageProto* page = article.add_pages();
191    page->set_url(entry.pages(i).url());
192    page->set_html("<div>" + entry.pages(i).url() + "</div>");
193  }
194  return article;
195}
196
197TEST_F(DomDistillerTaskTrackerTest, TestBlobFetcher) {
198  ArticleEntry entry_with_blob = GetDefaultEntry();
199  DistilledArticleProto stored_distilled_article =
200      CreateDistilledArticleForEntry(entry_with_blob);
201  InMemoryContentStore content_store(kDefaultMaxNumCachedEntries);
202  content_store.InjectContent(entry_with_blob, stored_distilled_article);
203  TestCancelCallback cancel_callback;
204
205  TaskTracker task_tracker(
206      entry_with_blob, cancel_callback.GetCallback(), &content_store);
207
208  FakeViewRequestDelegate viewer_delegate;
209  scoped_ptr<ViewerHandle> handle(task_tracker.AddViewer(&viewer_delegate));
210  base::RunLoop().RunUntilIdle();
211
212  const DistilledArticleProto* distilled_article;
213
214  EXPECT_CALL(viewer_delegate, OnArticleReady(_))
215      .WillOnce(testing::SaveArg<0>(&distilled_article));
216
217  task_tracker.StartBlobFetcher();
218  base::RunLoop().RunUntilIdle();
219
220  EXPECT_EQ(stored_distilled_article.SerializeAsString(),
221            distilled_article->SerializeAsString());
222
223  EXPECT_FALSE(cancel_callback.Cancelled());
224}
225
226TEST_F(DomDistillerTaskTrackerTest, TestBlobFetcherFinishesFirst) {
227  MockDistillerFactory distiller_factory;
228  FakeDistiller* distiller = new FakeDistiller(false);
229  EXPECT_CALL(distiller_factory, CreateDistillerImpl())
230      .WillOnce(Return(distiller));
231
232  ArticleEntry entry_with_blob = GetDefaultEntry();
233  DistilledArticleProto stored_distilled_article =
234      CreateDistilledArticleForEntry(entry_with_blob);
235  InMemoryContentStore content_store(kDefaultMaxNumCachedEntries);
236  content_store.InjectContent(entry_with_blob, stored_distilled_article);
237  TestCancelCallback cancel_callback;
238  TaskTracker task_tracker(
239      entry_with_blob, cancel_callback.GetCallback(), &content_store);
240
241  FakeViewRequestDelegate viewer_delegate;
242  scoped_ptr<ViewerHandle> handle(task_tracker.AddViewer(&viewer_delegate));
243  base::RunLoop().RunUntilIdle();
244
245  DistilledArticleProto distilled_article;
246
247  EXPECT_CALL(viewer_delegate, OnArticleReady(_))
248      .WillOnce(testing::SaveArgPointee<0>(&distilled_article));
249  bool distiller_destroyed = false;
250  EXPECT_CALL(*distiller, Die())
251      .WillOnce(testing::Assign(&distiller_destroyed, true));
252
253  task_tracker.StartDistiller(&distiller_factory,
254                              scoped_ptr<DistillerPage>().Pass());
255  task_tracker.StartBlobFetcher();
256  base::RunLoop().RunUntilIdle();
257
258  testing::Mock::VerifyAndClearExpectations(&viewer_delegate);
259  EXPECT_EQ(stored_distilled_article.SerializeAsString(),
260            distilled_article.SerializeAsString());
261
262  EXPECT_TRUE(distiller_destroyed);
263  EXPECT_FALSE(cancel_callback.Cancelled());
264  base::RunLoop().RunUntilIdle();
265}
266
267TEST_F(DomDistillerTaskTrackerTest, TestBlobFetcherWithoutBlob) {
268  MockDistillerFactory distiller_factory;
269  FakeDistiller* distiller = new FakeDistiller(false);
270  EXPECT_CALL(distiller_factory, CreateDistillerImpl())
271      .WillOnce(Return(distiller));
272
273  ArticleEntry entry(GetDefaultEntry());
274  InMemoryContentStore content_store(kDefaultMaxNumCachedEntries);
275  scoped_ptr<DistilledArticleProto> distilled_article(
276      new DistilledArticleProto(CreateDistilledArticleForEntry(entry)));
277
278  TestCancelCallback cancel_callback;
279  TaskTracker task_tracker(
280      GetDefaultEntry(), cancel_callback.GetCallback(), &content_store);
281
282  FakeViewRequestDelegate viewer_delegate;
283  scoped_ptr<ViewerHandle> handle(task_tracker.AddViewer(&viewer_delegate));
284  base::RunLoop().RunUntilIdle();
285
286  task_tracker.StartBlobFetcher();
287  task_tracker.StartDistiller(&distiller_factory,
288                              scoped_ptr<DistillerPage>().Pass());
289
290  // OnArticleReady shouldn't be called until distillation finishes (i.e. the
291  // blob fetcher shouldn't return distilled content).
292  EXPECT_CALL(viewer_delegate, OnArticleReady(_)).Times(0);
293  base::RunLoop().RunUntilIdle();
294
295  EXPECT_CALL(viewer_delegate, OnArticleReady(_));
296  distiller->RunDistillerCallback(distilled_article.Pass());
297  base::RunLoop().RunUntilIdle();
298
299  EXPECT_FALSE(cancel_callback.Cancelled());
300}
301
302TEST_F(DomDistillerTaskTrackerTest, TestDistillerFailsFirst) {
303  MockDistillerFactory distiller_factory;
304  FakeDistiller* distiller = new FakeDistiller(false);
305  EXPECT_CALL(distiller_factory, CreateDistillerImpl())
306      .WillOnce(Return(distiller));
307
308  ArticleEntry entry(GetDefaultEntry());
309  MockContentStore content_store;
310
311  TestCancelCallback cancel_callback;
312  TaskTracker task_tracker(
313      GetDefaultEntry(), cancel_callback.GetCallback(), &content_store);
314
315  FakeViewRequestDelegate viewer_delegate;
316  scoped_ptr<ViewerHandle> handle(task_tracker.AddViewer(&viewer_delegate));
317
318  DistilledContentStore::LoadCallback content_store_load_callback;
319  EXPECT_CALL(content_store, LoadContent(_, _))
320      .WillOnce(testing::SaveArg<1>(&content_store_load_callback));
321
322  task_tracker.StartDistiller(&distiller_factory,
323                              scoped_ptr<DistillerPage>().Pass());
324  task_tracker.StartBlobFetcher();
325
326  EXPECT_CALL(viewer_delegate, OnArticleReady(_)).Times(0);
327  distiller->RunDistillerCallback(
328      scoped_ptr<DistilledArticleProto>(new DistilledArticleProto));
329  base::RunLoop().RunUntilIdle();
330
331  EXPECT_CALL(viewer_delegate, OnArticleReady(_));
332  content_store_load_callback.Run(
333      true,
334      scoped_ptr<DistilledArticleProto>(
335          new DistilledArticleProto(CreateDistilledArticleForEntry(entry))));
336  base::RunLoop().RunUntilIdle();
337
338  EXPECT_FALSE(cancel_callback.Cancelled());
339}
340
341TEST_F(DomDistillerTaskTrackerTest, ContentIsSaved) {
342  MockDistillerFactory distiller_factory;
343  FakeDistiller* distiller = new FakeDistiller(false);
344  EXPECT_CALL(distiller_factory, CreateDistillerImpl())
345      .WillOnce(Return(distiller));
346
347  ArticleEntry entry(GetDefaultEntry());
348  DistilledArticleProto distilled_article =
349      CreateDistilledArticleForEntry(entry);
350
351  MockContentStore content_store;
352  TestCancelCallback cancel_callback;
353  TaskTracker task_tracker(
354      GetDefaultEntry(), cancel_callback.GetCallback(), &content_store);
355
356  FakeViewRequestDelegate viewer_delegate;
357  scoped_ptr<ViewerHandle> handle(task_tracker.AddViewer(&viewer_delegate));
358
359  DistilledArticleProto stored_distilled_article;
360  DistilledContentStore::LoadCallback content_store_load_callback;
361  EXPECT_CALL(content_store, SaveContent(_, _, _))
362      .WillOnce(testing::SaveArg<1>(&stored_distilled_article));
363
364  task_tracker.StartDistiller(&distiller_factory,
365                              scoped_ptr<DistillerPage>().Pass());
366
367  EXPECT_CALL(viewer_delegate, OnArticleReady(_));
368  distiller->RunDistillerCallback(scoped_ptr<DistilledArticleProto>(
369      new DistilledArticleProto(distilled_article)));
370  base::RunLoop().RunUntilIdle();
371
372  ASSERT_EQ(stored_distilled_article.SerializeAsString(),
373            distilled_article.SerializeAsString());
374  EXPECT_FALSE(cancel_callback.Cancelled());
375}
376
377}  // namespace test
378}  // namespace dom_distiller
379