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 14 using testing::Return; 15 using testing::_; 16 17 namespace dom_distiller { 18 namespace test { 19 20 class 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 29 class 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 39 class 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 52 class MockSaveCallback { 53 public: 54 MOCK_METHOD3(Save, 55 void(const ArticleEntry&, const DistilledArticleProto*, bool)); 56 }; 57 58 class 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 84 TEST_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 93 TEST_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 103 TEST_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 121 TEST_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 140 TEST_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 162 TEST_F(DomDistillerTaskTrackerTest, TestDistillerFails) { 163 MockDistillerFactory distiller_factory; 164 FakeDistiller* distiller = new FakeDistiller(false); 165 EXPECT_CALL(distiller_factory, CreateDistillerImpl()) 166 .WillOnce(Return(distiller)); 167 168 TestCancelCallback cancel_callback; 169 TaskTracker task_tracker( 170 GetDefaultEntry(), cancel_callback.GetCallback(), NULL); 171 172 FakeViewRequestDelegate viewer_delegate; 173 scoped_ptr<ViewerHandle> handle(task_tracker.AddViewer(&viewer_delegate)); 174 base::RunLoop().RunUntilIdle(); 175 176 EXPECT_CALL(viewer_delegate, OnArticleReady(_)); 177 178 task_tracker.StartDistiller(&distiller_factory, 179 scoped_ptr<DistillerPage>().Pass()); 180 distiller->RunDistillerCallback( 181 scoped_ptr<DistilledArticleProto>(new DistilledArticleProto)); 182 base::RunLoop().RunUntilIdle(); 183 184 EXPECT_FALSE(cancel_callback.Cancelled()); 185 } 186 187 TEST_F(DomDistillerTaskTrackerTest, 188 TestSaveCallbackCalledOnDistillationComplete) { 189 MockDistillerFactory distiller_factory; 190 FakeDistiller* distiller = new FakeDistiller(true); 191 EXPECT_CALL(distiller_factory, CreateDistillerImpl()) 192 .WillOnce(Return(distiller)); 193 TestCancelCallback cancel_callback; 194 TaskTracker task_tracker( 195 GetDefaultEntry(), cancel_callback.GetCallback(), NULL); 196 197 MockSaveCallback save_callback; 198 task_tracker.AddSaveCallback( 199 base::Bind(&MockSaveCallback::Save, base::Unretained(&save_callback))); 200 base::RunLoop().RunUntilIdle(); 201 202 EXPECT_CALL(save_callback, Save(_, _, _)); 203 204 task_tracker.StartDistiller(&distiller_factory, 205 scoped_ptr<DistillerPage>().Pass()); 206 base::RunLoop().RunUntilIdle(); 207 208 EXPECT_TRUE(cancel_callback.Cancelled()); 209 } 210 211 DistilledArticleProto CreateDistilledArticleForEntry( 212 const ArticleEntry& entry) { 213 DistilledArticleProto article; 214 for (int i = 0; i < entry.pages_size(); ++i) { 215 DistilledPageProto* page = article.add_pages(); 216 page->set_url(entry.pages(i).url()); 217 page->set_html("<div>" + entry.pages(i).url() + "</div>"); 218 } 219 return article; 220 } 221 222 TEST_F(DomDistillerTaskTrackerTest, TestBlobFetcher) { 223 ArticleEntry entry_with_blob = GetDefaultEntry(); 224 DistilledArticleProto stored_distilled_article = 225 CreateDistilledArticleForEntry(entry_with_blob); 226 InMemoryContentStore content_store(kDefaultMaxNumCachedEntries); 227 content_store.InjectContent(entry_with_blob, stored_distilled_article); 228 TestCancelCallback cancel_callback; 229 230 TaskTracker task_tracker( 231 entry_with_blob, cancel_callback.GetCallback(), &content_store); 232 233 FakeViewRequestDelegate viewer_delegate; 234 scoped_ptr<ViewerHandle> handle(task_tracker.AddViewer(&viewer_delegate)); 235 base::RunLoop().RunUntilIdle(); 236 237 const DistilledArticleProto* distilled_article; 238 239 EXPECT_CALL(viewer_delegate, OnArticleReady(_)) 240 .WillOnce(testing::SaveArg<0>(&distilled_article)); 241 242 task_tracker.StartBlobFetcher(); 243 base::RunLoop().RunUntilIdle(); 244 245 EXPECT_EQ(stored_distilled_article.SerializeAsString(), 246 distilled_article->SerializeAsString()); 247 248 EXPECT_FALSE(cancel_callback.Cancelled()); 249 } 250 251 TEST_F(DomDistillerTaskTrackerTest, TestBlobFetcherFinishesFirst) { 252 MockDistillerFactory distiller_factory; 253 FakeDistiller* distiller = new FakeDistiller(false); 254 EXPECT_CALL(distiller_factory, CreateDistillerImpl()) 255 .WillOnce(Return(distiller)); 256 257 ArticleEntry entry_with_blob = GetDefaultEntry(); 258 DistilledArticleProto stored_distilled_article = 259 CreateDistilledArticleForEntry(entry_with_blob); 260 InMemoryContentStore content_store(kDefaultMaxNumCachedEntries); 261 content_store.InjectContent(entry_with_blob, stored_distilled_article); 262 TestCancelCallback cancel_callback; 263 TaskTracker task_tracker( 264 entry_with_blob, cancel_callback.GetCallback(), &content_store); 265 266 FakeViewRequestDelegate viewer_delegate; 267 scoped_ptr<ViewerHandle> handle(task_tracker.AddViewer(&viewer_delegate)); 268 base::RunLoop().RunUntilIdle(); 269 270 DistilledArticleProto distilled_article; 271 272 EXPECT_CALL(viewer_delegate, OnArticleReady(_)) 273 .WillOnce(testing::SaveArgPointee<0>(&distilled_article)); 274 bool distiller_destroyed = false; 275 EXPECT_CALL(*distiller, Die()) 276 .WillOnce(testing::Assign(&distiller_destroyed, true)); 277 278 task_tracker.StartDistiller(&distiller_factory, 279 scoped_ptr<DistillerPage>().Pass()); 280 task_tracker.StartBlobFetcher(); 281 base::RunLoop().RunUntilIdle(); 282 283 testing::Mock::VerifyAndClearExpectations(&viewer_delegate); 284 EXPECT_EQ(stored_distilled_article.SerializeAsString(), 285 distilled_article.SerializeAsString()); 286 287 EXPECT_TRUE(distiller_destroyed); 288 EXPECT_FALSE(cancel_callback.Cancelled()); 289 base::RunLoop().RunUntilIdle(); 290 } 291 292 TEST_F(DomDistillerTaskTrackerTest, TestBlobFetcherWithoutBlob) { 293 MockDistillerFactory distiller_factory; 294 FakeDistiller* distiller = new FakeDistiller(false); 295 EXPECT_CALL(distiller_factory, CreateDistillerImpl()) 296 .WillOnce(Return(distiller)); 297 298 ArticleEntry entry(GetDefaultEntry()); 299 InMemoryContentStore content_store(kDefaultMaxNumCachedEntries); 300 scoped_ptr<DistilledArticleProto> distilled_article( 301 new DistilledArticleProto(CreateDistilledArticleForEntry(entry))); 302 303 TestCancelCallback cancel_callback; 304 TaskTracker task_tracker( 305 GetDefaultEntry(), cancel_callback.GetCallback(), &content_store); 306 307 FakeViewRequestDelegate viewer_delegate; 308 scoped_ptr<ViewerHandle> handle(task_tracker.AddViewer(&viewer_delegate)); 309 base::RunLoop().RunUntilIdle(); 310 311 task_tracker.StartBlobFetcher(); 312 task_tracker.StartDistiller(&distiller_factory, 313 scoped_ptr<DistillerPage>().Pass()); 314 315 // OnArticleReady shouldn't be called until distillation finishes (i.e. the 316 // blob fetcher shouldn't return distilled content). 317 EXPECT_CALL(viewer_delegate, OnArticleReady(_)).Times(0); 318 base::RunLoop().RunUntilIdle(); 319 320 EXPECT_CALL(viewer_delegate, OnArticleReady(_)); 321 distiller->RunDistillerCallback(distilled_article.Pass()); 322 base::RunLoop().RunUntilIdle(); 323 324 EXPECT_FALSE(cancel_callback.Cancelled()); 325 } 326 327 TEST_F(DomDistillerTaskTrackerTest, TestDistillerFailsFirst) { 328 MockDistillerFactory distiller_factory; 329 FakeDistiller* distiller = new FakeDistiller(false); 330 EXPECT_CALL(distiller_factory, CreateDistillerImpl()) 331 .WillOnce(Return(distiller)); 332 333 ArticleEntry entry(GetDefaultEntry()); 334 MockContentStore content_store; 335 336 TestCancelCallback cancel_callback; 337 TaskTracker task_tracker( 338 GetDefaultEntry(), cancel_callback.GetCallback(), &content_store); 339 340 FakeViewRequestDelegate viewer_delegate; 341 scoped_ptr<ViewerHandle> handle(task_tracker.AddViewer(&viewer_delegate)); 342 343 DistilledContentStore::LoadCallback content_store_load_callback; 344 EXPECT_CALL(content_store, LoadContent(_, _)) 345 .WillOnce(testing::SaveArg<1>(&content_store_load_callback)); 346 347 task_tracker.StartDistiller(&distiller_factory, 348 scoped_ptr<DistillerPage>().Pass()); 349 task_tracker.StartBlobFetcher(); 350 351 EXPECT_CALL(viewer_delegate, OnArticleReady(_)).Times(0); 352 distiller->RunDistillerCallback( 353 scoped_ptr<DistilledArticleProto>(new DistilledArticleProto)); 354 base::RunLoop().RunUntilIdle(); 355 356 EXPECT_CALL(viewer_delegate, OnArticleReady(_)); 357 content_store_load_callback.Run( 358 true, 359 scoped_ptr<DistilledArticleProto>( 360 new DistilledArticleProto(CreateDistilledArticleForEntry(entry)))); 361 base::RunLoop().RunUntilIdle(); 362 363 EXPECT_FALSE(cancel_callback.Cancelled()); 364 } 365 366 TEST_F(DomDistillerTaskTrackerTest, ContentIsSaved) { 367 MockDistillerFactory distiller_factory; 368 FakeDistiller* distiller = new FakeDistiller(false); 369 EXPECT_CALL(distiller_factory, CreateDistillerImpl()) 370 .WillOnce(Return(distiller)); 371 372 ArticleEntry entry(GetDefaultEntry()); 373 DistilledArticleProto distilled_article = 374 CreateDistilledArticleForEntry(entry); 375 376 MockContentStore content_store; 377 TestCancelCallback cancel_callback; 378 TaskTracker task_tracker( 379 GetDefaultEntry(), cancel_callback.GetCallback(), &content_store); 380 381 FakeViewRequestDelegate viewer_delegate; 382 scoped_ptr<ViewerHandle> handle(task_tracker.AddViewer(&viewer_delegate)); 383 384 DistilledArticleProto stored_distilled_article; 385 DistilledContentStore::LoadCallback content_store_load_callback; 386 EXPECT_CALL(content_store, SaveContent(_, _, _)) 387 .WillOnce(testing::SaveArg<1>(&stored_distilled_article)); 388 389 task_tracker.StartDistiller(&distiller_factory, 390 scoped_ptr<DistillerPage>().Pass()); 391 392 EXPECT_CALL(viewer_delegate, OnArticleReady(_)); 393 distiller->RunDistillerCallback(scoped_ptr<DistilledArticleProto>( 394 new DistilledArticleProto(distilled_article))); 395 base::RunLoop().RunUntilIdle(); 396 397 ASSERT_EQ(stored_distilled_article.SerializeAsString(), 398 distilled_article.SerializeAsString()); 399 EXPECT_FALSE(cancel_callback.Cancelled()); 400 } 401 402 } // namespace test 403 } // namespace dom_distiller 404