1 // Copyright 2014 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 "base/files/file_enumerator.h" 6 #include "base/files/file_path.h" 7 #include "base/files/file_util.h" 8 #include "base/prefs/scoped_user_pref_update.h" 9 #include "base/threading/sequenced_worker_pool.h" 10 #include "base/values.h" 11 #include "chrome/browser/extensions/extension_garbage_collector.h" 12 #include "chrome/browser/extensions/extension_service.h" 13 #include "chrome/browser/extensions/extension_service_test_base.h" 14 #include "chrome/browser/extensions/install_tracker.h" 15 #include "chrome/browser/profiles/profile.h" 16 #include "chrome/common/chrome_constants.h" 17 #include "chrome/test/base/testing_profile.h" 18 #include "content/public/browser/browser_thread.h" 19 #include "content/public/browser/plugin_service.h" 20 #include "content/public/test/test_browser_thread_bundle.h" 21 #include "extensions/browser/extension_prefs.h" 22 23 namespace extensions { 24 25 class ExtensionGarbageCollectorUnitTest : public ExtensionServiceTestBase { 26 protected: 27 void InitPluginService() { 28 #if defined(ENABLE_PLUGINS) 29 content::PluginService::GetInstance()->Init(); 30 #endif 31 } 32 33 // Garbage collection, in production, runs on multiple threads. This is 34 // important to test to make sure we don't violate thread safety. Use a real 35 // task runner for these tests. 36 // TODO (rdevlin.cronin): It's kind of hacky that we have to do these things 37 // since we're using ExtensionServiceTestBase. Instead, we should probably do 38 // something like use an options flag, and handle it all in the base class. 39 void InitFileTaskRunner() { 40 service_->SetFileTaskRunnerForTesting( 41 content::BrowserThread::GetBlockingPool() 42 ->GetSequencedTaskRunnerWithShutdownBehavior( 43 content::BrowserThread::GetBlockingPool() 44 ->GetNamedSequenceToken("ext_install-"), 45 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)); 46 47 } 48 49 // A delayed task to call GarbageCollectExtensions is posted by 50 // ExtensionGarbageCollector's constructor. But, as the test won't wait for 51 // the delayed task to be called, we have to call it manually instead. 52 void GarbageCollectExtensions() { 53 ExtensionGarbageCollector::Get(profile_.get()) 54 ->GarbageCollectExtensionsForTest(); 55 // Wait for GarbageCollectExtensions task to complete. 56 content::BrowserThread::GetBlockingPool()->FlushForTesting(); 57 } 58 }; 59 60 // Test that partially deleted extensions are cleaned up during startup. 61 TEST_F(ExtensionGarbageCollectorUnitTest, CleanupOnStartup) { 62 const std::string kExtensionId = "behllobkkfkfnphdnhnkndlbkcpglgmj"; 63 64 InitPluginService(); 65 InitializeGoodInstalledExtensionService(); 66 InitFileTaskRunner(); 67 68 // Simulate that one of them got partially deleted by clearing its pref. 69 { 70 DictionaryPrefUpdate update(profile_->GetPrefs(), "extensions.settings"); 71 base::DictionaryValue* dict = update.Get(); 72 ASSERT_TRUE(dict != NULL); 73 dict->Remove(kExtensionId, NULL); 74 } 75 76 service_->Init(); 77 GarbageCollectExtensions(); 78 79 base::FileEnumerator dirs(extensions_install_dir(), 80 false, // not recursive 81 base::FileEnumerator::DIRECTORIES); 82 size_t count = 0; 83 while (!dirs.Next().empty()) 84 count++; 85 86 // We should have only gotten two extensions now. 87 EXPECT_EQ(2u, count); 88 89 // And extension1 dir should now be toast. 90 base::FilePath extension_dir = 91 extensions_install_dir().AppendASCII(kExtensionId); 92 ASSERT_FALSE(base::PathExists(extension_dir)); 93 } 94 95 // Test that garbage collection doesn't delete anything while a crx is being 96 // installed. 97 TEST_F(ExtensionGarbageCollectorUnitTest, NoCleanupDuringInstall) { 98 const std::string kExtensionId = "behllobkkfkfnphdnhnkndlbkcpglgmj"; 99 100 InitPluginService(); 101 InitializeGoodInstalledExtensionService(); 102 InitFileTaskRunner(); 103 104 // Simulate that one of them got partially deleted by clearing its pref. 105 { 106 DictionaryPrefUpdate update(profile_->GetPrefs(), "extensions.settings"); 107 base::DictionaryValue* dict = update.Get(); 108 ASSERT_TRUE(dict != NULL); 109 dict->Remove(kExtensionId, NULL); 110 } 111 112 service_->Init(); 113 114 // Simulate a CRX installation. 115 InstallTracker::Get(profile_.get())->OnBeginCrxInstall(kExtensionId); 116 117 GarbageCollectExtensions(); 118 119 // extension1 dir should still exist. 120 base::FilePath extension_dir = 121 extensions_install_dir().AppendASCII(kExtensionId); 122 ASSERT_TRUE(base::PathExists(extension_dir)); 123 124 // Finish CRX installation and re-run garbage collection. 125 InstallTracker::Get(profile_.get())->OnFinishCrxInstall(kExtensionId, false); 126 GarbageCollectExtensions(); 127 128 // extension1 dir should be gone 129 ASSERT_FALSE(base::PathExists(extension_dir)); 130 } 131 132 // Test that GarbageCollectExtensions deletes the right versions of an 133 // extension. 134 TEST_F(ExtensionGarbageCollectorUnitTest, GarbageCollectWithPendingUpdates) { 135 InitPluginService(); 136 137 base::FilePath source_install_dir = 138 data_dir().AppendASCII("pending_updates").AppendASCII("Extensions"); 139 base::FilePath pref_path = 140 source_install_dir.DirName().Append(chrome::kPreferencesFilename); 141 142 InitializeInstalledExtensionService(pref_path, source_install_dir); 143 InitFileTaskRunner(); 144 145 // This is the directory that is going to be deleted, so make sure it actually 146 // is there before the garbage collection. 147 ASSERT_TRUE(base::PathExists(extensions_install_dir().AppendASCII( 148 "hpiknbiabeeppbpihjehijgoemciehgk/3"))); 149 150 GarbageCollectExtensions(); 151 152 // Verify that the pending update for the first extension didn't get 153 // deleted. 154 EXPECT_TRUE(base::PathExists(extensions_install_dir().AppendASCII( 155 "bjafgdebaacbbbecmhlhpofkepfkgcpa/1.0"))); 156 EXPECT_TRUE(base::PathExists(extensions_install_dir().AppendASCII( 157 "bjafgdebaacbbbecmhlhpofkepfkgcpa/2.0"))); 158 EXPECT_TRUE(base::PathExists(extensions_install_dir().AppendASCII( 159 "hpiknbiabeeppbpihjehijgoemciehgk/2"))); 160 EXPECT_FALSE(base::PathExists(extensions_install_dir().AppendASCII( 161 "hpiknbiabeeppbpihjehijgoemciehgk/3"))); 162 } 163 164 // Test that pending updates are properly handled on startup. 165 TEST_F(ExtensionGarbageCollectorUnitTest, UpdateOnStartup) { 166 InitPluginService(); 167 168 base::FilePath source_install_dir = 169 data_dir().AppendASCII("pending_updates").AppendASCII("Extensions"); 170 base::FilePath pref_path = 171 source_install_dir.DirName().Append(chrome::kPreferencesFilename); 172 173 InitializeInstalledExtensionService(pref_path, source_install_dir); 174 InitFileTaskRunner(); 175 176 // This is the directory that is going to be deleted, so make sure it actually 177 // is there before the garbage collection. 178 ASSERT_TRUE(base::PathExists(extensions_install_dir().AppendASCII( 179 "hpiknbiabeeppbpihjehijgoemciehgk/3"))); 180 181 service_->Init(); 182 GarbageCollectExtensions(); 183 184 // Verify that the pending update for the first extension got installed. 185 EXPECT_FALSE(base::PathExists(extensions_install_dir().AppendASCII( 186 "bjafgdebaacbbbecmhlhpofkepfkgcpa/1.0"))); 187 EXPECT_TRUE(base::PathExists(extensions_install_dir().AppendASCII( 188 "bjafgdebaacbbbecmhlhpofkepfkgcpa/2.0"))); 189 EXPECT_TRUE(base::PathExists(extensions_install_dir().AppendASCII( 190 "hpiknbiabeeppbpihjehijgoemciehgk/2"))); 191 EXPECT_FALSE(base::PathExists(extensions_install_dir().AppendASCII( 192 "hpiknbiabeeppbpihjehijgoemciehgk/3"))); 193 194 // Make sure update information got deleted. 195 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_.get()); 196 EXPECT_FALSE( 197 prefs->GetDelayedInstallInfo("bjafgdebaacbbbecmhlhpofkepfkgcpa")); 198 } 199 200 } // namespace extensions 201