Chromium Code Reviews| 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 #include <vector> | |
| 10 | |
| 11 #include "base/callback.h" | |
| 12 #include "base/files/file_path.h" | |
| 13 #include "base/memory/ptr_util.h" | |
| 14 #include "base/memory/ref_counted.h" | |
| 15 #include "base/test/simple_test_tick_clock.h" | |
| 16 #include "chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate.h" | |
| 17 #include "chrome/browser/extensions/extension_service.h" | |
| 18 #include "chrome/browser/extensions/extension_service_test_with_install.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(ExtensionDownloader* downloader, | |
| 93 ExtensionDownloaderDelegate* delegate, | |
| 94 scoped_ptr<ManifestFetchData> fetch_data) override { | |
|
Devlin
2016/04/15 17:57:38
std::unique_ptr
asargent_no_longer_on_chrome
2016/04/19 17:56:53
Fixed (here and in extension_downloader_delegate.h
| |
| 95 // Instead of immediately firing callbacks to the delegate in matching | |
| 96 // cases below, we instead post a task since the delegate typically isn't | |
| 97 // expecting a synchronous reply (the real code has to go do at least one | |
| 98 // network request before getting a response, so this is is a reasonable | |
| 99 // expectation by delegates). | |
| 100 for (const std::string& id : fetch_data->extension_ids()) { | |
| 101 auto no_update = no_updates_.find(id); | |
| 102 if (no_update != no_updates_.end()) { | |
| 103 no_updates_.erase(no_update); | |
| 104 base::MessageLoop::current()->task_runner()->PostTask( | |
| 105 FROM_HERE, | |
| 106 base::Bind(&ExtensionDownloaderDelegate::OnExtensionDownloadFailed, | |
| 107 base::Unretained(delegate), id, | |
| 108 ExtensionDownloaderDelegate::NO_UPDATE_AVAILABLE, | |
| 109 ExtensionDownloaderDelegate::PingResult(), | |
| 110 fetch_data->request_ids())); | |
| 111 continue; | |
| 112 } | |
| 113 auto update = updates_.find(id); | |
| 114 if (update != updates_.end()) { | |
| 115 CRXFileInfo info(id, update->second.path, "" /* no hash */); | |
| 116 std::string version = update->second.version; | |
| 117 updates_.erase(update); | |
| 118 base::MessageLoop::current()->task_runner()->PostTask( | |
| 119 FROM_HERE, | |
| 120 base::Bind( | |
| 121 &ExtensionDownloaderDelegate::OnExtensionDownloadFinished, | |
| 122 base::Unretained(delegate), info, | |
| 123 false /* file_ownership_passed */, GURL(), version, | |
| 124 ExtensionDownloaderDelegate::PingResult(), | |
| 125 fetch_data->request_ids(), | |
| 126 ExtensionDownloaderDelegate::InstallCallback())); | |
| 127 continue; | |
| 128 } | |
| 129 ADD_FAILURE() << "Unexpected extension id " << id; | |
| 130 } | |
| 131 } | |
| 132 | |
| 133 private: | |
| 134 // Simple holder for the data passed in AddUpdateResponse calls. | |
| 135 struct DownloadFinishedArgs { | |
| 136 base::FilePath path; | |
| 137 std::string version; | |
| 138 }; | |
| 139 | |
| 140 // These keep track of what response we should give for update checks, keyed | |
| 141 // by extension id. A given extension id should only appear in one or the | |
| 142 // other. | |
| 143 std::set<std::string> no_updates_; | |
| 144 std::map<std::string, DownloadFinishedArgs> updates_; | |
| 145 | |
| 146 DISALLOW_COPY_AND_ASSIGN(DownloaderTestDelegate); | |
| 147 }; | |
| 148 | |
| 149 // Helper to let test code wait for and return an update check result. | |
| 150 class UpdateCheckResultCatcher { | |
| 151 public: | |
| 152 UpdateCheckResultCatcher() {} | |
| 153 | |
| 154 void OnResult(const RuntimeAPIDelegate::UpdateCheckResult& result) { | |
| 155 EXPECT_EQ(nullptr, result_.get()); | |
| 156 result_.reset(new RuntimeAPIDelegate::UpdateCheckResult( | |
| 157 result.success, result.response, result.version)); | |
| 158 if (run_loop_) | |
| 159 run_loop_->Quit(); | |
| 160 } | |
| 161 | |
| 162 std::unique_ptr<RuntimeAPIDelegate::UpdateCheckResult> WaitForResult() { | |
| 163 if (!result_) { | |
| 164 run_loop_.reset(new base::RunLoop); | |
| 165 run_loop_->Run(); | |
| 166 } | |
| 167 return std::move(result_); | |
| 168 } | |
| 169 | |
| 170 private: | |
| 171 std::unique_ptr<RuntimeAPIDelegate::UpdateCheckResult> result_; | |
| 172 std::unique_ptr<base::RunLoop> run_loop_; | |
| 173 | |
| 174 DISALLOW_COPY_AND_ASSIGN(UpdateCheckResultCatcher); | |
| 175 }; | |
| 176 | |
| 177 class ChromeRuntimeAPIDelegateTest : public ExtensionServiceTestWithInstall { | |
| 178 public: | |
| 179 ChromeRuntimeAPIDelegateTest() {} | |
| 180 | |
| 181 void SetUp() override { | |
| 182 ExtensionServiceTestWithInstall::SetUp(); | |
| 183 ExtensionDownloader::set_test_delegate(&downloader_test_delegate_); | |
| 184 ChromeRuntimeAPIDelegate::set_tick_clock_for_tests(&clock_); | |
| 185 | |
| 186 InitializeExtensionServiceWithUpdater(); | |
| 187 runtime_delegate_.reset(new ChromeRuntimeAPIDelegate(browser_context())); | |
| 188 service()->set_install_updates_when_idle_for_test(true); | |
| 189 | |
| 190 EventRouterFactory::GetInstance()->SetTestingFactory( | |
| 191 browser_context(), &TestEventRouterFactoryFunction); | |
| 192 } | |
| 193 | |
| 194 void TearDown() override { | |
| 195 ExtensionDownloader::set_test_delegate(nullptr); | |
| 196 ChromeRuntimeAPIDelegate::set_tick_clock_for_tests(nullptr); | |
| 197 ExtensionServiceTestWithInstall::TearDown(); | |
| 198 } | |
| 199 | |
| 200 // Uses runtime_delegate_ to runs an update check for |id|, expecting | |
|
Devlin
2016/04/15 17:57:37
nit: to run
asargent_no_longer_on_chrome
2016/04/19 17:56:53
Done.
| |
| 201 // |expected_response| and (if an update was available) |expected_version|. | |
| 202 // The |expected_response| should be one of 'throttled', 'no_update', or | |
| 203 // 'update_available'. | |
| 204 void DoUpdateCheck(const std::string& id, | |
| 205 const std::string& expected_response, | |
| 206 const std::string& expected_version) { | |
| 207 UpdateCheckResultCatcher catcher; | |
| 208 EXPECT_TRUE(runtime_delegate_->CheckForUpdates( | |
| 209 id, base::Bind(&UpdateCheckResultCatcher::OnResult, | |
| 210 base::Unretained(&catcher)))); | |
| 211 std::unique_ptr<RuntimeAPIDelegate::UpdateCheckResult> result = | |
| 212 catcher.WaitForResult(); | |
| 213 ASSERT_NE(nullptr, result.get()); | |
| 214 EXPECT_TRUE(result->success); | |
| 215 EXPECT_EQ(expected_response, result->response); | |
| 216 EXPECT_EQ(expected_version, result->version); | |
| 217 } | |
| 218 | |
| 219 protected: | |
| 220 // A clock we pass to the code used for throttling, so that we can manually | |
| 221 // increment time to test various throttling scenarios. | |
| 222 base::SimpleTestTickClock clock_; | |
| 223 | |
| 224 // Used for intercepting update check requests and possibly returning fake | |
| 225 // download results. | |
| 226 DownloaderTestDelegate downloader_test_delegate_; | |
| 227 | |
| 228 // The object whose behavior we're testing. | |
| 229 scoped_ptr<RuntimeAPIDelegate> runtime_delegate_; | |
|
Devlin
2016/04/15 17:57:37
unique_ptr
asargent_no_longer_on_chrome
2016/04/19 17:56:53
Done.
| |
| 230 | |
| 231 private: | |
| 232 DISALLOW_COPY_AND_ASSIGN(ChromeRuntimeAPIDelegateTest); | |
| 233 }; | |
| 234 | |
| 235 TEST_F(ChromeRuntimeAPIDelegateTest, RequestUpdateCheck) { | |
| 236 base::FilePath v1_path = data_dir().AppendASCII("autoupdate/v1.crx"); | |
| 237 base::FilePath v2_path = data_dir().AppendASCII("autoupdate/v2.crx"); | |
| 238 | |
| 239 // Start by installing version 1. | |
| 240 scoped_refptr<const Extension> v1(InstallCRX(v1_path, INSTALL_NEW)); | |
| 241 std::string id = v1->id(); | |
| 242 | |
| 243 // Make it look like our test extension listens for the | |
| 244 // runtime.onUpdateAvailable event, so that it won't be updated immediately | |
| 245 // when the ExtensionUpdater hands the new version to the ExtensionService. | |
|
Devlin
2016/04/15 17:57:37
For my own edification, where's this logic?
asargent_no_longer_on_chrome
2016/04/19 17:56:53
It's in ExtensionService::ShouldDelayExtensionUpda
| |
| 246 TestEventRouter* event_router = | |
| 247 static_cast<TestEventRouter*>(EventRouter::Get(browser_context())); | |
| 248 event_router->AddFakeListener(id, "runtime.onUpdateAvailable"); | |
| 249 | |
| 250 // Run an update check that should get a "no_update" response. | |
| 251 downloader_test_delegate_.AddNoUpdateResponse(id); | |
| 252 DoUpdateCheck(id, "no_update", ""); | |
| 253 | |
| 254 // Check again after a short delay - we should be throttled because | |
| 255 // not enough time has passed. | |
| 256 clock_.Advance(base::TimeDelta::FromMinutes(15)); | |
| 257 downloader_test_delegate_.AddNoUpdateResponse(id); | |
| 258 DoUpdateCheck(id, "throttled", ""); | |
| 259 | |
| 260 // Now simulate checking a few times at a 6 hour interval - none of these | |
| 261 // should be throttled. | |
| 262 for (int i = 0; i < 5; i++) { | |
| 263 clock_.Advance(base::TimeDelta::FromHours(6)); | |
| 264 downloader_test_delegate_.AddNoUpdateResponse(id); | |
| 265 DoUpdateCheck(id, "no_update", ""); | |
| 266 } | |
| 267 | |
| 268 // Run an update check that should get an "update_available" response. This | |
| 269 // actually causes the new version to be downloaded/unpacked, but the install | |
| 270 // will not complete until we reload the extension. | |
| 271 clock_.Advance(base::TimeDelta::FromDays(1)); | |
| 272 downloader_test_delegate_.AddUpdateResponse(id, v2_path, "2.0"); | |
| 273 DoUpdateCheck(id, "update_available", "2.0"); | |
| 274 | |
| 275 // Call again after short delay - it should be throttled instead of getting | |
| 276 // another "update_available" response. | |
| 277 clock_.Advance(base::TimeDelta::FromMinutes(30)); | |
| 278 downloader_test_delegate_.AddUpdateResponse(id, v2_path, "2.0"); | |
| 279 DoUpdateCheck(id, "throttled", ""); | |
| 280 | |
| 281 // Reload the extension, causing the delayed update to v2 to happen, then do | |
| 282 // another update check - we should get a no_update instead of throttled. | |
| 283 service()->ReloadExtension(id); | |
| 284 const Extension* current = | |
| 285 ExtensionRegistry::Get(browser_context())->GetInstalledExtension(id); | |
| 286 ASSERT_NE(nullptr, current); | |
| 287 EXPECT_EQ("2.0", current->VersionString()); | |
| 288 clock_.Advance(base::TimeDelta::FromSeconds(10)); | |
| 289 downloader_test_delegate_.AddNoUpdateResponse(id); | |
| 290 DoUpdateCheck(id, "no_update", ""); | |
| 291 | |
| 292 // Check again after short delay; we should be throttled. | |
| 293 clock_.Advance(base::TimeDelta::FromMinutes(5)); | |
| 294 DoUpdateCheck(id, "throttled", ""); | |
| 295 | |
| 296 // Call again after a longer delay, we should should be unthrottled. | |
| 297 clock_.Advance(base::TimeDelta::FromHours(8)); | |
| 298 downloader_test_delegate_.AddNoUpdateResponse(id); | |
| 299 DoUpdateCheck(id, "no_update", ""); | |
| 300 } | |
| 301 | |
| 302 } // namespace | |
| 303 | |
| 304 } // namespace extensions | |
| OLD | NEW |