1
# tests.syncdaemon.test_eq_inotify
3
# Author: Facundo Batista <facundo@canonical.com>
5
# Copyright 2009 Canonical Ltd.
7
# This program is free software: you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License version 3, as published
9
# by the Free Software Foundation.
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranties of
13
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14
# PURPOSE. See the GNU General Public License for more details.
16
# You should have received a copy of the GNU General Public License along
17
# with this program. If not, see <http://www.gnu.org/licenses/>.
19
'''Tests for the Event Queue part that uses inotify.'''
25
from twisted.internet import defer, reactor
26
from tests.syncdaemon.test_eventqueue import BaseEQTestCase
29
class WatchTests(BaseEQTestCase):
30
'''Test the EQ API to add and remove watchs.'''
32
def test_add_watch(self):
33
'''Test that watchs can be added.'''
34
# we should have what we asked for
35
self.eq.inotify_add_watch(self.root_dir)
36
self.assertTrue(self.root_dir in self.eq._watchs)
38
# we shouldn't have other stuff
39
self.assertTrue("not-added-dir" not in self.eq._watchs)
41
def test_rm_watch(self):
42
'''Test that watchs can be removed.'''
43
# remove what we added
44
self.eq.inotify_add_watch(self.root_dir)
45
self.eq.inotify_rm_watch(self.root_dir)
46
self.assertTrue(self.root_dir not in self.eq._watchs)
48
# remove different stuff
49
self.eq.inotify_add_watch(self.root_dir)
50
self.assertRaises(ValueError,
51
self.eq.inotify_rm_watch, "not-added-dir")
54
class DynamicHitMe(object):
55
'''Helper class to test a sequence of signals.'''
56
def __init__(self, should_events, test_machinery):
57
self.should_events = []
58
for i, info in enumerate(should_events):
59
self.should_events.append((i,) + info)
60
self.final_step = self.should_events[-1][0]
61
self.should_events.reverse()
62
self.test_machinery = test_machinery
64
def __getattr__(self, name):
65
'''typical method faker'''
66
if not name.startswith("handle_"):
69
asked_event = name[7:]
71
# to what we should match
72
test_info = self.should_events.pop()
74
should_evtname = test_info[1]
75
should_args = test_info[2:]
77
def to_check(*asked_args):
78
'''the function that actually be called'''
79
if asked_args != should_args:
80
self.test_machinery.finished_error(
81
"In step %d received wrong args (%r)" % (step, asked_args))
83
if step == self.final_step:
84
self.test_machinery.finished_ok()
86
if should_evtname != asked_event:
87
msg = "Event %r asked in bad order (%d)" % (asked_event, step)
88
self.test_machinery.finished_error(msg)
92
class BaseTwisted(BaseEQTestCase):
93
'''Base class for twisted tests.'''
95
# this timeout must be bigger than the one used in event_queue
100
BaseEQTestCase.setUp(self)
101
# create the deferred for the tests
102
self._deferred = defer.Deferred()
104
def finished_ok(self):
105
'''Called to indicate that the tests finished ok.'''
106
self._deferred.callback(True)
108
def finished_error(self, msg):
109
'''Called to indicate that the tests finished badly.'''
110
self._deferred.errback(Exception(msg))
113
class SignalingTests(BaseTwisted):
114
'''Test the whole stuff to receive signals.'''
116
def test_file_create_close(self):
117
'''Test receiving the create and close signals on files.'''
118
testfile = os.path.join(self.root_dir, "foo")
120
# helper class, pylint: disable-msg=C0111
122
# class-closure, cannot use self, pylint: disable-msg=E0213
123
def __init__(innerself):
126
def handle_FS_FILE_CREATE(innerself, path):
128
self.finished_error("received a wrong path")
130
innerself.hist.append("create")
132
def handle_FS_FILE_CLOSE_WRITE(innerself, path):
134
self.finished_error("received a wrong path")
136
if innerself.hist == ["create"]:
140
msg = "Finished in bad condition: %s" % innerself.hist
141
self.finished_error(msg)
143
self.eq.inotify_add_watch(self.root_dir)
144
self.eq.subscribe(HitMe())
147
open(testfile, "w").close()
148
return self._deferred
150
def test_dir_create(self):
151
'''Test receiving the create signal on dirs.'''
152
testdir = os.path.join(self.root_dir, "foo")
154
# helper class, pylint: disable-msg=C0111
156
# class-closure, cannot use self, pylint: disable-msg=E0213
157
def handle_FS_DIR_CREATE(innerself, path):
159
self.finished_error("received a wrong path")
164
self.eq.inotify_add_watch(self.root_dir)
165
self.eq.subscribe(HitMe())
169
return self._deferred
171
def test_file_delete(self):
172
'''Test the delete signal on a file.'''
173
testfile = os.path.join(self.root_dir, "foo")
174
open(testfile, "w").close()
176
# helper class, pylint: disable-msg=C0111
178
# class-closure, cannot use self, pylint: disable-msg=E0213
179
def handle_FS_FILE_DELETE(innerself, path):
181
self.finished_error("received a wrong path")
185
self.eq.inotify_add_watch(self.root_dir)
186
self.eq.subscribe(HitMe())
190
return self._deferred
192
def test_dir_delete(self):
193
'''Test the delete signal on a dir.'''
194
testdir = os.path.join(self.root_dir, "foo")
197
# helper class, pylint: disable-msg=C0111
199
# class-closure, cannot use self, pylint: disable-msg=E0213
200
def handle_FS_DIR_DELETE(innerself, path):
202
self.finished_error("received a wrong path")
206
self.eq.inotify_add_watch(self.root_dir)
207
self.eq.subscribe(HitMe())
211
return self._deferred
213
def test_symlink(self):
214
'''Test that symlinks are ignored.'''
215
testdir = os.path.join(self.root_dir, "foo")
217
fromfile = os.path.join(self.root_dir, "from")
218
open(fromfile, "w").close()
219
symlpath = os.path.join(testdir, "syml")
221
class DontHitMe(object):
222
'''we shouldn't be called'''
223
# class-closure, cannot use self, pylint: disable-msg=E0213
224
def handle_default(innerself, *a):
225
'''Something here? Error!'''
226
self.finished_error("don't hit me! received %s" % (a,))
232
# set up everything and freeze
233
self.eq.inotify_add_watch(testdir)
234
self.eq.subscribe(DontHitMe())
236
os.symlink(fromfile, symlpath)
237
reactor.callLater(.1, confirm)
238
return self._deferred
240
def test_file_moved_from(self):
241
'''Test receiving the delete signal on a file when moved_from.'''
242
fromfile = os.path.join(self.root_dir, "foo")
243
helpdir = os.path.join(self.root_dir, "dir")
244
tofile = os.path.join(helpdir, "foo")
245
open(fromfile, "w").close()
248
# helper class, pylint: disable-msg=C0111
250
# class-closure, cannot use self, pylint: disable-msg=E0213
251
def handle_FS_FILE_DELETE(innerself, path):
253
self.finished_error("received a wrong path")
259
self.eq.inotify_add_watch(self.root_dir)
260
self.eq.subscribe(HitMe())
263
os.rename(fromfile, tofile)
264
return self._deferred
266
def test_dir_moved_from(self):
267
'''Test receiving the delete signal on a dir when it's moved_from.'''
268
fromdir = os.path.join(self.root_dir, "foo")
269
helpdir = os.path.join(self.root_dir, "dir")
270
todir = os.path.join(helpdir, "foo")
274
# helper class, pylint: disable-msg=C0111
276
# class-closure, cannot use self, pylint: disable-msg=E0213
277
def handle_FS_DIR_DELETE(innerself, path):
279
self.finished_error("received a wrong path")
285
self.eq.inotify_add_watch(self.root_dir)
286
self.eq.subscribe(HitMe())
289
os.rename(fromdir, todir)
290
return self._deferred
292
def test_file_moved_to(self):
293
'''Test receiving the create signal on a file when it's moved_to.'''
294
fromfile = os.path.join(self.root_dir, "dir", "foo")
295
tofile = os.path.join(self.root_dir, "foo")
296
helpdir = os.path.join(self.root_dir, "dir")
298
open(fromfile, "w").close()
300
# helper class, pylint: disable-msg=C0111
302
# class-closure, cannot use self, pylint: disable-msg=E0213
303
def handle_FS_FILE_CREATE(innerself, path):
305
self.finished_error("received a wrong path")
311
self.eq.inotify_add_watch(self.root_dir)
312
self.eq.subscribe(HitMe())
315
os.rename(fromfile, tofile)
316
return self._deferred
318
def test_dir_moved_to(self):
319
'''Test receiving the create signal on a file when it's moved_to.'''
320
fromdir = os.path.join(self.root_dir, "dir", "foo")
321
todir = os.path.join(self.root_dir, "foo")
322
helpdir = os.path.join(self.root_dir, "dir")
326
# helper class, pylint: disable-msg=C0111
328
# class-closure, cannot use self, pylint: disable-msg=E0213
329
def handle_FS_DIR_CREATE(innerself, path):
331
self.finished_error("received a wrong path")
337
self.eq.inotify_add_watch(self.root_dir)
338
self.eq.subscribe(HitMe())
341
os.rename(fromdir, todir)
342
return self._deferred
344
def test_lots_of_changes(self):
345
'''Test doing several operations on files.'''
346
helpdir = os.path.join(self.root_dir, "dir")
348
mypath = functools.partial(os.path.join, self.root_dir)
350
self.eq.inotify_add_watch(self.root_dir)
353
("FS_FILE_CREATE", mypath("foo")),
354
("FS_FILE_CLOSE_WRITE", mypath("foo")),
355
("FS_FILE_DELETE", mypath("foo")),
356
("FS_FILE_CREATE", mypath("bar")),
357
("FS_FILE_CLOSE_WRITE", mypath("bar")),
358
("FS_FILE_CREATE", mypath("foo")),
359
("FS_FILE_CLOSE_WRITE", mypath("foo")),
360
("FS_FILE_DELETE", mypath("bar")),
361
("FS_FILE_DELETE", mypath("foo")),
362
("FS_FILE_CREATE", mypath("bar")),
363
("FS_FILE_CLOSE_WRITE", mypath("bar")),
364
("FS_FILE_DELETE", mypath("bar")),
366
self.eq.subscribe(DynamicHitMe(should_events, self))
368
# generate the events
369
open(mypath("foo"), "w").close()
370
os.rename(mypath("foo"), mypath("dir", "foo"))
371
open(mypath("bar"), "w").close()
372
os.rename(mypath("dir", "foo"), mypath("foo"))
373
os.rename(mypath("bar"), mypath("dir", "bar"))
374
os.remove(mypath("foo"))
375
os.rename(mypath("dir", "bar"), mypath("bar"))
376
os.remove(mypath("bar"))
377
return self._deferred
379
def test_file_moved_inside(self):
380
'''Test the synthesis of the FILE_MOVE event.'''
381
fromfile = os.path.join(self.root_dir, "foo")
382
self.fs.create(fromfile, "")
383
self.fs.set_node_id(fromfile, "from_node_id")
384
tofile = os.path.join(self.root_dir, "bar")
385
self.fs.create(tofile, "")
386
self.fs.set_node_id(tofile, "to_node_id")
387
open(fromfile, "w").close()
389
# helper class, pylint: disable-msg=C0111
391
# class-closure, cannot use self, pylint: disable-msg=E0213
392
def handle_FS_FILE_MOVE(innerself, path_from, path_to):
393
if path_from != fromfile:
394
self.finished_error("received a wrong path in from")
395
elif path_to != tofile:
396
self.finished_error("received a wrong path in to")
401
self.eq.inotify_add_watch(self.root_dir)
402
self.eq.subscribe(HitMe())
405
os.rename(fromfile, tofile)
406
return self._deferred
408
def test_dir_moved_inside(self):
409
'''Test the synthesis of the DIR_MOVE event.'''
410
fromdir = os.path.join(self.root_dir, "foo")
411
self.fs.create(fromdir, "")
412
self.fs.set_node_id(fromdir, "from_node_id")
413
todir = os.path.join(self.root_dir, "bar")
414
self.fs.create(todir, "")
415
self.fs.set_node_id(todir, "to_node_id")
418
# helper class, pylint: disable-msg=C0111
420
# class-closure, cannot use self, pylint: disable-msg=E0213
421
def handle_FS_DIR_MOVE(innerself, path_from, path_to):
422
if path_from != fromdir:
423
self.finished_error("received a wrong path in from")
424
elif path_to != todir:
425
self.finished_error("received a wrong path in to")
430
self.eq.inotify_add_watch(self.root_dir)
431
self.eq.subscribe(HitMe())
434
os.rename(fromdir, todir)
435
return self._deferred
437
def test_file_moved_inside_mixed(self):
438
'''Test the synthesis of the FILE_MOVE event with more events.'''
439
helpdir = os.path.join(self.root_dir, "dir")
441
mypath = functools.partial(os.path.join, self.root_dir)
442
self.fs.create(mypath('foo'), "")
443
self.fs.set_node_id(mypath('foo'), "foo_node_id")
444
self.fs.create(mypath('bar'), "")
445
self.fs.set_node_id(mypath('bar'), "bar_node_id")
448
self.eq.inotify_add_watch(self.root_dir)
451
("FS_FILE_CREATE", mypath("foo")),
452
("FS_FILE_CLOSE_WRITE", mypath("foo")),
453
("FS_FILE_CREATE", mypath("bar")),
454
("FS_FILE_CLOSE_WRITE", mypath("bar")),
455
("FS_FILE_DELETE", mypath("foo")),
456
("FS_FILE_CREATE", mypath("foo")),
457
("FS_FILE_MOVE", mypath("bar"), mypath("baz")),
458
("FS_FILE_DELETE", mypath("foo")),
459
("FS_FILE_DELETE", mypath("baz")),
461
self.eq.subscribe(DynamicHitMe(should_events, self))
463
# generate the events
464
open(mypath("foo"), "w").close()
465
open(mypath("bar"), "w").close()
466
os.rename(mypath("foo"), mypath("dir", "foo"))
467
os.rename(mypath("dir", "foo"), mypath("foo"))
468
os.rename(mypath("bar"), mypath("baz"))
469
os.remove(mypath("foo"))
470
os.remove(mypath("baz"))
471
return self._deferred
473
def test_dir_with_contents_moved_outside(self):
474
''' test the move of a dir outside the watched diresctory.'''
475
root = os.path.join(self.root_dir, "watched_root")
477
trash = os.path.join(self.root_dir, "trash")
480
testdir = os.path.join(root, "testdir")
481
self.eq.fs.create(testdir, '')
482
self.eq.fs.set_node_id(testdir, 'testdir_id')
484
testfile = os.path.join(testdir, "testfile")
485
self.eq.fs.create(testfile, '')
486
self.eq.fs.set_node_id(testfile, 'testfile_id')
487
open(testfile, 'w').close()
489
paths = [testdir, testfile]
490
# helper class, pylint: disable-msg=C0111
492
# class-closure, cannot use self, pylint: disable-msg=E0213
493
def handle_FS_DIR_DELETE(innerself, path):
494
expected = paths.pop()
496
self.finished_error("received a wrong path, expected:"
497
" %s was: %s " % (expected, path))
498
elif len(paths) == 0:
501
def handle_FS_FILE_DELETE(innerself, path):
502
self.assertEqual(paths.pop(), path)
504
self.eq.inotify_add_watch(root)
505
self.eq.subscribe(HitMe())
508
os.rename(testdir, os.path.join(trash, os.path.basename(testdir)))
509
return self._deferred
511
def test_creation_inside_a_moved_directory(self):
512
'''Test that renaming a directory is supported.'''
513
testdir = os.path.join(self.root_dir, "testdir")
514
self.eq.fs.create(testdir, '')
515
self.eq.fs.set_node_id(testdir, 'testdir_id')
517
newdirname = os.path.join(self.root_dir, "newdir")
519
# helper class, pylint: disable-msg=C0111
521
# class-closure, cannot use self, pylint: disable-msg=E0213
522
def handle_FS_FILE_CREATE(innerself, path):
523
if path != newfilepath:
524
self.finished_error("received a wrong path")
526
os.remove(newfilepath)
530
self.eq.inotify_add_watch(self.root_dir)
531
self.eq.inotify_add_watch(testdir)
532
self.eq.subscribe(HitMe())
535
os.rename(testdir, newdirname)
538
newfilepath = os.path.join(newdirname, "afile")
539
open(newfilepath, "w").close()
540
return self._deferred
542
def test_outside_file_moved_to(self):
543
'''Test receiving the create signal on a file when it's moved_to.'''
544
fromfile = os.path.join(self.root_dir, "foo")
545
root_dir = os.path.join(self.root_dir, "my_files")
546
tofile = os.path.join(root_dir, "foo")
547
mypath = functools.partial(os.path.join, root_dir)
549
open(fromfile, "w").close()
552
("FS_FILE_CREATE", mypath("foo")),
553
("FS_FILE_CLOSE_WRITE", mypath("foo")),
555
self.eq.subscribe(DynamicHitMe(should_events, self))
556
self.eq.inotify_add_watch(root_dir)
559
os.rename(fromfile, tofile)
560
return self._deferred
562
def test_outside_dir_with_contents_moved_to(self):
563
'''Test receiving the create signal on a file when it's moved_to.'''
564
fromdir = os.path.join(self.root_dir, "foo_dir")
565
fromfile = os.path.join(fromdir, "foo")
566
root_dir = os.path.join(self.root_dir, "my_files")
567
mypath = functools.partial(os.path.join, root_dir)
568
todir = os.path.join(root_dir, "foo_dir")
571
open(fromfile, "w").close()
574
("FS_DIR_CREATE", mypath("foo_dir")),
576
self.eq.subscribe(DynamicHitMe(should_events, self))
577
self.eq.inotify_add_watch(root_dir)
580
os.rename(fromdir, todir)
581
return self._deferred
583
class FreezeTests(BaseTwisted):
584
'''Test the freeze mechanism.'''
587
'''API for freeze/freeze_commit stuff.'''
589
self.assertRaises(TypeError, self.eq.freeze_begin)
590
self.assertRaises(TypeError, self.eq.freeze_begin, 1, 2)
591
self.assertRaises(TypeError, self.eq.freeze_commit)
592
self.assertRaises(TypeError, self.eq.freeze_commit, 1, 2)
593
self.assertRaises(TypeError, self.eq.freeze_rollback, 1)
596
self.assertRaises(ValueError, self.eq.freeze_commit, [])
597
self.assertRaises(ValueError, self.eq.freeze_rollback)
599
# freeze, no-double-freeze, freeze_commit, no post-commit or rollback
600
self.eq.freeze_begin(1)
601
self.assertRaises(ValueError, self.eq.freeze_begin, 1)
602
self.eq.freeze_commit([])
603
self.assertRaises(ValueError, self.eq.freeze_commit, [])
604
self.assertRaises(ValueError, self.eq.freeze_rollback)
606
# freeze, rollback, no post-commit or rollback
607
self.eq.freeze_begin(1)
608
self.assertRaises(ValueError, self.eq.freeze_begin, 1)
609
self.eq.freeze_rollback()
610
self.assertRaises(ValueError, self.eq.freeze_commit, [])
611
self.assertRaises(ValueError, self.eq.freeze_rollback)
613
def test_commit_no_middle_events(self):
614
'''Commit behaviour when nothing happened in the middle.'''
615
testdir = os.path.join(self.root_dir, "foo")
618
# helper class, pylint: disable-msg=C0111
620
# class-closure, cannot use self, pylint: disable-msg=E0213
621
def handle_FS_DIR_DELETE(innerself, path):
623
self.finished_error("received a wrong path")
628
'''release with handcrafted event and check result.'''
629
d = self.eq.freeze_commit([("FS_DIR_DELETE", "foobar")])
634
self.finished_error("should not be dirty here")
637
# set up everything and freeze
638
self.eq.inotify_add_watch(testdir)
639
self.eq.subscribe(HitMe())
640
self.eq.freeze_begin(testdir)
642
reactor.callLater(.1, freeze_commit)
643
return self._deferred
645
def test_commit_middle_events(self):
646
'''Commit behaviour when something happened in the middle.'''
647
testdir = os.path.join(self.root_dir, "foo")
648
testfile = os.path.join(testdir, "bar")
651
class DontHitMe(object):
652
'''we shouldn't be called'''
653
# class-closure, cannot use self, pylint: disable-msg=E0213
654
def handle_default(innerself, *a):
655
'''Something here? Error!'''
656
self.finished_error("don't hit me! received %s" % (a,))
659
'''release and check result.'''
660
d = self.eq.freeze_commit([("FS_DIR_DELETE", "foobar")])
664
self.finished_error("it *should* be dirty here")
669
# set up everything and freeze
670
self.eq.inotify_add_watch(testdir)
671
self.eq.subscribe(DontHitMe())
672
self.eq.freeze_begin(testdir)
674
open(testfile, "w").close()
675
reactor.callLater(.1, freeze_commit)
676
return self._deferred
678
def test_rollback(self):
679
'''Check rollback.'''
680
testdir = os.path.join(self.root_dir, "foo")
681
testfile = os.path.join(testdir, "bar")
684
# helper class, pylint: disable-msg=C0111
686
# class-closure, cannot use self, pylint: disable-msg=E0213
687
def handle_FS_DIR_DELETE(innerself, path):
689
self.finished_error("received a wrong path")
694
def freeze_rollback():
695
'''release with handcrafted event and check result.'''
696
self.eq.freeze_rollback()
697
self.eq.freeze_begin(testdir)
698
reactor.callLater(.1,
699
self.eq.freeze_commit, [("FS_DIR_DELETE", "foobar")])
701
# set up everything and freeze
702
self.eq.inotify_add_watch(testdir)
703
self.eq.subscribe(HitMe())
704
self.eq.freeze_begin(testdir)
706
# don't matter if had changes, rollback cleans them
707
open(testfile, "w").close()
708
reactor.callLater(.1, freeze_rollback)
709
return self._deferred
711
def test_selective(self):
712
'''Check that it's frozen only for a path.'''
713
testdir = os.path.join(self.root_dir, "foo")
715
testfile = os.path.join(self.root_dir, "bar")
717
# helper class, pylint: disable-msg=C0111
719
# class-closure, cannot use self, pylint: disable-msg=E0213
720
def __init__(innerself):
723
def handle_FS_FILE_CREATE(innerself, path):
725
self.finished_error("received a wrong path")
727
innerself.hist.append("create")
729
def handle_FS_FILE_CLOSE_WRITE(innerself, path):
731
self.finished_error("received a wrong path")
733
if innerself.hist == ["create"]:
737
msg = "Finished in bad condition: %s" % innerself.hist
738
self.finished_error(msg)
741
self.eq.inotify_add_watch(self.root_dir)
742
self.eq.inotify_add_watch(testdir)
743
self.eq.subscribe(HitMe())
745
# only freeze one path
746
self.eq.freeze_begin(testdir)
748
# generate events in the nonfrozen path
749
open(testfile, "w").close()
751
return self._deferred
755
# pylint: disable-msg=C0111
756
return unittest.TestLoader().loadTestsFromName(__name__)
759
# pylint: disable-msg=C0111
760
loader = unittest.TestLoader()
761
suite = unittest.TestSuite()
762
suite.addTests(loader.loadTestsFromTestCase(SignalingTests))
765
if __name__ == "__main__":