OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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_path_watcher.h" | |
6 | |
7 #if defined(OS_WIN) | |
8 #include <windows.h> | |
9 #include <aclapi.h> | |
10 #elif defined(OS_POSIX) | |
11 #include <sys/stat.h> | |
12 #endif | |
13 | |
14 #include <set> | |
15 | |
16 #include "base/basictypes.h" | |
17 #include "base/bind.h" | |
18 #include "base/bind_helpers.h" | |
19 #include "base/compiler_specific.h" | |
20 #include "base/files/file_path.h" | |
21 #include "base/files/file_util.h" | |
22 #include "base/files/scoped_temp_dir.h" | |
23 #include "base/message_loop/message_loop.h" | |
24 #include "base/message_loop/message_loop_proxy.h" | |
25 #include "base/run_loop.h" | |
26 #include "base/stl_util.h" | |
27 #include "base/strings/stringprintf.h" | |
28 #include "base/synchronization/waitable_event.h" | |
29 #include "base/test/test_file_util.h" | |
30 #include "base/test/test_timeouts.h" | |
31 #include "base/threading/thread.h" | |
32 #include "testing/gtest/include/gtest/gtest.h" | |
33 | |
34 namespace base { | |
35 | |
36 namespace { | |
37 | |
38 class TestDelegate; | |
39 | |
40 // Aggregates notifications from the test delegates and breaks the message loop | |
41 // the test thread is waiting on once they all came in. | |
42 class NotificationCollector | |
43 : public base::RefCountedThreadSafe<NotificationCollector> { | |
44 public: | |
45 NotificationCollector() | |
46 : loop_(base::MessageLoopProxy::current()) {} | |
47 | |
48 // Called from the file thread by the delegates. | |
49 void OnChange(TestDelegate* delegate) { | |
50 loop_->PostTask(FROM_HERE, | |
51 base::Bind(&NotificationCollector::RecordChange, this, | |
52 base::Unretained(delegate))); | |
53 } | |
54 | |
55 void Register(TestDelegate* delegate) { | |
56 delegates_.insert(delegate); | |
57 } | |
58 | |
59 void Reset() { | |
60 signaled_.clear(); | |
61 } | |
62 | |
63 bool Success() { | |
64 return signaled_ == delegates_; | |
65 } | |
66 | |
67 private: | |
68 friend class base::RefCountedThreadSafe<NotificationCollector>; | |
69 ~NotificationCollector() {} | |
70 | |
71 void RecordChange(TestDelegate* delegate) { | |
72 // Warning: |delegate| is Unretained. Do not dereference. | |
73 ASSERT_TRUE(loop_->BelongsToCurrentThread()); | |
74 ASSERT_TRUE(delegates_.count(delegate)); | |
75 signaled_.insert(delegate); | |
76 | |
77 // Check whether all delegates have been signaled. | |
78 if (signaled_ == delegates_) | |
79 loop_->PostTask(FROM_HERE, MessageLoop::QuitWhenIdleClosure()); | |
80 } | |
81 | |
82 // Set of registered delegates. | |
83 std::set<TestDelegate*> delegates_; | |
84 | |
85 // Set of signaled delegates. | |
86 std::set<TestDelegate*> signaled_; | |
87 | |
88 // The loop we should break after all delegates signaled. | |
89 scoped_refptr<base::MessageLoopProxy> loop_; | |
90 }; | |
91 | |
92 class TestDelegateBase : public SupportsWeakPtr<TestDelegateBase> { | |
93 public: | |
94 TestDelegateBase() {} | |
95 virtual ~TestDelegateBase() {} | |
96 | |
97 virtual void OnFileChanged(const FilePath& path, bool error) = 0; | |
98 | |
99 private: | |
100 DISALLOW_COPY_AND_ASSIGN(TestDelegateBase); | |
101 }; | |
102 | |
103 // A mock class for testing. Gmock is not appropriate because it is not | |
104 // thread-safe for setting expectations. Thus the test code cannot safely | |
105 // reset expectations while the file watcher is running. | |
106 // Instead, TestDelegate gets the notifications from FilePathWatcher and uses | |
107 // NotificationCollector to aggregate the results. | |
108 class TestDelegate : public TestDelegateBase { | |
109 public: | |
110 explicit TestDelegate(NotificationCollector* collector) | |
111 : collector_(collector) { | |
112 collector_->Register(this); | |
113 } | |
114 ~TestDelegate() override {} | |
115 | |
116 void OnFileChanged(const FilePath& path, bool error) override { | |
117 if (error) | |
118 ADD_FAILURE() << "Error " << path.value(); | |
119 else | |
120 collector_->OnChange(this); | |
121 } | |
122 | |
123 private: | |
124 scoped_refptr<NotificationCollector> collector_; | |
125 | |
126 DISALLOW_COPY_AND_ASSIGN(TestDelegate); | |
127 }; | |
128 | |
129 void SetupWatchCallback(const FilePath& target, | |
130 FilePathWatcher* watcher, | |
131 TestDelegateBase* delegate, | |
132 bool recursive_watch, | |
133 bool* result, | |
134 base::WaitableEvent* completion) { | |
135 *result = watcher->Watch(target, recursive_watch, | |
136 base::Bind(&TestDelegateBase::OnFileChanged, | |
137 delegate->AsWeakPtr())); | |
138 completion->Signal(); | |
139 } | |
140 | |
141 class FilePathWatcherTest : public testing::Test { | |
142 public: | |
143 FilePathWatcherTest() | |
144 : file_thread_("FilePathWatcherTest") {} | |
145 | |
146 ~FilePathWatcherTest() override {} | |
147 | |
148 protected: | |
149 void SetUp() override { | |
150 // Create a separate file thread in order to test proper thread usage. | |
151 base::Thread::Options options(MessageLoop::TYPE_IO, 0); | |
152 ASSERT_TRUE(file_thread_.StartWithOptions(options)); | |
153 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); | |
154 collector_ = new NotificationCollector(); | |
155 } | |
156 | |
157 void TearDown() override { RunLoop().RunUntilIdle(); } | |
158 | |
159 void DeleteDelegateOnFileThread(TestDelegate* delegate) { | |
160 file_thread_.message_loop_proxy()->DeleteSoon(FROM_HERE, delegate); | |
161 } | |
162 | |
163 FilePath test_file() { | |
164 return temp_dir_.path().AppendASCII("FilePathWatcherTest"); | |
165 } | |
166 | |
167 FilePath test_link() { | |
168 return temp_dir_.path().AppendASCII("FilePathWatcherTest.lnk"); | |
169 } | |
170 | |
171 // Write |content| to |file|. Returns true on success. | |
172 bool WriteFile(const FilePath& file, const std::string& content) { | |
173 int write_size = ::base::WriteFile(file, content.c_str(), content.length()); | |
174 return write_size == static_cast<int>(content.length()); | |
175 } | |
176 | |
177 bool SetupWatch(const FilePath& target, | |
178 FilePathWatcher* watcher, | |
179 TestDelegateBase* delegate, | |
180 bool recursive_watch) WARN_UNUSED_RESULT; | |
181 | |
182 bool WaitForEvents() WARN_UNUSED_RESULT { | |
183 collector_->Reset(); | |
184 loop_.Run(); | |
185 return collector_->Success(); | |
186 } | |
187 | |
188 NotificationCollector* collector() { return collector_.get(); } | |
189 | |
190 MessageLoop loop_; | |
191 base::Thread file_thread_; | |
192 ScopedTempDir temp_dir_; | |
193 scoped_refptr<NotificationCollector> collector_; | |
194 | |
195 private: | |
196 DISALLOW_COPY_AND_ASSIGN(FilePathWatcherTest); | |
197 }; | |
198 | |
199 bool FilePathWatcherTest::SetupWatch(const FilePath& target, | |
200 FilePathWatcher* watcher, | |
201 TestDelegateBase* delegate, | |
202 bool recursive_watch) { | |
203 base::WaitableEvent completion(false, false); | |
204 bool result; | |
205 file_thread_.message_loop_proxy()->PostTask( | |
206 FROM_HERE, | |
207 base::Bind(SetupWatchCallback, target, watcher, delegate, recursive_watch, | |
208 &result, &completion)); | |
209 completion.Wait(); | |
210 return result; | |
211 } | |
212 | |
213 // Basic test: Create the file and verify that we notice. | |
214 TEST_F(FilePathWatcherTest, NewFile) { | |
215 FilePathWatcher watcher; | |
216 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); | |
217 ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false)); | |
218 | |
219 ASSERT_TRUE(WriteFile(test_file(), "content")); | |
220 ASSERT_TRUE(WaitForEvents()); | |
221 DeleteDelegateOnFileThread(delegate.release()); | |
222 } | |
223 | |
224 // Verify that modifying the file is caught. | |
225 TEST_F(FilePathWatcherTest, ModifiedFile) { | |
226 ASSERT_TRUE(WriteFile(test_file(), "content")); | |
227 | |
228 FilePathWatcher watcher; | |
229 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); | |
230 ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false)); | |
231 | |
232 // Now make sure we get notified if the file is modified. | |
233 ASSERT_TRUE(WriteFile(test_file(), "new content")); | |
234 ASSERT_TRUE(WaitForEvents()); | |
235 DeleteDelegateOnFileThread(delegate.release()); | |
236 } | |
237 | |
238 // Verify that moving the file into place is caught. | |
239 TEST_F(FilePathWatcherTest, MovedFile) { | |
240 FilePath source_file(temp_dir_.path().AppendASCII("source")); | |
241 ASSERT_TRUE(WriteFile(source_file, "content")); | |
242 | |
243 FilePathWatcher watcher; | |
244 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); | |
245 ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false)); | |
246 | |
247 // Now make sure we get notified if the file is modified. | |
248 ASSERT_TRUE(base::Move(source_file, test_file())); | |
249 ASSERT_TRUE(WaitForEvents()); | |
250 DeleteDelegateOnFileThread(delegate.release()); | |
251 } | |
252 | |
253 TEST_F(FilePathWatcherTest, DeletedFile) { | |
254 ASSERT_TRUE(WriteFile(test_file(), "content")); | |
255 | |
256 FilePathWatcher watcher; | |
257 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); | |
258 ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false)); | |
259 | |
260 // Now make sure we get notified if the file is deleted. | |
261 base::DeleteFile(test_file(), false); | |
262 ASSERT_TRUE(WaitForEvents()); | |
263 DeleteDelegateOnFileThread(delegate.release()); | |
264 } | |
265 | |
266 // Used by the DeleteDuringNotify test below. | |
267 // Deletes the FilePathWatcher when it's notified. | |
268 class Deleter : public TestDelegateBase { | |
269 public: | |
270 Deleter(FilePathWatcher* watcher, MessageLoop* loop) | |
271 : watcher_(watcher), | |
272 loop_(loop) { | |
273 } | |
274 ~Deleter() override {} | |
275 | |
276 void OnFileChanged(const FilePath&, bool) override { | |
277 watcher_.reset(); | |
278 loop_->PostTask(FROM_HERE, MessageLoop::QuitWhenIdleClosure()); | |
279 } | |
280 | |
281 FilePathWatcher* watcher() const { return watcher_.get(); } | |
282 | |
283 private: | |
284 scoped_ptr<FilePathWatcher> watcher_; | |
285 MessageLoop* loop_; | |
286 | |
287 DISALLOW_COPY_AND_ASSIGN(Deleter); | |
288 }; | |
289 | |
290 // Verify that deleting a watcher during the callback doesn't crash. | |
291 TEST_F(FilePathWatcherTest, DeleteDuringNotify) { | |
292 FilePathWatcher* watcher = new FilePathWatcher; | |
293 // Takes ownership of watcher. | |
294 scoped_ptr<Deleter> deleter(new Deleter(watcher, &loop_)); | |
295 ASSERT_TRUE(SetupWatch(test_file(), watcher, deleter.get(), false)); | |
296 | |
297 ASSERT_TRUE(WriteFile(test_file(), "content")); | |
298 ASSERT_TRUE(WaitForEvents()); | |
299 | |
300 // We win if we haven't crashed yet. | |
301 // Might as well double-check it got deleted, too. | |
302 ASSERT_TRUE(deleter->watcher() == NULL); | |
303 } | |
304 | |
305 // Verify that deleting the watcher works even if there is a pending | |
306 // notification. | |
307 // Flaky on MacOS (and ARM linux): http://crbug.com/85930 | |
308 TEST_F(FilePathWatcherTest, DISABLED_DestroyWithPendingNotification) { | |
309 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); | |
310 FilePathWatcher* watcher = new FilePathWatcher; | |
311 ASSERT_TRUE(SetupWatch(test_file(), watcher, delegate.get(), false)); | |
312 ASSERT_TRUE(WriteFile(test_file(), "content")); | |
313 file_thread_.message_loop_proxy()->DeleteSoon(FROM_HERE, watcher); | |
314 DeleteDelegateOnFileThread(delegate.release()); | |
315 } | |
316 | |
317 TEST_F(FilePathWatcherTest, MultipleWatchersSingleFile) { | |
318 FilePathWatcher watcher1, watcher2; | |
319 scoped_ptr<TestDelegate> delegate1(new TestDelegate(collector())); | |
320 scoped_ptr<TestDelegate> delegate2(new TestDelegate(collector())); | |
321 ASSERT_TRUE(SetupWatch(test_file(), &watcher1, delegate1.get(), false)); | |
322 ASSERT_TRUE(SetupWatch(test_file(), &watcher2, delegate2.get(), false)); | |
323 | |
324 ASSERT_TRUE(WriteFile(test_file(), "content")); | |
325 ASSERT_TRUE(WaitForEvents()); | |
326 DeleteDelegateOnFileThread(delegate1.release()); | |
327 DeleteDelegateOnFileThread(delegate2.release()); | |
328 } | |
329 | |
330 // Verify that watching a file whose parent directory doesn't exist yet works if | |
331 // the directory and file are created eventually. | |
332 TEST_F(FilePathWatcherTest, NonExistentDirectory) { | |
333 FilePathWatcher watcher; | |
334 FilePath dir(temp_dir_.path().AppendASCII("dir")); | |
335 FilePath file(dir.AppendASCII("file")); | |
336 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); | |
337 ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get(), false)); | |
338 | |
339 ASSERT_TRUE(base::CreateDirectory(dir)); | |
340 | |
341 ASSERT_TRUE(WriteFile(file, "content")); | |
342 | |
343 VLOG(1) << "Waiting for file creation"; | |
344 ASSERT_TRUE(WaitForEvents()); | |
345 | |
346 ASSERT_TRUE(WriteFile(file, "content v2")); | |
347 VLOG(1) << "Waiting for file change"; | |
348 ASSERT_TRUE(WaitForEvents()); | |
349 | |
350 ASSERT_TRUE(base::DeleteFile(file, false)); | |
351 VLOG(1) << "Waiting for file deletion"; | |
352 ASSERT_TRUE(WaitForEvents()); | |
353 DeleteDelegateOnFileThread(delegate.release()); | |
354 } | |
355 | |
356 // Exercises watch reconfiguration for the case that directories on the path | |
357 // are rapidly created. | |
358 TEST_F(FilePathWatcherTest, DirectoryChain) { | |
359 FilePath path(temp_dir_.path()); | |
360 std::vector<std::string> dir_names; | |
361 for (int i = 0; i < 20; i++) { | |
362 std::string dir(base::StringPrintf("d%d", i)); | |
363 dir_names.push_back(dir); | |
364 path = path.AppendASCII(dir); | |
365 } | |
366 | |
367 FilePathWatcher watcher; | |
368 FilePath file(path.AppendASCII("file")); | |
369 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); | |
370 ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get(), false)); | |
371 | |
372 FilePath sub_path(temp_dir_.path()); | |
373 for (std::vector<std::string>::const_iterator d(dir_names.begin()); | |
374 d != dir_names.end(); ++d) { | |
375 sub_path = sub_path.AppendASCII(*d); | |
376 ASSERT_TRUE(base::CreateDirectory(sub_path)); | |
377 } | |
378 VLOG(1) << "Create File"; | |
379 ASSERT_TRUE(WriteFile(file, "content")); | |
380 VLOG(1) << "Waiting for file creation"; | |
381 ASSERT_TRUE(WaitForEvents()); | |
382 | |
383 ASSERT_TRUE(WriteFile(file, "content v2")); | |
384 VLOG(1) << "Waiting for file modification"; | |
385 ASSERT_TRUE(WaitForEvents()); | |
386 DeleteDelegateOnFileThread(delegate.release()); | |
387 } | |
388 | |
389 #if defined(OS_MACOSX) | |
390 // http://crbug.com/85930 | |
391 #define DisappearingDirectory DISABLED_DisappearingDirectory | |
392 #endif | |
393 TEST_F(FilePathWatcherTest, DisappearingDirectory) { | |
394 FilePathWatcher watcher; | |
395 FilePath dir(temp_dir_.path().AppendASCII("dir")); | |
396 FilePath file(dir.AppendASCII("file")); | |
397 ASSERT_TRUE(base::CreateDirectory(dir)); | |
398 ASSERT_TRUE(WriteFile(file, "content")); | |
399 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); | |
400 ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get(), false)); | |
401 | |
402 ASSERT_TRUE(base::DeleteFile(dir, true)); | |
403 ASSERT_TRUE(WaitForEvents()); | |
404 DeleteDelegateOnFileThread(delegate.release()); | |
405 } | |
406 | |
407 // Tests that a file that is deleted and reappears is tracked correctly. | |
408 TEST_F(FilePathWatcherTest, DeleteAndRecreate) { | |
409 ASSERT_TRUE(WriteFile(test_file(), "content")); | |
410 FilePathWatcher watcher; | |
411 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); | |
412 ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false)); | |
413 | |
414 ASSERT_TRUE(base::DeleteFile(test_file(), false)); | |
415 VLOG(1) << "Waiting for file deletion"; | |
416 ASSERT_TRUE(WaitForEvents()); | |
417 | |
418 ASSERT_TRUE(WriteFile(test_file(), "content")); | |
419 VLOG(1) << "Waiting for file creation"; | |
420 ASSERT_TRUE(WaitForEvents()); | |
421 DeleteDelegateOnFileThread(delegate.release()); | |
422 } | |
423 | |
424 TEST_F(FilePathWatcherTest, WatchDirectory) { | |
425 FilePathWatcher watcher; | |
426 FilePath dir(temp_dir_.path().AppendASCII("dir")); | |
427 FilePath file1(dir.AppendASCII("file1")); | |
428 FilePath file2(dir.AppendASCII("file2")); | |
429 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); | |
430 ASSERT_TRUE(SetupWatch(dir, &watcher, delegate.get(), false)); | |
431 | |
432 ASSERT_TRUE(base::CreateDirectory(dir)); | |
433 VLOG(1) << "Waiting for directory creation"; | |
434 ASSERT_TRUE(WaitForEvents()); | |
435 | |
436 ASSERT_TRUE(WriteFile(file1, "content")); | |
437 VLOG(1) << "Waiting for file1 creation"; | |
438 ASSERT_TRUE(WaitForEvents()); | |
439 | |
440 #if !defined(OS_MACOSX) | |
441 // Mac implementation does not detect files modified in a directory. | |
442 ASSERT_TRUE(WriteFile(file1, "content v2")); | |
443 VLOG(1) << "Waiting for file1 modification"; | |
444 ASSERT_TRUE(WaitForEvents()); | |
445 #endif // !OS_MACOSX | |
446 | |
447 ASSERT_TRUE(base::DeleteFile(file1, false)); | |
448 VLOG(1) << "Waiting for file1 deletion"; | |
449 ASSERT_TRUE(WaitForEvents()); | |
450 | |
451 ASSERT_TRUE(WriteFile(file2, "content")); | |
452 VLOG(1) << "Waiting for file2 creation"; | |
453 ASSERT_TRUE(WaitForEvents()); | |
454 DeleteDelegateOnFileThread(delegate.release()); | |
455 } | |
456 | |
457 TEST_F(FilePathWatcherTest, MoveParent) { | |
458 FilePathWatcher file_watcher; | |
459 FilePathWatcher subdir_watcher; | |
460 FilePath dir(temp_dir_.path().AppendASCII("dir")); | |
461 FilePath dest(temp_dir_.path().AppendASCII("dest")); | |
462 FilePath subdir(dir.AppendASCII("subdir")); | |
463 FilePath file(subdir.AppendASCII("file")); | |
464 scoped_ptr<TestDelegate> file_delegate(new TestDelegate(collector())); | |
465 ASSERT_TRUE(SetupWatch(file, &file_watcher, file_delegate.get(), false)); | |
466 scoped_ptr<TestDelegate> subdir_delegate(new TestDelegate(collector())); | |
467 ASSERT_TRUE(SetupWatch(subdir, &subdir_watcher, subdir_delegate.get(), | |
468 false)); | |
469 | |
470 // Setup a directory hierarchy. | |
471 ASSERT_TRUE(base::CreateDirectory(subdir)); | |
472 ASSERT_TRUE(WriteFile(file, "content")); | |
473 VLOG(1) << "Waiting for file creation"; | |
474 ASSERT_TRUE(WaitForEvents()); | |
475 | |
476 // Move the parent directory. | |
477 base::Move(dir, dest); | |
478 VLOG(1) << "Waiting for directory move"; | |
479 ASSERT_TRUE(WaitForEvents()); | |
480 DeleteDelegateOnFileThread(file_delegate.release()); | |
481 DeleteDelegateOnFileThread(subdir_delegate.release()); | |
482 } | |
483 | |
484 TEST_F(FilePathWatcherTest, RecursiveWatch) { | |
485 FilePathWatcher watcher; | |
486 FilePath dir(temp_dir_.path().AppendASCII("dir")); | |
487 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); | |
488 bool setup_result = SetupWatch(dir, &watcher, delegate.get(), true); | |
489 if (!FilePathWatcher::RecursiveWatchAvailable()) { | |
490 ASSERT_FALSE(setup_result); | |
491 DeleteDelegateOnFileThread(delegate.release()); | |
492 return; | |
493 } | |
494 ASSERT_TRUE(setup_result); | |
495 | |
496 // Main directory("dir") creation. | |
497 ASSERT_TRUE(base::CreateDirectory(dir)); | |
498 ASSERT_TRUE(WaitForEvents()); | |
499 | |
500 // Create "$dir/file1". | |
501 FilePath file1(dir.AppendASCII("file1")); | |
502 ASSERT_TRUE(WriteFile(file1, "content")); | |
503 ASSERT_TRUE(WaitForEvents()); | |
504 | |
505 // Create "$dir/subdir". | |
506 FilePath subdir(dir.AppendASCII("subdir")); | |
507 ASSERT_TRUE(base::CreateDirectory(subdir)); | |
508 ASSERT_TRUE(WaitForEvents()); | |
509 | |
510 // Create "$dir/subdir/subdir_file1". | |
511 FilePath subdir_file1(subdir.AppendASCII("subdir_file1")); | |
512 ASSERT_TRUE(WriteFile(subdir_file1, "content")); | |
513 ASSERT_TRUE(WaitForEvents()); | |
514 | |
515 // Create "$dir/subdir/subdir_child_dir". | |
516 FilePath subdir_child_dir(subdir.AppendASCII("subdir_child_dir")); | |
517 ASSERT_TRUE(base::CreateDirectory(subdir_child_dir)); | |
518 ASSERT_TRUE(WaitForEvents()); | |
519 | |
520 // Create "$dir/subdir/subdir_child_dir/child_dir_file1". | |
521 FilePath child_dir_file1(subdir_child_dir.AppendASCII("child_dir_file1")); | |
522 ASSERT_TRUE(WriteFile(child_dir_file1, "content v2")); | |
523 ASSERT_TRUE(WaitForEvents()); | |
524 | |
525 // Write into "$dir/subdir/subdir_child_dir/child_dir_file1". | |
526 ASSERT_TRUE(WriteFile(child_dir_file1, "content")); | |
527 ASSERT_TRUE(WaitForEvents()); | |
528 | |
529 // Modify "$dir/subdir/subdir_child_dir/child_dir_file1" attributes. | |
530 ASSERT_TRUE(base::MakeFileUnreadable(child_dir_file1)); | |
531 ASSERT_TRUE(WaitForEvents()); | |
532 | |
533 // Delete "$dir/subdir/subdir_file1". | |
534 ASSERT_TRUE(base::DeleteFile(subdir_file1, false)); | |
535 ASSERT_TRUE(WaitForEvents()); | |
536 | |
537 // Delete "$dir/subdir/subdir_child_dir/child_dir_file1". | |
538 ASSERT_TRUE(base::DeleteFile(child_dir_file1, false)); | |
539 ASSERT_TRUE(WaitForEvents()); | |
540 DeleteDelegateOnFileThread(delegate.release()); | |
541 } | |
542 | |
543 #if defined(OS_POSIX) | |
544 TEST_F(FilePathWatcherTest, RecursiveWithSymLink) { | |
545 if (!FilePathWatcher::RecursiveWatchAvailable()) | |
546 return; | |
547 | |
548 FilePathWatcher watcher; | |
549 FilePath test_dir(temp_dir_.path().AppendASCII("test_dir")); | |
550 ASSERT_TRUE(base::CreateDirectory(test_dir)); | |
551 FilePath symlink(test_dir.AppendASCII("symlink")); | |
552 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); | |
553 ASSERT_TRUE(SetupWatch(symlink, &watcher, delegate.get(), true)); | |
554 | |
555 // Link creation. | |
556 FilePath target1(temp_dir_.path().AppendASCII("target1")); | |
557 ASSERT_TRUE(base::CreateSymbolicLink(target1, symlink)); | |
558 ASSERT_TRUE(WaitForEvents()); | |
559 | |
560 // Target1 creation. | |
561 ASSERT_TRUE(base::CreateDirectory(target1)); | |
562 ASSERT_TRUE(WaitForEvents()); | |
563 | |
564 // Create a file in target1. | |
565 FilePath target1_file(target1.AppendASCII("file")); | |
566 ASSERT_TRUE(WriteFile(target1_file, "content")); | |
567 ASSERT_TRUE(WaitForEvents()); | |
568 | |
569 // Link change. | |
570 FilePath target2(temp_dir_.path().AppendASCII("target2")); | |
571 ASSERT_TRUE(base::CreateDirectory(target2)); | |
572 ASSERT_TRUE(base::DeleteFile(symlink, false)); | |
573 ASSERT_TRUE(base::CreateSymbolicLink(target2, symlink)); | |
574 ASSERT_TRUE(WaitForEvents()); | |
575 | |
576 // Create a file in target2. | |
577 FilePath target2_file(target2.AppendASCII("file")); | |
578 ASSERT_TRUE(WriteFile(target2_file, "content")); | |
579 ASSERT_TRUE(WaitForEvents()); | |
580 | |
581 DeleteDelegateOnFileThread(delegate.release()); | |
582 } | |
583 #endif // OS_POSIX | |
584 | |
585 TEST_F(FilePathWatcherTest, MoveChild) { | |
586 FilePathWatcher file_watcher; | |
587 FilePathWatcher subdir_watcher; | |
588 FilePath source_dir(temp_dir_.path().AppendASCII("source")); | |
589 FilePath source_subdir(source_dir.AppendASCII("subdir")); | |
590 FilePath source_file(source_subdir.AppendASCII("file")); | |
591 FilePath dest_dir(temp_dir_.path().AppendASCII("dest")); | |
592 FilePath dest_subdir(dest_dir.AppendASCII("subdir")); | |
593 FilePath dest_file(dest_subdir.AppendASCII("file")); | |
594 | |
595 // Setup a directory hierarchy. | |
596 ASSERT_TRUE(base::CreateDirectory(source_subdir)); | |
597 ASSERT_TRUE(WriteFile(source_file, "content")); | |
598 | |
599 scoped_ptr<TestDelegate> file_delegate(new TestDelegate(collector())); | |
600 ASSERT_TRUE(SetupWatch(dest_file, &file_watcher, file_delegate.get(), false)); | |
601 scoped_ptr<TestDelegate> subdir_delegate(new TestDelegate(collector())); | |
602 ASSERT_TRUE(SetupWatch(dest_subdir, &subdir_watcher, subdir_delegate.get(), | |
603 false)); | |
604 | |
605 // Move the directory into place, s.t. the watched file appears. | |
606 ASSERT_TRUE(base::Move(source_dir, dest_dir)); | |
607 ASSERT_TRUE(WaitForEvents()); | |
608 DeleteDelegateOnFileThread(file_delegate.release()); | |
609 DeleteDelegateOnFileThread(subdir_delegate.release()); | |
610 } | |
611 | |
612 // Verify that changing attributes on a file is caught | |
613 TEST_F(FilePathWatcherTest, FileAttributesChanged) { | |
614 ASSERT_TRUE(WriteFile(test_file(), "content")); | |
615 FilePathWatcher watcher; | |
616 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); | |
617 ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false)); | |
618 | |
619 // Now make sure we get notified if the file is modified. | |
620 ASSERT_TRUE(base::MakeFileUnreadable(test_file())); | |
621 ASSERT_TRUE(WaitForEvents()); | |
622 DeleteDelegateOnFileThread(delegate.release()); | |
623 } | |
624 | |
625 #if defined(OS_LINUX) | |
626 | |
627 // Verify that creating a symlink is caught. | |
628 TEST_F(FilePathWatcherTest, CreateLink) { | |
629 FilePathWatcher watcher; | |
630 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); | |
631 // Note that we are watching the symlink | |
632 ASSERT_TRUE(SetupWatch(test_link(), &watcher, delegate.get(), false)); | |
633 | |
634 // Now make sure we get notified if the link is created. | |
635 // Note that test_file() doesn't have to exist. | |
636 ASSERT_TRUE(CreateSymbolicLink(test_file(), test_link())); | |
637 ASSERT_TRUE(WaitForEvents()); | |
638 DeleteDelegateOnFileThread(delegate.release()); | |
639 } | |
640 | |
641 // Verify that deleting a symlink is caught. | |
642 TEST_F(FilePathWatcherTest, DeleteLink) { | |
643 // Unfortunately this test case only works if the link target exists. | |
644 // TODO(craig) fix this as part of crbug.com/91561. | |
645 ASSERT_TRUE(WriteFile(test_file(), "content")); | |
646 ASSERT_TRUE(CreateSymbolicLink(test_file(), test_link())); | |
647 FilePathWatcher watcher; | |
648 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); | |
649 ASSERT_TRUE(SetupWatch(test_link(), &watcher, delegate.get(), false)); | |
650 | |
651 // Now make sure we get notified if the link is deleted. | |
652 ASSERT_TRUE(base::DeleteFile(test_link(), false)); | |
653 ASSERT_TRUE(WaitForEvents()); | |
654 DeleteDelegateOnFileThread(delegate.release()); | |
655 } | |
656 | |
657 // Verify that modifying a target file that a link is pointing to | |
658 // when we are watching the link is caught. | |
659 TEST_F(FilePathWatcherTest, ModifiedLinkedFile) { | |
660 ASSERT_TRUE(WriteFile(test_file(), "content")); | |
661 ASSERT_TRUE(CreateSymbolicLink(test_file(), test_link())); | |
662 FilePathWatcher watcher; | |
663 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); | |
664 // Note that we are watching the symlink. | |
665 ASSERT_TRUE(SetupWatch(test_link(), &watcher, delegate.get(), false)); | |
666 | |
667 // Now make sure we get notified if the file is modified. | |
668 ASSERT_TRUE(WriteFile(test_file(), "new content")); | |
669 ASSERT_TRUE(WaitForEvents()); | |
670 DeleteDelegateOnFileThread(delegate.release()); | |
671 } | |
672 | |
673 // Verify that creating a target file that a link is pointing to | |
674 // when we are watching the link is caught. | |
675 TEST_F(FilePathWatcherTest, CreateTargetLinkedFile) { | |
676 ASSERT_TRUE(CreateSymbolicLink(test_file(), test_link())); | |
677 FilePathWatcher watcher; | |
678 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); | |
679 // Note that we are watching the symlink. | |
680 ASSERT_TRUE(SetupWatch(test_link(), &watcher, delegate.get(), false)); | |
681 | |
682 // Now make sure we get notified if the target file is created. | |
683 ASSERT_TRUE(WriteFile(test_file(), "content")); | |
684 ASSERT_TRUE(WaitForEvents()); | |
685 DeleteDelegateOnFileThread(delegate.release()); | |
686 } | |
687 | |
688 // Verify that deleting a target file that a link is pointing to | |
689 // when we are watching the link is caught. | |
690 TEST_F(FilePathWatcherTest, DeleteTargetLinkedFile) { | |
691 ASSERT_TRUE(WriteFile(test_file(), "content")); | |
692 ASSERT_TRUE(CreateSymbolicLink(test_file(), test_link())); | |
693 FilePathWatcher watcher; | |
694 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); | |
695 // Note that we are watching the symlink. | |
696 ASSERT_TRUE(SetupWatch(test_link(), &watcher, delegate.get(), false)); | |
697 | |
698 // Now make sure we get notified if the target file is deleted. | |
699 ASSERT_TRUE(base::DeleteFile(test_file(), false)); | |
700 ASSERT_TRUE(WaitForEvents()); | |
701 DeleteDelegateOnFileThread(delegate.release()); | |
702 } | |
703 | |
704 // Verify that watching a file whose parent directory is a link that | |
705 // doesn't exist yet works if the symlink is created eventually. | |
706 TEST_F(FilePathWatcherTest, LinkedDirectoryPart1) { | |
707 FilePathWatcher watcher; | |
708 FilePath dir(temp_dir_.path().AppendASCII("dir")); | |
709 FilePath link_dir(temp_dir_.path().AppendASCII("dir.lnk")); | |
710 FilePath file(dir.AppendASCII("file")); | |
711 FilePath linkfile(link_dir.AppendASCII("file")); | |
712 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); | |
713 // dir/file should exist. | |
714 ASSERT_TRUE(base::CreateDirectory(dir)); | |
715 ASSERT_TRUE(WriteFile(file, "content")); | |
716 // Note that we are watching dir.lnk/file which doesn't exist yet. | |
717 ASSERT_TRUE(SetupWatch(linkfile, &watcher, delegate.get(), false)); | |
718 | |
719 ASSERT_TRUE(CreateSymbolicLink(dir, link_dir)); | |
720 VLOG(1) << "Waiting for link creation"; | |
721 ASSERT_TRUE(WaitForEvents()); | |
722 | |
723 ASSERT_TRUE(WriteFile(file, "content v2")); | |
724 VLOG(1) << "Waiting for file change"; | |
725 ASSERT_TRUE(WaitForEvents()); | |
726 | |
727 ASSERT_TRUE(base::DeleteFile(file, false)); | |
728 VLOG(1) << "Waiting for file deletion"; | |
729 ASSERT_TRUE(WaitForEvents()); | |
730 DeleteDelegateOnFileThread(delegate.release()); | |
731 } | |
732 | |
733 // Verify that watching a file whose parent directory is a | |
734 // dangling symlink works if the directory is created eventually. | |
735 TEST_F(FilePathWatcherTest, LinkedDirectoryPart2) { | |
736 FilePathWatcher watcher; | |
737 FilePath dir(temp_dir_.path().AppendASCII("dir")); | |
738 FilePath link_dir(temp_dir_.path().AppendASCII("dir.lnk")); | |
739 FilePath file(dir.AppendASCII("file")); | |
740 FilePath linkfile(link_dir.AppendASCII("file")); | |
741 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); | |
742 // Now create the link from dir.lnk pointing to dir but | |
743 // neither dir nor dir/file exist yet. | |
744 ASSERT_TRUE(CreateSymbolicLink(dir, link_dir)); | |
745 // Note that we are watching dir.lnk/file. | |
746 ASSERT_TRUE(SetupWatch(linkfile, &watcher, delegate.get(), false)); | |
747 | |
748 ASSERT_TRUE(base::CreateDirectory(dir)); | |
749 ASSERT_TRUE(WriteFile(file, "content")); | |
750 VLOG(1) << "Waiting for dir/file creation"; | |
751 ASSERT_TRUE(WaitForEvents()); | |
752 | |
753 ASSERT_TRUE(WriteFile(file, "content v2")); | |
754 VLOG(1) << "Waiting for file change"; | |
755 ASSERT_TRUE(WaitForEvents()); | |
756 | |
757 ASSERT_TRUE(base::DeleteFile(file, false)); | |
758 VLOG(1) << "Waiting for file deletion"; | |
759 ASSERT_TRUE(WaitForEvents()); | |
760 DeleteDelegateOnFileThread(delegate.release()); | |
761 } | |
762 | |
763 // Verify that watching a file with a symlink on the path | |
764 // to the file works. | |
765 TEST_F(FilePathWatcherTest, LinkedDirectoryPart3) { | |
766 FilePathWatcher watcher; | |
767 FilePath dir(temp_dir_.path().AppendASCII("dir")); | |
768 FilePath link_dir(temp_dir_.path().AppendASCII("dir.lnk")); | |
769 FilePath file(dir.AppendASCII("file")); | |
770 FilePath linkfile(link_dir.AppendASCII("file")); | |
771 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); | |
772 ASSERT_TRUE(base::CreateDirectory(dir)); | |
773 ASSERT_TRUE(CreateSymbolicLink(dir, link_dir)); | |
774 // Note that we are watching dir.lnk/file but the file doesn't exist yet. | |
775 ASSERT_TRUE(SetupWatch(linkfile, &watcher, delegate.get(), false)); | |
776 | |
777 ASSERT_TRUE(WriteFile(file, "content")); | |
778 VLOG(1) << "Waiting for file creation"; | |
779 ASSERT_TRUE(WaitForEvents()); | |
780 | |
781 ASSERT_TRUE(WriteFile(file, "content v2")); | |
782 VLOG(1) << "Waiting for file change"; | |
783 ASSERT_TRUE(WaitForEvents()); | |
784 | |
785 ASSERT_TRUE(base::DeleteFile(file, false)); | |
786 VLOG(1) << "Waiting for file deletion"; | |
787 ASSERT_TRUE(WaitForEvents()); | |
788 DeleteDelegateOnFileThread(delegate.release()); | |
789 } | |
790 | |
791 #endif // OS_LINUX | |
792 | |
793 enum Permission { | |
794 Read, | |
795 Write, | |
796 Execute | |
797 }; | |
798 | |
799 #if defined(OS_MACOSX) | |
800 bool ChangeFilePermissions(const FilePath& path, Permission perm, bool allow) { | |
801 struct stat stat_buf; | |
802 | |
803 if (stat(path.value().c_str(), &stat_buf) != 0) | |
804 return false; | |
805 | |
806 mode_t mode = 0; | |
807 switch (perm) { | |
808 case Read: | |
809 mode = S_IRUSR | S_IRGRP | S_IROTH; | |
810 break; | |
811 case Write: | |
812 mode = S_IWUSR | S_IWGRP | S_IWOTH; | |
813 break; | |
814 case Execute: | |
815 mode = S_IXUSR | S_IXGRP | S_IXOTH; | |
816 break; | |
817 default: | |
818 ADD_FAILURE() << "unknown perm " << perm; | |
819 return false; | |
820 } | |
821 if (allow) { | |
822 stat_buf.st_mode |= mode; | |
823 } else { | |
824 stat_buf.st_mode &= ~mode; | |
825 } | |
826 return chmod(path.value().c_str(), stat_buf.st_mode) == 0; | |
827 } | |
828 #endif // defined(OS_MACOSX) | |
829 | |
830 #if defined(OS_MACOSX) | |
831 // Linux implementation of FilePathWatcher doesn't catch attribute changes. | |
832 // http://crbug.com/78043 | |
833 // Windows implementation of FilePathWatcher catches attribute changes that | |
834 // don't affect the path being watched. | |
835 // http://crbug.com/78045 | |
836 | |
837 // Verify that changing attributes on a directory works. | |
838 TEST_F(FilePathWatcherTest, DirAttributesChanged) { | |
839 FilePath test_dir1(temp_dir_.path().AppendASCII("DirAttributesChangedDir1")); | |
840 FilePath test_dir2(test_dir1.AppendASCII("DirAttributesChangedDir2")); | |
841 FilePath test_file(test_dir2.AppendASCII("DirAttributesChangedFile")); | |
842 // Setup a directory hierarchy. | |
843 ASSERT_TRUE(base::CreateDirectory(test_dir1)); | |
844 ASSERT_TRUE(base::CreateDirectory(test_dir2)); | |
845 ASSERT_TRUE(WriteFile(test_file, "content")); | |
846 | |
847 FilePathWatcher watcher; | |
848 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); | |
849 ASSERT_TRUE(SetupWatch(test_file, &watcher, delegate.get(), false)); | |
850 | |
851 // We should not get notified in this case as it hasn't affected our ability | |
852 // to access the file. | |
853 ASSERT_TRUE(ChangeFilePermissions(test_dir1, Read, false)); | |
854 loop_.PostDelayedTask(FROM_HERE, | |
855 MessageLoop::QuitWhenIdleClosure(), | |
856 TestTimeouts::tiny_timeout()); | |
857 ASSERT_FALSE(WaitForEvents()); | |
858 ASSERT_TRUE(ChangeFilePermissions(test_dir1, Read, true)); | |
859 | |
860 // We should get notified in this case because filepathwatcher can no | |
861 // longer access the file | |
862 ASSERT_TRUE(ChangeFilePermissions(test_dir1, Execute, false)); | |
863 ASSERT_TRUE(WaitForEvents()); | |
864 ASSERT_TRUE(ChangeFilePermissions(test_dir1, Execute, true)); | |
865 DeleteDelegateOnFileThread(delegate.release()); | |
866 } | |
867 | |
868 #endif // OS_MACOSX | |
869 } // namespace | |
870 | |
871 } // namespace base | |
OLD | NEW |