| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <utility> |
| #include <vector> |
| |
| #include "base/callback.h" |
| #include "chrome/browser/apps/app_service/app_service_proxy.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/gfx/image/image_skia_rep.h" |
| |
| class AppServiceProxyTest : public testing::Test { |
| protected: |
| using UniqueReleaser = std::unique_ptr<apps::IconLoader::Releaser>; |
| |
| class FakeIconLoader : public apps::IconLoader { |
| public: |
| void FlushPendingCallbacks() { |
| for (auto& callback : pending_callbacks_) { |
| auto iv = apps::mojom::IconValue::New(); |
| iv->icon_compression = apps::mojom::IconCompression::kUncompressed; |
| iv->uncompressed = |
| gfx::ImageSkia(gfx::ImageSkiaRep(gfx::Size(1, 1), 1.0f)); |
| iv->is_placeholder_icon = false; |
| |
| std::move(callback).Run(std::move(iv)); |
| num_inner_finished_callbacks_++; |
| } |
| pending_callbacks_.clear(); |
| } |
| |
| int NumInnerFinishedCallbacks() { return num_inner_finished_callbacks_; } |
| int NumPendingCallbacks() { return pending_callbacks_.size(); } |
| |
| private: |
| apps::mojom::IconKeyPtr GetIconKey(const std::string& app_id) override { |
| return apps::mojom::IconKey::New(0, 0, 0); |
| } |
| |
| std::unique_ptr<Releaser> LoadIconFromIconKey( |
| apps::mojom::AppType app_type, |
| const std::string& app_id, |
| apps::mojom::IconKeyPtr icon_key, |
| apps::mojom::IconCompression icon_compression, |
| int32_t size_hint_in_dip, |
| bool allow_placeholder_icon, |
| apps::mojom::Publisher::LoadIconCallback callback) override { |
| if (icon_compression == apps::mojom::IconCompression::kUncompressed) { |
| pending_callbacks_.push_back(std::move(callback)); |
| } |
| return nullptr; |
| } |
| |
| int num_inner_finished_callbacks_ = 0; |
| std::vector<apps::mojom::Publisher::LoadIconCallback> pending_callbacks_; |
| }; |
| |
| UniqueReleaser LoadIcon(apps::IconLoader* loader, const std::string& app_id) { |
| static constexpr auto app_type = apps::mojom::AppType::kWeb; |
| static constexpr auto icon_compression = |
| apps::mojom::IconCompression::kUncompressed; |
| static constexpr int32_t size_hint_in_dip = 1; |
| static bool allow_placeholder_icon = false; |
| |
| return loader->LoadIcon(app_type, app_id, icon_compression, |
| size_hint_in_dip, allow_placeholder_icon, |
| base::BindOnce(&AppServiceProxyTest::OnLoadIcon, |
| base::Unretained(this))); |
| } |
| |
| void OverrideAppServiceProxyInnerIconLoader(apps::AppServiceProxy* proxy, |
| apps::IconLoader* icon_loader) { |
| proxy->OverrideInnerIconLoaderForTesting(icon_loader); |
| } |
| |
| void OnLoadIcon(apps::mojom::IconValuePtr icon_value) { |
| num_outer_finished_callbacks_++; |
| } |
| |
| int NumOuterFinishedCallbacks() { return num_outer_finished_callbacks_; } |
| |
| int num_outer_finished_callbacks_ = 0; |
| }; |
| |
| TEST_F(AppServiceProxyTest, IconCache) { |
| // This is mostly a sanity check. For an isolated, comprehensive unit test of |
| // the IconCache code, see icon_cache_unittest.cc. |
| // |
| // This tests an AppServiceProxy as a 'black box', which uses an |
| // IconCache but also other IconLoader filters, such as an IconCoalescer. |
| |
| apps::AppServiceProxy proxy(nullptr); |
| FakeIconLoader fake; |
| OverrideAppServiceProxyInnerIconLoader(&proxy, &fake); |
| |
| // The next LoadIcon call should be a cache miss. |
| UniqueReleaser c0 = LoadIcon(&proxy, "cromulent"); |
| EXPECT_EQ(1, fake.NumPendingCallbacks()); |
| EXPECT_EQ(0, fake.NumInnerFinishedCallbacks()); |
| EXPECT_EQ(0, NumOuterFinishedCallbacks()); |
| |
| // After a cache miss, manually trigger the inner callback. |
| fake.FlushPendingCallbacks(); |
| EXPECT_EQ(0, fake.NumPendingCallbacks()); |
| EXPECT_EQ(1, fake.NumInnerFinishedCallbacks()); |
| EXPECT_EQ(1, NumOuterFinishedCallbacks()); |
| |
| // The next LoadIcon call should be a cache hit. |
| UniqueReleaser c1 = LoadIcon(&proxy, "cromulent"); |
| EXPECT_EQ(0, fake.NumPendingCallbacks()); |
| EXPECT_EQ(1, fake.NumInnerFinishedCallbacks()); |
| EXPECT_EQ(2, NumOuterFinishedCallbacks()); |
| |
| // Destroy the IconLoader::Releaser's, clearing the cache. |
| c0.reset(); |
| c1.reset(); |
| |
| // The next LoadIcon call should be a cache miss. |
| UniqueReleaser c2 = LoadIcon(&proxy, "cromulent"); |
| EXPECT_EQ(1, fake.NumPendingCallbacks()); |
| EXPECT_EQ(1, fake.NumInnerFinishedCallbacks()); |
| EXPECT_EQ(2, NumOuterFinishedCallbacks()); |
| |
| // After a cache miss, manually trigger the inner callback. |
| fake.FlushPendingCallbacks(); |
| EXPECT_EQ(0, fake.NumPendingCallbacks()); |
| EXPECT_EQ(2, fake.NumInnerFinishedCallbacks()); |
| EXPECT_EQ(3, NumOuterFinishedCallbacks()); |
| } |
| |
| TEST_F(AppServiceProxyTest, IconCoalescer) { |
| // This is mostly a sanity check. For an isolated, comprehensive unit test of |
| // the IconCoalescer code, see icon_coalescer_unittest.cc. |
| // |
| // This tests an AppServiceProxy as a 'black box', which uses an |
| // IconCoalescer but also other IconLoader filters, such as an IconCache. |
| |
| apps::AppServiceProxy proxy(nullptr); |
| FakeIconLoader fake; |
| OverrideAppServiceProxyInnerIconLoader(&proxy, &fake); |
| |
| // Issue 4 LoadIcon requests, 2 after de-duplication. |
| UniqueReleaser a0 = LoadIcon(&proxy, "avocet"); |
| UniqueReleaser a1 = LoadIcon(&proxy, "avocet"); |
| UniqueReleaser b2 = LoadIcon(&proxy, "brolga"); |
| UniqueReleaser a3 = LoadIcon(&proxy, "avocet"); |
| EXPECT_EQ(2, fake.NumPendingCallbacks()); |
| EXPECT_EQ(0, fake.NumInnerFinishedCallbacks()); |
| EXPECT_EQ(0, NumOuterFinishedCallbacks()); |
| |
| // Resolve their responses. |
| fake.FlushPendingCallbacks(); |
| EXPECT_EQ(0, fake.NumPendingCallbacks()); |
| EXPECT_EQ(2, fake.NumInnerFinishedCallbacks()); |
| EXPECT_EQ(4, NumOuterFinishedCallbacks()); |
| |
| // Issue another request, that triggers neither IconCache nor IconCoalescer. |
| UniqueReleaser c4 = LoadIcon(&proxy, "curlew"); |
| EXPECT_EQ(1, fake.NumPendingCallbacks()); |
| EXPECT_EQ(2, fake.NumInnerFinishedCallbacks()); |
| EXPECT_EQ(4, NumOuterFinishedCallbacks()); |
| |
| // Destroying the IconLoader::Releaser shouldn't affect the fact that there's |
| // an in-flight "curlew" request to the FakeIconLoader. |
| c4.reset(); |
| EXPECT_EQ(1, fake.NumPendingCallbacks()); |
| EXPECT_EQ(2, fake.NumInnerFinishedCallbacks()); |
| EXPECT_EQ(4, NumOuterFinishedCallbacks()); |
| |
| // Issuing another "curlew" request should coalesce with the in-flight one. |
| UniqueReleaser c5 = LoadIcon(&proxy, "curlew"); |
| EXPECT_EQ(1, fake.NumPendingCallbacks()); |
| EXPECT_EQ(2, fake.NumInnerFinishedCallbacks()); |
| EXPECT_EQ(4, NumOuterFinishedCallbacks()); |
| |
| // Resolving the in-flight request to the inner IconLoader, |fake|, should |
| // resolve the two coalesced requests to the outer IconLoader, |proxy|. |
| fake.FlushPendingCallbacks(); |
| EXPECT_EQ(0, fake.NumPendingCallbacks()); |
| EXPECT_EQ(3, fake.NumInnerFinishedCallbacks()); |
| EXPECT_EQ(6, NumOuterFinishedCallbacks()); |
| } |