OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013 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 // Browser test for basic Chrome OS file manager functionality: | |
6 // - The file list is updated when a file is added externally to the Downloads | |
7 // folder. | |
8 // - Selecting a file and copy-pasting it with the keyboard copies the file. | |
9 // - Selecting a file and pressing delete deletes it. | |
10 | |
11 #include <algorithm> | |
12 #include <string> | |
13 | |
14 #include "base/callback.h" | |
15 #include "base/file_util.h" | |
16 #include "base/files/file_path.h" | |
17 #include "base/files/file_path_watcher.h" | |
18 #include "base/platform_file.h" | |
19 #include "base/threading/platform_thread.h" | |
20 #include "base/time.h" | |
21 #include "base/utf_string_conversions.h" | |
22 #include "chrome/browser/extensions/component_loader.h" | |
23 #include "chrome/browser/extensions/extension_apitest.h" | |
24 #include "chrome/browser/extensions/extension_service.h" | |
25 #include "chrome/browser/extensions/extension_test_message_listener.h" | |
26 #include "chrome/browser/profiles/profile.h" | |
27 #include "chrome/browser/ui/browser_window.h" | |
28 #include "chrome/browser/ui/tabs/tab_strip_model.h" | |
29 #include "chrome/common/chrome_switches.h" | |
30 #include "chrome/common/extensions/extension.h" | |
31 #include "chrome/test/base/ui_test_utils.h" | |
32 #include "chromeos/chromeos_switches.h" | |
33 #include "content/public/browser/browser_context.h" | |
34 #include "content/public/browser/render_view_host.h" | |
35 #include "net/base/escape.h" | |
36 #include "webkit/fileapi/external_mount_points.h" | |
37 | |
38 namespace { | |
39 | |
40 const char kFileManagerExtensionId[] = "hhaomjibdihmijegdhdafkllkbggdgoj"; | |
41 | |
42 const char kKeyboardTestFileName[] = "world.mpeg"; | |
43 const int64 kKeyboardTestFileSize = 1000; | |
44 const char kKeyboardTestFileCopyName[] = "world (1).mpeg"; | |
45 | |
46 // The base test class. Used by FileManagerBrowserLocalTest and | |
47 // FileManagerBrowserDriveTest. | |
48 // TODO(satorux): Add the latter: crbug.com/224534. | |
49 class FileManagerBrowserTestBase : public ExtensionApiTest { | |
50 protected: | |
51 // Loads the file manager extension, navigating it to |directory_path| for | |
52 // testing, and waits for it to finish initializing. This is invoked at the | |
53 // start of each test (it crashes if run in SetUp). | |
54 void StartFileManager(const std::string& directory_path); | |
55 | |
56 // Loads our testing extension and sends it a string identifying the current | |
57 // test. | |
58 void StartTest(const std::string& test_name); | |
59 }; | |
60 | |
61 void FileManagerBrowserTestBase::StartFileManager( | |
62 const std::string& directory_path) { | |
63 std::string file_manager_url = | |
64 (std::string("chrome-extension://") + | |
65 kFileManagerExtensionId + | |
66 "/main.html#" + | |
67 net::EscapeQueryParamValue(directory_path, false /* use_plus */)); | |
68 | |
69 ui_test_utils::NavigateToURL(browser(), GURL(file_manager_url)); | |
70 | |
71 // This is sent by the file manager when it's finished initializing. | |
72 ExtensionTestMessageListener listener("worker-initialized", false); | |
73 ASSERT_TRUE(listener.WaitUntilSatisfied()); | |
74 } | |
75 | |
76 void FileManagerBrowserTestBase::StartTest(const std::string& test_name) { | |
77 base::FilePath path = test_data_dir_.AppendASCII("file_manager_browsertest"); | |
78 const extensions::Extension* extension = LoadExtensionAsComponent(path); | |
79 ASSERT_TRUE(extension); | |
80 | |
81 ExtensionTestMessageListener listener("which test", true); | |
82 ASSERT_TRUE(listener.WaitUntilSatisfied()); | |
83 listener.Reply(test_name); | |
84 } | |
85 | |
86 // The boolean parameter, retrieved by GetParam(), is true if testing in the | |
87 // guest mode. See SetUpCommandLine() below for details. | |
88 class FileManagerBrowserLocalTest : public FileManagerBrowserTestBase, | |
89 public ::testing::WithParamInterface<bool> { | |
90 public: | |
91 virtual void SetUp() OVERRIDE { | |
92 extensions::ComponentLoader::EnableBackgroundExtensionsForTesting(); | |
93 | |
94 ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir()); | |
95 downloads_path_ = tmp_dir_.path().Append("Downloads"); | |
96 ASSERT_TRUE(file_util::CreateDirectory(downloads_path_)); | |
97 | |
98 CreateTestFile("hello.txt", 123, "4 Sep 1998 12:34:56"); | |
99 CreateTestFile("My Desktop Background.png", 1024, "18 Jan 2038 01:02:03"); | |
100 CreateTestFile(kKeyboardTestFileName, kKeyboardTestFileSize, | |
101 "4 July 2012 10:35:00"); | |
102 CreateTestDirectory("photos", "1 Jan 1980 23:59:59"); | |
103 // Files starting with . are filtered out in | |
104 // file_manager/js/directory_contents.js, so this should not be shown. | |
105 CreateTestDirectory(".warez", "26 Oct 1985 13:39"); | |
106 | |
107 ExtensionApiTest::SetUp(); | |
108 } | |
109 | |
110 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { | |
111 bool in_guest_mode = GetParam(); | |
112 if (in_guest_mode) { | |
113 command_line->AppendSwitch(chromeos::switches::kGuestSession); | |
114 command_line->AppendSwitch(switches::kIncognito); | |
115 } | |
116 ExtensionApiTest::SetUpCommandLine(command_line); | |
117 } | |
118 | |
119 protected: | |
120 // Creates a file with the given |name|, |length|, and |modification_time|. | |
121 void CreateTestFile(const std::string& name, | |
122 int length, | |
123 const std::string& modification_time); | |
124 | |
125 // Creates an empty directory with the given |name| and |modification_time|. | |
126 void CreateTestDirectory(const std::string& name, | |
127 const std::string& modification_time); | |
128 | |
129 // Add a mount point to the fake Downloads directory. Should be called | |
130 // before StartFileManager(). | |
131 void AddMountPointToFakeDownloads(); | |
132 | |
133 // Path to the fake Downloads directory used in the test. | |
134 base::FilePath downloads_path_; | |
135 | |
136 private: | |
137 base::ScopedTempDir tmp_dir_; | |
138 }; | |
139 | |
140 INSTANTIATE_TEST_CASE_P(InGuestMode, | |
141 FileManagerBrowserLocalTest, | |
142 ::testing::Values(true)); | |
143 | |
144 INSTANTIATE_TEST_CASE_P(InNonGuestMode, | |
145 FileManagerBrowserLocalTest, | |
146 ::testing::Values(false)); | |
147 | |
148 void FileManagerBrowserLocalTest::CreateTestFile( | |
149 const std::string& name, | |
150 int length, | |
151 const std::string& modification_time) { | |
152 ASSERT_GE(length, 0); | |
153 base::FilePath path = downloads_path_.AppendASCII(name); | |
154 int flags = base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE; | |
155 bool created = false; | |
156 base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED; | |
157 base::PlatformFile file = base::CreatePlatformFile(path, flags, | |
158 &created, &error); | |
159 ASSERT_TRUE(created); | |
160 ASSERT_FALSE(error) << error; | |
161 ASSERT_TRUE(base::TruncatePlatformFile(file, length)); | |
162 ASSERT_TRUE(base::ClosePlatformFile(file)); | |
163 base::Time time; | |
164 ASSERT_TRUE(base::Time::FromString(modification_time.c_str(), &time)); | |
165 ASSERT_TRUE(file_util::SetLastModifiedTime(path, time)); | |
166 } | |
167 | |
168 void FileManagerBrowserLocalTest::CreateTestDirectory( | |
169 const std::string& name, | |
170 const std::string& modification_time) { | |
171 base::FilePath path = downloads_path_.AppendASCII(name); | |
172 ASSERT_TRUE(file_util::CreateDirectory(path)); | |
173 base::Time time; | |
174 ASSERT_TRUE(base::Time::FromString(modification_time.c_str(), &time)); | |
175 ASSERT_TRUE(file_util::SetLastModifiedTime(path, time)); | |
176 } | |
177 | |
178 void FileManagerBrowserLocalTest::AddMountPointToFakeDownloads() { | |
179 // Install our fake Downloads mount point first. | |
180 fileapi::ExternalMountPoints* mount_points = | |
181 content::BrowserContext::GetMountPoints(profile()); | |
182 ASSERT_TRUE(mount_points->RevokeFileSystem("Downloads")); | |
183 ASSERT_TRUE(mount_points->RegisterFileSystem( | |
184 "Downloads", fileapi::kFileSystemTypeNativeLocal, downloads_path_)); | |
185 } | |
186 | |
187 // Monitors changes to a single file until the supplied condition callback | |
188 // returns true. Usage: | |
189 // TestFilePathWatcher watcher(path_to_file, MyConditionCallback); | |
190 // watcher.StartAndWaitUntilReady(); | |
191 // ... trigger filesystem modification ... | |
192 // watcher.RunMessageLoopUntilConditionSatisfied(); | |
193 class TestFilePathWatcher { | |
194 public: | |
195 typedef base::Callback<bool(const base::FilePath& file_path)> | |
196 ConditionCallback; | |
197 | |
198 // Stores the supplied |path| and |condition| for later use (no side effects). | |
199 TestFilePathWatcher(const base::FilePath& path, | |
200 const ConditionCallback& condition); | |
201 | |
202 // Waits (running a message pump) until the callback returns true or | |
203 // FilePathWatcher reports an error. Return true on success. | |
204 bool RunMessageLoopUntilConditionSatisfied(); | |
205 | |
206 private: | |
207 // Starts the FilePathWatcher to watch the target file. Also check if the | |
208 // condition is already met. | |
209 void StartWatching(); | |
210 | |
211 // FilePathWatcher callback (on the FILE thread). Posts Done() to the UI | |
212 // thread when the condition is satisfied or there is an error. | |
213 void FilePathWatcherCallback(const base::FilePath& path, bool error); | |
214 | |
215 const base::FilePath path_; | |
216 ConditionCallback condition_; | |
217 scoped_ptr<base::FilePathWatcher> watcher_; | |
218 base::RunLoop run_loop_; | |
219 base::Closure quit_closure_; | |
220 bool failed_; | |
221 }; | |
222 | |
223 TestFilePathWatcher::TestFilePathWatcher(const base::FilePath& path, | |
224 const ConditionCallback& condition) | |
225 : path_(path), | |
226 condition_(condition), | |
227 quit_closure_(run_loop_.QuitClosure()), | |
228 failed_(false) { | |
229 } | |
230 | |
231 void TestFilePathWatcher::StartWatching() { | |
232 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); | |
233 | |
234 watcher_.reset(new base::FilePathWatcher); | |
235 bool ok = watcher_->Watch( | |
236 path_, false /*recursive*/, | |
237 base::Bind(&TestFilePathWatcher::FilePathWatcherCallback, | |
238 base::Unretained(this))); | |
239 DCHECK(ok); | |
240 | |
241 // If the condition was already met before FilePathWatcher was launched, | |
242 // FilePathWatcher won't be able to detect a change, so check the condition | |
243 // here. | |
244 if (condition_.Run(path_)) { | |
245 watcher_.reset(); | |
246 content::BrowserThread::PostTask(content::BrowserThread::UI, | |
247 FROM_HERE, | |
248 quit_closure_); | |
249 return; | |
250 } | |
251 } | |
252 | |
253 void TestFilePathWatcher::FilePathWatcherCallback(const base::FilePath& path, | |
254 bool failed) { | |
255 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); | |
256 DCHECK_EQ(path_.value(), path.value()); | |
257 | |
258 if (failed || condition_.Run(path)) { | |
259 failed_ = failed; | |
260 watcher_.reset(); | |
261 content::BrowserThread::PostTask(content::BrowserThread::UI, | |
262 FROM_HERE, | |
263 quit_closure_); | |
264 } | |
265 } | |
266 | |
267 bool TestFilePathWatcher::RunMessageLoopUntilConditionSatisfied() { | |
268 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
269 | |
270 content::BrowserThread::PostTask( | |
271 content::BrowserThread::FILE, | |
272 FROM_HERE, | |
273 base::Bind(&TestFilePathWatcher::StartWatching, | |
274 base::Unretained(this))); | |
275 | |
276 // Wait until the condition is met. | |
277 run_loop_.Run(); | |
278 return !failed_; | |
279 } | |
280 | |
281 // Returns true if a file with the given size is present at |path|. | |
282 bool FilePresentWithSize(const int64 file_size, | |
283 const base::FilePath& path) { | |
284 int64 copy_size = 0; | |
285 // If the file doesn't exist yet this will fail and we'll keep waiting. | |
286 if (!file_util::GetFileSize(path, ©_size)) | |
287 return false; | |
288 return (copy_size == file_size); | |
289 } | |
290 | |
291 // Returns true if a file is not present at |path|. | |
292 bool FileNotPresent(const base::FilePath& path) { | |
293 return !file_util::PathExists(path); | |
294 }; | |
295 | |
296 IN_PROC_BROWSER_TEST_P(FileManagerBrowserLocalTest, TestFileDisplay) { | |
297 AddMountPointToFakeDownloads(); | |
298 StartFileManager("/Downloads"); | |
299 | |
300 ResultCatcher catcher; | |
301 | |
302 StartTest("file display"); | |
303 | |
304 ExtensionTestMessageListener listener("initial check done", true); | |
305 ASSERT_TRUE(listener.WaitUntilSatisfied()); | |
306 CreateTestFile("newly added file.mp3", 2000, "4 Sep 1998 00:00:00"); | |
307 listener.Reply("file added"); | |
308 | |
309 ASSERT_TRUE(catcher.GetNextResult()) << catcher.message(); | |
310 } | |
311 | |
312 IN_PROC_BROWSER_TEST_P(FileManagerBrowserLocalTest, TestKeyboardCopy) { | |
313 AddMountPointToFakeDownloads(); | |
314 StartFileManager("/Downloads"); | |
315 | |
316 base::FilePath copy_path = | |
317 downloads_path_.AppendASCII(kKeyboardTestFileCopyName); | |
318 ASSERT_FALSE(file_util::PathExists(copy_path)); | |
319 | |
320 ResultCatcher catcher; | |
321 StartTest("keyboard copy"); | |
322 | |
323 ASSERT_TRUE(catcher.GetNextResult()) << catcher.message(); | |
324 | |
325 TestFilePathWatcher watcher( | |
326 copy_path, | |
327 base::Bind(FilePresentWithSize, kKeyboardTestFileSize)); | |
328 ASSERT_TRUE(watcher.RunMessageLoopUntilConditionSatisfied()); | |
329 | |
330 // Check that it was a copy, not a move. | |
331 base::FilePath source_path = | |
332 downloads_path_.AppendASCII(kKeyboardTestFileName); | |
333 ASSERT_TRUE(file_util::PathExists(source_path)); | |
334 } | |
335 | |
336 IN_PROC_BROWSER_TEST_P(FileManagerBrowserLocalTest, TestKeyboardDelete) { | |
337 AddMountPointToFakeDownloads(); | |
338 StartFileManager("/Downloads"); | |
339 | |
340 base::FilePath delete_path = | |
341 downloads_path_.AppendASCII(kKeyboardTestFileName); | |
342 ASSERT_TRUE(file_util::PathExists(delete_path)); | |
343 | |
344 ResultCatcher catcher; | |
345 StartTest("keyboard delete"); | |
346 ASSERT_TRUE(catcher.GetNextResult()) << catcher.message(); | |
347 | |
348 TestFilePathWatcher watcher(delete_path, | |
349 base::Bind(FileNotPresent)); | |
350 ASSERT_TRUE(watcher.RunMessageLoopUntilConditionSatisfied()); | |
351 } | |
352 | |
353 } // namespace | |
OLD | NEW |