Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(433)

Side by Side Diff: components/ntp_snippets/remote/ntp_snippets_service_unittest.cc

Issue 2386103009: NTPSnippetsService: Garbage collect orphaned images at startup. (Closed)
Patch Set: final comments Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « components/ntp_snippets/remote/ntp_snippets_service.cc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "components/ntp_snippets/remote/ntp_snippets_service.h" 5 #include "components/ntp_snippets/remote/ntp_snippets_service.h"
6 6
7 #include <memory> 7 #include <memory>
8 #include <utility> 8 #include <utility>
9 #include <vector> 9 #include <vector>
10 10
(...skipping 208 matching lines...) Expand 10 before | Expand all | Expand 10 after
219 // fail to parse as snippets. 219 // fail to parse as snippets.
220 size_t pos = json_str.find("\"fullPageUrl\""); 220 size_t pos = json_str.find("\"fullPageUrl\"");
221 if (pos == std::string::npos) { 221 if (pos == std::string::npos) {
222 NOTREACHED(); 222 NOTREACHED();
223 return std::string(); 223 return std::string();
224 } 224 }
225 json_str[pos + 1] = 'x'; 225 json_str[pos + 1] = 'x';
226 return json_str; 226 return json_str;
227 } 227 }
228 228
229 using ServeImageCallback = base::Callback<void(
230 const std::string&,
231 base::Callback<void(const std::string&, const gfx::Image&)>)>;
232
229 void ServeOneByOneImage( 233 void ServeOneByOneImage(
234 image_fetcher::ImageFetcherDelegate* notify,
230 const std::string& id, 235 const std::string& id,
231 base::Callback<void(const std::string&, const gfx::Image&)> callback) { 236 base::Callback<void(const std::string&, const gfx::Image&)> callback) {
232 base::ThreadTaskRunnerHandle::Get()->PostTask( 237 base::ThreadTaskRunnerHandle::Get()->PostTask(
233 FROM_HERE, base::Bind(callback, id, gfx::test::CreateImage(1, 1))); 238 FROM_HERE, base::Bind(callback, id, gfx::test::CreateImage(1, 1)));
239 notify->OnImageDataFetched(id, "1-by-1-image-data");
234 } 240 }
235 241
236 void ParseJson( 242 void ParseJson(
237 const std::string& json, 243 const std::string& json,
238 const ntp_snippets::NTPSnippetsFetcher::SuccessCallback& success_callback, 244 const ntp_snippets::NTPSnippetsFetcher::SuccessCallback& success_callback,
239 const ntp_snippets::NTPSnippetsFetcher::ErrorCallback& error_callback) { 245 const ntp_snippets::NTPSnippetsFetcher::ErrorCallback& error_callback) {
240 base::JSONReader json_reader; 246 base::JSONReader json_reader;
241 std::unique_ptr<base::Value> value = json_reader.ReadToValue(json); 247 std::unique_ptr<base::Value> value = json_reader.ReadToValue(json);
242 if (value) { 248 if (value) {
243 success_callback.Run(std::move(value)); 249 success_callback.Run(std::move(value));
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after
332 338
333 private: 339 private:
334 base::WaitableEvent loaded_; 340 base::WaitableEvent loaded_;
335 std::map<Category, CategoryStatus, Category::CompareByID> statuses_; 341 std::map<Category, CategoryStatus, Category::CompareByID> statuses_;
336 std::map<Category, std::vector<ContentSuggestion>, Category::CompareByID> 342 std::map<Category, std::vector<ContentSuggestion>, Category::CompareByID>
337 suggestions_; 343 suggestions_;
338 344
339 DISALLOW_COPY_AND_ASSIGN(FakeContentSuggestionsProviderObserver); 345 DISALLOW_COPY_AND_ASSIGN(FakeContentSuggestionsProviderObserver);
340 }; 346 };
341 347
348 class FakeImageDecoder : public image_fetcher::ImageDecoder {
349 public:
350 FakeImageDecoder() {}
351 ~FakeImageDecoder() override = default;
352 void DecodeImage(
353 const std::string& image_data,
354 const image_fetcher::ImageDecodedCallback& callback) override {
355 callback.Run(decoded_image_);
356 }
357
358 void SetDecodedImage(const gfx::Image& image) { decoded_image_ = image; }
359
360 private:
361 gfx::Image decoded_image_;
362 };
363
342 } // namespace 364 } // namespace
343 365
344 class NTPSnippetsServiceTest : public ::testing::Test { 366 class NTPSnippetsServiceTest : public ::testing::Test {
345 public: 367 public:
346 NTPSnippetsServiceTest() 368 NTPSnippetsServiceTest()
347 : params_manager_(ntp_snippets::kStudyName, 369 : params_manager_(ntp_snippets::kStudyName,
348 {{"content_suggestions_backend", 370 {{"content_suggestions_backend",
349 kTestContentSuggestionsServerEndpoint}}), 371 kTestContentSuggestionsServerEndpoint}}),
350 fake_url_fetcher_factory_( 372 fake_url_fetcher_factory_(
351 /*default_factory=*/&failing_url_fetcher_factory_), 373 /*default_factory=*/&failing_url_fetcher_factory_),
352 test_url_(kTestContentSuggestionsServerWithAPIKey), 374 test_url_(kTestContentSuggestionsServerWithAPIKey),
353 user_classifier_(/*pref_service=*/nullptr), 375 user_classifier_(/*pref_service=*/nullptr),
354 image_fetcher_(nullptr) { 376 image_fetcher_(nullptr),
377 image_decoder_(nullptr) {
355 NTPSnippetsService::RegisterProfilePrefs(utils_.pref_service()->registry()); 378 NTPSnippetsService::RegisterProfilePrefs(utils_.pref_service()->registry());
356 RequestThrottler::RegisterProfilePrefs(utils_.pref_service()->registry()); 379 RequestThrottler::RegisterProfilePrefs(utils_.pref_service()->registry());
357 380
358 // Since no SuggestionsService is injected in tests, we need to force the 381 // Since no SuggestionsService is injected in tests, we need to force the
359 // service to fetch from all hosts. 382 // service to fetch from all hosts.
360 base::CommandLine::ForCurrentProcess()->AppendSwitch( 383 base::CommandLine::ForCurrentProcess()->AppendSwitch(
361 switches::kDontRestrict); 384 switches::kDontRestrict);
362 EXPECT_TRUE(database_dir_.CreateUniqueTempDir()); 385 EXPECT_TRUE(database_dir_.CreateUniqueTempDir());
363 } 386 }
364 387
(...skipping 23 matching lines...) Expand all
388 base::MakeUnique<NTPSnippetsFetcher>( 411 base::MakeUnique<NTPSnippetsFetcher>(
389 utils_.fake_signin_manager(), fake_token_service_.get(), 412 utils_.fake_signin_manager(), fake_token_service_.get(),
390 std::move(request_context_getter), utils_.pref_service(), 413 std::move(request_context_getter), utils_.pref_service(),
391 &category_factory_, base::Bind(&ParseJson), kAPIKey); 414 &category_factory_, base::Bind(&ParseJson), kAPIKey);
392 415
393 utils_.fake_signin_manager()->SignIn("foo@bar.com"); 416 utils_.fake_signin_manager()->SignIn("foo@bar.com");
394 snippets_fetcher->SetPersonalizationForTesting( 417 snippets_fetcher->SetPersonalizationForTesting(
395 NTPSnippetsFetcher::Personalization::kNonPersonal); 418 NTPSnippetsFetcher::Personalization::kNonPersonal);
396 419
397 auto image_fetcher = base::MakeUnique<NiceMock<MockImageFetcher>>(); 420 auto image_fetcher = base::MakeUnique<NiceMock<MockImageFetcher>>();
421
398 image_fetcher_ = image_fetcher.get(); 422 image_fetcher_ = image_fetcher.get();
423 EXPECT_CALL(*image_fetcher, SetImageFetcherDelegate(_));
424 auto image_decoder = base::MakeUnique<FakeImageDecoder>();
425 image_decoder_ = image_decoder.get();
399 EXPECT_FALSE(observer_); 426 EXPECT_FALSE(observer_);
400 observer_ = base::MakeUnique<FakeContentSuggestionsProviderObserver>(); 427 observer_ = base::MakeUnique<FakeContentSuggestionsProviderObserver>();
401 return base::MakeUnique<NTPSnippetsService>( 428 return base::MakeUnique<NTPSnippetsService>(
402 observer_.get(), &category_factory_, utils_.pref_service(), nullptr, 429 observer_.get(), &category_factory_, utils_.pref_service(), nullptr,
403 "fr", &user_classifier_, &scheduler_, std::move(snippets_fetcher), 430 "fr", &user_classifier_, &scheduler_, std::move(snippets_fetcher),
404 std::move(image_fetcher), /*image_decoder=*/nullptr, 431 std::move(image_fetcher), std::move(image_decoder),
405 base::MakeUnique<NTPSnippetsDatabase>(database_dir_.GetPath(), 432 base::MakeUnique<NTPSnippetsDatabase>(database_dir_.GetPath(),
406 task_runner), 433 task_runner),
407 base::MakeUnique<NTPSnippetsStatusService>(utils_.fake_signin_manager(), 434 base::MakeUnique<NTPSnippetsStatusService>(utils_.fake_signin_manager(),
408 utils_.pref_service())); 435 utils_.pref_service()));
409 } 436 }
410 437
411 void WaitForSnippetsServiceInitialization(bool set_empty_response = true) { 438 void WaitForSnippetsServiceInitialization(bool set_empty_response = true) {
412 EXPECT_TRUE(observer_); 439 EXPECT_TRUE(observer_);
413 EXPECT_FALSE(observer_->Loaded()); 440 EXPECT_FALSE(observer_->Loaded());
414 441
(...skipping 24 matching lines...) Expand all
439 return ContentSuggestion::ID(other_category(), id_within_category); 466 return ContentSuggestion::ID(other_category(), id_within_category);
440 } 467 }
441 468
442 Category other_category() { return category_factory_.FromRemoteCategory(2); } 469 Category other_category() { return category_factory_.FromRemoteCategory(2); }
443 470
444 protected: 471 protected:
445 const GURL& test_url() { return test_url_; } 472 const GURL& test_url() { return test_url_; }
446 FakeContentSuggestionsProviderObserver& observer() { return *observer_; } 473 FakeContentSuggestionsProviderObserver& observer() { return *observer_; }
447 MockScheduler& mock_scheduler() { return scheduler_; } 474 MockScheduler& mock_scheduler() { return scheduler_; }
448 NiceMock<MockImageFetcher>* image_fetcher() { return image_fetcher_; } 475 NiceMock<MockImageFetcher>* image_fetcher() { return image_fetcher_; }
476 FakeImageDecoder* image_decoder() { return image_decoder_; }
449 477
450 // Provide the json to be returned by the fake fetcher. 478 // Provide the json to be returned by the fake fetcher.
451 void SetUpFetchResponse(const std::string& json) { 479 void SetUpFetchResponse(const std::string& json) {
452 fake_url_fetcher_factory_.SetFakeResponse(test_url_, json, net::HTTP_OK, 480 fake_url_fetcher_factory_.SetFakeResponse(test_url_, json, net::HTTP_OK,
453 net::URLRequestStatus::SUCCESS); 481 net::URLRequestStatus::SUCCESS);
454 } 482 }
455 483
456 void LoadFromJSONString(NTPSnippetsService* service, 484 void LoadFromJSONString(NTPSnippetsService* service,
457 const std::string& json) { 485 const std::string& json) {
458 SetUpFetchResponse(json); 486 SetUpFetchResponse(json);
459 service->FetchSnippets(true); 487 service->FetchSnippets(true);
460 base::RunLoop().RunUntilIdle(); 488 base::RunLoop().RunUntilIdle();
461 } 489 }
462 490
463 private: 491 private:
464 variations::testing::VariationParamsManager params_manager_; 492 variations::testing::VariationParamsManager params_manager_;
465 test::NTPSnippetsTestUtils utils_; 493 test::NTPSnippetsTestUtils utils_;
466 base::MessageLoop message_loop_; 494 base::MessageLoop message_loop_;
467 FailingFakeURLFetcherFactory failing_url_fetcher_factory_; 495 FailingFakeURLFetcherFactory failing_url_fetcher_factory_;
468 // Instantiation of factory automatically sets itself as URLFetcher's factory. 496 // Instantiation of factory automatically sets itself as URLFetcher's factory.
469 net::FakeURLFetcherFactory fake_url_fetcher_factory_; 497 net::FakeURLFetcherFactory fake_url_fetcher_factory_;
470 const GURL test_url_; 498 const GURL test_url_;
471 std::unique_ptr<OAuth2TokenService> fake_token_service_; 499 std::unique_ptr<OAuth2TokenService> fake_token_service_;
472 UserClassifier user_classifier_; 500 UserClassifier user_classifier_;
473 NiceMock<MockScheduler> scheduler_; 501 NiceMock<MockScheduler> scheduler_;
474 std::unique_ptr<FakeContentSuggestionsProviderObserver> observer_; 502 std::unique_ptr<FakeContentSuggestionsProviderObserver> observer_;
475 CategoryFactory category_factory_; 503 CategoryFactory category_factory_;
476 NiceMock<MockImageFetcher>* image_fetcher_; 504 NiceMock<MockImageFetcher>* image_fetcher_;
505 FakeImageDecoder* image_decoder_;
477 506
478 base::ScopedTempDir database_dir_; 507 base::ScopedTempDir database_dir_;
479 508
480 DISALLOW_COPY_AND_ASSIGN(NTPSnippetsServiceTest); 509 DISALLOW_COPY_AND_ASSIGN(NTPSnippetsServiceTest);
481 }; 510 };
482 511
483 TEST_F(NTPSnippetsServiceTest, ScheduleOnStart) { 512 TEST_F(NTPSnippetsServiceTest, ScheduleOnStart) {
484 // We should get two |Schedule| calls: The first when initialization 513 // We should get two |Schedule| calls: The first when initialization
485 // completes, the second one after the automatic (since the service doesn't 514 // completes, the second one after the automatic (since the service doesn't
486 // have any data yet) fetch finishes. 515 // have any data yet) fetch finishes.
(...skipping 531 matching lines...) Expand 10 before | Expand all | Expand 10 after
1018 EXPECT_FALSE(service->GetSnippetsForTesting(articles_category()).empty()); 1047 EXPECT_FALSE(service->GetSnippetsForTesting(articles_category()).empty());
1019 } 1048 }
1020 1049
1021 TEST_F(NTPSnippetsServiceTest, ImageReturnedWithTheSameId) { 1050 TEST_F(NTPSnippetsServiceTest, ImageReturnedWithTheSameId) {
1022 auto service = MakeSnippetsService(); 1051 auto service = MakeSnippetsService();
1023 1052
1024 LoadFromJSONString(service.get(), GetTestJson({GetSnippet()})); 1053 LoadFromJSONString(service.get(), GetTestJson({GetSnippet()}));
1025 1054
1026 gfx::Image image; 1055 gfx::Image image;
1027 MockFunction<void(const gfx::Image&)> image_fetched; 1056 MockFunction<void(const gfx::Image&)> image_fetched;
1057 ServeImageCallback cb = base::Bind(&ServeOneByOneImage, service.get());
1028 { 1058 {
1029 InSequence s; 1059 InSequence s;
1030 EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _)) 1060 EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _))
1031 .WillOnce(WithArgs<0, 2>(Invoke(ServeOneByOneImage))); 1061 .WillOnce(WithArgs<0, 2>(Invoke(&cb, &ServeImageCallback::Run)));
1032 EXPECT_CALL(image_fetched, Call(_)).WillOnce(SaveArg<0>(&image)); 1062 EXPECT_CALL(image_fetched, Call(_)).WillOnce(SaveArg<0>(&image));
1033 } 1063 }
1034 1064
1035 service->FetchSuggestionImage( 1065 service->FetchSuggestionImage(
1036 MakeArticleID(kSnippetUrl), 1066 MakeArticleID(kSnippetUrl),
1037 base::Bind(&MockFunction<void(const gfx::Image&)>::Call, 1067 base::Bind(&MockFunction<void(const gfx::Image&)>::Call,
1038 base::Unretained(&image_fetched))); 1068 base::Unretained(&image_fetched)));
1039 base::RunLoop().RunUntilIdle(); 1069 base::RunLoop().RunUntilIdle();
1040 // Check that the image by ServeOneByOneImage is really served. 1070 // Check that the image by ServeOneByOneImage is really served.
1041 EXPECT_EQ(1, image.Width()); 1071 EXPECT_EQ(1, image.Width());
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
1095 EXPECT_THAT(service->GetSnippetsForTesting(articles_category()), SizeIs(1)); 1125 EXPECT_THAT(service->GetSnippetsForTesting(articles_category()), SizeIs(1));
1096 1126
1097 // Sign in to check a transition from signed out to signed in. 1127 // Sign in to check a transition from signed out to signed in.
1098 SetUpFetchResponse(GetTestJson({GetSnippetN(1), GetSnippetN(2)})); 1128 SetUpFetchResponse(GetTestJson({GetSnippetN(1), GetSnippetN(2)}));
1099 service->OnSnippetsStatusChanged(SnippetsStatus::ENABLED_AND_SIGNED_OUT, 1129 service->OnSnippetsStatusChanged(SnippetsStatus::ENABLED_AND_SIGNED_OUT,
1100 SnippetsStatus::ENABLED_AND_SIGNED_IN); 1130 SnippetsStatus::ENABLED_AND_SIGNED_IN);
1101 base::RunLoop().RunUntilIdle(); 1131 base::RunLoop().RunUntilIdle();
1102 EXPECT_THAT(service->GetSnippetsForTesting(articles_category()), SizeIs(2)); 1132 EXPECT_THAT(service->GetSnippetsForTesting(articles_category()), SizeIs(2));
1103 } 1133 }
1104 1134
1135 namespace {
1136
1137 gfx::Image FetchImage(NTPSnippetsService* service,
1138 const ContentSuggestion::ID& suggestion_id) {
1139 gfx::Image result;
1140 base::RunLoop run_loop;
1141 service->FetchSuggestionImage(suggestion_id,
1142 base::Bind(
1143 [](base::Closure signal, gfx::Image* output,
1144 const gfx::Image& loaded) {
1145 *output = loaded;
1146 signal.Run();
1147 },
1148 run_loop.QuitClosure(), &result));
1149 run_loop.Run();
1150 return result;
1151 }
1152
1153 } // namespace
1154
1155 TEST_F(NTPSnippetsServiceTest, ShouldClearOrphanedImagesOnRestart) {
1156 auto service = MakeSnippetsService();
1157
1158 LoadFromJSONString(service.get(), GetTestJson({GetSnippet()}));
1159 ServeImageCallback cb = base::Bind(&ServeOneByOneImage, service.get());
1160
1161 EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _))
1162 .WillOnce(WithArgs<0, 2>(Invoke(&cb, &ServeImageCallback::Run)));
1163 image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
1164
1165 gfx::Image image = FetchImage(service.get(), MakeArticleID(kSnippetUrl));
1166 EXPECT_EQ(1, image.Width());
1167 EXPECT_FALSE(image.IsEmpty());
1168
1169 // Send new suggestion which don't include the snippet referencing the image.
1170 LoadFromJSONString(service.get(),
1171 GetTestJson({GetSnippetWithUrl(
1172 "http://something.com/pletely/unrelated")}));
1173 // The image should still be available until a restart happens.
1174 EXPECT_FALSE(FetchImage(service.get(), MakeArticleID(kSnippetUrl)).IsEmpty());
1175 ResetSnippetsService(&service);
1176 // After the restart, the image should be garbage collected.
1177 EXPECT_TRUE(FetchImage(service.get(), MakeArticleID(kSnippetUrl)).IsEmpty());
1178 }
1179
1105 } // namespace ntp_snippets 1180 } // namespace ntp_snippets
OLDNEW
« no previous file with comments | « components/ntp_snippets/remote/ntp_snippets_service.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698