Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / internet / test / test_inotify.py
1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
3
4 """
5 Tests for the inotify wrapper in L{twisted.internet.inotify}.
6 """
7
8 from twisted.internet import defer, reactor
9 from twisted.python import filepath, runtime
10 from twisted.trial import unittest
11
12 try:
13     from twisted.python import _inotify
14 except ImportError:
15     inotify = None
16 else:
17     from twisted.internet import inotify
18
19
20
21 class TestINotify(unittest.TestCase):
22     """
23     Define all the tests for the basic functionality exposed by
24     L{inotify.INotify}.
25     """
26     if not runtime.platform.supportsINotify():
27         skip = "This platform doesn't support INotify."
28
29     def setUp(self):
30         self.dirname = filepath.FilePath(self.mktemp())
31         self.dirname.createDirectory()
32         self.inotify = inotify.INotify()
33         self.inotify.startReading()
34         self.addCleanup(self.inotify.loseConnection)
35
36
37     def test_initializationErrors(self):
38         """
39         L{inotify.INotify} emits a C{RuntimeError} when initialized
40         in an environment that doesn't support inotify as we expect it.
41
42         We just try to raise an exception for every possible case in
43         the for loop in L{inotify.INotify._inotify__init__}.
44         """
45         class FakeINotify:
46             def init(self):
47                 raise inotify.INotifyError()
48         self.patch(inotify.INotify, '_inotify', FakeINotify())
49         self.assertRaises(inotify.INotifyError, inotify.INotify)
50
51
52     def _notificationTest(self, mask, operation, expectedPath=None):
53         """
54         Test notification from some filesystem operation.
55
56         @param mask: The event mask to use when setting up the watch.
57
58         @param operation: A function which will be called with the
59             name of a file in the watched directory and which should
60             trigger the event.
61
62         @param expectedPath: Optionally, the name of the path which is
63             expected to come back in the notification event; this will
64             also be passed to C{operation} (primarily useful when the
65             operation is being done to the directory itself, not a
66             file in it).
67
68         @return: A L{Deferred} which fires successfully when the
69             expected event has been received or fails otherwise.
70         """
71         if expectedPath is None:
72             expectedPath = self.dirname.child("foo.bar")
73         notified = defer.Deferred()
74         def cbNotified((watch, filename, events)):
75             self.assertEqual(filename, expectedPath)
76             self.assertTrue(events & mask)
77         notified.addCallback(cbNotified)
78
79         self.inotify.watch(
80             self.dirname, mask=mask,
81             callbacks=[lambda *args: notified.callback(args)])
82         operation(expectedPath)
83         return notified
84
85
86     def test_access(self):
87         """
88         Reading from a file in a monitored directory sends an
89         C{inotify.IN_ACCESS} event to the callback.
90         """
91         def operation(path):
92             path.setContent("foo")
93             path.getContent()
94
95         return self._notificationTest(inotify.IN_ACCESS, operation)
96
97
98     def test_modify(self):
99         """
100         Writing to a file in a monitored directory sends an
101         C{inotify.IN_MODIFY} event to the callback.
102         """
103         def operation(path):
104             fObj = path.open("w")
105             fObj.write('foo')
106             fObj.close()
107
108         return self._notificationTest(inotify.IN_MODIFY, operation)
109
110
111     def test_attrib(self):
112         """
113         Changing the metadata of a a file in a monitored directory
114         sends an C{inotify.IN_ATTRIB} event to the callback.
115         """
116         def operation(path):
117             path.touch()
118             path.touch()
119
120         return self._notificationTest(inotify.IN_ATTRIB, operation)
121
122
123     def test_closeWrite(self):
124         """
125         Closing a file which was open for writing in a monitored
126         directory sends an C{inotify.IN_CLOSE_WRITE} event to the
127         callback.
128         """
129         def operation(path):
130             fObj = path.open("w")
131             fObj.close()
132
133         return self._notificationTest(inotify.IN_CLOSE_WRITE, operation)
134
135
136     def test_closeNoWrite(self):
137         """
138         Closing a file which was open for reading but not writing in a
139         monitored directory sends an C{inotify.IN_CLOSE_NOWRITE} event
140         to the callback.
141         """
142         def operation(path):
143             path.touch()
144             fObj = path.open("r")
145             fObj.close()
146
147         return self._notificationTest(inotify.IN_CLOSE_NOWRITE, operation)
148
149
150     def test_open(self):
151         """
152         Opening a file in a monitored directory sends an
153         C{inotify.IN_OPEN} event to the callback.
154         """
155         def operation(path):
156             fObj = path.open("w")
157             fObj.close()
158
159         return self._notificationTest(inotify.IN_OPEN, operation)
160
161
162     def test_movedFrom(self):
163         """
164         Moving a file out of a monitored directory sends an
165         C{inotify.IN_MOVED_FROM} event to the callback.
166         """
167         def operation(path):
168             fObj = path.open("w")
169             fObj.close()
170             path.moveTo(filepath.FilePath(self.mktemp()))
171
172         return self._notificationTest(inotify.IN_MOVED_FROM, operation)
173
174
175     def test_movedTo(self):
176         """
177         Moving a file into a monitored directory sends an
178         C{inotify.IN_MOVED_TO} event to the callback.
179         """
180         def operation(path):
181             p = filepath.FilePath(self.mktemp())
182             p.touch()
183             p.moveTo(path)
184
185         return self._notificationTest(inotify.IN_MOVED_TO, operation)
186
187
188     def test_create(self):
189         """
190         Creating a file in a monitored directory sends an
191         C{inotify.IN_CREATE} event to the callback.
192         """
193         def operation(path):
194             fObj = path.open("w")
195             fObj.close()
196
197         return self._notificationTest(inotify.IN_CREATE, operation)
198
199
200     def test_delete(self):
201         """
202         Deleting a file in a monitored directory sends an
203         C{inotify.IN_DELETE} event to the callback.
204         """
205         def operation(path):
206             path.touch()
207             path.remove()
208
209         return self._notificationTest(inotify.IN_DELETE, operation)
210
211
212     def test_deleteSelf(self):
213         """
214         Deleting the monitored directory itself sends an
215         C{inotify.IN_DELETE_SELF} event to the callback.
216         """
217         def operation(path):
218             path.remove()
219
220         return self._notificationTest(
221             inotify.IN_DELETE_SELF, operation, expectedPath=self.dirname)
222
223
224     def test_moveSelf(self):
225         """
226         Renaming the monitored directory itself sends an
227         C{inotify.IN_MOVE_SELF} event to the callback.
228         """
229         def operation(path):
230             path.moveTo(filepath.FilePath(self.mktemp()))
231
232         return self._notificationTest(
233             inotify.IN_MOVE_SELF, operation, expectedPath=self.dirname)
234
235
236     def test_simpleSubdirectoryAutoAdd(self):
237         """
238         L{inotify.INotify} when initialized with autoAdd==True adds
239         also adds the created subdirectories to the watchlist.
240         """
241         def _callback(wp, filename, mask):
242             # We are notified before we actually process new
243             # directories, so we need to defer this check.
244             def _():
245                 try:
246                     self.assertTrue(self.inotify._isWatched(subdir))
247                     d.callback(None)
248                 except Exception:
249                     d.errback()
250             reactor.callLater(0, _)
251
252         checkMask = inotify.IN_ISDIR | inotify.IN_CREATE
253         self.inotify.watch(
254             self.dirname, mask=checkMask, autoAdd=True,
255             callbacks=[_callback])
256         subdir = self.dirname.child('test')
257         d = defer.Deferred()
258         subdir.createDirectory()
259         return d
260
261
262     def test_simpleDeleteDirectory(self):
263         """
264         L{inotify.INotify} removes a directory from the watchlist when
265         it's removed from the filesystem.
266         """
267         calls = []
268         def _callback(wp, filename, mask):
269             # We are notified before we actually process new
270             # directories, so we need to defer this check.
271             def _():
272                 try:
273                     self.assertTrue(self.inotify._isWatched(subdir))
274                     subdir.remove()
275                 except Exception:
276                     d.errback()
277             def _eb():
278                 # second call, we have just removed the subdir
279                 try:
280                     self.assertTrue(not self.inotify._isWatched(subdir))
281                     d.callback(None)
282                 except Exception:
283                     d.errback()
284
285             if not calls:
286                 # first call, it's the create subdir
287                 calls.append(filename)
288                 reactor.callLater(0, _)
289
290             else:
291                 reactor.callLater(0, _eb)
292
293         checkMask = inotify.IN_ISDIR | inotify.IN_CREATE
294         self.inotify.watch(
295             self.dirname, mask=checkMask, autoAdd=True,
296             callbacks=[_callback])
297         subdir = self.dirname.child('test')
298         d = defer.Deferred()
299         subdir.createDirectory()
300         return d
301
302
303     def test_ignoreDirectory(self):
304         """
305         L{inotify.INotify.ignore} removes a directory from the watchlist
306         """
307         self.inotify.watch(self.dirname, autoAdd=True)
308         self.assertTrue(self.inotify._isWatched(self.dirname))
309         self.inotify.ignore(self.dirname)
310         self.assertFalse(self.inotify._isWatched(self.dirname))
311
312
313     def test_humanReadableMask(self):
314         """
315         L{inotify.humaReadableMask} translates all the possible event
316         masks to a human readable string.
317         """
318         for mask, value in inotify._FLAG_TO_HUMAN:
319             self.assertEqual(inotify.humanReadableMask(mask)[0], value)
320
321         checkMask = (
322             inotify.IN_CLOSE_WRITE | inotify.IN_ACCESS | inotify.IN_OPEN)
323         self.assertEqual(
324             set(inotify.humanReadableMask(checkMask)),
325             set(['close_write', 'access', 'open']))
326
327
328     def test_recursiveWatch(self):
329         """
330         L{inotify.INotify.watch} with recursive==True will add all the
331         subdirectories under the given path to the watchlist.
332         """
333         subdir = self.dirname.child('test')
334         subdir2 = subdir.child('test2')
335         subdir3 = subdir2.child('test3')
336         subdir3.makedirs()
337         dirs = [subdir, subdir2, subdir3]
338         self.inotify.watch(self.dirname, recursive=True)
339         # let's even call this twice so that we test that nothing breaks
340         self.inotify.watch(self.dirname, recursive=True)
341         for d in dirs:
342             self.assertTrue(self.inotify._isWatched(d))
343
344
345     def test_connectionLostError(self):
346         """
347         L{inotify.INotify.connectionLost} if there's a problem while closing
348         the fd shouldn't raise the exception but should log the error
349         """
350         import os
351         in_ = inotify.INotify()
352         os.close(in_._fd)
353         in_.loseConnection()
354         self.flushLoggedErrors()
355
356     def test_noAutoAddSubdirectory(self):
357         """
358         L{inotify.INotify.watch} with autoAdd==False will stop inotify
359         from watching subdirectories created under the watched one.
360         """
361         def _callback(wp, fp, mask):
362             # We are notified before we actually process new
363             # directories, so we need to defer this check.
364             def _():
365                 try:
366                     self.assertFalse(self.inotify._isWatched(subdir.path))
367                     d.callback(None)
368                 except Exception:
369                     d.errback()
370             reactor.callLater(0, _)
371
372         checkMask = inotify.IN_ISDIR | inotify.IN_CREATE
373         self.inotify.watch(
374             self.dirname, mask=checkMask, autoAdd=False,
375             callbacks=[_callback])
376         subdir = self.dirname.child('test')
377         d = defer.Deferred()
378         subdir.createDirectory()
379         return d
380
381
382     def test_seriesOfWatchAndIgnore(self):
383         """
384         L{inotify.INotify} will watch a filepath for events even if the same
385         path is repeatedly added/removed/re-added to the watchpoints.
386         """
387         expectedPath = self.dirname.child("foo.bar2")
388         expectedPath.touch()
389
390         notified = defer.Deferred()
391         def cbNotified((ignored, filename, events)):
392             self.assertEqual(filename, expectedPath)
393             self.assertTrue(events & inotify.IN_DELETE_SELF)
394
395         def callIt(*args):
396             notified.callback(args)
397
398         # Watch, ignore, watch again to get into the state being tested.
399         self.assertTrue(self.inotify.watch(expectedPath, callbacks=[callIt]))
400         self.inotify.ignore(expectedPath)
401         self.assertTrue(
402             self.inotify.watch(
403                 expectedPath, mask=inotify.IN_DELETE_SELF, callbacks=[callIt]))
404
405         notified.addCallback(cbNotified)
406
407         # Apparently in kernel version < 2.6.25, inofify has a bug in the way
408         # similar events are coalesced.  So, be sure to generate a different
409         # event here than the touch() at the top of this method might have
410         # generated.
411         expectedPath.remove()
412
413         return notified
414
415
416     def test_ignoreFilePath(self):
417         """
418         L{inotify.INotify} will ignore a filepath after it has been removed from
419         the watch list.
420         """
421         expectedPath = self.dirname.child("foo.bar2")
422         expectedPath.touch()
423         expectedPath2 = self.dirname.child("foo.bar3")
424         expectedPath2.touch()
425
426         notified = defer.Deferred()
427         def cbNotified((ignored, filename, events)):
428             self.assertEqual(filename, expectedPath2)
429             self.assertTrue(events & inotify.IN_DELETE_SELF)
430
431         def callIt(*args):
432             notified.callback(args)
433
434         self.assertTrue(
435             self.inotify.watch(
436                 expectedPath, inotify.IN_DELETE_SELF, callbacks=[callIt]))
437         notified.addCallback(cbNotified)
438
439         self.assertTrue(
440             self.inotify.watch(
441                 expectedPath2, inotify.IN_DELETE_SELF, callbacks=[callIt]))
442
443         self.inotify.ignore(expectedPath)
444
445         expectedPath.remove()
446         expectedPath2.remove()
447
448         return notified
449
450
451     def test_ignoreNonWatchedFile(self):
452         """
453         L{inotify.INotify} will raise KeyError if a non-watched filepath is
454         ignored.
455         """
456         expectedPath = self.dirname.child("foo.ignored")
457         expectedPath.touch()
458
459         self.assertRaises(KeyError, self.inotify.ignore, expectedPath)
460
461
462     def test_complexSubdirectoryAutoAdd(self):
463         """
464         L{inotify.INotify} with autoAdd==True for a watched path
465         generates events for every file or directory already present
466         in a newly created subdirectory under the watched one.
467
468         This tests that we solve a race condition in inotify even though
469         we may generate duplicate events.
470         """
471         calls = set()
472         def _callback(wp, filename, mask):
473             calls.add(filename)
474             if len(calls) == 6:
475                 try:
476                     self.assertTrue(self.inotify._isWatched(subdir))
477                     self.assertTrue(self.inotify._isWatched(subdir2))
478                     self.assertTrue(self.inotify._isWatched(subdir3))
479                     created = someFiles + [subdir, subdir2, subdir3]
480                     self.assertEqual(len(calls), len(created))
481                     self.assertEqual(calls, set(created))
482                 except Exception:
483                     d.errback()
484                 else:
485                     d.callback(None)
486
487         checkMask = inotify.IN_ISDIR | inotify.IN_CREATE
488         self.inotify.watch(
489             self.dirname, mask=checkMask, autoAdd=True,
490             callbacks=[_callback])
491         subdir = self.dirname.child('test')
492         subdir2 = subdir.child('test2')
493         subdir3 = subdir2.child('test3')
494         d = defer.Deferred()
495         subdir3.makedirs()
496
497         someFiles = [subdir.child('file1.dat'),
498                      subdir2.child('file2.dat'),
499                      subdir3.child('file3.dat')]
500         # Add some files in pretty much all the directories so that we
501         # see that we process all of them.
502         for i, filename in enumerate(someFiles):
503             filename.setContent(filename.path)
504         return d