component_updater_service_unittest.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
1// Copyright (c) 2012 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 "chrome/browser/component_updater/component_updater_service.h"
6
7#include "base/compiler_specific.h"
8#include "base/file_path.h"
9#include "base/file_util.h"
10#include "base/memory/scoped_vector.h"
11#include "base/message_loop.h"
12#include "base/path_service.h"
13#include "base/values.h"
14#include "chrome/browser/component_updater/component_updater_interceptor.h"
15#include "chrome/common/chrome_notification_types.h"
16#include "chrome/common/chrome_paths.h"
17#include "content/public/browser/notification_observer.h"
18#include "content/public/browser/notification_service.h"
19#include "content/public/test/test_browser_thread.h"
20#include "content/public/test/test_notification_tracker.h"
21#include "googleurl/src/gurl.h"
22#include "libxml/globals.h"
23#include "net/url_request/url_fetcher.h"
24#include "net/url_request/url_request_test_util.h"
25#include "testing/gtest/include/gtest/gtest.h"
26
27using content::BrowserThread;
28using content::TestNotificationTracker;
29
30namespace {
31// Overrides some of the component updater behaviors so it is easier to test
32// and loops faster. In actual usage it takes hours do to a full cycle.
33class TestConfigurator : public ComponentUpdateService::Configurator {
34 public:
35  TestConfigurator() : times_(1) {
36  }
37
38  virtual int InitialDelay() OVERRIDE { return 0; }
39
40  virtual int NextCheckDelay() OVERRIDE {
41    // This is called when a new full cycle of checking for updates is going
42    // to happen. In test we normally only test one cycle so it is a good
43    // time to break from the test messageloop Run() method so the test can
44    // finish.
45    if (--times_ > 0)
46      return 1;
47
48    MessageLoop::current()->Quit();
49    return 0;
50  }
51
52  virtual int StepDelay() OVERRIDE {
53    return 0;
54  }
55
56  virtual int MinimumReCheckWait() OVERRIDE {
57    return 0;
58  }
59
60  virtual GURL UpdateUrl() OVERRIDE { return GURL("http://localhost/upd"); }
61
62  virtual const char* ExtraRequestParams() OVERRIDE { return "extra=foo"; }
63
64  virtual size_t UrlSizeLimit() OVERRIDE { return 256; }
65
66  virtual net::URLRequestContextGetter* RequestContext() OVERRIDE {
67    return new TestURLRequestContextGetter(
68        BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO));
69  }
70
71  // Don't use the utility process to decode files.
72  virtual bool InProcess() OVERRIDE { return true; }
73
74  virtual void OnEvent(Events event, int extra) OVERRIDE { }
75
76  // Set how many update checks are called, the default value is just once.
77  void SetLoopCount(int times) { times_ = times; }
78
79 private:
80  int times_;
81};
82
83class TestInstaller : public ComponentInstaller {
84 public :
85  explicit TestInstaller()
86      : error_(0), install_count_(0) {
87  }
88
89  virtual void OnUpdateError(int error) OVERRIDE {
90    EXPECT_NE(0, error);
91    error_ = error;
92  }
93
94  virtual bool Install(base::DictionaryValue* manifest,
95                       const FilePath& unpack_path) OVERRIDE {
96    ++install_count_;
97    delete manifest;
98    return file_util::Delete(unpack_path, true);
99  }
100
101  int error() const { return error_; }
102
103  int install_count() const { return install_count_; }
104
105 private:
106  int error_;
107  int install_count_;
108};
109
110// component 1 has extension id "jebgalgnebhfojomionfpkfelancnnkf", and
111// the RSA public key the following hash:
112const uint8 jebg_hash[] = {0x94,0x16,0x0b,0x6d,0x41,0x75,0xe9,0xec,0x8e,0xd5,
113                           0xfa,0x54,0xb0,0xd2,0xdd,0xa5,0x6e,0x05,0x6b,0xe8,
114                           0x73,0x47,0xf6,0xc4,0x11,0x9f,0xbc,0xb3,0x09,0xb3,
115                           0x5b,0x40};
116// component 2 has extension id "abagagagagagagagagagagagagagagag", and
117// the RSA public key the following hash:
118const uint8 abag_hash[] = {0x01,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
119                           0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
120                           0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
121                           0x06,0x01};
122
123const char header_ok_reply[] =
124    "HTTP/1.1 200 OK\0"
125    "Content-type: text/html\0"
126    "\0";
127
128const char expected_crx_url[] =
129    "http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx";
130
131}  // namespace
132
133// Common fixture for all the component updater tests.
134class ComponentUpdaterTest : public testing::Test {
135 public:
136  enum TestComponents {
137    kTestComponent_abag,
138    kTestComponent_jebg
139  };
140
141  ComponentUpdaterTest() : component_updater_(NULL), test_config_(NULL) {
142    // The component updater instance under test.
143    test_config_ = new TestConfigurator;
144    component_updater_.reset(ComponentUpdateServiceFactory(test_config_));
145    // The test directory is chrome/test/data/components.
146    PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_);
147    test_data_dir_ = test_data_dir_.AppendASCII("components");
148
149    // Subscribe to all component updater notifications.
150    const int notifications[] = {
151      chrome::NOTIFICATION_COMPONENT_UPDATER_STARTED,
152      chrome::NOTIFICATION_COMPONENT_UPDATER_SLEEPING,
153      chrome::NOTIFICATION_COMPONENT_UPDATE_FOUND,
154      chrome::NOTIFICATION_COMPONENT_UPDATE_READY
155    };
156
157    for (int ix = 0; ix != arraysize(notifications); ++ix) {
158      notification_tracker_.ListenFor(
159          notifications[ix], content::NotificationService::AllSources());
160    }
161    net::URLFetcher::SetEnableInterceptionForTests(true);
162  }
163
164  ~ComponentUpdaterTest() {
165    net::URLFetcher::SetEnableInterceptionForTests(false);
166  }
167
168  void TearDown() {
169    xmlCleanupGlobals();
170  }
171
172  ComponentUpdateService* component_updater() {
173    return component_updater_.get();
174  }
175
176  // Makes the full path to a component updater test file.
177  const FilePath test_file(const char* file) {
178    return test_data_dir_.AppendASCII(file);
179  }
180
181  TestNotificationTracker& notification_tracker() {
182    return notification_tracker_;
183  }
184
185  TestConfigurator* test_configurator() {
186    return test_config_;
187  }
188
189  void RegisterComponent(CrxComponent* com,
190                         TestComponents component,
191                         const Version& version) {
192    if (component == kTestComponent_abag) {
193      com->name = "test_abag";
194      com->pk_hash.assign(abag_hash, abag_hash + arraysize(abag_hash));
195    } else {
196      com->name = "test_jebg";
197      com->pk_hash.assign(jebg_hash, jebg_hash + arraysize(jebg_hash));
198    }
199    com->version = version;
200    TestInstaller* installer = new TestInstaller;
201    com->installer = installer;
202    test_installers_.push_back(installer);
203    component_updater_->RegisterComponent(*com);
204  }
205
206 private:
207  scoped_ptr<ComponentUpdateService> component_updater_;
208  FilePath test_data_dir_;
209  TestNotificationTracker notification_tracker_;
210  TestConfigurator* test_config_;
211  // ComponentInstaller objects to delete after each test.
212  ScopedVector<TestInstaller> test_installers_;
213};
214
215// Verify that our test fixture work and the component updater can
216// be created and destroyed with no side effects.
217TEST_F(ComponentUpdaterTest, VerifyFixture) {
218  EXPECT_TRUE(component_updater() != NULL);
219  EXPECT_EQ(0ul, notification_tracker().size());
220}
221
222// Verify that the component updater can be caught in a quick
223// start-shutdown situation. Failure of this test will be a crash. Also
224// if there is no work to do, there are no notifications generated.
225TEST_F(ComponentUpdaterTest, StartStop) {
226  MessageLoop message_loop;
227  content::TestBrowserThread ui_thread(BrowserThread::UI, &message_loop);
228
229  component_updater()->Start();
230  message_loop.RunAllPending();
231  component_updater()->Stop();
232
233  EXPECT_EQ(0ul, notification_tracker().size());
234}
235
236// Verify that when the server has no updates, we go back to sleep and
237// the COMPONENT_UPDATER_STARTED and COMPONENT_UPDATER_SLEEPING notifications
238// are generated.
239TEST_F(ComponentUpdaterTest, CheckCrxSleep) {
240  MessageLoop message_loop;
241  content::TestBrowserThread ui_thread(BrowserThread::UI, &message_loop);
242  content::TestBrowserThread file_thread(BrowserThread::FILE);
243  content::TestBrowserThread io_thread(BrowserThread::IO);
244
245  io_thread.StartIOThread();
246  file_thread.Start();
247
248  scoped_refptr<ComponentUpdateInterceptor>
249      interceptor(new ComponentUpdateInterceptor());
250
251  CrxComponent com;
252  RegisterComponent(&com, kTestComponent_abag, Version("1.1"));
253
254  const char expected_update_url[] =
255      "http://localhost/upd?extra=foo&x=id%3D"
256      "abagagagagagagagagagagagagagagag%26v%3D1.1%26uc";
257
258  interceptor->SetResponse(expected_update_url,
259                           header_ok_reply,
260                           test_file("updatecheck_reply_1.xml"));
261
262  // We loop twice, but there are no updates so we expect two sleep messages.
263  test_configurator()->SetLoopCount(2);
264  component_updater()->Start();
265
266  ASSERT_EQ(1ul, notification_tracker().size());
267  TestNotificationTracker::Event ev1 = notification_tracker().at(0);
268  EXPECT_EQ(chrome::NOTIFICATION_COMPONENT_UPDATER_STARTED, ev1.type);
269
270  message_loop.Run();
271
272  ASSERT_EQ(3ul, notification_tracker().size());
273  TestNotificationTracker::Event ev2 = notification_tracker().at(1);
274  EXPECT_EQ(chrome::NOTIFICATION_COMPONENT_UPDATER_SLEEPING, ev2.type);
275  TestNotificationTracker::Event ev3 = notification_tracker().at(2);
276  EXPECT_EQ(chrome::NOTIFICATION_COMPONENT_UPDATER_SLEEPING, ev2.type);
277  EXPECT_EQ(2, interceptor->hit_count());
278
279  EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error());
280  EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->install_count());
281
282  component_updater()->Stop();
283
284  // Loop twice again but this case we simulate a server error by returning
285  // an empty file.
286
287  interceptor->SetResponse(expected_update_url,
288                           header_ok_reply,
289                           test_file("updatecheck_reply_empty"));
290
291  notification_tracker().Reset();
292  test_configurator()->SetLoopCount(2);
293  component_updater()->Start();
294
295  message_loop.Run();
296
297  ASSERT_EQ(3ul, notification_tracker().size());
298  ev1 = notification_tracker().at(0);
299  EXPECT_EQ(chrome::NOTIFICATION_COMPONENT_UPDATER_STARTED, ev1.type);
300  ev2 = notification_tracker().at(1);
301  EXPECT_EQ(chrome::NOTIFICATION_COMPONENT_UPDATER_SLEEPING, ev2.type);
302  ev3 = notification_tracker().at(2);
303  EXPECT_EQ(chrome::NOTIFICATION_COMPONENT_UPDATER_SLEEPING, ev2.type);
304  EXPECT_EQ(4, interceptor->hit_count());
305
306  EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error());
307  EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->install_count());
308
309  component_updater()->Stop();
310}
311
312// Verify that we can check for updates and install one component. Besides
313// the notifications above NOTIFICATION_COMPONENT_UPDATE_FOUND and
314// NOTIFICATION_COMPONENT_UPDATE_READY should have been fired. We do two loops
315// so the second time around there should be nothing left to do.
316// We also check that only 3 network requests are issued:
317// 1- manifest check
318// 2- download crx
319// 3- second manifest check.
320TEST_F(ComponentUpdaterTest, InstallCrx) {
321  MessageLoop message_loop;
322  content::TestBrowserThread ui_thread(BrowserThread::UI, &message_loop);
323  content::TestBrowserThread file_thread(BrowserThread::FILE);
324  content::TestBrowserThread io_thread(BrowserThread::IO);
325
326  io_thread.StartIOThread();
327  file_thread.Start();
328
329  scoped_refptr<ComponentUpdateInterceptor>
330      interceptor(new ComponentUpdateInterceptor());
331
332  CrxComponent com1;
333  RegisterComponent(&com1, kTestComponent_jebg, Version("0.9"));
334  CrxComponent com2;
335  RegisterComponent(&com2, kTestComponent_abag, Version("2.2"));
336
337  const char expected_update_url_1[] =
338      "http://localhost/upd?extra=foo&x=id%3D"
339      "jebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26uc&x=id%3D"
340      "abagagagagagagagagagagagagagagag%26v%3D2.2%26uc";
341
342  const char expected_update_url_2[] =
343      "http://localhost/upd?extra=foo&x=id%3D"
344      "abagagagagagagagagagagagagagagag%26v%3D2.2%26uc&x=id%3D"
345      "jebgalgnebhfojomionfpkfelancnnkf%26v%3D1.0%26uc";
346
347  interceptor->SetResponse(expected_update_url_1, header_ok_reply,
348                           test_file("updatecheck_reply_1.xml"));
349  interceptor->SetResponse(expected_update_url_2, header_ok_reply,
350                           test_file("updatecheck_reply_1.xml"));
351  interceptor->SetResponse(expected_crx_url, header_ok_reply,
352                           test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"));
353
354  test_configurator()->SetLoopCount(2);
355
356  component_updater()->Start();
357  message_loop.Run();
358
359  EXPECT_EQ(0, static_cast<TestInstaller*>(com1.installer)->error());
360  EXPECT_EQ(1, static_cast<TestInstaller*>(com1.installer)->install_count());
361  EXPECT_EQ(3, interceptor->hit_count());
362
363  ASSERT_EQ(5ul, notification_tracker().size());
364
365  TestNotificationTracker::Event ev1 = notification_tracker().at(1);
366  EXPECT_EQ(chrome::NOTIFICATION_COMPONENT_UPDATE_FOUND, ev1.type);
367
368  TestNotificationTracker::Event ev2 = notification_tracker().at(2);
369  EXPECT_EQ(chrome::NOTIFICATION_COMPONENT_UPDATE_READY, ev2.type);
370
371  TestNotificationTracker::Event ev3 = notification_tracker().at(3);
372  EXPECT_EQ(chrome::NOTIFICATION_COMPONENT_UPDATER_SLEEPING, ev3.type);
373
374  TestNotificationTracker::Event ev4 = notification_tracker().at(4);
375  EXPECT_EQ(chrome::NOTIFICATION_COMPONENT_UPDATER_SLEEPING, ev3.type);
376
377  component_updater()->Stop();
378}
379
380// This test checks that the "prodversionmin" value is handled correctly. In
381// particular there should not be an install because the minimum product
382// version is much higher than of chrome.
383TEST_F(ComponentUpdaterTest, ProdVersionCheck) {
384  MessageLoop message_loop;
385  content::TestBrowserThread ui_thread(BrowserThread::UI, &message_loop);
386  content::TestBrowserThread file_thread(BrowserThread::FILE);
387  content::TestBrowserThread io_thread(BrowserThread::IO);
388
389  io_thread.StartIOThread();
390  file_thread.Start();
391
392  scoped_refptr<ComponentUpdateInterceptor>
393      interceptor(new ComponentUpdateInterceptor());
394
395  CrxComponent com;
396  RegisterComponent(&com, kTestComponent_jebg, Version("0.9"));
397
398  const char expected_update_url[] =
399      "http://localhost/upd?extra=foo&x=id%3D"
400      "jebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26uc";
401
402  interceptor->SetResponse(expected_update_url,
403                           header_ok_reply,
404                           test_file("updatecheck_reply_2.xml"));
405  interceptor->SetResponse(expected_crx_url, header_ok_reply,
406                           test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"));
407
408  test_configurator()->SetLoopCount(1);
409  component_updater()->Start();
410  message_loop.Run();
411
412  EXPECT_EQ(1, interceptor->hit_count());
413  EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error());
414  EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->install_count());
415
416  component_updater()->Stop();
417}
418