| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2016 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 <deque> |
| 6 #include <memory> |
| 7 #include <set> |
| 8 #include <utility> |
| 9 |
| 10 #include "base/callback.h" |
| 11 #include "base/files/file_path.h" |
| 12 #include "base/memory/ptr_util.h" |
| 13 #include "base/memory/ref_counted.h" |
| 14 #include "base/test/simple_test_tick_clock.h" |
| 15 #include "chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate.h" |
| 16 #include "chrome/browser/extensions/extension_service.h" |
| 17 #include "chrome/browser/extensions/extension_service_test_with_install.h" |
| 18 #include "chrome/browser/extensions/test_extension_system.h" |
| 19 #include "chrome/browser/extensions/update_install_gate.h" |
| 20 #include "chrome/browser/extensions/updater/extension_updater.h" |
| 21 #include "extensions/browser/event_router.h" |
| 22 #include "extensions/browser/event_router_factory.h" |
| 23 #include "extensions/browser/extension_prefs.h" |
| 24 #include "extensions/browser/extension_registry.h" |
| 25 #include "extensions/browser/updater/extension_downloader.h" |
| 26 #include "extensions/browser/updater/extension_downloader_test_delegate.h" |
| 27 #include "testing/gtest/include/gtest/gtest.h" |
| 28 |
| 29 namespace extensions { |
| 30 |
| 31 namespace { |
| 32 |
| 33 // A fake EventRouter that lets us pretend an extension has a listener |
| 34 // registered for named events. |
| 35 class TestEventRouter : public EventRouter { |
| 36 public: |
| 37 explicit TestEventRouter(content::BrowserContext* context) |
| 38 : EventRouter(context, ExtensionPrefs::Get(context)) {} |
| 39 ~TestEventRouter() override {} |
| 40 |
| 41 // An entry in our fake event registry. |
| 42 using Entry = std::pair<std::string, std::string>; |
| 43 |
| 44 bool ExtensionHasEventListener(const std::string& extension_id, |
| 45 const std::string& event_name) override { |
| 46 return fake_registry_.find(Entry(extension_id, event_name)) != |
| 47 fake_registry_.end(); |
| 48 } |
| 49 |
| 50 // Pretend that |extension_id| is listening for |event_name|. |
| 51 void AddFakeListener(const std::string& extension_id, |
| 52 const std::string& event_name) { |
| 53 fake_registry_.insert(Entry(extension_id, event_name)); |
| 54 } |
| 55 |
| 56 private: |
| 57 std::set<Entry> fake_registry_; |
| 58 |
| 59 DISALLOW_COPY_AND_ASSIGN(TestEventRouter); |
| 60 }; |
| 61 |
| 62 std::unique_ptr<KeyedService> TestEventRouterFactoryFunction( |
| 63 content::BrowserContext* context) { |
| 64 return base::WrapUnique(new TestEventRouter(context)); |
| 65 } |
| 66 |
| 67 // This class lets us intercept extension update checks and respond as if |
| 68 // either no update was found, or one was (and it was downloaded). |
| 69 class DownloaderTestDelegate : public ExtensionDownloaderTestDelegate { |
| 70 public: |
| 71 DownloaderTestDelegate() {} |
| 72 |
| 73 // On the next update check for extension |id|, we'll respond that no update |
| 74 // is available. |
| 75 void AddNoUpdateResponse(const std::string& id) { |
| 76 no_updates_.insert(id); |
| 77 if (updates_.find(id) != updates_.end()) |
| 78 updates_.erase(id); |
| 79 } |
| 80 |
| 81 // On the next update check for extension |id|, pretend that an update to |
| 82 // version |version| has been downloaded to |path|. |
| 83 void AddUpdateResponse(const std::string& id, |
| 84 const base::FilePath& path, |
| 85 const std::string& version) { |
| 86 if (no_updates_.find(id) != no_updates_.end()) |
| 87 no_updates_.erase(id); |
| 88 DownloadFinishedArgs args; |
| 89 args.path = path; |
| 90 args.version = version; |
| 91 updates_[id] = std::move(args); |
| 92 } |
| 93 |
| 94 void StartUpdateCheck( |
| 95 ExtensionDownloader* downloader, |
| 96 ExtensionDownloaderDelegate* delegate, |
| 97 std::unique_ptr<ManifestFetchData> fetch_data) override { |
| 98 // Instead of immediately firing callbacks to the delegate in matching |
| 99 // cases below, we instead post a task since the delegate typically isn't |
| 100 // expecting a synchronous reply (the real code has to go do at least one |
| 101 // network request before getting a response, so this is is a reasonable |
| 102 // expectation by delegates). |
| 103 for (const std::string& id : fetch_data->extension_ids()) { |
| 104 auto no_update = no_updates_.find(id); |
| 105 if (no_update != no_updates_.end()) { |
| 106 no_updates_.erase(no_update); |
| 107 base::MessageLoop::current()->task_runner()->PostTask( |
| 108 FROM_HERE, |
| 109 base::Bind(&ExtensionDownloaderDelegate::OnExtensionDownloadFailed, |
| 110 base::Unretained(delegate), id, |
| 111 ExtensionDownloaderDelegate::NO_UPDATE_AVAILABLE, |
| 112 ExtensionDownloaderDelegate::PingResult(), |
| 113 fetch_data->request_ids())); |
| 114 continue; |
| 115 } |
| 116 auto update = updates_.find(id); |
| 117 if (update != updates_.end()) { |
| 118 CRXFileInfo info(id, update->second.path, "" /* no hash */); |
| 119 std::string version = update->second.version; |
| 120 updates_.erase(update); |
| 121 base::MessageLoop::current()->task_runner()->PostTask( |
| 122 FROM_HERE, |
| 123 base::Bind( |
| 124 &ExtensionDownloaderDelegate::OnExtensionDownloadFinished, |
| 125 base::Unretained(delegate), info, |
| 126 false /* file_ownership_passed */, GURL(), version, |
| 127 ExtensionDownloaderDelegate::PingResult(), |
| 128 fetch_data->request_ids(), |
| 129 ExtensionDownloaderDelegate::InstallCallback())); |
| 130 continue; |
| 131 } |
| 132 ADD_FAILURE() << "Unexpected extension id " << id; |
| 133 } |
| 134 } |
| 135 |
| 136 private: |
| 137 // Simple holder for the data passed in AddUpdateResponse calls. |
| 138 struct DownloadFinishedArgs { |
| 139 base::FilePath path; |
| 140 std::string version; |
| 141 }; |
| 142 |
| 143 // These keep track of what response we should give for update checks, keyed |
| 144 // by extension id. A given extension id should only appear in one or the |
| 145 // other. |
| 146 std::set<std::string> no_updates_; |
| 147 std::map<std::string, DownloadFinishedArgs> updates_; |
| 148 |
| 149 DISALLOW_COPY_AND_ASSIGN(DownloaderTestDelegate); |
| 150 }; |
| 151 |
| 152 // Helper to let test code wait for and return an update check result. |
| 153 class UpdateCheckResultCatcher { |
| 154 public: |
| 155 UpdateCheckResultCatcher() {} |
| 156 |
| 157 void OnResult(const RuntimeAPIDelegate::UpdateCheckResult& result) { |
| 158 EXPECT_EQ(nullptr, result_.get()); |
| 159 result_.reset(new RuntimeAPIDelegate::UpdateCheckResult( |
| 160 result.success, result.response, result.version)); |
| 161 if (run_loop_) |
| 162 run_loop_->Quit(); |
| 163 } |
| 164 |
| 165 std::unique_ptr<RuntimeAPIDelegate::UpdateCheckResult> WaitForResult() { |
| 166 if (!result_) { |
| 167 run_loop_.reset(new base::RunLoop); |
| 168 run_loop_->Run(); |
| 169 } |
| 170 return std::move(result_); |
| 171 } |
| 172 |
| 173 private: |
| 174 std::unique_ptr<RuntimeAPIDelegate::UpdateCheckResult> result_; |
| 175 std::unique_ptr<base::RunLoop> run_loop_; |
| 176 |
| 177 DISALLOW_COPY_AND_ASSIGN(UpdateCheckResultCatcher); |
| 178 }; |
| 179 |
| 180 class ChromeRuntimeAPIDelegateTest : public ExtensionServiceTestWithInstall { |
| 181 public: |
| 182 ChromeRuntimeAPIDelegateTest() {} |
| 183 |
| 184 void SetUp() override { |
| 185 ExtensionServiceTestWithInstall::SetUp(); |
| 186 ExtensionDownloader::set_test_delegate(&downloader_test_delegate_); |
| 187 ChromeRuntimeAPIDelegate::set_tick_clock_for_tests(&clock_); |
| 188 |
| 189 InitializeExtensionServiceWithUpdater(); |
| 190 runtime_delegate_.reset(new ChromeRuntimeAPIDelegate(browser_context())); |
| 191 service()->updater()->SetExtensionCacheForTesting(nullptr); |
| 192 EventRouterFactory::GetInstance()->SetTestingFactory( |
| 193 browser_context(), &TestEventRouterFactoryFunction); |
| 194 |
| 195 // Setup the ExtensionService so that extension updates won't complete |
| 196 // installation until the extension is idle. |
| 197 update_install_gate_.reset(new UpdateInstallGate(service())); |
| 198 service()->RegisterInstallGate(ExtensionPrefs::DELAY_REASON_WAIT_FOR_IDLE, |
| 199 update_install_gate_.get()); |
| 200 static_cast<TestExtensionSystem*>(ExtensionSystem::Get(browser_context())) |
| 201 ->SetReady(); |
| 202 } |
| 203 |
| 204 void TearDown() override { |
| 205 ExtensionDownloader::set_test_delegate(nullptr); |
| 206 ChromeRuntimeAPIDelegate::set_tick_clock_for_tests(nullptr); |
| 207 ExtensionServiceTestWithInstall::TearDown(); |
| 208 } |
| 209 |
| 210 // Uses runtime_delegate_ to run an update check for |id|, expecting |
| 211 // |expected_response| and (if an update was available) |expected_version|. |
| 212 // The |expected_response| should be one of 'throttled', 'no_update', or |
| 213 // 'update_available'. |
| 214 void DoUpdateCheck(const std::string& id, |
| 215 const std::string& expected_response, |
| 216 const std::string& expected_version) { |
| 217 UpdateCheckResultCatcher catcher; |
| 218 EXPECT_TRUE(runtime_delegate_->CheckForUpdates( |
| 219 id, base::Bind(&UpdateCheckResultCatcher::OnResult, |
| 220 base::Unretained(&catcher)))); |
| 221 std::unique_ptr<RuntimeAPIDelegate::UpdateCheckResult> result = |
| 222 catcher.WaitForResult(); |
| 223 ASSERT_NE(nullptr, result.get()); |
| 224 EXPECT_TRUE(result->success); |
| 225 EXPECT_EQ(expected_response, result->response); |
| 226 EXPECT_EQ(expected_version, result->version); |
| 227 } |
| 228 |
| 229 protected: |
| 230 // A clock we pass to the code used for throttling, so that we can manually |
| 231 // increment time to test various throttling scenarios. |
| 232 base::SimpleTestTickClock clock_; |
| 233 |
| 234 // Used for intercepting update check requests and possibly returning fake |
| 235 // download results. |
| 236 DownloaderTestDelegate downloader_test_delegate_; |
| 237 |
| 238 // The object whose behavior we're testing. |
| 239 std::unique_ptr<RuntimeAPIDelegate> runtime_delegate_; |
| 240 |
| 241 // For preventing extensions from being updated immediately. |
| 242 std::unique_ptr<UpdateInstallGate> update_install_gate_; |
| 243 |
| 244 private: |
| 245 DISALLOW_COPY_AND_ASSIGN(ChromeRuntimeAPIDelegateTest); |
| 246 }; |
| 247 |
| 248 TEST_F(ChromeRuntimeAPIDelegateTest, RequestUpdateCheck) { |
| 249 base::FilePath v1_path = data_dir().AppendASCII("autoupdate/v1.crx"); |
| 250 base::FilePath v2_path = data_dir().AppendASCII("autoupdate/v2.crx"); |
| 251 |
| 252 // Start by installing version 1. |
| 253 scoped_refptr<const Extension> v1(InstallCRX(v1_path, INSTALL_NEW)); |
| 254 std::string id = v1->id(); |
| 255 |
| 256 // Make it look like our test extension listens for the |
| 257 // runtime.onUpdateAvailable event, so that it won't be updated immediately |
| 258 // when the ExtensionUpdater hands the new version to the ExtensionService. |
| 259 TestEventRouter* event_router = |
| 260 static_cast<TestEventRouter*>(EventRouter::Get(browser_context())); |
| 261 event_router->AddFakeListener(id, "runtime.onUpdateAvailable"); |
| 262 |
| 263 // Run an update check that should get a "no_update" response. |
| 264 downloader_test_delegate_.AddNoUpdateResponse(id); |
| 265 DoUpdateCheck(id, "no_update", ""); |
| 266 |
| 267 // Check again after a short delay - we should be throttled because |
| 268 // not enough time has passed. |
| 269 clock_.Advance(base::TimeDelta::FromMinutes(15)); |
| 270 downloader_test_delegate_.AddNoUpdateResponse(id); |
| 271 DoUpdateCheck(id, "throttled", ""); |
| 272 |
| 273 // Now simulate checking a few times at a 6 hour interval - none of these |
| 274 // should be throttled. |
| 275 for (int i = 0; i < 5; i++) { |
| 276 clock_.Advance(base::TimeDelta::FromHours(6)); |
| 277 downloader_test_delegate_.AddNoUpdateResponse(id); |
| 278 DoUpdateCheck(id, "no_update", ""); |
| 279 } |
| 280 |
| 281 // Run an update check that should get an "update_available" response. This |
| 282 // actually causes the new version to be downloaded/unpacked, but the install |
| 283 // will not complete until we reload the extension. |
| 284 clock_.Advance(base::TimeDelta::FromDays(1)); |
| 285 downloader_test_delegate_.AddUpdateResponse(id, v2_path, "2.0"); |
| 286 DoUpdateCheck(id, "update_available", "2.0"); |
| 287 |
| 288 // Call again after short delay - it should be throttled instead of getting |
| 289 // another "update_available" response. |
| 290 clock_.Advance(base::TimeDelta::FromMinutes(30)); |
| 291 downloader_test_delegate_.AddUpdateResponse(id, v2_path, "2.0"); |
| 292 DoUpdateCheck(id, "throttled", ""); |
| 293 |
| 294 // Reload the extension, causing the delayed update to v2 to happen, then do |
| 295 // another update check - we should get a no_update instead of throttled. |
| 296 service()->ReloadExtension(id); |
| 297 const Extension* current = |
| 298 ExtensionRegistry::Get(browser_context())->GetInstalledExtension(id); |
| 299 ASSERT_NE(nullptr, current); |
| 300 EXPECT_EQ("2.0", current->VersionString()); |
| 301 clock_.Advance(base::TimeDelta::FromSeconds(10)); |
| 302 downloader_test_delegate_.AddNoUpdateResponse(id); |
| 303 DoUpdateCheck(id, "no_update", ""); |
| 304 |
| 305 // Check again after short delay; we should be throttled. |
| 306 clock_.Advance(base::TimeDelta::FromMinutes(5)); |
| 307 DoUpdateCheck(id, "throttled", ""); |
| 308 |
| 309 // Call again after a longer delay, we should should be unthrottled. |
| 310 clock_.Advance(base::TimeDelta::FromHours(8)); |
| 311 downloader_test_delegate_.AddNoUpdateResponse(id); |
| 312 DoUpdateCheck(id, "no_update", ""); |
| 313 } |
| 314 |
| 315 } // namespace |
| 316 |
| 317 } // namespace extensions |
| OLD | NEW |