// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/component_updater/component_updater_service.h" #include "base/compiler_specific.h" #include "base/file_path.h" #include "base/file_util.h" #include "base/path_service.h" #include "base/values.h" #include "chrome/browser/component_updater/component_updater_interceptor.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_notification_types.h" #include "chrome/test/base/test_notification_tracker.h" #include "chrome/test/base/test_url_request_context_getter.h" #include "chrome/test/base/testing_browser_process_test.h" #include "content/browser/browser_thread.h" #include "content/common/notification_observer.h" #include "content/common/notification_service.h" #include "content/common/url_fetcher.h" #include "googleurl/src/gurl.h" #include "libxml/globals.h" #include "testing/gtest/include/gtest/gtest.h" namespace { // Overrides some of the component updater behaviors so it is easier to test // and loops faster. In actual usage it takes hours do to a full cycle. class TestConfigurator : public ComponentUpdateService::Configurator { public: TestConfigurator() : times_(1) { } virtual int InitialDelay() OVERRIDE { return 0; } virtual int NextCheckDelay() OVERRIDE { // This is called when a new full cycle of checking for updates is going // to happen. In test we normally only test one cycle so it is a good // time to break from the test messageloop Run() method so the test can // finish. if (--times_ > 0) return 1; MessageLoop::current()->Quit(); return 0; } virtual int StepDelay() OVERRIDE { return 0; } virtual int MinimumReCheckWait() OVERRIDE { return 0; } virtual GURL UpdateUrl() OVERRIDE { return GURL("http://localhost/upd"); } virtual size_t UrlSizeLimit() OVERRIDE { return 256; } virtual net::URLRequestContextGetter* RequestContext() OVERRIDE { return new TestURLRequestContextGetter(); } // Don't use the utility process to decode files. virtual bool InProcess() OVERRIDE { return true; } // Set how many update checks are called, the default value is just once. void SetLoopCount(int times) { times_ = times; } private: int times_; }; class TestInstaller : public ComponentInstaller { public : explicit TestInstaller() : error_(0), install_count_(0) { } virtual void OnUpdateError(int error) OVERRIDE { EXPECT_NE(0, error); error_ = error; } virtual bool Install(base::DictionaryValue* manifest, const FilePath& unpack_path) OVERRIDE { ++install_count_; delete manifest; return file_util::Delete(unpack_path, true); } int error() const { return error_; } int install_count() const { return install_count_; } private: int error_; int install_count_; }; // component 1 has extension id "jebgalgnebhfojomionfpkfelancnnkf", and // the RSA public key the following hash: const uint8 jebg_hash[] = {0x94,0x16,0x0b,0x6d,0x41,0x75,0xe9,0xec,0x8e,0xd5, 0xfa,0x54,0xb0,0xd2,0xdd,0xa5,0x6e,0x05,0x6b,0xe8, 0x73,0x47,0xf6,0xc4,0x11,0x9f,0xbc,0xb3,0x09,0xb3, 0x5b,0x40}; // component 2 has extension id "abagagagagagagagagagagagagagagag", and // the RSA public key the following hash: const uint8 abag_hash[] = {0x01,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06, 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06, 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06, 0x06,0x01}; const char header_ok_reply[] = "HTTP/1.1 200 OK\0" "Content-type: text/html\0" "\0"; } // namespace // Common fixture for all the component updater tests. class ComponentUpdaterTest : public TestingBrowserProcessTest { public: ComponentUpdaterTest() : component_updater_(NULL), test_config_(NULL) { // The component updater instance under test. test_config_ = new TestConfigurator; component_updater_.reset(ComponentUpdateServiceFactory(test_config_)); // The test directory is chrome/test/data/components. PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_); test_data_dir_ = test_data_dir_.AppendASCII("components"); // Subscribe to all component updater notifications. const int notifications[] = { chrome::NOTIFICATION_COMPONENT_UPDATER_STARTED, chrome::NOTIFICATION_COMPONENT_UPDATER_SLEEPING, chrome::NOTIFICATION_COMPONENT_UPDATE_FOUND, chrome::NOTIFICATION_COMPONENT_UPDATE_READY }; for (int ix = 0; ix != arraysize(notifications); ++ix) { notification_tracker_.ListenFor(notifications[ix], NotificationService::AllSources()); } URLFetcher::enable_interception_for_tests(true); } ~ComponentUpdaterTest() { URLFetcher::enable_interception_for_tests(false); } ComponentUpdateService* component_updater() { return component_updater_.get(); } // Makes the full path to a component updater test file. const FilePath test_file(const char* file) { return test_data_dir_.AppendASCII(file); } TestNotificationTracker& notification_tracker() { return notification_tracker_; } TestConfigurator* test_configurator() { return test_config_; } private: scoped_ptr component_updater_; FilePath test_data_dir_; TestNotificationTracker notification_tracker_; TestConfigurator* test_config_; }; // Verify that our test fixture work and the component updater can // be created and destroyed with no side effects. TEST_F(ComponentUpdaterTest, VerifyFixture) { EXPECT_TRUE(component_updater() != NULL); EXPECT_EQ(0ul, notification_tracker().size()); } // Verify that the component updater can be caught in a quick // start-shutdown situation. Failure of this test will be a crash. Also // if there is no work to do, there are no notifications generated. TEST_F(ComponentUpdaterTest, StartStop) { MessageLoop message_loop; BrowserThread ui_thread(BrowserThread::UI, &message_loop); component_updater()->Start(); message_loop.RunAllPending(); component_updater()->Stop(); EXPECT_EQ(0ul, notification_tracker().size()); } // Verify that when the server has no updates, we go back to sleep and // the COMPONENT_UPDATER_STARTED and COMPONENT_UPDATER_SLEEPING notifications // are generated. TEST_F(ComponentUpdaterTest, CheckCrxSleep) { MessageLoop message_loop; BrowserThread ui_thread(BrowserThread::UI, &message_loop); BrowserThread file_thread(BrowserThread::FILE); BrowserThread io_thread(BrowserThread::IO); io_thread.StartWithOptions(base::Thread::Options(MessageLoop::TYPE_IO, 0)); file_thread.Start(); scoped_refptr interceptor(new ComponentUpdateInterceptor()); CrxComponent com; com.name = "test_abag"; com.pk_hash.assign(abag_hash, abag_hash + arraysize(abag_hash)); com.version = Version("1.1"); com.installer = new TestInstaller; component_updater()->RegisterComponent(com); const char expected_update_url[] = "http://localhost/upd?x=id%3D" "abagagagagagagagagagagagagagagag%26v%3D1.1%26uc"; interceptor->SetResponse(expected_update_url, header_ok_reply, test_file("updatecheck_reply_1.xml")); // We loop twice, but there are no updates so we expect two sleep messages. test_configurator()->SetLoopCount(2); component_updater()->Start(); ASSERT_EQ(1ul, notification_tracker().size()); TestNotificationTracker::Event ev1 = notification_tracker().at(0); EXPECT_EQ(chrome::NOTIFICATION_COMPONENT_UPDATER_STARTED, ev1.type); message_loop.Run(); ASSERT_EQ(3ul, notification_tracker().size()); TestNotificationTracker::Event ev2 = notification_tracker().at(1); EXPECT_EQ(chrome::NOTIFICATION_COMPONENT_UPDATER_SLEEPING, ev2.type); TestNotificationTracker::Event ev3 = notification_tracker().at(2); EXPECT_EQ(chrome::NOTIFICATION_COMPONENT_UPDATER_SLEEPING, ev2.type); EXPECT_EQ(2, interceptor->hit_count()); EXPECT_EQ(0, static_cast(com.installer)->error()); EXPECT_EQ(0, static_cast(com.installer)->install_count()); component_updater()->Stop(); // Loop twice again but this case we simulate a server error by returning // an empty file. interceptor->SetResponse(expected_update_url, header_ok_reply, test_file("updatecheck_reply_empty")); notification_tracker().Reset(); test_configurator()->SetLoopCount(2); component_updater()->Start(); message_loop.Run(); ASSERT_EQ(3ul, notification_tracker().size()); ev1 = notification_tracker().at(0); EXPECT_EQ(chrome::NOTIFICATION_COMPONENT_UPDATER_STARTED, ev1.type); ev2 = notification_tracker().at(1); EXPECT_EQ(chrome::NOTIFICATION_COMPONENT_UPDATER_SLEEPING, ev2.type); ev3 = notification_tracker().at(2); EXPECT_EQ(chrome::NOTIFICATION_COMPONENT_UPDATER_SLEEPING, ev2.type); EXPECT_EQ(4, interceptor->hit_count()); EXPECT_EQ(0, static_cast(com.installer)->error()); EXPECT_EQ(0, static_cast(com.installer)->install_count()); component_updater()->Stop(); delete com.installer; xmlCleanupGlobals(); } // Verify that we can check for updates and install one component. Besides // the notifications above NOTIFICATION_COMPONENT_UPDATE_FOUND and // NOTIFICATION_COMPONENT_UPDATE_READY should have been fired. We do two loops // so the second time arround there should be nothing left to do. TEST_F(ComponentUpdaterTest, InstallCrx) { MessageLoop message_loop; BrowserThread ui_thread(BrowserThread::UI, &message_loop); BrowserThread file_thread(BrowserThread::FILE); BrowserThread io_thread(BrowserThread::IO); io_thread.StartWithOptions(base::Thread::Options(MessageLoop::TYPE_IO, 0)); file_thread.Start(); scoped_refptr interceptor(new ComponentUpdateInterceptor()); CrxComponent com1; com1.name = "test_jebg"; com1.pk_hash.assign(jebg_hash, jebg_hash + arraysize(jebg_hash)); com1.version = Version("0.9"); com1.installer = new TestInstaller; CrxComponent com2; com2.name = "test_abag"; com2.pk_hash.assign(abag_hash, abag_hash + arraysize(abag_hash)); com2.version = Version("2.2"); com2.installer = new TestInstaller; component_updater()->RegisterComponent(com1); component_updater()->RegisterComponent(com2); const char expected_update_url_1[] = "http://localhost/upd?x=id%3D" "jebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26uc&x=id%3D" "abagagagagagagagagagagagagagagag%26v%3D2.2%26uc"; const char expected_update_url_2[] = "http://localhost/upd?x=id%3D" "abagagagagagagagagagagagagagagag%26v%3D2.2%26uc&x=id%3D" "jebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26uc"; const char expected_crx_url[] = "http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx"; interceptor->SetResponse(expected_update_url_1, header_ok_reply, test_file("updatecheck_reply_1.xml")); interceptor->SetResponse(expected_update_url_2, header_ok_reply, test_file("updatecheck_reply_1.xml")); interceptor->SetResponse(expected_crx_url, header_ok_reply, test_file("jebgalgnebhfojomionfpkfelancnnkf.crx")); test_configurator()->SetLoopCount(2); component_updater()->Start(); message_loop.Run(); ASSERT_EQ(5ul, notification_tracker().size()); TestNotificationTracker::Event ev1 = notification_tracker().at(1); EXPECT_EQ(chrome::NOTIFICATION_COMPONENT_UPDATE_FOUND, ev1.type); TestNotificationTracker::Event ev2 = notification_tracker().at(2); EXPECT_EQ(chrome::NOTIFICATION_COMPONENT_UPDATE_READY, ev2.type); TestNotificationTracker::Event ev3 = notification_tracker().at(3); EXPECT_EQ(chrome::NOTIFICATION_COMPONENT_UPDATER_SLEEPING, ev3.type); TestNotificationTracker::Event ev4 = notification_tracker().at(4); EXPECT_EQ(chrome::NOTIFICATION_COMPONENT_UPDATER_SLEEPING, ev3.type); EXPECT_EQ(0, static_cast(com1.installer)->error()); EXPECT_EQ(1, static_cast(com1.installer)->install_count()); EXPECT_EQ(2, interceptor->hit_count()); component_updater()->Stop(); delete com1.installer; delete com2.installer; xmlCleanupGlobals(); }