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