file_path_watcher_browsertest.cc revision ddb351dbec246cf1fab5ec20d2d5520909041de1
1// Copyright (c) 2011 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#include <set>
8
9#if defined(OS_WIN)
10#include <windows.h>
11#include <aclapi.h>
12#elif defined(OS_POSIX)
13#include <sys/stat.h>
14#endif
15
16#include "base/basictypes.h"
17#include "base/compiler_specific.h"
18#include "base/file_path.h"
19#include "base/file_util.h"
20#include "base/memory/scoped_temp_dir.h"
21#include "base/message_loop.h"
22#include "base/message_loop_proxy.h"
23#include "base/path_service.h"
24#include "base/string_util.h"
25#include "base/stl_util-inl.h"
26#include "base/synchronization/waitable_event.h"
27#include "base/test/test_timeouts.h"
28#include "base/threading/thread.h"
29#include "testing/gtest/include/gtest/gtest.h"
30
31namespace base {
32namespace files {
33
34namespace {
35
36class TestDelegate;
37
38// Aggregates notifications from the test delegates and breaks the message loop
39// the test thread is waiting on once they all came in.
40class NotificationCollector
41    : public base::RefCountedThreadSafe<NotificationCollector> {
42 public:
43  NotificationCollector()
44      : loop_(base::MessageLoopProxy::CreateForCurrentThread()) {}
45
46  // Called from the file thread by the delegates.
47  void OnChange(TestDelegate* delegate) {
48    loop_->PostTask(FROM_HERE,
49                    NewRunnableMethod(this,
50                                      &NotificationCollector::RecordChange,
51                                      make_scoped_refptr(delegate)));
52  }
53
54  void Register(TestDelegate* delegate) {
55    delegates_.insert(delegate);
56  }
57
58  void Reset() {
59    signaled_.clear();
60  }
61
62  bool Success() {
63    return signaled_ == delegates_;
64  }
65
66 private:
67  void RecordChange(TestDelegate* delegate) {
68    ASSERT_TRUE(loop_->BelongsToCurrentThread());
69    ASSERT_TRUE(delegates_.count(delegate));
70    signaled_.insert(delegate);
71
72    // Check whether all delegates have been signaled.
73    if (signaled_ == delegates_)
74      loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask());
75  }
76
77  // Set of registered delegates.
78  std::set<TestDelegate*> delegates_;
79
80  // Set of signaled delegates.
81  std::set<TestDelegate*> signaled_;
82
83  // The loop we should break after all delegates signaled.
84  scoped_refptr<base::MessageLoopProxy> loop_;
85};
86
87// A mock FilePathWatcher::Delegate for testing. I'd rather use gmock, but it's
88// not thread safe for setting expectations, so the test code couldn't safely
89// reset expectations while the file watcher is running. In order to allow this,
90// we keep simple thread safe status flags in TestDelegate.
91class TestDelegate : public FilePathWatcher::Delegate {
92 public:
93  // The message loop specified by |loop| will be quit if a notification is
94  // received while the delegate is |armed_|. Note that the testing code must
95  // guarantee |loop| outlives the file thread on which OnFilePathChanged runs.
96  explicit TestDelegate(NotificationCollector* collector)
97      : collector_(collector) {
98    collector_->Register(this);
99  }
100
101  virtual void OnFilePathChanged(const FilePath&) {
102    collector_->OnChange(this);
103  }
104
105  virtual void OnFilePathError(const FilePath& path) {
106    ADD_FAILURE() << "Error " << path.value();
107  }
108
109 private:
110  scoped_refptr<NotificationCollector> collector_;
111
112  DISALLOW_COPY_AND_ASSIGN(TestDelegate);
113};
114
115// A helper class for setting up watches on the file thread.
116class SetupWatchTask : public Task {
117 public:
118  SetupWatchTask(const FilePath& target,
119                 FilePathWatcher* watcher,
120                 FilePathWatcher::Delegate* delegate,
121                 bool* result,
122                 base::WaitableEvent* completion)
123      : target_(target),
124        watcher_(watcher),
125        delegate_(delegate),
126        result_(result),
127        completion_(completion) {}
128
129  void Run() {
130    *result_ = watcher_->Watch(target_, delegate_);
131    completion_->Signal();
132  }
133
134 private:
135  const FilePath target_;
136  FilePathWatcher* watcher_;
137  FilePathWatcher::Delegate* delegate_;
138  bool* result_;
139  base::WaitableEvent* completion_;
140
141  DISALLOW_COPY_AND_ASSIGN(SetupWatchTask);
142};
143
144class FilePathWatcherTest : public testing::Test {
145 public:
146  FilePathWatcherTest()
147      : file_thread_("FilePathWatcherTest") {}
148
149  virtual ~FilePathWatcherTest() {}
150
151 protected:
152  virtual void SetUp() {
153    // Create a separate file thread in order to test proper thread usage.
154    base::Thread::Options options(MessageLoop::TYPE_IO, 0);
155    ASSERT_TRUE(file_thread_.StartWithOptions(options));
156    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
157    collector_ = new NotificationCollector();
158  }
159
160  virtual void TearDown() {
161    loop_.RunAllPending();
162  }
163
164  FilePath test_file() {
165    return temp_dir_.path().AppendASCII("FilePathWatcherTest");
166  }
167
168  // Write |content| to |file|. Returns true on success.
169  bool WriteFile(const FilePath& file, const std::string& content) {
170    int write_size = file_util::WriteFile(file, content.c_str(),
171                                          content.length());
172    return write_size == static_cast<int>(content.length());
173  }
174
175  bool SetupWatch(const FilePath& target,
176                  FilePathWatcher* watcher,
177                  FilePathWatcher::Delegate* delegate) WARN_UNUSED_RESULT {
178    base::WaitableEvent completion(false, false);
179    bool result;
180    file_thread_.message_loop_proxy()->PostTask(FROM_HERE,
181         new SetupWatchTask(target,
182                            watcher,
183                            delegate,
184                            &result,
185                            &completion));
186    completion.Wait();
187    return result;
188  }
189
190  bool WaitForEvents() WARN_UNUSED_RESULT {
191    collector_->Reset();
192    loop_.Run();
193    return collector_->Success();
194  }
195
196  NotificationCollector* collector() { return collector_.get(); }
197
198  MessageLoop loop_;
199  base::Thread file_thread_;
200  ScopedTempDir temp_dir_;
201  scoped_refptr<NotificationCollector> collector_;
202};
203
204// Basic test: Create the file and verify that we notice.
205TEST_F(FilePathWatcherTest, NewFile) {
206  FilePathWatcher watcher;
207  scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
208  ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get()));
209
210  ASSERT_TRUE(WriteFile(test_file(), "content"));
211  ASSERT_TRUE(WaitForEvents());
212}
213
214// Verify that modifying the file is caught.
215TEST_F(FilePathWatcherTest, ModifiedFile) {
216  ASSERT_TRUE(WriteFile(test_file(), "content"));
217
218  FilePathWatcher watcher;
219  scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
220  ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get()));
221
222  // Now make sure we get notified if the file is modified.
223  ASSERT_TRUE(WriteFile(test_file(), "new content"));
224  ASSERT_TRUE(WaitForEvents());
225}
226
227// Verify that moving the file into place is caught.
228TEST_F(FilePathWatcherTest, MovedFile) {
229  FilePath source_file(temp_dir_.path().AppendASCII("source"));
230  ASSERT_TRUE(WriteFile(source_file, "content"));
231
232  FilePathWatcher watcher;
233  scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
234  ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get()));
235
236  // Now make sure we get notified if the file is modified.
237  ASSERT_TRUE(file_util::Move(source_file, test_file()));
238  ASSERT_TRUE(WaitForEvents());
239}
240
241TEST_F(FilePathWatcherTest, DeletedFile) {
242  ASSERT_TRUE(WriteFile(test_file(), "content"));
243
244  FilePathWatcher watcher;
245  scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
246  ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get()));
247
248  // Now make sure we get notified if the file is deleted.
249  file_util::Delete(test_file(), false);
250  ASSERT_TRUE(WaitForEvents());
251}
252
253// Used by the DeleteDuringNotify test below.
254// Deletes the FilePathWatcher when it's notified.
255class Deleter : public FilePathWatcher::Delegate {
256 public:
257  Deleter(FilePathWatcher* watcher, MessageLoop* loop)
258      : watcher_(watcher),
259        loop_(loop) {
260  }
261
262  virtual void OnFilePathChanged(const FilePath& path) {
263    watcher_.reset();
264    loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask());
265  }
266
267  scoped_ptr<FilePathWatcher> watcher_;
268  MessageLoop* loop_;
269};
270
271// Verify that deleting a watcher during the callback doesn't crash.
272TEST_F(FilePathWatcherTest, DeleteDuringNotify) {
273  FilePathWatcher* watcher = new FilePathWatcher;
274  // Takes ownership of watcher.
275  scoped_refptr<Deleter> deleter(new Deleter(watcher, &loop_));
276  ASSERT_TRUE(SetupWatch(test_file(), watcher, deleter.get()));
277
278  ASSERT_TRUE(WriteFile(test_file(), "content"));
279  ASSERT_TRUE(WaitForEvents());
280
281  // We win if we haven't crashed yet.
282  // Might as well double-check it got deleted, too.
283  ASSERT_TRUE(deleter->watcher_.get() == NULL);
284}
285
286// Verify that deleting the watcher works even if there is a pending
287// notification.
288TEST_F(FilePathWatcherTest, DestroyWithPendingNotification) {
289  scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
290  FilePathWatcher* watcher = new FilePathWatcher;
291  ASSERT_TRUE(SetupWatch(test_file(), watcher, delegate.get()));
292  ASSERT_TRUE(WriteFile(test_file(), "content"));
293  file_thread_.message_loop_proxy()->DeleteSoon(FROM_HERE, watcher);
294}
295
296TEST_F(FilePathWatcherTest, MultipleWatchersSingleFile) {
297  FilePathWatcher watcher1, watcher2;
298  scoped_refptr<TestDelegate> delegate1(new TestDelegate(collector()));
299  scoped_refptr<TestDelegate> delegate2(new TestDelegate(collector()));
300  ASSERT_TRUE(SetupWatch(test_file(), &watcher1, delegate1.get()));
301  ASSERT_TRUE(SetupWatch(test_file(), &watcher2, delegate2.get()));
302
303  ASSERT_TRUE(WriteFile(test_file(), "content"));
304  ASSERT_TRUE(WaitForEvents());
305}
306
307// Verify that watching a file whose parent directory doesn't exist yet works if
308// the directory and file are created eventually.
309TEST_F(FilePathWatcherTest, NonExistentDirectory) {
310  FilePathWatcher watcher;
311  FilePath dir(temp_dir_.path().AppendASCII("dir"));
312  FilePath file(dir.AppendASCII("file"));
313  scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
314  ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get()));
315
316  ASSERT_TRUE(file_util::CreateDirectory(dir));
317
318  ASSERT_TRUE(WriteFile(file, "content"));
319
320  VLOG(1) << "Waiting for file creation";
321  ASSERT_TRUE(WaitForEvents());
322
323  ASSERT_TRUE(WriteFile(file, "content v2"));
324  VLOG(1) << "Waiting for file change";
325  ASSERT_TRUE(WaitForEvents());
326
327  ASSERT_TRUE(file_util::Delete(file, false));
328  VLOG(1) << "Waiting for file deletion";
329  ASSERT_TRUE(WaitForEvents());
330}
331
332// Exercises watch reconfiguration for the case that directories on the path
333// are rapidly created.
334TEST_F(FilePathWatcherTest, DirectoryChain) {
335  FilePath path(temp_dir_.path());
336  std::vector<std::string> dir_names;
337  for (int i = 0; i < 20; i++) {
338    std::string dir(StringPrintf("d%d", i));
339    dir_names.push_back(dir);
340    path = path.AppendASCII(dir);
341  }
342
343  FilePathWatcher watcher;
344  FilePath file(path.AppendASCII("file"));
345  scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
346  ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get()));
347
348  FilePath sub_path(temp_dir_.path());
349  for (std::vector<std::string>::const_iterator d(dir_names.begin());
350       d != dir_names.end(); ++d) {
351    sub_path = sub_path.AppendASCII(*d);
352    ASSERT_TRUE(file_util::CreateDirectory(sub_path));
353  }
354  VLOG(1) << "Create File";
355  ASSERT_TRUE(WriteFile(file, "content"));
356  VLOG(1) << "Waiting for file creation";
357  ASSERT_TRUE(WaitForEvents());
358
359  ASSERT_TRUE(WriteFile(file, "content v2"));
360  VLOG(1) << "Waiting for file modification";
361  ASSERT_TRUE(WaitForEvents());
362}
363
364TEST_F(FilePathWatcherTest, DisappearingDirectory) {
365  FilePathWatcher watcher;
366  FilePath dir(temp_dir_.path().AppendASCII("dir"));
367  FilePath file(dir.AppendASCII("file"));
368  ASSERT_TRUE(file_util::CreateDirectory(dir));
369  ASSERT_TRUE(WriteFile(file, "content"));
370  scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
371  ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get()));
372
373  ASSERT_TRUE(file_util::Delete(dir, true));
374  ASSERT_TRUE(WaitForEvents());
375}
376
377// Tests that a file that is deleted and reappears is tracked correctly.
378TEST_F(FilePathWatcherTest, DeleteAndRecreate) {
379  ASSERT_TRUE(WriteFile(test_file(), "content"));
380  FilePathWatcher watcher;
381  scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
382  ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get()));
383
384  ASSERT_TRUE(file_util::Delete(test_file(), false));
385  VLOG(1) << "Waiting for file deletion";
386  ASSERT_TRUE(WaitForEvents());
387
388  ASSERT_TRUE(WriteFile(test_file(), "content"));
389  VLOG(1) << "Waiting for file creation";
390  ASSERT_TRUE(WaitForEvents());
391}
392
393TEST_F(FilePathWatcherTest, WatchDirectory) {
394  FilePathWatcher watcher;
395  FilePath dir(temp_dir_.path().AppendASCII("dir"));
396  FilePath file1(dir.AppendASCII("file1"));
397  FilePath file2(dir.AppendASCII("file2"));
398  scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
399  ASSERT_TRUE(SetupWatch(dir, &watcher, delegate.get()));
400
401  ASSERT_TRUE(file_util::CreateDirectory(dir));
402  VLOG(1) << "Waiting for directory creation";
403  ASSERT_TRUE(WaitForEvents());
404
405  ASSERT_TRUE(WriteFile(file1, "content"));
406  VLOG(1) << "Waiting for file1 creation";
407  ASSERT_TRUE(WaitForEvents());
408
409#if !defined(OS_MACOSX)
410  // Mac implementation does not detect files modified in a directory.
411  ASSERT_TRUE(WriteFile(file1, "content v2"));
412  VLOG(1) << "Waiting for file1 modification";
413  ASSERT_TRUE(WaitForEvents());
414#endif  // !OS_MACOSX
415
416  ASSERT_TRUE(file_util::Delete(file1, false));
417  VLOG(1) << "Waiting for file1 deletion";
418  ASSERT_TRUE(WaitForEvents());
419
420  ASSERT_TRUE(WriteFile(file2, "content"));
421  VLOG(1) << "Waiting for file2 creation";
422  ASSERT_TRUE(WaitForEvents());
423}
424
425TEST_F(FilePathWatcherTest, MoveParent) {
426  FilePathWatcher file_watcher;
427  FilePathWatcher subdir_watcher;
428  FilePath dir(temp_dir_.path().AppendASCII("dir"));
429  FilePath dest(temp_dir_.path().AppendASCII("dest"));
430  FilePath subdir(dir.AppendASCII("subdir"));
431  FilePath file(subdir.AppendASCII("file"));
432  scoped_refptr<TestDelegate> file_delegate(new TestDelegate(collector()));
433  ASSERT_TRUE(SetupWatch(file, &file_watcher, file_delegate.get()));
434  scoped_refptr<TestDelegate> subdir_delegate(new TestDelegate(collector()));
435  ASSERT_TRUE(SetupWatch(subdir, &subdir_watcher, subdir_delegate.get()));
436
437  // Setup a directory hierarchy.
438  ASSERT_TRUE(file_util::CreateDirectory(subdir));
439  ASSERT_TRUE(WriteFile(file, "content"));
440  VLOG(1) << "Waiting for file creation";
441  ASSERT_TRUE(WaitForEvents());
442
443  // Move the parent directory.
444  file_util::Move(dir, dest);
445  VLOG(1) << "Waiting for directory move";
446  ASSERT_TRUE(WaitForEvents());
447}
448
449TEST_F(FilePathWatcherTest, MoveChild) {
450  FilePathWatcher file_watcher;
451  FilePathWatcher subdir_watcher;
452  FilePath source_dir(temp_dir_.path().AppendASCII("source"));
453  FilePath source_subdir(source_dir.AppendASCII("subdir"));
454  FilePath source_file(source_subdir.AppendASCII("file"));
455  FilePath dest_dir(temp_dir_.path().AppendASCII("dest"));
456  FilePath dest_subdir(dest_dir.AppendASCII("subdir"));
457  FilePath dest_file(dest_subdir.AppendASCII("file"));
458
459  // Setup a directory hierarchy.
460  ASSERT_TRUE(file_util::CreateDirectory(source_subdir));
461  ASSERT_TRUE(WriteFile(source_file, "content"));
462
463  scoped_refptr<TestDelegate> file_delegate(new TestDelegate(collector()));
464  ASSERT_TRUE(SetupWatch(dest_file, &file_watcher, file_delegate.get()));
465  scoped_refptr<TestDelegate> subdir_delegate(new TestDelegate(collector()));
466  ASSERT_TRUE(SetupWatch(dest_subdir, &subdir_watcher, subdir_delegate.get()));
467
468  // Move the directory into place, s.t. the watched file appears.
469  ASSERT_TRUE(file_util::Move(source_dir, dest_dir));
470  ASSERT_TRUE(WaitForEvents());
471}
472
473#if !defined(OS_LINUX)
474// Linux implementation of FilePathWatcher doesn't catch attribute changes.
475// http://crbug.com/78043
476
477// Verify that changing attributes on a file is caught
478TEST_F(FilePathWatcherTest, FileAttributesChanged) {
479  ASSERT_TRUE(WriteFile(test_file(), "content"));
480  FilePathWatcher watcher;
481  scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
482  ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get()));
483
484  // Now make sure we get notified if the file is modified.
485  ASSERT_TRUE(file_util::MakeFileUnreadable(test_file()));
486  ASSERT_TRUE(WaitForEvents());
487}
488
489#endif  // !OS_LINUX
490
491enum Permission {
492  Read,
493  Write,
494  Execute
495};
496
497bool ChangeFilePermissions(const FilePath& path, Permission perm, bool allow) {
498#if defined(OS_POSIX)
499  struct stat stat_buf;
500
501  if (stat(path.value().c_str(), &stat_buf) != 0)
502    return false;
503
504  mode_t mode = 0;
505  switch (perm) {
506    case Read:
507      mode = S_IRUSR | S_IRGRP | S_IROTH;
508      break;
509    case Write:
510      mode = S_IWUSR | S_IWGRP | S_IWOTH;
511      break;
512    case Execute:
513      mode = S_IXUSR | S_IXGRP | S_IXOTH;
514      break;
515    default:
516      ADD_FAILURE() << "unknown perm " << perm;
517      return false;
518  }
519  if (allow) {
520    stat_buf.st_mode |= mode;
521  } else {
522    stat_buf.st_mode &= ~mode;
523  }
524  return chmod(path.value().c_str(), stat_buf.st_mode) == 0;
525
526#elif defined(OS_WIN)
527  PACL old_dacl;
528  PSECURITY_DESCRIPTOR security_descriptor;
529  if (GetNamedSecurityInfo(const_cast<wchar_t*>(path.value().c_str()),
530                           SE_FILE_OBJECT,
531                           DACL_SECURITY_INFORMATION, NULL, NULL, &old_dacl,
532                           NULL, &security_descriptor) != ERROR_SUCCESS)
533    return false;
534
535  DWORD mode = 0;
536  switch (perm) {
537    case Read:
538      mode = GENERIC_READ;
539      break;
540    case Write:
541      mode = GENERIC_WRITE;
542      break;
543    case Execute:
544      mode = GENERIC_EXECUTE;
545      break;
546    default:
547      ADD_FAILURE() << "unknown perm " << perm;
548      return false;
549  }
550
551  // Deny Read access for the current user.
552  EXPLICIT_ACCESS change;
553  change.grfAccessPermissions = mode;
554  change.grfAccessMode = allow ? GRANT_ACCESS : DENY_ACCESS;
555  change.grfInheritance = 0;
556  change.Trustee.pMultipleTrustee = NULL;
557  change.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
558  change.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
559  change.Trustee.TrusteeType = TRUSTEE_IS_USER;
560  change.Trustee.ptstrName = L"CURRENT_USER";
561
562  PACL new_dacl;
563  if (SetEntriesInAcl(1, &change, old_dacl, &new_dacl) != ERROR_SUCCESS) {
564    LocalFree(security_descriptor);
565    return false;
566  }
567
568  DWORD rc = SetNamedSecurityInfo(const_cast<wchar_t*>(path.value().c_str()),
569                                  SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
570                                  NULL, NULL, new_dacl, NULL);
571  LocalFree(security_descriptor);
572  LocalFree(new_dacl);
573
574  return rc == ERROR_SUCCESS;
575#else
576  NOTIMPLEMENTED();
577  return false;
578#endif
579}
580
581#if defined(OS_MACOSX)
582// Linux implementation of FilePathWatcher doesn't catch attribute changes.
583// http://crbug.com/78043
584// Windows implementation of FilePathWatcher catches attribute changes that
585// don't affect the path being watched.
586// http://crbug.com/78045
587
588// Verify that changing attributes on a directory works.
589TEST_F(FilePathWatcherTest, DirAttributesChanged) {
590  FilePath test_dir1(temp_dir_.path().AppendASCII("DirAttributesChangedDir1"));
591  FilePath test_dir2(test_dir1.AppendASCII("DirAttributesChangedDir2"));
592  FilePath test_file(test_dir2.AppendASCII("DirAttributesChangedFile"));
593  // Setup a directory hierarchy.
594  ASSERT_TRUE(file_util::CreateDirectory(test_dir1));
595  ASSERT_TRUE(file_util::CreateDirectory(test_dir2));
596  ASSERT_TRUE(WriteFile(test_file, "content"));
597
598  FilePathWatcher watcher;
599  scoped_refptr<TestDelegate> delegate(new TestDelegate(collector()));
600  ASSERT_TRUE(SetupWatch(test_file, &watcher, delegate.get()));
601
602  // We should not get notified in this case as it hasn't affected our ability
603  // to access the file.
604  ASSERT_TRUE(ChangeFilePermissions(test_dir1, Read, false));
605  loop_.PostDelayedTask(FROM_HERE,
606                        new MessageLoop::QuitTask,
607                        TestTimeouts::tiny_timeout_ms());
608  ASSERT_FALSE(WaitForEvents());
609  ASSERT_TRUE(ChangeFilePermissions(test_dir1, Read, true));
610
611  // We should get notified in this case because filepathwatcher can no
612  // longer access the file
613  ASSERT_TRUE(ChangeFilePermissions(test_dir1, Execute, false));
614  ASSERT_TRUE(WaitForEvents());
615  ASSERT_TRUE(ChangeFilePermissions(test_dir1, Execute, true));
616}
617
618#endif  // OS_MACOSX
619}  // namespace
620
621}  // namespace files
622}  // namespace base
623