blob: b080ccaa594599d55556a57cc512bd3fbb15e4ce [file] [log] [blame]
// 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_notification_types.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/test_url_request_context_getter.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_service.h"
#include "content/test/test_browser_thread.h"
#include "content/public/common/url_fetcher.h"
#include "content/test/test_notification_tracker.h"
#include "googleurl/src/gurl.h"
#include "libxml/globals.h"
#include "testing/gtest/include/gtest/gtest.h"
using content::BrowserThread;
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 const char* ExtraRequestParams() OVERRIDE { return "extra=foo"; }
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; }
virtual void OnEvent(Events event, int extra) OVERRIDE { }
// 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";
const char expected_crx_url[] =
"http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx";
} // namespace
// Common fixture for all the component updater tests.
class ComponentUpdaterTest : public testing::Test {
public:
enum TestComponents {
kTestComponent_abag,
kTestComponent_jebg
};
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], content::NotificationService::AllSources());
}
content::URLFetcher::SetEnableInterceptionForTests(true);
}
~ComponentUpdaterTest() {
content::URLFetcher::SetEnableInterceptionForTests(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_;
}
void RegisterComponent(CrxComponent* com,
TestComponents component,
const Version& version) {
if (component == kTestComponent_abag) {
com->name = "test_abag";
com->pk_hash.assign(abag_hash, abag_hash + arraysize(abag_hash));
} else {
com->name = "test_jebg";
com->pk_hash.assign(jebg_hash, jebg_hash + arraysize(jebg_hash));
}
com->version = version;
com->installer = new TestInstaller;
component_updater_->RegisterComponent(*com);
}
private:
scoped_ptr<ComponentUpdateService> 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;
content::TestBrowserThread 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;
content::TestBrowserThread ui_thread(BrowserThread::UI, &message_loop);
content::TestBrowserThread file_thread(BrowserThread::FILE);
content::TestBrowserThread io_thread(BrowserThread::IO);
io_thread.StartIOThread();
file_thread.Start();
scoped_refptr<ComponentUpdateInterceptor>
interceptor(new ComponentUpdateInterceptor());
CrxComponent com;
RegisterComponent(&com, kTestComponent_abag, Version("1.1"));
const char expected_update_url[] =
"http://localhost/upd?extra=foo&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<TestInstaller*>(com.installer)->error());
EXPECT_EQ(0, static_cast<TestInstaller*>(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<TestInstaller*>(com.installer)->error());
EXPECT_EQ(0, static_cast<TestInstaller*>(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.
// We also check that only 3 network requests are issued:
// 1- manifest check
// 2- download crx
// 3- second manifest check.
TEST_F(ComponentUpdaterTest, InstallCrx) {
MessageLoop message_loop;
content::TestBrowserThread ui_thread(BrowserThread::UI, &message_loop);
content::TestBrowserThread file_thread(BrowserThread::FILE);
content::TestBrowserThread io_thread(BrowserThread::IO);
io_thread.StartIOThread();
file_thread.Start();
scoped_refptr<ComponentUpdateInterceptor>
interceptor(new ComponentUpdateInterceptor());
CrxComponent com1;
RegisterComponent(&com1, kTestComponent_jebg, Version("0.9"));
CrxComponent com2;
RegisterComponent(&com2, kTestComponent_abag, Version("2.2"));
const char expected_update_url_1[] =
"http://localhost/upd?extra=foo&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?extra=foo&x=id%3D"
"abagagagagagagagagagagagagagagag%26v%3D2.2%26uc&x=id%3D"
"jebgalgnebhfojomionfpkfelancnnkf%26v%3D1.0%26uc";
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();
EXPECT_EQ(0, static_cast<TestInstaller*>(com1.installer)->error());
EXPECT_EQ(1, static_cast<TestInstaller*>(com1.installer)->install_count());
EXPECT_EQ(3, interceptor->hit_count());
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);
component_updater()->Stop();
delete com1.installer;
delete com2.installer;
xmlCleanupGlobals();
}
// This test checks that the "prodversionmin" value is handled correctly. In
// particular there should not be an install because the minimun product
// version is much higher than of chrome.
TEST_F(ComponentUpdaterTest, ProdVersionCheck) {
MessageLoop message_loop;
content::TestBrowserThread ui_thread(BrowserThread::UI, &message_loop);
content::TestBrowserThread file_thread(BrowserThread::FILE);
content::TestBrowserThread io_thread(BrowserThread::IO);
io_thread.StartIOThread();
file_thread.Start();
scoped_refptr<ComponentUpdateInterceptor>
interceptor(new ComponentUpdateInterceptor());
CrxComponent com;
RegisterComponent(&com, kTestComponent_jebg, Version("0.9"));
const char expected_update_url[] =
"http://localhost/upd?extra=foo&x=id%3D"
"jebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26uc";
interceptor->SetResponse(expected_update_url,
header_ok_reply,
test_file("updatecheck_reply_2.xml"));
interceptor->SetResponse(expected_crx_url, header_ok_reply,
test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"));
test_configurator()->SetLoopCount(1);
component_updater()->Start();
message_loop.Run();
EXPECT_EQ(1, interceptor->hit_count());
EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error());
EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->install_count());
component_updater()->Stop();
}