1 // Copyright 2018 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/feed/core/feed_image_manager.h"
6 
7 #include <memory>
8 #include <string>
9 #include <utility>
10 #include <vector>
11 
12 #include "base/bind.h"
13 #include "base/files/scoped_temp_dir.h"
14 #include "base/test/metrics/histogram_tester.h"
15 #include "base/test/mock_callback.h"
16 #include "base/test/task_environment.h"
17 #include "base/threading/sequenced_task_runner_handle.h"
18 #include "base/timer/timer.h"
19 #include "components/image_fetcher/core/image_decoder.h"
20 #include "components/image_fetcher/core/image_fetcher_impl.h"
21 #include "services/network/public/cpp/shared_url_loader_factory.h"
22 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
23 #include "services/network/test/test_url_loader_factory.h"
24 #include "testing/gmock/include/gmock/gmock.h"
25 #include "testing/gtest/include/gtest/gtest.h"
26 #include "ui/gfx/image/image.h"
27 #include "ui/gfx/image/image_unittest_util.h"
28 
29 using base::HistogramTester;
30 using testing::_;
31 
32 namespace feed {
33 
34 namespace {
35 const char kImageURL[] = "http://pie.com/";
36 const char kImageURL2[] = "http://cake.com/";
37 const char kImageData[] = "pie image";
38 const char kImageData2[] = "cake image";
39 
40 const char kUmaImageLoadSuccessHistogramName[] =
41     "ContentSuggestions.Feed.Image.FetchResult";
42 const char kUmaCacheLoadHistogramName[] =
43     "ContentSuggestions.Feed.Image.LoadFromCacheTime";
44 const char kUmaNetworkLoadHistogramName[] =
45     "ContentSuggestions.Feed.Image.LoadFromNetworkTime";
46 
47 // Keep in sync with DIMENSION_UNKNOWN in
48 // third_party/feed_library/src/main/java/com/
49 //  google/android/libraries/feed/host/imageloader/ImageLoaderApi.java.
50 const int DIMENSION_UNKNOWN = -1;
51 
52 class FakeImageDecoder : public image_fetcher::ImageDecoder {
53  public:
DecodeImage(const std::string & image_data,const gfx::Size & desired_image_frame_size,const image_fetcher::ImageDecodedCallback & callback)54   void DecodeImage(
55       const std::string& image_data,
56       const gfx::Size& desired_image_frame_size,
57       const image_fetcher::ImageDecodedCallback& callback) override {
58     desired_image_frame_size_ = desired_image_frame_size;
59     gfx::Image image;
60     if (valid_ && !image_data.empty()) {
61       ASSERT_EQ(image_data_, image_data);
62       image = gfx::test::CreateImage();
63     }
64     base::SequencedTaskRunnerHandle::Get()->PostTask(
65         FROM_HERE, base::BindRepeating(callback, image));
66   }
SetDecodingValid(bool valid)67   void SetDecodingValid(bool valid) { valid_ = valid; }
SetExpectedData(std::string data)68   void SetExpectedData(std::string data) { image_data_ = data; }
GetDesiredImageFrameSize()69   gfx::Size GetDesiredImageFrameSize() { return desired_image_frame_size_; }
70 
71  private:
72   bool valid_ = true;
73   std::string image_data_;
74   gfx::Size desired_image_frame_size_;
75 };
76 
77 }  // namespace
78 
79 class FeedImageManagerTest : public testing::Test {
80  public:
FeedImageManagerTest()81   FeedImageManagerTest() {}
82 
~FeedImageManagerTest()83   ~FeedImageManagerTest() override {
84     feed_image_manager_.reset();
85     // We need to run until idle after deleting the database, because
86     // ProtoDatabase deletes the actual LevelDB asynchronously.
87     RunUntilIdle();
88   }
89 
SetUp()90   void SetUp() override {
91     ASSERT_TRUE(database_dir_.CreateUniqueTempDir());
92 
93     std::unique_ptr<FeedImageDatabase> image_database =
94         std::make_unique<FeedImageDatabase>(database_dir_.GetPath());
95     image_database_ = image_database.get();
96 
97     shared_factory_ =
98         base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
99             &test_url_loader_factory_);
100 
101     auto decoder = std::make_unique<FakeImageDecoder>();
102     decoder->SetExpectedData(kImageData);
103     fake_image_decoder_ = decoder.get();
104 
105     feed_image_manager_ = std::make_unique<FeedImageManager>(
106         std::make_unique<image_fetcher::ImageFetcherImpl>(std::move(decoder),
107                                                           shared_factory_),
108         std::move(image_database));
109 
110     RunUntilIdle();
111 
112     ASSERT_TRUE(image_database_->IsInitialized());
113   }
114 
RunUntilIdle()115   void RunUntilIdle() { task_environment_.RunUntilIdle(); }
116 
image_database()117   FeedImageDatabase* image_database() { return image_database_; }
118 
feed_image_manager()119   FeedImageManager* feed_image_manager() { return feed_image_manager_.get(); }
120 
garbage_collection_timer()121   base::OneShotTimer& garbage_collection_timer() {
122     return feed_image_manager_->garbage_collection_timer_;
123   }
124 
StartGarbageCollection()125   void StartGarbageCollection() {
126     feed_image_manager_->DoGarbageCollectionIfNeeded();
127   }
128 
StopGarbageCollection()129   void StopGarbageCollection() { feed_image_manager_->StopGarbageCollection(); }
130 
fake_image_decoder()131   FakeImageDecoder* fake_image_decoder() { return fake_image_decoder_; }
132 
test_url_loader_factory()133   network::TestURLLoaderFactory* test_url_loader_factory() {
134     return &test_url_loader_factory_;
135   }
136 
histogram()137   HistogramTester& histogram() { return histogram_; }
138 
139   MOCK_METHOD1(OnImageLoaded, void(const std::string&));
140 
141  private:
142   network::TestURLLoaderFactory test_url_loader_factory_;
143   scoped_refptr<network::SharedURLLoaderFactory> shared_factory_;
144   std::unique_ptr<FeedImageManager> feed_image_manager_;
145   FeedImageDatabase* image_database_;
146   base::ScopedTempDir database_dir_;
147   FakeImageDecoder* fake_image_decoder_;
148   base::test::TaskEnvironment task_environment_;
149   HistogramTester histogram_;
150 
151   DISALLOW_COPY_AND_ASSIGN(FeedImageManagerTest);
152 };
153 
TEST_F(FeedImageManagerTest,FetchEmptyUrlVector)154 TEST_F(FeedImageManagerTest, FetchEmptyUrlVector) {
155   base::MockCallback<ImageFetchedCallback> image_callback;
156 
157   // Make sure an empty image passed to callback.
158   EXPECT_CALL(
159       image_callback,
160       Run(testing::Property(&gfx::Image::IsEmpty, testing::Eq(true)), -1));
161   feed_image_manager()->FetchImage(std::vector<std::string>(),
162                                    DIMENSION_UNKNOWN, DIMENSION_UNKNOWN,
163                                    image_callback.Get());
164 
165   RunUntilIdle();
166 }
167 
TEST_F(FeedImageManagerTest,FetchImageFromCache)168 TEST_F(FeedImageManagerTest, FetchImageFromCache) {
169   // Save the image in the database.
170   image_database()->SaveImage(kImageURL, kImageData);
171   RunUntilIdle();
172 
173   base::MockCallback<ImageFetchedCallback> image_callback;
174   EXPECT_CALL(
175       image_callback,
176       Run(testing::Property(&gfx::Image::IsEmpty, testing::Eq(false)), 0));
177   feed_image_manager()->FetchImage(std::vector<std::string>({kImageURL}), 100,
178                                    200, image_callback.Get());
179 
180   RunUntilIdle();
181 
182   ASSERT_EQ(fake_image_decoder()->GetDesiredImageFrameSize().width(), 100);
183   ASSERT_EQ(fake_image_decoder()->GetDesiredImageFrameSize().height(), 200);
184 }
185 
TEST_F(FeedImageManagerTest,FetchImagePopulatesCache)186 TEST_F(FeedImageManagerTest, FetchImagePopulatesCache) {
187   // Expect the image to be fetched by URL.
188   {
189     test_url_loader_factory()->AddResponse(kImageURL, kImageData);
190     base::MockCallback<ImageFetchedCallback> image_callback;
191     EXPECT_CALL(
192         image_callback,
193         Run(testing::Property(&gfx::Image::IsEmpty, testing::Eq(false)), 0));
194     feed_image_manager()->FetchImage(std::vector<std::string>({kImageURL}),
195                                      DIMENSION_UNKNOWN, DIMENSION_UNKNOWN,
196                                      image_callback.Get());
197 
198     RunUntilIdle();
199   }
200   // Make sure the image data is in the database.
201   {
202     EXPECT_CALL(*this, OnImageLoaded(kImageData));
203     image_database()->LoadImage(
204         kImageURL, base::BindOnce(&FeedImageManagerTest::OnImageLoaded,
205                                   base::Unretained(this)));
206     RunUntilIdle();
207   }
208   // Fetch again. The cache should be populated, no network request is needed.
209   {
210     test_url_loader_factory()->ClearResponses();
211     base::MockCallback<ImageFetchedCallback> image_callback;
212     EXPECT_CALL(
213         image_callback,
214         Run(testing::Property(&gfx::Image::IsEmpty, testing::Eq(false)), 0));
215     feed_image_manager()->FetchImage(std::vector<std::string>({kImageURL}),
216                                      DIMENSION_UNKNOWN, DIMENSION_UNKNOWN,
217                                      image_callback.Get());
218 
219     RunUntilIdle();
220   }
221 }
222 
TEST_F(FeedImageManagerTest,FetchSecondImageIfFirstFailed)223 TEST_F(FeedImageManagerTest, FetchSecondImageIfFirstFailed) {
224   // Expect the image to be fetched by URL.
225   {
226     test_url_loader_factory()->AddResponse(kImageURL, kImageData,
227                                            net::HTTP_NOT_FOUND);
228     test_url_loader_factory()->AddResponse(kImageURL2, kImageData2);
229     base::MockCallback<ImageFetchedCallback> image_callback;
230     EXPECT_CALL(
231         image_callback,
232         Run(testing::Property(&gfx::Image::IsEmpty, testing::Eq(false)), 1));
233     fake_image_decoder()->SetExpectedData(kImageData2);
234     feed_image_manager()->FetchImage(
235         std::vector<std::string>({kImageURL, kImageURL2}), DIMENSION_UNKNOWN,
236         DIMENSION_UNKNOWN, image_callback.Get());
237 
238     RunUntilIdle();
239   }
240   // Make sure the image data is in the database.
241   {
242     EXPECT_CALL(*this, OnImageLoaded(kImageData2));
243     image_database()->LoadImage(
244         kImageURL2, base::BindOnce(&FeedImageManagerTest::OnImageLoaded,
245                                    base::Unretained(this)));
246     RunUntilIdle();
247   }
248 }
249 
TEST_F(FeedImageManagerTest,DecodingErrorWillDeleteCache)250 TEST_F(FeedImageManagerTest, DecodingErrorWillDeleteCache) {
251   // Save the image in the database.
252   image_database()->SaveImage(kImageURL, kImageData);
253   RunUntilIdle();
254   {
255     test_url_loader_factory()->AddResponse(kImageURL, kImageData);
256     // Set decoding always error.
257     fake_image_decoder()->SetDecodingValid(false);
258     base::MockCallback<ImageFetchedCallback> image_callback;
259 
260     EXPECT_CALL(
261         image_callback,
262         Run(testing::Property(&gfx::Image::IsEmpty, testing::Eq(true)), -1));
263     feed_image_manager()->FetchImage(std::vector<std::string>({kImageURL}),
264                                      DIMENSION_UNKNOWN, DIMENSION_UNKNOWN,
265                                      image_callback.Get());
266 
267     RunUntilIdle();
268   }
269   // Make sure the image data was deleted from database.
270   {
271     EXPECT_CALL(*this, OnImageLoaded(std::string()));
272     image_database()->LoadImage(
273         kImageURL, base::BindOnce(&FeedImageManagerTest::OnImageLoaded,
274                                   base::Unretained(this)));
275     RunUntilIdle();
276   }
277 }
278 
TEST_F(FeedImageManagerTest,GarbageCollectionRunOnStart)279 TEST_F(FeedImageManagerTest, GarbageCollectionRunOnStart) {
280   // Garbage Collection is scheduled by default.
281   EXPECT_TRUE(garbage_collection_timer().IsRunning());
282 
283   StopGarbageCollection();
284   EXPECT_FALSE(garbage_collection_timer().IsRunning());
285 
286   // StartGarbageCollection will start the garbage collection, but won't
287   // schedule the next one.
288   StartGarbageCollection();
289   EXPECT_FALSE(garbage_collection_timer().IsRunning());
290 
291   // After finishing the garbage collection cycle, the next garbage collection
292   // will be scheduled.
293   RunUntilIdle();
294   EXPECT_TRUE(garbage_collection_timer().IsRunning());
295 }
296 
TEST_F(FeedImageManagerTest,InvalidUrlHistogramFailure)297 TEST_F(FeedImageManagerTest, InvalidUrlHistogramFailure) {
298   base::MockCallback<ImageFetchedCallback> image_callback;
299   feed_image_manager()->FetchImage(std::vector<std::string>({""}),
300                                    DIMENSION_UNKNOWN, DIMENSION_UNKNOWN,
301                                    image_callback.Get());
302 
303   RunUntilIdle();
304 
305   histogram().ExpectTotalCount(kUmaCacheLoadHistogramName, 0);
306   histogram().ExpectTotalCount(kUmaNetworkLoadHistogramName, 0);
307   histogram().ExpectTotalCount(kUmaImageLoadSuccessHistogramName, 1);
308   histogram().ExpectBucketCount(kUmaImageLoadSuccessHistogramName,
309                                 FeedImageFetchResult::kFailure, 1);
310 }
311 
TEST_F(FeedImageManagerTest,FetchImageFromCachHistogram)312 TEST_F(FeedImageManagerTest, FetchImageFromCachHistogram) {
313   // Save the image in the database.
314   image_database()->SaveImage(kImageURL, kImageData);
315   RunUntilIdle();
316 
317   base::MockCallback<ImageFetchedCallback> image_callback;
318   feed_image_manager()->FetchImage(std::vector<std::string>({kImageURL}),
319                                    DIMENSION_UNKNOWN, DIMENSION_UNKNOWN,
320                                    image_callback.Get());
321 
322   RunUntilIdle();
323 
324   histogram().ExpectTotalCount(kUmaCacheLoadHistogramName, 1);
325   histogram().ExpectTotalCount(kUmaNetworkLoadHistogramName, 0);
326   histogram().ExpectTotalCount(kUmaImageLoadSuccessHistogramName, 1);
327   histogram().ExpectBucketCount(kUmaImageLoadSuccessHistogramName,
328                                 FeedImageFetchResult::kSuccessCached, 1);
329 }
330 
TEST_F(FeedImageManagerTest,FetchImageFromNetworkHistogram)331 TEST_F(FeedImageManagerTest, FetchImageFromNetworkHistogram) {
332   test_url_loader_factory()->AddResponse(kImageURL, kImageData);
333   base::MockCallback<ImageFetchedCallback> image_callback;
334   feed_image_manager()->FetchImage(std::vector<std::string>({kImageURL}),
335                                    DIMENSION_UNKNOWN, DIMENSION_UNKNOWN,
336                                    image_callback.Get());
337 
338   RunUntilIdle();
339 
340   histogram().ExpectTotalCount(kUmaCacheLoadHistogramName, 0);
341   histogram().ExpectTotalCount(kUmaNetworkLoadHistogramName, 1);
342   histogram().ExpectTotalCount(kUmaImageLoadSuccessHistogramName, 1);
343   histogram().ExpectBucketCount(kUmaImageLoadSuccessHistogramName,
344                                 FeedImageFetchResult::kSuccessFetched, 1);
345 }
346 
TEST_F(FeedImageManagerTest,FetchImageFromNetworkEmptyHistogram)347 TEST_F(FeedImageManagerTest, FetchImageFromNetworkEmptyHistogram) {
348   test_url_loader_factory()->AddResponse(kImageURL, "");
349   base::MockCallback<ImageFetchedCallback> image_callback;
350   feed_image_manager()->FetchImage(std::vector<std::string>({kImageURL}),
351                                    DIMENSION_UNKNOWN, DIMENSION_UNKNOWN,
352                                    image_callback.Get());
353 
354   RunUntilIdle();
355 
356   histogram().ExpectTotalCount(kUmaCacheLoadHistogramName, 0);
357   histogram().ExpectTotalCount(kUmaNetworkLoadHistogramName, 0);
358   histogram().ExpectTotalCount(kUmaImageLoadSuccessHistogramName, 1);
359   histogram().ExpectBucketCount(kUmaImageLoadSuccessHistogramName,
360                                 FeedImageFetchResult::kFailure, 1);
361 }
362 
TEST_F(FeedImageManagerTest,NetworkDecodingErrorHistogram)363 TEST_F(FeedImageManagerTest, NetworkDecodingErrorHistogram) {
364   test_url_loader_factory()->AddResponse(kImageURL, kImageData);
365   fake_image_decoder()->SetDecodingValid(false);
366 
367   base::MockCallback<ImageFetchedCallback> image_callback;
368   feed_image_manager()->FetchImage(std::vector<std::string>({kImageURL}),
369                                    DIMENSION_UNKNOWN, DIMENSION_UNKNOWN,
370                                    image_callback.Get());
371 
372   RunUntilIdle();
373 
374   histogram().ExpectTotalCount(kUmaCacheLoadHistogramName, 0);
375   histogram().ExpectTotalCount(kUmaNetworkLoadHistogramName, 0);
376   histogram().ExpectTotalCount(kUmaImageLoadSuccessHistogramName, 1);
377   histogram().ExpectBucketCount(kUmaImageLoadSuccessHistogramName,
378                                 FeedImageFetchResult::kFailure, 1);
379 }
380 
381 }  // namespace feed
382