~james-w/+junk/test_watcher

1 by James Westby
Prototype implementation
1
# Licensed under the MIT license
2
# http://opensource.org/licenses/mit-license.php
3
# Copyright 2008 Adroll.com and Valentino Volonghi <dialtone@adroll.com>
4
5
from twisted.internet import defer, reactor
6
from twisted.python import filepath
7
from twisted.trial import unittest
8
9
import inotify
10
11
class TestINotify(unittest.TestCase):
12
    def setUp(self):
13
        self.dirname = filepath.FilePath(self.mktemp())
14
        self.dirname.createDirectory()
15
        self.inotify = inotify.INotify()
16
17
    def tearDown(self):
18
        self.inotify.release()
19
        self.inotify = None
20
        self.dirname.remove()
21
22
    def test_notifications(self):
23
        """
24
        Test that a notification is actually delivered on a file
25
        creation.
26
        """
27
        NEW_FILENAME = "new_file.file"
28
        EXTRA_ARG = "HELLO"
29
        checkMask = inotify.IN_CREATE | inotify.IN_CLOSE_WRITE
30
        calls = []
31
        
32
        # We actually expect 2 calls here, one when we create
33
        # and one when we close the file after writing it.
34
        def _callback(wp, filename, mask, data):
35
            try:
36
                self.assertEquals(filename, NEW_FILENAME)
37
                self.assertEquals(data, EXTRA_ARG)
38
                calls.append(filename)
39
                if len(calls) == 2:
40
                    self.assert_(mask & inotify.IN_CLOSE_WRITE)
41
                    d.callback(None)
42
                elif len(calls) == 1:
43
                    self.assert_(mask & inotify.IN_CREATE)
44
            except Exception, e:
45
                d.errback(e)
46
47
        self.inotify.watch(
48
            self.dirname, mask=checkMask,
49
            callbacks=(_callback, EXTRA_ARG)
50
        )
51
        d = defer.Deferred()
52
        f = self.dirname.child(NEW_FILENAME).open('wb')
53
        f.write("hello darling")
54
        f.close()
55
        return d
56
57
    def test_simpleSubdirectoryAutoAdd(self):
58
        """
59
        Test that when a subdirectory is added to a watched directory
60
        it is also added to the watched list.
61
        """
62
        def _callback(wp, filename, mask, data):
63
            # We are notified before we actually process new
64
            # directories, so we need to defer this check.
65
            def _():
66
                try:
67
                    self.assert_(self.inotify.isWatched(SUBDIR.path))
68
                    d.callback(None)
69
                except Exception, e:
70
                    d.errback(e)
71
            reactor.callLater(0, _)
72
73
        checkMask = inotify.IN_ISDIR | inotify.IN_CREATE
74
        self.inotify.watch(
75
            self.dirname, mask=checkMask, autoAdd=True,
76
            callbacks=(_callback, None)
77
        )
78
        SUBDIR = self.dirname.child('test')
79
        d = defer.Deferred()
80
        SUBDIR.createDirectory()
81
        return d
82
    
83
    def test_simpleDeleteDirectory(self):
84
        """
85
        Test that when a subdirectory is added and then removed it is
86
        also removed from the watchlist
87
        """
88
        calls = []
89
        def _callback(wp, filename, mask, data):
90
            # We are notified before we actually process new
91
            # directories, so we need to defer this check.
92
            def _():
93
                try:
94
                    self.assert_(self.inotify.isWatched(SUBDIR.path))
95
                    SUBDIR.remove()
96
                except Exception, e:
97
                    print e
98
                    d.errback(e)
99
            def _eb():
100
                # second call, we have just removed the subdir
101
                try:
102
                    self.assert_(not self.inotify.isWatched(SUBDIR.path))
103
                    d.callback(None)
104
                except Exception, e:
105
                    print e
106
                    d.errback(e)
107
                
108
            if not calls:
109
                # first call, it's the create subdir
110
                calls.append(filename)
111
                reactor.callLater(0.1, _)
112
            
113
            else:
114
                reactor.callLater(0.1, _eb)
115
116
        checkMask = inotify.IN_ISDIR | inotify.IN_CREATE
117
        self.inotify.watch(
118
            self.dirname, mask=checkMask, autoAdd=True,
119
            callbacks=(_callback, None)
120
        )
121
        SUBDIR = self.dirname.child('test')
122
        d = defer.Deferred()
123
        SUBDIR.createDirectory()
124
        return d
125
    
126
    def test_ignoreDirectory(self):
127
        """
128
        Test that ignoring a directory correctly removes it from the
129
        watchlist without removing it from the filesystem.
130
        """
131
        self.inotify.watch(
132
            self.dirname, autoAdd=True
133
        )
134
        self.assert_(self.inotify.isWatched(self.dirname))
135
        self.inotify.ignore(self.dirname)
