OLD | NEW |
| (Empty) |
1 // Copyright (c) 2010 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 "chrome/browser/file_path_watcher/file_path_watcher.h" | |
6 | |
7 #include <set> | |
8 | |
9 #include "base/basictypes.h" | |
10 #include "base/file_path.h" | |
11 #include "base/file_util.h" | |
12 #include "base/message_loop.h" | |
13 #include "base/message_loop_proxy.h" | |
14 #include "base/path_service.h" | |
15 #include "base/scoped_temp_dir.h" | |
16 #include "base/string_util.h" | |
17 #include "base/stl_util-inl.h" | |
18 #include "base/synchronization/waitable_event.h" | |
19 #include "testing/gtest/include/gtest/gtest.h" | |
20 | |
21 #if defined(OS_MACOSX) | |
22 // TODO(mnissler): There are flakes on Mac (http://crbug.com/54822) at least for | |
23 // FilePathWatcherTest.MultipleWatchersSingleFile. | |
24 #define MAYBE(name) FLAKY_ ## name | |
25 #else | |
26 #define MAYBE(name) name | |
27 #endif | |
28 | |
29 namespace { | |
30 | |
31 class TestDelegate; | |
32 | |
33 // Aggregates notifications from the test delegates and breaks the message loop | |
34 // the test thread is waiting on once they all came in. | |
35 class NotificationCollector | |
36 : public base::RefCountedThreadSafe<NotificationCollector> { | |
37 public: | |
38 NotificationCollector() | |
39 : loop_(base::MessageLoopProxy::CreateForCurrentThread()) {} | |
40 | |
41 // Called from the file thread by the delegates. | |
42 void OnChange(TestDelegate* delegate) { | |
43 loop_->PostTask(FROM_HERE, | |
44 NewRunnableMethod(this, | |
45 &NotificationCollector::RecordChange, | |
46 make_scoped_refptr(delegate))); | |
47 } | |
48 | |
49 void Register(TestDelegate* delegate) { | |
50 delegates_.insert(delegate); | |
51 } | |
52 | |
53 void Reset() { | |
54 signaled_.clear(); | |
55 } | |
56 | |
57 private: | |
58 void RecordChange(TestDelegate* delegate) { | |
59 ASSERT_TRUE(loop_->BelongsToCurrentThread()); | |
60 ASSERT_TRUE(delegates_.count(delegate)); | |
61 signaled_.insert(delegate); | |
62 | |
63 // Check whether all delegates have been signaled. | |
64 if (signaled_ == delegates_) | |
65 loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask()); | |
66 } | |
67 | |
68 // Set of registered delegates. | |
69 std::set<TestDelegate*> delegates_; | |
70 | |
71 // Set of signaled delegates. | |
72 std::set<TestDelegate*> signaled_; | |
73 | |
74 // The loop we should break after all delegates signaled. | |
75 scoped_refptr<base::MessageLoopProxy> loop_; | |
76 }; | |
77 | |
78 // A mock FilePathWatcher::Delegate for testing. I'd rather use gmock, but it's | |
79 // not thread safe for setting expectations, so the test code couldn't safely | |
80 // reset expectations while the file watcher is running. In order to allow this, | |
81 // we keep simple thread safe status flags in TestDelegate. | |
82 class TestDelegate : public FilePathWatcher::Delegate { | |
83 public: | |
84 // The message loop specified by |loop| will be quit if a notification is | |
85 // received while the delegate is |armed_|. Note that the testing code must | |
86 // guarantee |loop| outlives the file thread on which OnFilePathChanged runs. | |
87 explicit TestDelegate(NotificationCollector* collector) | |
88 : collector_(collector) { | |
89 collector_->Register(this); | |
90 } | |
91 | |
92 virtual void OnFilePathChanged(const FilePath&) { | |
93 collector_->OnChange(this); | |
94 } | |
95 | |
96 private: | |
97 scoped_refptr<NotificationCollector> collector_; | |
98 | |
99 DISALLOW_COPY_AND_ASSIGN(TestDelegate); | |
100 }; | |
101 | |
102 // A helper class for setting up watches on the file thread. | |
103 class SetupWatchTask : public Task { | |
104 public: | |
105 SetupWatchTask(const FilePath& target, | |
106 FilePathWatcher* watcher, | |
107 FilePathWatcher::Delegate* delegate, | |
108 bool* result, | |
109 base::WaitableEvent* completion) | |
110 : target_(target), | |
111 watcher_(watcher), | |
112 delegate_(delegate), | |
113 result_(result), | |
114 completion_(completion) {} | |
115 | |
116 void Run() { | |
117 *result_ = watcher_->Watch(target_, delegate_); | |
118 completion_->Signal(); | |
119 } | |
120 | |
121 private: | |
122 const FilePath target_; | |
123 FilePathWatcher* watcher_; | |
124 FilePathWatcher::Delegate* delegate_; | |
125 bool* result_; | |
126 base::WaitableEvent* completion_; | |
127 | |
128 DISALLOW_COPY_AND_ASSIGN(SetupWatchTask); | |
129 }; | |
130 | |
131 class FilePathWatcherTest : public testing::Test { | |
132 public: | |
133 // Implementation of FilePathWatcher on Mac requires UI loop. | |
134 FilePathWatcherTest() | |
135 : loop_(MessageLoop::TYPE_UI), | |
136 ui_thread_(BrowserThread::UI, &loop_) { | |
137 } | |
138 | |
139 protected: | |
140 virtual void SetUp() { | |
141 // Create a separate file thread in order to test proper thread usage. | |
142 file_thread_.reset(new BrowserThread(BrowserThread::FILE)); | |
143 file_thread_->Start(); | |
144 temp_dir_.reset(new ScopedTempDir); | |
145 ASSERT_TRUE(temp_dir_->CreateUniqueTempDir()); | |
146 collector_ = new NotificationCollector(); | |
147 } | |
148 | |
149 virtual void TearDown() { | |
150 loop_.RunAllPending(); | |
151 file_thread_.reset(); | |
152 } | |
153 | |
154 FilePath test_file() { | |
155 return temp_dir_->path().AppendASCII("FilePathWatcherTest"); | |
156 } | |
157 | |
158 // Write |content| to |file|. Returns true on success. | |
159 bool WriteFile(const FilePath& file, const std::string& content) { | |
160 int write_size = file_util::WriteFile(file, content.c_str(), | |
161 content.length()); | |
162 return write_size == static_cast<int>(content.length()); | |
163 } | |
164 | |
165 void SetupWatch(const FilePath& target, | |
166 FilePathWatcher* watcher, | |
167 FilePathWatcher::Delegate* delegate) { | |
168 base::WaitableEvent completion(false, false); | |
169 bool result; | |
170 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, | |
171 new SetupWatchTask(target, watcher, delegate, &result, &completion)); | |
172 completion.Wait(); | |
173 ASSERT_TRUE(result); | |
174 } | |
175 | |
176 void WaitForEvents() { | |
177 collector_->Reset(); | |
178 loop_.Run(); | |
179 } | |
180 | |
181 NotificationCollector* collector() { return collector_.get(); } | |
182 | |
183 MessageLoop loop_; | |
184 BrowserThread ui_thread_; | |
185 scoped_ptr<BrowserThread> file_thread_; | |
186 scoped_ptr<ScopedTempDir> temp_dir_; | |
187 scoped_refptr<NotificationCollector> collector_; | |
188 }; | |
189 | |
190 // Basic test: Create the file and verify that we notice. | |
191 TEST_F(FilePathWatcherTest, MAYBE(NewFile)) { | |
192 FilePathWatcher watcher; | |
193 scoped_refptr<TestDelegate> delegate(new TestDelegate(collector())); | |
194 SetupWatch(test_file(), &watcher, delegate.get()); | |
195 | |
196 ASSERT_TRUE(WriteFile(test_file(), "content")); | |
197 WaitForEvents(); | |
198 } | |
199 | |
200 // Verify that modifying the file is caught. | |
201 TEST_F(FilePathWatcherTest, MAYBE(ModifiedFile)) { | |
202 ASSERT_TRUE(WriteFile(test_file(), "content")); | |
203 | |
204 FilePathWatcher watcher; | |
205 scoped_refptr<TestDelegate> delegate(new TestDelegate(collector())); | |
206 SetupWatch(test_file(), &watcher, delegate.get()); | |
207 | |
208 // Now make sure we get notified if the file is modified. | |
209 ASSERT_TRUE(WriteFile(test_file(), "new content")); | |
210 WaitForEvents(); | |
211 } | |
212 | |
213 // Verify that moving the file into place is caught. | |
214 TEST_F(FilePathWatcherTest, MAYBE(MovedFile)) { | |
215 FilePath source_file(temp_dir_->path().AppendASCII("source")); | |
216 ASSERT_TRUE(WriteFile(source_file, "content")); | |
217 | |
218 FilePathWatcher watcher; | |
219 scoped_refptr<TestDelegate> delegate(new TestDelegate(collector())); | |
220 SetupWatch(test_file(), &watcher, delegate.get()); | |
221 | |
222 // Now make sure we get notified if the file is modified. | |
223 ASSERT_TRUE(file_util::Move(source_file, test_file())); | |
224 WaitForEvents(); | |
225 } | |
226 | |
227 TEST_F(FilePathWatcherTest, MAYBE(DeletedFile)) { | |
228 ASSERT_TRUE(WriteFile(test_file(), "content")); | |
229 | |
230 FilePathWatcher watcher; | |
231 scoped_refptr<TestDelegate> delegate(new TestDelegate(collector())); | |
232 SetupWatch(test_file(), &watcher, delegate.get()); | |
233 | |
234 // Now make sure we get notified if the file is deleted. | |
235 file_util::Delete(test_file(), false); | |
236 WaitForEvents(); | |
237 } | |
238 | |
239 // Used by the DeleteDuringNotify test below. | |
240 // Deletes the FilePathWatcher when it's notified. | |
241 class Deleter : public FilePathWatcher::Delegate { | |
242 public: | |
243 Deleter(FilePathWatcher* watcher, MessageLoop* loop) | |
244 : watcher_(watcher), | |
245 loop_(loop) { | |
246 } | |
247 | |
248 virtual void OnFilePathChanged(const FilePath& path) { | |
249 watcher_.reset(NULL); | |
250 loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask()); | |
251 } | |
252 | |
253 scoped_ptr<FilePathWatcher> watcher_; | |
254 MessageLoop* loop_; | |
255 }; | |
256 | |
257 // Verify that deleting a watcher during the callback doesn't crash. | |
258 TEST_F(FilePathWatcherTest, DeleteDuringNotify) { | |
259 FilePathWatcher* watcher = new FilePathWatcher; | |
260 // Takes ownership of watcher. | |
261 scoped_refptr<Deleter> deleter(new Deleter(watcher, &loop_)); | |
262 SetupWatch(test_file(), watcher, deleter.get()); | |
263 | |
264 ASSERT_TRUE(WriteFile(test_file(), "content")); | |
265 WaitForEvents(); | |
266 | |
267 // We win if we haven't crashed yet. | |
268 // Might as well double-check it got deleted, too. | |
269 ASSERT_TRUE(deleter->watcher_.get() == NULL); | |
270 } | |
271 | |
272 // Verify that deleting the watcher works even if there is a pending | |
273 // notification. | |
274 TEST_F(FilePathWatcherTest, DestroyWithPendingNotification) { | |
275 scoped_refptr<TestDelegate> delegate(new TestDelegate(collector())); | |
276 FilePathWatcher* watcher = new FilePathWatcher; | |
277 SetupWatch(test_file(), watcher, delegate.get()); | |
278 ASSERT_TRUE(WriteFile(test_file(), "content")); | |
279 BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, watcher); | |
280 } | |
281 | |
282 TEST_F(FilePathWatcherTest, MAYBE(MultipleWatchersSingleFile)) { | |
283 FilePathWatcher watcher1, watcher2; | |
284 scoped_refptr<TestDelegate> delegate1(new TestDelegate(collector())); | |
285 scoped_refptr<TestDelegate> delegate2(new TestDelegate(collector())); | |
286 SetupWatch(test_file(), &watcher1, delegate1.get()); | |
287 SetupWatch(test_file(), &watcher2, delegate2.get()); | |
288 | |
289 ASSERT_TRUE(WriteFile(test_file(), "content")); | |
290 WaitForEvents(); | |
291 } | |
292 | |
293 // Verify that watching a file whose parent directory doesn't exist yet works if | |
294 // the directory and file are created eventually. | |
295 TEST_F(FilePathWatcherTest, NonExistentDirectory) { | |
296 FilePathWatcher watcher; | |
297 FilePath dir(temp_dir_->path().AppendASCII("dir")); | |
298 FilePath file(dir.AppendASCII("file")); | |
299 scoped_refptr<TestDelegate> delegate(new TestDelegate(collector())); | |
300 SetupWatch(file, &watcher, delegate.get()); | |
301 | |
302 ASSERT_TRUE(file_util::CreateDirectory(dir)); | |
303 | |
304 ASSERT_TRUE(WriteFile(file, "content")); | |
305 VLOG(1) << "Waiting for file creation"; | |
306 WaitForEvents(); | |
307 | |
308 ASSERT_TRUE(WriteFile(file, "content v2")); | |
309 VLOG(1) << "Waiting for file change"; | |
310 WaitForEvents(); | |
311 | |
312 ASSERT_TRUE(file_util::Delete(file, false)); | |
313 VLOG(1) << "Waiting for file deletion"; | |
314 WaitForEvents(); | |
315 } | |
316 | |
317 // Exercises watch reconfiguration for the case that directories on the path | |
318 // are rapidly created. | |
319 TEST_F(FilePathWatcherTest, DirectoryChain) { | |
320 FilePath path(temp_dir_->path()); | |
321 std::vector<std::string> dir_names; | |
322 for (int i = 0; i < 20; i++) { | |
323 std::string dir(StringPrintf("d%d", i)); | |
324 dir_names.push_back(dir); | |
325 path = path.AppendASCII(dir); | |
326 } | |
327 | |
328 FilePathWatcher watcher; | |
329 FilePath file(path.AppendASCII("file")); | |
330 scoped_refptr<TestDelegate> delegate(new TestDelegate(collector())); | |
331 SetupWatch(file, &watcher, delegate.get()); | |
332 | |
333 FilePath sub_path(temp_dir_->path()); | |
334 for (std::vector<std::string>::const_iterator d(dir_names.begin()); | |
335 d != dir_names.end(); ++d) { | |
336 sub_path = sub_path.AppendASCII(*d); | |
337 ASSERT_TRUE(file_util::CreateDirectory(sub_path)); | |
338 } | |
339 ASSERT_TRUE(WriteFile(file, "content")); | |
340 VLOG(1) << "Waiting for file creation"; | |
341 WaitForEvents(); | |
342 | |
343 ASSERT_TRUE(WriteFile(file, "content v2")); | |
344 VLOG(1) << "Waiting for file modification"; | |
345 WaitForEvents(); | |
346 } | |
347 | |
348 TEST_F(FilePathWatcherTest, DisappearingDirectory) { | |
349 FilePathWatcher watcher; | |
350 FilePath dir(temp_dir_->path().AppendASCII("dir")); | |
351 FilePath file(dir.AppendASCII("file")); | |
352 ASSERT_TRUE(file_util::CreateDirectory(dir)); | |
353 ASSERT_TRUE(WriteFile(file, "content")); | |
354 scoped_refptr<TestDelegate> delegate(new TestDelegate(collector())); | |
355 SetupWatch(file, &watcher, delegate.get()); | |
356 | |
357 ASSERT_TRUE(file_util::Delete(dir, true)); | |
358 WaitForEvents(); | |
359 } | |
360 | |
361 // Tests that a file that is deleted and reappears is tracked correctly. | |
362 TEST_F(FilePathWatcherTest, DeleteAndRecreate) { | |
363 ASSERT_TRUE(WriteFile(test_file(), "content")); | |
364 FilePathWatcher watcher; | |
365 scoped_refptr<TestDelegate> delegate(new TestDelegate(collector())); | |
366 SetupWatch(test_file(), &watcher, delegate.get()); | |
367 | |
368 ASSERT_TRUE(file_util::Delete(test_file(), false)); | |
369 VLOG(1) << "Waiting for file deletion"; | |
370 WaitForEvents(); | |
371 | |
372 ASSERT_TRUE(WriteFile(test_file(), "content")); | |
373 VLOG(1) << "Waiting for file creation"; | |
374 WaitForEvents(); | |
375 } | |
376 | |
377 TEST_F(FilePathWatcherTest, WatchDirectory) { | |
378 FilePathWatcher watcher; | |
379 FilePath dir(temp_dir_->path().AppendASCII("dir")); | |
380 FilePath file1(dir.AppendASCII("file1")); | |
381 FilePath file2(dir.AppendASCII("file2")); | |
382 scoped_refptr<TestDelegate> delegate(new TestDelegate(collector())); | |
383 SetupWatch(dir, &watcher, delegate.get()); | |
384 | |
385 ASSERT_TRUE(file_util::CreateDirectory(dir)); | |
386 VLOG(1) << "Waiting for directory creation"; | |
387 WaitForEvents(); | |
388 | |
389 ASSERT_TRUE(WriteFile(file1, "content")); | |
390 VLOG(1) << "Waiting for file1 creation"; | |
391 WaitForEvents(); | |
392 | |
393 ASSERT_TRUE(WriteFile(file1, "content v2")); | |
394 VLOG(1) << "Waiting for file1 modification"; | |
395 WaitForEvents(); | |
396 | |
397 ASSERT_TRUE(file_util::Delete(file1, false)); | |
398 VLOG(1) << "Waiting for file1 deletion"; | |
399 WaitForEvents(); | |
400 | |
401 ASSERT_TRUE(WriteFile(file2, "content")); | |
402 VLOG(1) << "Waiting for file2 creation"; | |
403 WaitForEvents(); | |
404 } | |
405 | |
406 TEST_F(FilePathWatcherTest, MoveParent) { | |
407 FilePathWatcher file_watcher; | |
408 FilePathWatcher subdir_watcher; | |
409 FilePath dir(temp_dir_->path().AppendASCII("dir")); | |
410 FilePath dest(temp_dir_->path().AppendASCII("dest")); | |
411 FilePath subdir(dir.AppendASCII("subdir")); | |
412 FilePath file(subdir.AppendASCII("file")); | |
413 scoped_refptr<TestDelegate> file_delegate(new TestDelegate(collector())); | |
414 SetupWatch(file, &file_watcher, file_delegate.get()); | |
415 scoped_refptr<TestDelegate> subdir_delegate(new TestDelegate(collector())); | |
416 SetupWatch(subdir, &subdir_watcher, subdir_delegate.get()); | |
417 | |
418 // Setup a directory hierarchy. | |
419 ASSERT_TRUE(file_util::CreateDirectory(subdir)); | |
420 ASSERT_TRUE(WriteFile(file, "content")); | |
421 VLOG(1) << "Waiting for file creation"; | |
422 WaitForEvents(); | |
423 | |
424 // Move the parent directory. | |
425 file_util::Move(dir, dest); | |
426 VLOG(1) << "Waiting for directory move"; | |
427 WaitForEvents(); | |
428 } | |
429 | |
430 TEST_F(FilePathWatcherTest, MoveChild) { | |
431 FilePathWatcher file_watcher; | |
432 FilePathWatcher subdir_watcher; | |
433 FilePath source_dir(temp_dir_->path().AppendASCII("source")); | |
434 FilePath source_subdir(source_dir.AppendASCII("subdir")); | |
435 FilePath source_file(source_subdir.AppendASCII("file")); | |
436 FilePath dest_dir(temp_dir_->path().AppendASCII("dest")); | |
437 FilePath dest_subdir(dest_dir.AppendASCII("subdir")); | |
438 FilePath dest_file(dest_subdir.AppendASCII("file")); | |
439 | |
440 // Setup a directory hierarchy. | |
441 ASSERT_TRUE(file_util::CreateDirectory(source_subdir)); | |
442 ASSERT_TRUE(WriteFile(source_file, "content")); | |
443 | |
444 scoped_refptr<TestDelegate> file_delegate(new TestDelegate(collector())); | |
445 SetupWatch(dest_file, &file_watcher, file_delegate.get()); | |
446 scoped_refptr<TestDelegate> subdir_delegate(new TestDelegate(collector())); | |
447 SetupWatch(dest_subdir, &subdir_watcher, subdir_delegate.get()); | |
448 | |
449 // Move the directory into place, s.t. the watched file appears. | |
450 ASSERT_TRUE(file_util::Move(source_dir, dest_dir)); | |
451 WaitForEvents(); | |
452 } | |
453 | |
454 } // namespace | |
OLD | NEW |