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
23namespace extensions {
24
25class 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.
61TEST_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.
97TEST_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.
134TEST_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.
165TEST_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