Chromium Code Reviews| Index: chrome/browser/extensions/extension_service_test_with_install.cc |
| diff --git a/chrome/browser/extensions/extension_service_test_with_install.cc b/chrome/browser/extensions/extension_service_test_with_install.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..82f863bc30398d34e0f431af32480e9793f357c1 |
| --- /dev/null |
| +++ b/chrome/browser/extensions/extension_service_test_with_install.cc |
| @@ -0,0 +1,456 @@ |
| +// Copyright 2015 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/extensions/extension_service_test_with_install.h" |
| + |
| +#include "base/files/file_util.h" |
| +#include "base/strings/utf_string_conversions.h" |
| +#include "chrome/browser/extensions/crx_installer.h" |
| +#include "chrome/browser/extensions/extension_creator.h" |
| +#include "chrome/browser/extensions/extension_error_reporter.h" |
| +#include "chrome/browser/profiles/profile.h" |
| +#include "content/public/browser/notification_service.h" |
| +#include "extensions/browser/extension_registry.h" |
| +#include "extensions/browser/notification_types.h" |
| +#include "testing/gtest/include/gtest/gtest.h" |
| + |
| +namespace extensions { |
| + |
| +namespace { |
| + |
| +struct ExtensionsOrder { |
| + bool operator()(const scoped_refptr<const Extension>& a, |
| + const scoped_refptr<const Extension>& b) { |
| + return a->name() < b->name(); |
| + } |
| +}; |
| + |
| +// Helper method to set up a WindowedNotificationObserver to wait for a |
| +// specific CrxInstaller to finish if we don't know the value of the |
| +// |installer| yet. |
| +bool IsCrxInstallerDone(extensions::CrxInstaller** installer, |
| + const content::NotificationSource& source, |
| + const content::NotificationDetails& details) { |
| + return content::Source<extensions::CrxInstaller>(source).ptr() == *installer; |
| +} |
| + |
| +} // namespace |
| + |
| +ExtensionServiceTestWithInstall::ExtensionServiceTestWithInstall() |
| + : installed_(nullptr), |
| + was_update_(false), |
| + unloaded_reason_(UnloadedExtensionInfo::REASON_UNDEFINED), |
| + expected_extensions_count_(0){ |
| + registrar_.Add(this, |
| + extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED, |
|
Devlin
2015/10/20 17:42:15
If you don't mind the slight extra churn, making t
Marc Treib
2015/10/21 12:51:53
Yes, let's try and keep this CL to trivial changes
|
| + content::NotificationService::AllSources()); |
| + registrar_.Add(this, |
| + extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED, |
| + content::NotificationService::AllSources()); |
| + registrar_.Add( |
| + this, |
| + extensions::NOTIFICATION_EXTENSION_WILL_BE_INSTALLED_DEPRECATED, |
| + content::NotificationService::AllSources()); |
| +} |
| + |
| +ExtensionServiceTestWithInstall::~ExtensionServiceTestWithInstall() {} |
| + |
| +// static |
| +std::vector<base::string16> ExtensionServiceTestWithInstall::GetErrors() { |
| + const std::vector<base::string16>* errors = |
| + ExtensionErrorReporter::GetInstance()->GetErrors(); |
| + std::vector<base::string16> ret_val; |
| + |
| + for (std::vector<base::string16>::const_iterator iter = errors->begin(); |
|
Devlin
2015/10/20 17:42:15
range-based
Marc Treib
2015/10/21 12:51:53
Done.
|
| + iter != errors->end(); ++iter) { |
| + std::string utf8_error = base::UTF16ToUTF8(*iter); |
| + if (utf8_error.find(".svn") == std::string::npos) { |
| + ret_val.push_back(*iter); |
| + } |
| + } |
| + |
| + // The tests rely on the errors being in a certain order, which can vary |
| + // depending on how filesystem iteration works. |
| + std::stable_sort(ret_val.begin(), ret_val.end()); |
| + |
| + return ret_val; |
| +} |
| + |
| +void ExtensionServiceTestWithInstall::PackCRX(const base::FilePath& dir_path, |
| + const base::FilePath& pem_path, |
| + const base::FilePath& crx_path) { |
| + // Use the existing pem key, if provided. |
| + base::FilePath pem_output_path; |
| + if (pem_path.value().empty()) { |
| + pem_output_path = crx_path.DirName().AppendASCII("temp.pem"); |
| + } else { |
| + ASSERT_TRUE(base::PathExists(pem_path)); |
| +} |
|
Devlin
2015/10/20 17:42:15
indentation
Marc Treib
2015/10/21 12:51:54
Done.
|
| + |
| + ASSERT_TRUE(base::DeleteFile(crx_path, false)); |
| + |
| + scoped_ptr<ExtensionCreator> creator(new ExtensionCreator()); |
| + ASSERT_TRUE(creator->Run(dir_path, |
| + crx_path, |
| + pem_path, |
| + pem_output_path, |
| + ExtensionCreator::kOverwriteCRX)); |
| + |
| + ASSERT_TRUE(base::PathExists(crx_path)); |
| +} |
| + |
| +const Extension* ExtensionServiceTestWithInstall::PackAndInstallCRX( |
| + const base::FilePath& dir_path, |
| + const base::FilePath& pem_path, |
| + InstallState install_state, |
| + int creation_flags) { |
| + base::FilePath crx_path; |
| + base::ScopedTempDir temp_dir; |
| + EXPECT_TRUE(temp_dir.CreateUniqueTempDir()); |
| + crx_path = temp_dir.path().AppendASCII("temp.crx"); |
| + |
| + PackCRX(dir_path, pem_path, crx_path); |
| + return InstallCRX(crx_path, install_state, creation_flags); |
| +} |
| + |
| +const Extension* ExtensionServiceTestWithInstall::PackAndInstallCRX( |
| + const base::FilePath& dir_path, |
| + const base::FilePath& pem_path, |
| + InstallState install_state) { |
| + return PackAndInstallCRX(dir_path, pem_path, install_state, |
| + Extension::NO_FLAGS); |
| +} |
| + |
| +const Extension* ExtensionServiceTestWithInstall::PackAndInstallCRX( |
| + const base::FilePath& dir_path, |
| + InstallState install_state) { |
| + return PackAndInstallCRX(dir_path, base::FilePath(), install_state, |
| + Extension::NO_FLAGS); |
| +} |
| + |
| +// Attempts to install an extension. Use INSTALL_FAILED if the installation |
| +// is expected to fail. |
| +// If |install_state| is INSTALL_UPDATED, and |expected_old_name| is |
| +// non-empty, expects that the existing extension's title was |
| +// |expected_old_name|. |
| +const Extension* ExtensionServiceTestWithInstall::InstallCRX( |
| + const base::FilePath& path, |
| + InstallState install_state, |
| + int creation_flags, |
| + const std::string& expected_old_name) { |
| + InstallCRXInternal(path, creation_flags); |
| + return VerifyCrxInstall(path, install_state, expected_old_name); |
| +} |
| + |
| +// Attempts to install an extension. Use INSTALL_FAILED if the installation |
| +// is expected to fail. |
| +const Extension* ExtensionServiceTestWithInstall::InstallCRX( |
| + const base::FilePath& path, |
| + InstallState install_state, |
| + int creation_flags) { |
| + return InstallCRX(path, install_state, creation_flags, std::string()); |
| +} |
| + |
| +// Attempts to install an extension. Use INSTALL_FAILED if the installation |
| +// is expected to fail. |
| +const Extension* ExtensionServiceTestWithInstall::InstallCRX( |
| + const base::FilePath& path, |
| + InstallState install_state) { |
| + return InstallCRX(path, install_state, Extension::NO_FLAGS); |
| +} |
| + |
| +const Extension* ExtensionServiceTestWithInstall::InstallCRXFromWebStore( |
| + const base::FilePath& path, |
| + InstallState install_state) { |
| + InstallCRXInternal(path, Extension::FROM_WEBSTORE); |
| + return VerifyCrxInstall(path, install_state); |
| +} |
| + |
| +const Extension* ExtensionServiceTestWithInstall::InstallCRXWithLocation( |
| + const base::FilePath& crx_path, |
| + Manifest::Location install_location, |
| + InstallState install_state) { |
| + EXPECT_TRUE(base::PathExists(crx_path)) |
| + << "Path does not exist: "<< crx_path.value().c_str(); |
| + // no client (silent install) |
| + scoped_refptr<CrxInstaller> installer( |
| + CrxInstaller::CreateSilent(service())); |
| + installer->set_install_source(install_location); |
| + |
| + content::WindowedNotificationObserver observer( |
| + extensions::NOTIFICATION_CRX_INSTALLER_DONE, |
| + content::NotificationService::AllSources()); |
| + installer->InstallCrx(crx_path); |
| + observer.Wait(); |
| + |
| + return VerifyCrxInstall(crx_path, install_state); |
| +} |
| + |
| +// Verifies the result of a CRX installation. Used by InstallCRX. Set the |
|
Devlin
2015/10/20 17:42:15
Seems like these comments should go in the .h?
Marc Treib
2015/10/21 12:51:54
Indeed. Done.
|
| +// |install_state| to INSTALL_FAILED if the installation is expected to fail. |
| +// Returns an Extension pointer if the install succeeded, NULL otherwise. |
| +const Extension* ExtensionServiceTestWithInstall::VerifyCrxInstall( |
| + const base::FilePath& path, |
| + InstallState install_state) { |
| + return VerifyCrxInstall(path, install_state, std::string()); |
| +} |
| + |
| +// Verifies the result of a CRX installation. Used by InstallCRX. Set the |
| +// |install_state| to INSTALL_FAILED if the installation is expected to fail. |
| +// If |install_state| is INSTALL_UPDATED, and |expected_old_name| is |
| +// non-empty, expects that the existing extension's title was |
| +// |expected_old_name|. |
| +// Returns an Extension pointer if the install succeeded, NULL otherwise. |
|
Devlin
2015/10/20 17:42:15
nit: s/NULL/nullptr||null
Marc Treib
2015/10/21 12:51:53
Done.
|
| +const Extension* ExtensionServiceTestWithInstall::VerifyCrxInstall( |
| + const base::FilePath& path, |
| + InstallState install_state, |
| + const std::string& expected_old_name) { |
| + std::vector<base::string16> errors = GetErrors(); |
| + const Extension* extension = NULL; |
| + if (install_state != INSTALL_FAILED) { |
| + if (install_state == INSTALL_NEW) |
| + ++expected_extensions_count_; |
| + |
| + EXPECT_TRUE(installed_) << path.value(); |
| + // If and only if INSTALL_UPDATED, it should have the is_update flag. |
| + EXPECT_EQ(install_state == INSTALL_UPDATED, was_update_) |
| + << path.value(); |
| + // If INSTALL_UPDATED, old_name_ should match the given string. |
| + if (install_state == INSTALL_UPDATED && !expected_old_name.empty()) |
| + EXPECT_EQ(expected_old_name, old_name_); |
| + EXPECT_EQ(0u, errors.size()) << path.value(); |
| + |
| + if (install_state == INSTALL_WITHOUT_LOAD) { |
| + EXPECT_EQ(0u, loaded_.size()) << path.value(); |
| + } else { |
| + EXPECT_EQ(1u, loaded_.size()) << path.value(); |
| + size_t actual_extension_count = |
| + registry()->enabled_extensions().size() + |
| + registry()->disabled_extensions().size(); |
| + EXPECT_EQ(expected_extensions_count_, actual_extension_count) << |
| + path.value(); |
| + extension = loaded_[0].get(); |
| + EXPECT_TRUE(service()->GetExtensionById(extension->id(), false)) |
| + << path.value(); |
| + } |
| + |
| + for (std::vector<base::string16>::iterator err = errors.begin(); |
| + err != errors.end(); ++err) { |
| + LOG(ERROR) << *err; |
| + } |
| + } else { |
| + EXPECT_FALSE(installed_) << path.value(); |
| + EXPECT_EQ(0u, loaded_.size()) << path.value(); |
| + EXPECT_EQ(1u, errors.size()) << path.value(); |
| + } |
| + |
| + installed_ = NULL; |
| + was_update_ = false; |
| + old_name_ = ""; |
| + loaded_.clear(); |
| + ExtensionErrorReporter::GetInstance()->ClearErrors(); |
| + return extension; |
| +} |
| + |
| +void ExtensionServiceTestWithInstall::PackCRXAndUpdateExtension( |
| + const std::string& id, |
| + const base::FilePath& dir_path, |
| + const base::FilePath& pem_path, |
| + UpdateState expected_state) { |
| + base::ScopedTempDir temp_dir; |
| + EXPECT_TRUE(temp_dir.CreateUniqueTempDir()); |
| + base::FilePath crx_path = temp_dir.path().AppendASCII("temp.crx"); |
| + |
| + PackCRX(dir_path, pem_path, crx_path); |
| + UpdateExtension(id, crx_path, expected_state); |
| +} |
| + |
| +void ExtensionServiceTestWithInstall::UpdateExtension( |
| + const std::string& id, |
| + const base::FilePath& in_path, |
| + UpdateState expected_state) { |
| + ASSERT_TRUE(base::PathExists(in_path)); |
| + |
| + // We need to copy this to a temporary location because Update() will delete |
| + // it. |
| + base::FilePath path = temp_dir().path(); |
| + path = path.Append(in_path.BaseName()); |
| + ASSERT_TRUE(base::CopyFile(in_path, path)); |
| + |
| + int previous_enabled_extension_count = |
| + registry()->enabled_extensions().size(); |
| + int previous_installed_extension_count = |
| + previous_enabled_extension_count + |
| + registry()->disabled_extensions().size(); |
| + |
| + extensions::CrxInstaller* installer = NULL; |
| + content::WindowedNotificationObserver observer( |
| + extensions::NOTIFICATION_CRX_INSTALLER_DONE, |
| + base::Bind(&IsCrxInstallerDone, &installer)); |
| + service()->UpdateExtension(extensions::CRXFileInfo(id, path), true, |
| + &installer); |
| + |
| + if (installer) |
| + observer.Wait(); |
| + else |
| + base::RunLoop().RunUntilIdle(); |
| + |
| + std::vector<base::string16> errors = GetErrors(); |
| + int error_count = errors.size(); |
| + int enabled_extension_count = registry()->enabled_extensions().size(); |
| + int installed_extension_count = |
| + enabled_extension_count + registry()->disabled_extensions().size(); |
| + |
| + int expected_error_count = (expected_state == FAILED) ? 1 : 0; |
| + EXPECT_EQ(expected_error_count, error_count) << path.value(); |
| + |
| + if (expected_state <= FAILED) { |
| + EXPECT_EQ(previous_enabled_extension_count, |
| + enabled_extension_count); |
| + EXPECT_EQ(previous_installed_extension_count, |
| + installed_extension_count); |
| + } else { |
| + int expected_installed_extension_count = |
| + (expected_state >= INSTALLED) ? 1 : 0; |
| + int expected_enabled_extension_count = |
| + (expected_state >= ENABLED) ? 1 : 0; |
| + EXPECT_EQ(expected_installed_extension_count, |
| + installed_extension_count); |
| + EXPECT_EQ(expected_enabled_extension_count, |
| + enabled_extension_count); |
| + } |
| + |
| + // Update() should the temporary input file. |
| + EXPECT_FALSE(base::PathExists(path)); |
| +} |
| + |
| +void ExtensionServiceTestWithInstall::UninstallExtension(const std::string& id, |
| + bool use_helper) { |
| + UninstallExtension(id, use_helper, Extension::ENABLED); |
| +} |
| + |
| +void ExtensionServiceTestWithInstall::UninstallExtension( |
| + const std::string& id, |
| + bool use_helper, |
| + Extension::State expected_state) { |
| + // Verify that the extension is installed. |
| + base::FilePath extension_path = extensions_install_dir().AppendASCII(id); |
| + EXPECT_TRUE(base::PathExists(extension_path)); |
| + size_t pref_key_count = GetPrefKeyCount(); |
| + EXPECT_GT(pref_key_count, 0u); |
| + ValidateIntegerPref(id, "state", expected_state); |
| + |
| + // Uninstall it. |
| + if (use_helper) { |
| + EXPECT_TRUE(ExtensionService::UninstallExtensionHelper( |
| + service(), id, extensions::UNINSTALL_REASON_FOR_TESTING)); |
| + } else { |
| + EXPECT_TRUE(service()->UninstallExtension( |
| + id, |
| + extensions::UNINSTALL_REASON_FOR_TESTING, |
| + base::Bind(&base::DoNothing), |
| + NULL)); |
| + } |
| + --expected_extensions_count_; |
| + |
| + // We should get an unload notification. |
| + EXPECT_FALSE(unloaded_id_.empty()); |
| + EXPECT_EQ(id, unloaded_id_); |
| + |
| + // Verify uninstalled state. |
| + size_t new_pref_key_count = GetPrefKeyCount(); |
| + if (new_pref_key_count == pref_key_count) { |
| + ValidateIntegerPref(id, "state", |
| + Extension::EXTERNAL_EXTENSION_UNINSTALLED); |
| + } else { |
| + EXPECT_EQ(new_pref_key_count, pref_key_count - 1); |
| + } |
| + |
| + // The extension should not be in the service anymore. |
| + EXPECT_FALSE(service()->GetInstalledExtension(id)); |
| + base::RunLoop().RunUntilIdle(); |
| + |
| + // The directory should be gone. |
| + EXPECT_FALSE(base::PathExists(extension_path)); |
| +} |
| + |
| +void ExtensionServiceTestWithInstall::TerminateExtension( |
| + const std::string& id) { |
| + const Extension* extension = service()->GetInstalledExtension(id); |
| + if (!extension) { |
| + ADD_FAILURE(); |
| + return; |
| + } |
| + service()->TrackTerminatedExtensionForTest(extension); |
| +} |
| + |
| +void ExtensionServiceTestWithInstall::Observe( |
| + int type, |
| + const content::NotificationSource& source, |
| + const content::NotificationDetails& details) { |
| + switch (type) { |
| + case extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED: { |
| + const Extension* extension = |
| + content::Details<const Extension>(details).ptr(); |
| + loaded_.push_back(make_scoped_refptr(extension)); |
| + // The tests rely on the errors being in a certain order, which can vary |
| + // depending on how filesystem iteration works. |
| + std::stable_sort(loaded_.begin(), loaded_.end(), ExtensionsOrder()); |
| + break; |
| + } |
| + |
| + case extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED: { |
| + UnloadedExtensionInfo* unloaded_info = |
| + content::Details<UnloadedExtensionInfo>(details).ptr(); |
| + const Extension* e = unloaded_info->extension; |
| + unloaded_id_ = e->id(); |
| + unloaded_reason_ = unloaded_info->reason; |
| + extensions::ExtensionList::iterator i = |
| + std::find(loaded_.begin(), loaded_.end(), e); |
| + // TODO(erikkay) fix so this can be an assert. Right now the tests |
| + // are manually calling clear() on loaded_, so this isn't doable. |
| + if (i == loaded_.end()) |
| + return; |
| + loaded_.erase(i); |
| + break; |
| + } |
| + case extensions::NOTIFICATION_EXTENSION_WILL_BE_INSTALLED_DEPRECATED: { |
| + const extensions::InstalledExtensionInfo* installed_info = |
| + content::Details<const extensions::InstalledExtensionInfo>(details) |
| + .ptr(); |
| + installed_ = installed_info->extension; |
| + was_update_ = installed_info->is_update; |
| + old_name_ = installed_info->old_name; |
| + break; |
| + } |
| + |
| + default: |
| + DCHECK(false); |
| + } |
| +} |
| + |
| +// Create a CrxInstaller and install the CRX file. |
| +// Instead of calling this method yourself, use InstallCRX(), which does extra |
| +// error checking. |
| +void ExtensionServiceTestWithInstall::InstallCRXInternal( |
| + const base::FilePath& crx_path, |
| + int creation_flags) { |
| + ASSERT_TRUE(base::PathExists(crx_path)) |
| + << "Path does not exist: "<< crx_path.value().c_str(); |
| + scoped_refptr<CrxInstaller> installer( |
| + CrxInstaller::CreateSilent(service())); |
| + installer->set_creation_flags(creation_flags); |
| + if (!(creation_flags & Extension::WAS_INSTALLED_BY_DEFAULT)) |
| + installer->set_allow_silent_install(true); |
| + |
| + content::WindowedNotificationObserver observer( |
| + extensions::NOTIFICATION_CRX_INSTALLER_DONE, |
| + content::Source<extensions::CrxInstaller>(installer.get())); |
| + |
| + installer->InstallCrx(crx_path); |
| + |
| + observer.Wait(); |
| +} |
| + |
| +} // namespace extensions |