136
        self.assert_(not self.inotify.isWatched(self.dirname))
137
    
138
    def test_watchPoint(self):
139
        """
140
        Test that Watch methods work as advertised
141
        """
142
        w = inotify.Watch('/tmp/foobar')
143
        f = lambda : 5
144
        w.addCallback(f)
145
        self.assert_(w.callbacks, [(f, None)])
146
    
147
    def test_flagToHuman(self):
148
        """
149
        Test the helper function
150
        """
151
        for mask, value in inotify._FLAG_TO_HUMAN.iteritems():
152
            self.assert_(inotify.flagToHuman(mask)[0], value)
153
        
154
        checkMask = inotify.IN_CLOSE_WRITE|inotify.IN_ACCESS|inotify.IN_OPEN
155
        self.assert_(
156
            len(inotify.flagToHuman(checkMask)),
157
            3
158
        )
159
    
160
    def test_recursiveWatch(self):
161
        """
162
        Test that a recursive watch correctly adds all the paths in
163
        the watched directory.
164
        """
165
        SUBDIR = self.dirname.child('test')
166
        SUBDIR2 = SUBDIR.child('test2')
167
        SUBDIR3 = SUBDIR2.child('test3')
168
        SUBDIR3.makedirs()
169
        DIRS = [SUBDIR, SUBDIR2, SUBDIR3]
170
        self.inotify.watch(self.dirname, recursive=True)
171
        # let's even call this twice so that we test that nothing breaks
172
        self.inotify.watch(self.dirname, recursive=True)
173
        for d in DIRS:
174
            self.assert_(self.inotify.isWatched(d))
175
    
176
    def test_noAutoAddSubdirectory(self):
177
        """
178
        Test that if auto_add is off we don't add a new directory
179
        """
180
        def _callback(wp, filename, mask, data):
181
            # We are notified before we actually process new
182
            # directories, so we need to defer this check.
183
            def _():
184
                try:
185
                    self.assert_(not self.inotify.isWatched(SUBDIR.path))
186
                    d.callback(None)
187
                except Exception, e:
188
                    d.errback(e)
189
            reactor.callLater(0, _)
190
191
        checkMask = inotify.IN_ISDIR | inotify.IN_CREATE
192
        self.inotify.watch(
193
            self.dirname, mask=checkMask, autoAdd=False,
194
            callbacks=(_callback, None)
195
        )
196
        SUBDIR = self.dirname.child('test')
197
        d = defer.Deferred()
198
        SUBDIR.createDirectory()
199
        return d
200
    
201
    def test_complexSubdirectoryAutoAdd(self):
202
        """
203
        Test that when we add one subdirectory with other new children
204
        and files we end up with the notifications for those files and
205
        with all those directories watched.
206
        
207
        This is basically the most critical testcase for inotify.
208
        """
209
        calls = set()
210
        def _callback(wp, filename, mask, data):
211
            # We are notified before we actually process new
212
            # directories, so we need to defer this check.
213
            def _():
214
                try:
215
                    self.assert_(self.inotify.isWatched(SUBDIR.path))
216
                    self.assert_(self.inotify.isWatched(SUBDIR2.path))
217
                    self.assert_(self.inotify.isWatched(SUBDIR3.path))
218
                    CREATED = SOME_FILES.union(
219
                        set([SUBDIR.basename(),
220
                             SUBDIR2.basename(),
221
                             SUBDIR3.basename()
222
                            ])
223
                    )
224
                    self.assert_(len(calls), len(CREATED))
225
                    self.assertEquals(calls, CREATED)
226
                except Exception, e:
227
                    d.errback(e)
228
                else:
229
                    d.callback(None)
230
            if not calls:
231
                # Just some delay to be sure, given how the algorithm
232
                # works for this we know that there's a new extra cycle
233
                # every subdirectory
234
                reactor.callLater(0.1, _)
235
            calls.add(filename)
236
237
        checkMask = inotify.IN_ISDIR | inotify.IN_CREATE
238
        self.inotify.watch(
239
            self.dirname, mask=checkMask, autoAdd=True,
240
            callbacks=(_callback, None)
241
        )
242
        SUBDIR = self.dirname.child('test')
243
        SUBDIR2 = SUBDIR.child('test2')
244
        SUBDIR3 = SUBDIR2.child('test3')
245
        SOME_FILES = set(["file1.dat", "file2.dat", "file3.dat"])
246
        d = defer.Deferred()
247
        SUBDIR3.makedirs()
248
        
249
        # Add some files in pretty much all the directories so that we
250
        # see that we process all of them.
251
        for i, filename in enumerate(SOME_FILES):
252
            if not i:
253
                S = SUBDIR
254
            if i == 1:
255
                S = SUBDIR2
256
            else:
257
                S = SUBDIR3
258
                
259
            S.child(filename).setContent(filename)
260
        return d