Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2015 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/extensions/extension_service_test_with_install.h" | |
| 6 | |
| 7 #include "base/files/file_util.h" | |
| 8 #include "base/strings/utf_string_conversions.h" | |
| 9 #include "chrome/browser/extensions/crx_installer.h" | |
| 10 #include "chrome/browser/extensions/extension_creator.h" | |
| 11 #include "chrome/browser/extensions/extension_error_reporter.h" | |
| 12 #include "chrome/browser/profiles/profile.h" | |
| 13 #include "content/public/browser/notification_service.h" | |
| 14 #include "extensions/browser/extension_registry.h" | |
| 15 #include "extensions/browser/notification_types.h" | |
| 16 #include "testing/gtest/include/gtest/gtest.h" | |
| 17 | |
| 18 namespace extensions { | |
| 19 | |
| 20 namespace { | |
| 21 | |
| 22 struct ExtensionsOrder { | |
| 23 bool operator()(const scoped_refptr<const Extension>& a, | |
| 24 const scoped_refptr<const Extension>& b) { | |
| 25 return a->name() < b->name(); | |
| 26 } | |
| 27 }; | |
| 28 | |
| 29 // Helper method to set up a WindowedNotificationObserver to wait for a | |
| 30 // specific CrxInstaller to finish if we don't know the value of the | |
| 31 // |installer| yet. | |
| 32 bool IsCrxInstallerDone(extensions::CrxInstaller** installer, | |
| 33 const content::NotificationSource& source, | |
| 34 const content::NotificationDetails& details) { | |
| 35 return content::Source<extensions::CrxInstaller>(source).ptr() == *installer; | |
| 36 } | |
| 37 | |
| 38 } // namespace | |
| 39 | |
| 40 ExtensionServiceTestWithInstall::ExtensionServiceTestWithInstall() | |
| 41 : installed_(nullptr), | |
| 42 was_update_(false), | |
| 43 unloaded_reason_(UnloadedExtensionInfo::REASON_UNDEFINED), | |
| 44 expected_extensions_count_(0){ | |
| 45 registrar_.Add(this, | |
| 46 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
| |
| 47 content::NotificationService::AllSources()); | |
| 48 registrar_.Add(this, | |
| 49 extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED, | |
| 50 content::NotificationService::AllSources()); | |
| 51 registrar_.Add( | |
| 52 this, | |
| 53 extensions::NOTIFICATION_EXTENSION_WILL_BE_INSTALLED_DEPRECATED, | |
| 54 content::NotificationService::AllSources()); | |
| 55 } | |
| 56 | |
| 57 ExtensionServiceTestWithInstall::~ExtensionServiceTestWithInstall() {} | |
| 58 | |
| 59 // static | |
| 60 std::vector<base::string16> ExtensionServiceTestWithInstall::GetErrors() { | |
| 61 const std::vector<base::string16>* errors = | |
| 62 ExtensionErrorReporter::GetInstance()->GetErrors(); | |
| 63 std::vector<base::string16> ret_val; | |
| 64 | |
| 65 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.
| |
| 66 iter != errors->end(); ++iter) { | |
| 67 std::string utf8_error = base::UTF16ToUTF8(*iter); | |
| 68 if (utf8_error.find(".svn") == std::string::npos) { | |
| 69 ret_val.push_back(*iter); | |
| 70 } | |
| 71 } | |
| 72 | |
| 73 // The tests rely on the errors being in a certain order, which can vary | |
| 74 // depending on how filesystem iteration works. | |
| 75 std::stable_sort(ret_val.begin(), ret_val.end()); | |
| 76 | |
| 77 return ret_val; | |
| 78 } | |
| 79 | |
| 80 void ExtensionServiceTestWithInstall::PackCRX(const base::FilePath& dir_path, | |
| 81 const base::FilePath& pem_path, | |
| 82 const base::FilePath& crx_path) { | |
| 83 // Use the existing pem key, if provided. | |
| 84 base::FilePath pem_output_path; | |
| 85 if (pem_path.value().empty()) { | |
| 86 pem_output_path = crx_path.DirName().AppendASCII("temp.pem"); | |
| 87 } else { | |
| 88 ASSERT_TRUE(base::PathExists(pem_path)); | |
| 89 } | |
|
Devlin
2015/10/20 17:42:15
indentation
Marc Treib
2015/10/21 12:51:54
Done.
| |
| 90 | |
| 91 ASSERT_TRUE(base::DeleteFile(crx_path, false)); | |
| 92 | |
| 93 scoped_ptr<ExtensionCreator> creator(new ExtensionCreator()); | |
| 94 ASSERT_TRUE(creator->Run(dir_path, | |
| 95 crx_path, | |
| 96 pem_path, | |
| 97 pem_output_path, | |
| 98 ExtensionCreator::kOverwriteCRX)); | |
| 99 | |
| 100 ASSERT_TRUE(base::PathExists(crx_path)); | |
| 101 } | |
| 102 | |
| 103 const Extension* ExtensionServiceTestWithInstall::PackAndInstallCRX( | |
| 104 const base::FilePath& dir_path, | |
| 105 const base::FilePath& pem_path, | |
| 106 InstallState install_state, | |
| 107 int creation_flags) { | |
| 108 base::FilePath crx_path; | |
| 109 base::ScopedTempDir temp_dir; | |
| 110 EXPECT_TRUE(temp_dir.CreateUniqueTempDir()); | |
| 111 crx_path = temp_dir.path().AppendASCII("temp.crx"); | |
| 112 | |
| 113 PackCRX(dir_path, pem_path, crx_path); | |
| 114 return InstallCRX(crx_path, install_state, creation_flags); | |
| 115 } | |
| 116 | |
| 117 const Extension* ExtensionServiceTestWithInstall::PackAndInstallCRX( | |
| 118 const base::FilePath& dir_path, | |
| 119 const base::FilePath& pem_path, | |
| 120 InstallState install_state) { | |
| 121 return PackAndInstallCRX(dir_path, pem_path, install_state, | |
| 122 Extension::NO_FLAGS); | |
| 123 } | |
| 124 | |
| 125 const Extension* ExtensionServiceTestWithInstall::PackAndInstallCRX( | |
| 126 const base::FilePath& dir_path, | |
| 127 InstallState install_state) { | |
| 128 return PackAndInstallCRX(dir_path, base::FilePath(), install_state, | |
| 129 Extension::NO_FLAGS); | |
| 130 } | |
| 131 | |
| 132 // Attempts to install an extension. Use INSTALL_FAILED if the installation | |
| 133 // is expected to fail. | |
| 134 // If |install_state| is INSTALL_UPDATED, and |expected_old_name| is | |
| 135 // non-empty, expects that the existing extension's title was | |
| 136 // |expected_old_name|. | |
| 137 const Extension* ExtensionServiceTestWithInstall::InstallCRX( | |
| 138 const base::FilePath& path, | |
| 139 InstallState install_state, | |
| 140 int creation_flags, | |
| 141 const std::string& expected_old_name) { | |
| 142 InstallCRXInternal(path, creation_flags); | |
| 143 return VerifyCrxInstall(path, install_state, expected_old_name); | |
| 144 } | |
| 145 | |
| 146 // Attempts to install an extension. Use INSTALL_FAILED if the installation | |
| 147 // is expected to fail. | |
| 148 const Extension* ExtensionServiceTestWithInstall::InstallCRX( | |
| 149 const base::FilePath& path, | |
| 150 InstallState install_state, | |
| 151 int creation_flags) { | |
| 152 return InstallCRX(path, install_state, creation_flags, std::string()); | |
| 153 } | |
| 154 | |
| 155 // Attempts to install an extension. Use INSTALL_FAILED if the installation | |
| 156 // is expected to fail. | |
| 157 const Extension* ExtensionServiceTestWithInstall::InstallCRX( | |
| 158 const base::FilePath& path, | |
| 159 InstallState install_state) { | |
| 160 return InstallCRX(path, install_state, Extension::NO_FLAGS); | |
| 161 } | |
| 162 | |
| 163 const Extension* ExtensionServiceTestWithInstall::InstallCRXFromWebStore( | |
| 164 const base::FilePath& path, | |
| 165 InstallState install_state) { | |
| 166 InstallCRXInternal(path, Extension::FROM_WEBSTORE); | |
| 167 return VerifyCrxInstall(path, install_state); | |
| 168 } | |
| 169 | |
| 170 const Extension* ExtensionServiceTestWithInstall::InstallCRXWithLocation( | |
| 171 const base::FilePath& crx_path, | |
| 172 Manifest::Location install_location, | |
| 173 InstallState install_state) { | |
| 174 EXPECT_TRUE(base::PathExists(crx_path)) | |
| 175 << "Path does not exist: "<< crx_path.value().c_str(); | |
| 176 // no client (silent install) | |
| 177 scoped_refptr<CrxInstaller> installer( | |
| 178 CrxInstaller::CreateSilent(service())); | |
| 179 installer->set_install_source(install_location); | |
| 180 | |
| 181 content::WindowedNotificationObserver observer( | |
| 182 extensions::NOTIFICATION_CRX_INSTALLER_DONE, | |
| 183 content::NotificationService::AllSources()); | |
| 184 installer->InstallCrx(crx_path); | |
| 185 observer.Wait(); | |
| 186 | |
| 187 return VerifyCrxInstall(crx_path, install_state); | |
| 188 } | |
| 189 | |
| 190 // 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.
| |
| 191 // |install_state| to INSTALL_FAILED if the installation is expected to fail. | |
| 192 // Returns an Extension pointer if the install succeeded, NULL otherwise. | |
| 193 const Extension* ExtensionServiceTestWithInstall::VerifyCrxInstall( | |
| 194 const base::FilePath& path, | |
| 195 InstallState install_state) { | |
| 196 return VerifyCrxInstall(path, install_state, std::string()); | |
| 197 } | |
| 198 | |
| 199 // Verifies the result of a CRX installation. Used by InstallCRX. Set the | |
| 200 // |install_state| to INSTALL_FAILED if the installation is expected to fail. | |
| 201 // If |install_state| is INSTALL_UPDATED, and |expected_old_name| is | |
| 202 // non-empty, expects that the existing extension's title was | |
| 203 // |expected_old_name|. | |
| 204 // 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.
| |
| 205 const Extension* ExtensionServiceTestWithInstall::VerifyCrxInstall( | |
| 206 const base::FilePath& path, | |
| 207 InstallState install_state, | |
| 208 const std::string& expected_old_name) { | |
| 209 std::vector<base::string16> errors = GetErrors(); | |
| 210 const Extension* extension = NULL; | |
| 211 if (install_state != INSTALL_FAILED) { | |
| 212 if (install_state == INSTALL_NEW) | |
| 213 ++expected_extensions_count_; | |
| 214 | |
| 215 EXPECT_TRUE(installed_) << path.value(); | |
| 216 // If and only if INSTALL_UPDATED, it should have the is_update flag. | |
| 217 EXPECT_EQ(install_state == INSTALL_UPDATED, was_update_) | |
| 218 << path.value(); | |
| 219 // If INSTALL_UPDATED, old_name_ should match the given string. | |
| 220 if (install_state == INSTALL_UPDATED && !expected_old_name.empty()) | |
| 221 EXPECT_EQ(expected_old_name, old_name_); | |
| 222 EXPECT_EQ(0u, errors.size()) << path.value(); | |
| 223 | |
| 224 if (install_state == INSTALL_WITHOUT_LOAD) { | |
| 225 EXPECT_EQ(0u, loaded_.size()) << path.value(); | |
| 226 } else { | |
| 227 EXPECT_EQ(1u, loaded_.size()) << path.value(); | |
| 228 size_t actual_extension_count = | |
| 229 registry()->enabled_extensions().size() + | |
| 230 registry()->disabled_extensions().size(); | |
| 231 EXPECT_EQ(expected_extensions_count_, actual_extension_count) << | |
| 232 path.value(); | |
| 233 extension = loaded_[0].get(); | |
| 234 EXPECT_TRUE(service()->GetExtensionById(extension->id(), false)) | |
| 235 << path.value(); | |
| 236 } | |
| 237 | |
| 238 for (std::vector<base::string16>::iterator err = errors.begin(); | |
| 239 err != errors.end(); ++err) { | |
| 240 LOG(ERROR) << *err; | |
| 241 } | |
| 242 } else { | |
| 243 EXPECT_FALSE(installed_) << path.value(); | |
| 244 EXPECT_EQ(0u, loaded_.size()) << path.value(); | |
| 245 EXPECT_EQ(1u, errors.size()) << path.value(); | |
| 246 } | |
| 247 | |
| 248 installed_ = NULL; | |
| 249 was_update_ = false; | |
| 250 old_name_ = ""; | |
| 251 loaded_.clear(); | |
| 252 ExtensionErrorReporter::GetInstance()->ClearErrors(); | |
| 253 return extension; | |
| 254 } | |
| 255 | |
| 256 void ExtensionServiceTestWithInstall::PackCRXAndUpdateExtension( | |
| 257 const std::string& id, | |
| 258 const base::FilePath& dir_path, | |
| 259 const base::FilePath& pem_path, | |
| 260 UpdateState expected_state) { | |
| 261 base::ScopedTempDir temp_dir; | |
| 262 EXPECT_TRUE(temp_dir.CreateUniqueTempDir()); | |
| 263 base::FilePath crx_path = temp_dir.path().AppendASCII("temp.crx"); | |
| 264 | |
| 265 PackCRX(dir_path, pem_path, crx_path); | |
| 266 UpdateExtension(id, crx_path, expected_state); | |
| 267 } | |
| 268 | |
| 269 void ExtensionServiceTestWithInstall::UpdateExtension( | |
| 270 const std::string& id, | |
| 271 const base::FilePath& in_path, | |
| 272 UpdateState expected_state) { | |
| 273 ASSERT_TRUE(base::PathExists(in_path)); | |
| 274 | |
| 275 // We need to copy this to a temporary location because Update() will delete | |
| 276 // it. | |
| 277 base::FilePath path = temp_dir().path(); | |
| 278 path = path.Append(in_path.BaseName()); | |
| 279 ASSERT_TRUE(base::CopyFile(in_path, path)); | |
| 280 | |
| 281 int previous_enabled_extension_count = | |
| 282 registry()->enabled_extensions().size(); | |
| 283 int previous_installed_extension_count = | |
| 284 previous_enabled_extension_count + | |
| 285 registry()->disabled_extensions().size(); | |
| 286 | |
| 287 extensions::CrxInstaller* installer = NULL; | |
| 288 content::WindowedNotificationObserver observer( | |
| 289 extensions::NOTIFICATION_CRX_INSTALLER_DONE, | |
| 290 base::Bind(&IsCrxInstallerDone, &installer)); | |
| 291 service()->UpdateExtension(extensions::CRXFileInfo(id, path), true, | |
| 292 &installer); | |
| 293 | |
| 294 if (installer) | |
| 295 observer.Wait(); | |
| 296 else | |
| 297 base::RunLoop().RunUntilIdle(); | |
| 298 | |
| 299 std::vector<base::string16> errors = GetErrors(); | |
| 300 int error_count = errors.size(); | |
| 301 int enabled_extension_count = registry()->enabled_extensions().size(); | |
| 302 int installed_extension_count = | |
| 303 enabled_extension_count + registry()->disabled_extensions().size(); | |
| 304 | |
| 305 int expected_error_count = (expected_state == FAILED) ? 1 : 0; | |
| 306 EXPECT_EQ(expected_error_count, error_count) << path.value(); | |
| 307 | |
| 308 if (expected_state <= FAILED) { | |
| 309 EXPECT_EQ(previous_enabled_extension_count, | |
| 310 enabled_extension_count); | |
| 311 EXPECT_EQ(previous_installed_extension_count, | |
| 312 installed_extension_count); | |
| 313 } else { | |
| 314 int expected_installed_extension_count = | |
| 315 (expected_state >= INSTALLED) ? 1 : 0; | |
| 316 int expected_enabled_extension_count = | |
| 317 (expected_state >= ENABLED) ? 1 : 0; | |
| 318 EXPECT_EQ(expected_installed_extension_count, | |
| 319 installed_extension_count); | |
| 320 EXPECT_EQ(expected_enabled_extension_count, | |
| 321 enabled_extension_count); | |
| 322 } | |
| 323 | |
| 324 // Update() should the temporary input file. | |
| 325 EXPECT_FALSE(base::PathExists(path)); | |
| 326 } | |
| 327 | |
| 328 void ExtensionServiceTestWithInstall::UninstallExtension(const std::string& id, | |
| 329 bool use_helper) { | |
| 330 UninstallExtension(id, use_helper, Extension::ENABLED); | |
| 331 } | |
| 332 | |
| 333 void ExtensionServiceTestWithInstall::UninstallExtension( | |
| 334 const std::string& id, | |
| 335 bool use_helper, | |
| 336 Extension::State expected_state) { | |
| 337 // Verify that the extension is installed. | |
| 338 base::FilePath extension_path = extensions_install_dir().AppendASCII(id); | |
| 339 EXPECT_TRUE(base::PathExists(extension_path)); | |
| 340 size_t pref_key_count = GetPrefKeyCount(); | |
| 341 EXPECT_GT(pref_key_count, 0u); | |
| 342 ValidateIntegerPref(id, "state", expected_state); | |
| 343 | |
| 344 // Uninstall it. | |
| 345 if (use_helper) { | |
| 346 EXPECT_TRUE(ExtensionService::UninstallExtensionHelper( | |
| 347 service(), id, extensions::UNINSTALL_REASON_FOR_TESTING)); | |
| 348 } else { | |
| 349 EXPECT_TRUE(service()->UninstallExtension( | |
| 350 id, | |
| 351 extensions::UNINSTALL_REASON_FOR_TESTING, | |
| 352 base::Bind(&base::DoNothing), | |
| 353 NULL)); | |
| 354 } | |
| 355 --expected_extensions_count_; | |
| 356 | |
| 357 // We should get an unload notification. | |
| 358 EXPECT_FALSE(unloaded_id_.empty()); | |
| 359 EXPECT_EQ(id, unloaded_id_); | |
| 360 | |
| 361 // Verify uninstalled state. | |
| 362 size_t new_pref_key_count = GetPrefKeyCount(); | |
| 363 if (new_pref_key_count == pref_key_count) { | |
| 364 ValidateIntegerPref(id, "state", | |
| 365 Extension::EXTERNAL_EXTENSION_UNINSTALLED); | |
| 366 } else { | |
| 367 EXPECT_EQ(new_pref_key_count, pref_key_count - 1); | |
| 368 } | |
| 369 | |
| 370 // The extension should not be in the service anymore. | |
| 371 EXPECT_FALSE(service()->GetInstalledExtension(id)); | |
| 372 base::RunLoop().RunUntilIdle(); | |
| 373 | |
| 374 // The directory should be gone. | |
| 375 EXPECT_FALSE(base::PathExists(extension_path)); | |
| 376 } | |
| 377 | |
| 378 void ExtensionServiceTestWithInstall::TerminateExtension( | |
| 379 const std::string& id) { | |
| 380 const Extension* extension = service()->GetInstalledExtension(id); | |
| 381 if (!extension) { | |
| 382 ADD_FAILURE(); | |
| 383 return; | |
| 384 } | |
| 385 service()->TrackTerminatedExtensionForTest(extension); | |
| 386 } | |
| 387 | |
| 388 void ExtensionServiceTestWithInstall::Observe( | |
| 389 int type, | |
| 390 const content::NotificationSource& source, | |
| 391 const content::NotificationDetails& details) { | |
| 392 switch (type) { | |
| 393 case extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED: { | |
| 394 const Extension* extension = | |
| 395 content::Details<const Extension>(details).ptr(); | |
| 396 loaded_.push_back(make_scoped_refptr(extension)); | |
| 397 // The tests rely on the errors being in a certain order, which can vary | |
| 398 // depending on how filesystem iteration works. | |
| 399 std::stable_sort(loaded_.begin(), loaded_.end(), ExtensionsOrder()); | |
| 400 break; | |
| 401 } | |
| 402 | |
| 403 case extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED: { | |
| 404 UnloadedExtensionInfo* unloaded_info = | |
| 405 content::Details<UnloadedExtensionInfo>(details).ptr(); | |
| 406 const Extension* e = unloaded_info->extension; | |
| 407 unloaded_id_ = e->id(); | |
| 408 unloaded_reason_ = unloaded_info->reason; | |
| 409 extensions::ExtensionList::iterator i = | |
| 410 std::find(loaded_.begin(), loaded_.end(), e); | |
| 411 // TODO(erikkay) fix so this can be an assert. Right now the tests | |
| 412 // are manually calling clear() on loaded_, so this isn't doable. | |
| 413 if (i == loaded_.end()) | |
| 414 return; | |
| 415 loaded_.erase(i); | |
| 416 break; | |
| 417 } | |
| 418 case extensions::NOTIFICATION_EXTENSION_WILL_BE_INSTALLED_DEPRECATED: { | |
| 419 const extensions::InstalledExtensionInfo* installed_info = | |
| 420 content::Details<const extensions::InstalledExtensionInfo>(details) | |
| 421 .ptr(); | |
| 422 installed_ = installed_info->extension; | |
| 423 was_update_ = installed_info->is_update; | |
| 424 old_name_ = installed_info->old_name; | |
| 425 break; | |
| 426 } | |
| 427 | |
| 428 default: | |
| 429 DCHECK(false); | |
| 430 } | |
| 431 } | |
| 432 | |
| 433 // Create a CrxInstaller and install the CRX file. | |
| 434 // Instead of calling this method yourself, use InstallCRX(), which does extra | |
| 435 // error checking. | |
| 436 void ExtensionServiceTestWithInstall::InstallCRXInternal( | |
| 437 const base::FilePath& crx_path, | |
| 438 int creation_flags) { | |
| 439 ASSERT_TRUE(base::PathExists(crx_path)) | |
| 440 << "Path does not exist: "<< crx_path.value().c_str(); | |
| 441 scoped_refptr<CrxInstaller> installer( | |
| 442 CrxInstaller::CreateSilent(service())); | |
| 443 installer->set_creation_flags(creation_flags); | |
| 444 if (!(creation_flags & Extension::WAS_INSTALLED_BY_DEFAULT)) | |
| 445 installer->set_allow_silent_install(true); | |
| 446 | |
| 447 content::WindowedNotificationObserver observer( | |
| 448 extensions::NOTIFICATION_CRX_INSTALLER_DONE, | |
| 449 content::Source<extensions::CrxInstaller>(installer.get())); | |
| 450 | |
| 451 installer->InstallCrx(crx_path); | |
| 452 | |
| 453 observer.Wait(); | |
| 454 } | |
| 455 | |
| 456 } // namespace extensions | |
| OLD | NEW |