|  | // Copyright 2014 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 "base/file_util.h" | 
|  | #include "base/files/file_enumerator.h" | 
|  | #include "base/files/file_path.h" | 
|  | #include "base/prefs/scoped_user_pref_update.h" | 
|  | #include "base/threading/sequenced_worker_pool.h" | 
|  | #include "base/values.h" | 
|  | #include "chrome/browser/extensions/extension_garbage_collector.h" | 
|  | #include "chrome/browser/extensions/extension_service.h" | 
|  | #include "chrome/browser/extensions/extension_service_test_base.h" | 
|  | #include "chrome/browser/extensions/install_tracker.h" | 
|  | #include "chrome/browser/profiles/profile.h" | 
|  | #include "chrome/common/chrome_constants.h" | 
|  | #include "chrome/test/base/testing_profile.h" | 
|  | #include "content/public/browser/browser_thread.h" | 
|  | #include "content/public/browser/plugin_service.h" | 
|  | #include "content/public/test/test_browser_thread_bundle.h" | 
|  | #include "extensions/browser/extension_prefs.h" | 
|  |  | 
|  | namespace extensions { | 
|  |  | 
|  | class ExtensionGarbageCollectorUnitTest : public ExtensionServiceTestBase { | 
|  | protected: | 
|  | void InitPluginService() { | 
|  | #if defined(ENABLE_PLUGINS) | 
|  | content::PluginService::GetInstance()->Init(); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | // Garbage collection, in production, runs on multiple threads. This is | 
|  | // important to test to make sure we don't violate thread safety. Use a real | 
|  | // task runner for these tests. | 
|  | // TODO (rdevlin.cronin): It's kind of hacky that we have to do these things | 
|  | // since we're using ExtensionServiceTestBase. Instead, we should probably do | 
|  | // something like use an options flag, and handle it all in the base class. | 
|  | void InitFileTaskRunner() { | 
|  | service_->SetFileTaskRunnerForTesting( | 
|  | content::BrowserThread::GetBlockingPool() | 
|  | ->GetSequencedTaskRunnerWithShutdownBehavior( | 
|  | content::BrowserThread::GetBlockingPool() | 
|  | ->GetNamedSequenceToken("ext_install-"), | 
|  | base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)); | 
|  |  | 
|  | } | 
|  |  | 
|  | // A delayed task to call GarbageCollectExtensions is posted by | 
|  | // ExtensionGarbageCollector's constructor. But, as the test won't wait for | 
|  | // the delayed task to be called, we have to call it manually instead. | 
|  | void GarbageCollectExtensions() { | 
|  | ExtensionGarbageCollector::Get(profile_.get()) | 
|  | ->GarbageCollectExtensionsForTest(); | 
|  | // Wait for GarbageCollectExtensions task to complete. | 
|  | content::BrowserThread::GetBlockingPool()->FlushForTesting(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | // Test that partially deleted extensions are cleaned up during startup. | 
|  | TEST_F(ExtensionGarbageCollectorUnitTest, CleanupOnStartup) { | 
|  | const std::string kExtensionId = "behllobkkfkfnphdnhnkndlbkcpglgmj"; | 
|  |  | 
|  | InitPluginService(); | 
|  | InitializeGoodInstalledExtensionService(); | 
|  | InitFileTaskRunner(); | 
|  |  | 
|  | // Simulate that one of them got partially deleted by clearing its pref. | 
|  | { | 
|  | DictionaryPrefUpdate update(profile_->GetPrefs(), "extensions.settings"); | 
|  | base::DictionaryValue* dict = update.Get(); | 
|  | ASSERT_TRUE(dict != NULL); | 
|  | dict->Remove(kExtensionId, NULL); | 
|  | } | 
|  |  | 
|  | service_->Init(); | 
|  | GarbageCollectExtensions(); | 
|  |  | 
|  | base::FileEnumerator dirs(extensions_install_dir(), | 
|  | false,  // not recursive | 
|  | base::FileEnumerator::DIRECTORIES); | 
|  | size_t count = 0; | 
|  | while (!dirs.Next().empty()) | 
|  | count++; | 
|  |  | 
|  | // We should have only gotten two extensions now. | 
|  | EXPECT_EQ(2u, count); | 
|  |  | 
|  | // And extension1 dir should now be toast. | 
|  | base::FilePath extension_dir = | 
|  | extensions_install_dir().AppendASCII(kExtensionId); | 
|  | ASSERT_FALSE(base::PathExists(extension_dir)); | 
|  | } | 
|  |  | 
|  | // Test that garbage collection doesn't delete anything while a crx is being | 
|  | // installed. | 
|  | TEST_F(ExtensionGarbageCollectorUnitTest, NoCleanupDuringInstall) { | 
|  | const std::string kExtensionId = "behllobkkfkfnphdnhnkndlbkcpglgmj"; | 
|  |  | 
|  | InitPluginService(); | 
|  | InitializeGoodInstalledExtensionService(); | 
|  | InitFileTaskRunner(); | 
|  |  | 
|  | // Simulate that one of them got partially deleted by clearing its pref. | 
|  | { | 
|  | DictionaryPrefUpdate update(profile_->GetPrefs(), "extensions.settings"); | 
|  | base::DictionaryValue* dict = update.Get(); | 
|  | ASSERT_TRUE(dict != NULL); | 
|  | dict->Remove(kExtensionId, NULL); | 
|  | } | 
|  |  | 
|  | service_->Init(); | 
|  |  | 
|  | // Simulate a CRX installation. | 
|  | InstallTracker::Get(profile_.get())->OnBeginCrxInstall(kExtensionId); | 
|  |  | 
|  | GarbageCollectExtensions(); | 
|  |  | 
|  | // extension1 dir should still exist. | 
|  | base::FilePath extension_dir = | 
|  | extensions_install_dir().AppendASCII(kExtensionId); | 
|  | ASSERT_TRUE(base::PathExists(extension_dir)); | 
|  |  | 
|  | // Finish CRX installation and re-run garbage collection. | 
|  | InstallTracker::Get(profile_.get())->OnFinishCrxInstall(kExtensionId, false); | 
|  | GarbageCollectExtensions(); | 
|  |  | 
|  | // extension1 dir should be gone | 
|  | ASSERT_FALSE(base::PathExists(extension_dir)); | 
|  | } | 
|  |  | 
|  | // Test that GarbageCollectExtensions deletes the right versions of an | 
|  | // extension. | 
|  | TEST_F(ExtensionGarbageCollectorUnitTest, GarbageCollectWithPendingUpdates) { | 
|  | InitPluginService(); | 
|  |  | 
|  | base::FilePath source_install_dir = | 
|  | data_dir().AppendASCII("pending_updates").AppendASCII("Extensions"); | 
|  | base::FilePath pref_path = | 
|  | source_install_dir.DirName().Append(chrome::kPreferencesFilename); | 
|  |  | 
|  | InitializeInstalledExtensionService(pref_path, source_install_dir); | 
|  | InitFileTaskRunner(); | 
|  |  | 
|  | // This is the directory that is going to be deleted, so make sure it actually | 
|  | // is there before the garbage collection. | 
|  | ASSERT_TRUE(base::PathExists(extensions_install_dir().AppendASCII( | 
|  | "hpiknbiabeeppbpihjehijgoemciehgk/3"))); | 
|  |  | 
|  | GarbageCollectExtensions(); | 
|  |  | 
|  | // Verify that the pending update for the first extension didn't get | 
|  | // deleted. | 
|  | EXPECT_TRUE(base::PathExists(extensions_install_dir().AppendASCII( | 
|  | "bjafgdebaacbbbecmhlhpofkepfkgcpa/1.0"))); | 
|  | EXPECT_TRUE(base::PathExists(extensions_install_dir().AppendASCII( | 
|  | "bjafgdebaacbbbecmhlhpofkepfkgcpa/2.0"))); | 
|  | EXPECT_TRUE(base::PathExists(extensions_install_dir().AppendASCII( | 
|  | "hpiknbiabeeppbpihjehijgoemciehgk/2"))); | 
|  | EXPECT_FALSE(base::PathExists(extensions_install_dir().AppendASCII( | 
|  | "hpiknbiabeeppbpihjehijgoemciehgk/3"))); | 
|  | } | 
|  |  | 
|  | // Test that pending updates are properly handled on startup. | 
|  | TEST_F(ExtensionGarbageCollectorUnitTest, UpdateOnStartup) { | 
|  | InitPluginService(); | 
|  |  | 
|  | base::FilePath source_install_dir = | 
|  | data_dir().AppendASCII("pending_updates").AppendASCII("Extensions"); | 
|  | base::FilePath pref_path = | 
|  | source_install_dir.DirName().Append(chrome::kPreferencesFilename); | 
|  |  | 
|  | InitializeInstalledExtensionService(pref_path, source_install_dir); | 
|  | InitFileTaskRunner(); | 
|  |  | 
|  | // This is the directory that is going to be deleted, so make sure it actually | 
|  | // is there before the garbage collection. | 
|  | ASSERT_TRUE(base::PathExists(extensions_install_dir().AppendASCII( | 
|  | "hpiknbiabeeppbpihjehijgoemciehgk/3"))); | 
|  |  | 
|  | service_->Init(); | 
|  | GarbageCollectExtensions(); | 
|  |  | 
|  | // Verify that the pending update for the first extension got installed. | 
|  | EXPECT_FALSE(base::PathExists(extensions_install_dir().AppendASCII( | 
|  | "bjafgdebaacbbbecmhlhpofkepfkgcpa/1.0"))); | 
|  | EXPECT_TRUE(base::PathExists(extensions_install_dir().AppendASCII( | 
|  | "bjafgdebaacbbbecmhlhpofkepfkgcpa/2.0"))); | 
|  | EXPECT_TRUE(base::PathExists(extensions_install_dir().AppendASCII( | 
|  | "hpiknbiabeeppbpihjehijgoemciehgk/2"))); | 
|  | EXPECT_FALSE(base::PathExists(extensions_install_dir().AppendASCII( | 
|  | "hpiknbiabeeppbpihjehijgoemciehgk/3"))); | 
|  |  | 
|  | // Make sure update information got deleted. | 
|  | ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_.get()); | 
|  | EXPECT_FALSE( | 
|  | prefs->GetDelayedInstallInfo("bjafgdebaacbbbecmhlhpofkepfkgcpa")); | 
|  | } | 
|  |  | 
|  | }  // namespace extensions |