Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(15)

Side by Side Diff: chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate_unittest.cc

Issue 1887253002: Rate limit programmatic update checks for extensions (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698