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