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