sinotify 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +8 -0
- data/History.txt +3 -0
- data/README.rdoc +141 -0
- data/README.txt +141 -0
- data/Rakefile +72 -0
- data/TODO +12 -0
- data/examples/watcher.rb +30 -0
- data/ext/extconf.rb +12 -0
- data/ext/src/inotify-syscalls.h +24 -0
- data/ext/src/inotify.h +113 -0
- data/ext/src/sinotify.c +205 -0
- data/lib/sinotify.rb +18 -0
- data/lib/sinotify/event.rb +185 -0
- data/lib/sinotify/notifier.rb +334 -0
- data/lib/sinotify/prim_event.rb +114 -0
- data/lib/sinotify/watch.rb +21 -0
- data/lib/sinotify_info.rb +47 -0
- data/sinotify.gemspec +80 -0
- data/spec/prim_notify_spec.rb +98 -0
- data/spec/sinotify_spec.rb +265 -0
- data/spec/spec_helper.rb +14 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +201 -0
- data/tasks/git.rake +40 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +292 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- data/tasks/zentest.rake +36 -0
- metadata +154 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
module Sinotify
|
2
|
+
|
3
|
+
VERSION = '0.0.2'
|
4
|
+
|
5
|
+
# :stopdoc:
|
6
|
+
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
7
|
+
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
8
|
+
|
9
|
+
# Returns the version string for the library.
|
10
|
+
#
|
11
|
+
def self.version
|
12
|
+
VERSION
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the library path for the module. If any arguments are given,
|
16
|
+
# they will be joined to the end of the libray path using
|
17
|
+
# <tt>File.join</tt>.
|
18
|
+
#
|
19
|
+
def self.libpath( *args )
|
20
|
+
args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns the lpath for the module. If any arguments are given,
|
24
|
+
# they will be joined to the end of the path using
|
25
|
+
# <tt>File.join</tt>.
|
26
|
+
#
|
27
|
+
def self.path( *args )
|
28
|
+
args.empty? ? PATH : ::File.join(PATH, args.flatten)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Utility method used to require all files ending in .rb that lie in the
|
32
|
+
# directory below this file that has the same name as the filename passed
|
33
|
+
# in. Optionally, a specific _directory_ name can be passed in such that
|
34
|
+
# the _filename_ does not have to be equivalent to the directory.
|
35
|
+
#
|
36
|
+
def self.require_all_libs_relative_to( fname, dir = nil )
|
37
|
+
dir ||= ::File.basename(fname, '.*')
|
38
|
+
search_me = ::File.expand_path(
|
39
|
+
::File.join(::File.dirname(fname), dir, '**', '*.rb'))
|
40
|
+
|
41
|
+
Dir.glob(search_me).sort.each {|rb| require rb}
|
42
|
+
end
|
43
|
+
|
44
|
+
# :startdoc:
|
45
|
+
|
46
|
+
end # module Sinotify
|
47
|
+
|
data/sinotify.gemspec
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{sinotify}
|
5
|
+
s.version = "0.0.2"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Steven Swerling"]
|
9
|
+
s.date = %q{2009-08-09}
|
10
|
+
s.description = %q{ALPHA Alert -- just uploaded initial release.
|
11
|
+
|
12
|
+
Linux inotify is a means to receive events describing file system activity (create, modify, delete, close, etc).
|
13
|
+
|
14
|
+
Sinotify was derived from aredridel's package (http://raa.ruby-lang.org/project/ruby-inotify/), with the addition of
|
15
|
+
Paul Boon's tweak for making the event_check thread more polite (see
|
16
|
+
http://www.mindbucket.com/2009/02/24/ruby-daemons-verifying-good-behavior/)
|
17
|
+
|
18
|
+
In sinotify, the classes Sinotify::PrimNotifier and Sinotify::PrimEvent provide a low level wrapper to inotify, with
|
19
|
+
the ability to establish 'watches' and then listen for inotify events using one of inotify's synchronous event loops,
|
20
|
+
and providing access to the events' masks (see 'man inotify' for details). Sinotify::PrimEvent class adds a little semantic sugar
|
21
|
+
to the event in to the form of 'etypes', which are just ruby symbols that describe the event mask. If the event has a
|
22
|
+
raw mask of (DELETE_SELF & IS_DIR), then the etypes array would be [:delete_self, :is_dir].
|
23
|
+
|
24
|
+
In addition to the 'straight' wrapper in inotify, sinotify provides an asynchronous implementation of the 'observer
|
25
|
+
pattern' for notification. In other words, Sinotify::Notifier listens in the background for inotify events, adapting
|
26
|
+
them into instances of Sinotify::Event as they come in and immediately placing them in a concurrent queue, from which
|
27
|
+
they are 'announced' to 'subscribers' of the event. [Sinotify uses the 'cosell' implementation of the Announcements
|
28
|
+
event notification framework, hence the terminology 'subscribe' and 'announce' rather then 'listen' and 'trigger' used
|
29
|
+
in the standard event observer pattern. See the 'cosell' package on github for details.]
|
30
|
+
|
31
|
+
A variety of 'knobs' are provided for controlling the behavior of the notifier: whether a watch should apply to a
|
32
|
+
single directory or should recurse into subdirectores, how fast it should broadcast queued events, etc (see
|
33
|
+
Sinotify::Notifier, and the example in the synopsis section below). An event 'spy' can also be setup to log all
|
34
|
+
Sinotify::PrimEvents and Sinotify::Events.
|
35
|
+
|
36
|
+
Sinotify::Event simplifies inotify's muddled event model, sending events only for those files/directories that have
|
37
|
+
changed. That's not to say you can't setup a notifier that recurses into subdirectories, just that any individual
|
38
|
+
event will apply to a single file, and not to its children. Also, event types are identified using words (in the form
|
39
|
+
of ruby :symbols) instead of inotify's event masks. See Sinotify::Event for more explanation.
|
40
|
+
|
41
|
+
The README for inotify:
|
42
|
+
|
43
|
+
http://www.kernel.org/pub/linux/kernel/people/rml/inotify/README
|
44
|
+
|
45
|
+
Selected quotes from the README for inotify:
|
46
|
+
|
47
|
+
* "Rumor is that the 'd' in 'dnotify' does not stand for 'directory' but for 'suck.'"
|
48
|
+
|
49
|
+
* "The 'i' in inotify does not stand for 'suck' but for 'inode' -- the logical
|
50
|
+
choice since inotify is inode-based."
|
51
|
+
|
52
|
+
(The 's' in 'sinotify' does in fact stand for 'suck.')}
|
53
|
+
s.email = %q{sswerling@yahoo.com}
|
54
|
+
s.extensions = ["ext/extconf.rb"]
|
55
|
+
s.extra_rdoc_files = ["History.txt", "README.txt"]
|
56
|
+
s.files = [".gitignore", "History.txt", "README.rdoc", "README.txt", "Rakefile", "TODO", "examples/watcher.rb", "ext/extconf.rb", "ext/src/inotify-syscalls.h", "ext/src/inotify.h", "ext/src/sinotify.c", "lib/sinotify.rb", "lib/sinotify/event.rb", "lib/sinotify/notifier.rb", "lib/sinotify/prim_event.rb", "lib/sinotify/watch.rb", "lib/sinotify_info.rb", "sinotify.gemspec", "spec/prim_notify_spec.rb", "spec/sinotify_spec.rb", "spec/spec_helper.rb"]
|
57
|
+
s.has_rdoc = true
|
58
|
+
s.homepage = %q{http://tab-a.slot-z.net}
|
59
|
+
s.rdoc_options = ["--inline-source", "--main", "README.txt"]
|
60
|
+
s.require_paths = ["lib", "ext"]
|
61
|
+
s.rubyforge_project = %q{sinotify}
|
62
|
+
s.rubygems_version = %q{1.3.2}
|
63
|
+
s.summary = %q{ALPHA Alert -- just uploaded initial release}
|
64
|
+
|
65
|
+
if s.respond_to? :specification_version then
|
66
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
67
|
+
s.specification_version = 3
|
68
|
+
|
69
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
70
|
+
s.add_runtime_dependency(%q<cosell>, [">= 0"])
|
71
|
+
s.add_development_dependency(%q<bones>, [">= 2.5.1"])
|
72
|
+
else
|
73
|
+
s.add_dependency(%q<cosell>, [">= 0"])
|
74
|
+
s.add_dependency(%q<bones>, [">= 2.5.1"])
|
75
|
+
end
|
76
|
+
else
|
77
|
+
s.add_dependency(%q<cosell>, [">= 0"])
|
78
|
+
s.add_dependency(%q<bones>, [">= 2.5.1"])
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
#
|
5
|
+
# The tests for the inotify wrapper.
|
6
|
+
# Mostly taken straight from ruby-inotify's tests, w/ some tweaks.
|
7
|
+
#
|
8
|
+
describe Sinotify::PrimNotifier do
|
9
|
+
|
10
|
+
before(:each) do
|
11
|
+
@inotify = Sinotify::PrimNotifier.new
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should be able to create and remove a watch descriptor" do
|
15
|
+
wd = @inotify.add_watch("/tmp", Sinotify::PrimEvent::CREATE)
|
16
|
+
wd.class.should be_eql(Fixnum)
|
17
|
+
wd.should_not be_eql(0)
|
18
|
+
@inotify.rm_watch(wd).should be_true
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should get events on watched directory and get name of altered file in watched directory" do
|
22
|
+
test_fn = "/tmp/sinotify-test"
|
23
|
+
FileUtils.rm_f test_fn
|
24
|
+
wd = @inotify.add_watch("/tmp", Sinotify::PrimEvent::DELETE | Sinotify::PrimEvent::CREATE)
|
25
|
+
begin
|
26
|
+
FileUtils.touch test_fn
|
27
|
+
@inotify.each_event do |ev|
|
28
|
+
#puts "-----------#{ev.etypes.inspect}"
|
29
|
+
ev.class.should be_eql(Sinotify::PrimEvent)
|
30
|
+
ev.name.should be_eql('sinotify-test')
|
31
|
+
ev.mask.should be_eql(Sinotify::PrimEvent::CREATE)
|
32
|
+
ev.has_etype?(:create).should be_true
|
33
|
+
ev.etypes.size.should be_eql(1)
|
34
|
+
ev.inspect.should be_eql "<Sinotify::PrimEvent :name => 'sinotify-test', :etypes => [:create], :mask => 100, :watch_descriptor => 1>"
|
35
|
+
break
|
36
|
+
end
|
37
|
+
FileUtils.rm_f test_fn
|
38
|
+
@inotify.each_event do |ev|
|
39
|
+
#puts "-----------#{ev.etypes.inspect}"
|
40
|
+
ev.has_etype?(:delete).should be_true
|
41
|
+
ev.etypes.size.should be_eql(1)
|
42
|
+
break
|
43
|
+
end
|
44
|
+
ensure
|
45
|
+
@inotify.rm_watch(wd)
|
46
|
+
FileUtils.rm_f test_fn
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should get events on watched file" do
|
51
|
+
test_fn = "/tmp/sinotify-test"
|
52
|
+
FileUtils.touch test_fn
|
53
|
+
wd = @inotify.add_watch(test_fn, Sinotify::PrimEvent::ATTRIB | Sinotify::PrimEvent::DELETE | Sinotify::PrimEvent::MODIFY)
|
54
|
+
begin
|
55
|
+
FileUtils.touch test_fn
|
56
|
+
@inotify.each_event do |ev|
|
57
|
+
ev.name.should be_nil # name is only set when watching a directory
|
58
|
+
ev.has_etype?(:attrib).should be_true
|
59
|
+
break
|
60
|
+
end
|
61
|
+
|
62
|
+
File.open(test_fn, 'a'){|f| f << 'hi'}
|
63
|
+
@inotify.each_event do |ev|
|
64
|
+
ev.name.should be_nil # name is only set when watching a directory
|
65
|
+
ev.has_etype?(:modify).should be_true
|
66
|
+
break
|
67
|
+
end
|
68
|
+
|
69
|
+
FileUtils.rm_f test_fn
|
70
|
+
@inotify.each_event do |ev|
|
71
|
+
#puts "-----------#{ev.inspect}"
|
72
|
+
# TODO: Look into this -- when deleting a file, it gets an event of type :attrib instead of :delete.
|
73
|
+
# Is this a bug or something I am doing?
|
74
|
+
# ev.has_etype?(:delete).should be_true
|
75
|
+
break
|
76
|
+
end
|
77
|
+
|
78
|
+
# since the event is deleted, it should not be possible to remove the watch
|
79
|
+
lambda{@inotify.rm_watch(wd)}.should raise_error
|
80
|
+
|
81
|
+
ensure
|
82
|
+
@inotify.rm_watch(wd) rescue nil
|
83
|
+
FileUtils.rm_f test_fn
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
protected
|
88
|
+
|
89
|
+
def little_bench(msg, &block)
|
90
|
+
start = Time.now
|
91
|
+
result = block.call
|
92
|
+
puts "#{msg}: #{Time.now - start} sec"
|
93
|
+
return result
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# EOF
|
98
|
+
|
@@ -0,0 +1,265 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), %w[spec_helper])
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
class MockPrimEvent
|
5
|
+
attr_accessor :etypes, :mask, :wd, :name
|
6
|
+
end
|
7
|
+
|
8
|
+
#
|
9
|
+
# WARNING: These tests are a bit brittle. They depend on events taking place in threads as a result
|
10
|
+
# of filesytem events (inotify events). Sometimes the file system events dont come in as fast as
|
11
|
+
# desirable for the test, or sometimes ruby threads themselves may not get scheduled fast enough.
|
12
|
+
# If a test is failing on your system, it may start to succeed if you increase the values
|
13
|
+
# in tiny_pause!, pause!, or big_pause! methods below.
|
14
|
+
#
|
15
|
+
describe Sinotify do
|
16
|
+
|
17
|
+
# A lot of Sinotify work occurs in background threads (eg. adding watches, adding subdirectories),
|
18
|
+
# so the tests may insert a tiny pauses to allow the bg threads to do their thing before making
|
19
|
+
# any assertions.
|
20
|
+
def tiny_pause!
|
21
|
+
25.times{sleep 0.0001}
|
22
|
+
end
|
23
|
+
def pause!
|
24
|
+
25.times{sleep 0.005}
|
25
|
+
end
|
26
|
+
def big_pause!
|
27
|
+
10.times{sleep 0.1}
|
28
|
+
end
|
29
|
+
|
30
|
+
def reset_test_dir!
|
31
|
+
raise 'not gonna happen' unless @test_root_dir =~ /\/tmp\//
|
32
|
+
FileUtils.rm_rf(@test_root_dir)
|
33
|
+
FileUtils.mkdir(@test_root_dir)
|
34
|
+
('a'..'z').each{|ch| FileUtils.mkdir(File.join(@test_root_dir, ch))}
|
35
|
+
pause!
|
36
|
+
end
|
37
|
+
|
38
|
+
before(:each) do
|
39
|
+
@test_root_dir = '/tmp/sinotifytestdir'
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should properly create event mask from etypes" do
|
43
|
+
notifier = Sinotify::Notifier.new('/tmp', :recurse => false, :etypes => [:create, :modify])
|
44
|
+
|
45
|
+
#decided to make raw_mask private: notifier.raw_mask.should be_eql(Sinotify::CREATE | Sinotify::MODIFY)
|
46
|
+
|
47
|
+
# should not be able to create a notifier using a bogus event type (eg. 'blah')
|
48
|
+
lambda{Sinotify::Notifier.new('/tmp', :recurse => false, :etypes => [:blah])}.should raise_error
|
49
|
+
lambda{Sinotify::Notifier.new('/tmp', :recurse => false, :etypes => [:create, :blah])}.should raise_error
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should properly create Event from PrimEvent" do
|
53
|
+
# mimic delete of a directory -- change :delete_self into :delete
|
54
|
+
prim_event = MockPrimEvent.new
|
55
|
+
prim_event.etypes = [:delete_self]
|
56
|
+
watch = Sinotify::Watch.new(:is_dir => true, :path => '/tmp')
|
57
|
+
event = Sinotify::Event.from_prim_event_and_watch(prim_event, watch)
|
58
|
+
event.etypes.should be_include(:delete)
|
59
|
+
event.etypes.should_not be_include(:delete_self)
|
60
|
+
|
61
|
+
# :close should get added if event is :close_nowrite or :close_write
|
62
|
+
prim_event = MockPrimEvent.new
|
63
|
+
prim_event.etypes = [:close_nowrite]
|
64
|
+
event = Sinotify::Event.from_prim_event_and_watch(prim_event, watch)
|
65
|
+
event.etypes.should be_include(:close)
|
66
|
+
prim_event = MockPrimEvent.new
|
67
|
+
prim_event.etypes = [:close_write]
|
68
|
+
event = Sinotify::Event.from_prim_event_and_watch(prim_event, watch)
|
69
|
+
event.etypes.should be_include(:close)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should add watches for all child directories if recursive" do
|
73
|
+
|
74
|
+
reset_test_dir!
|
75
|
+
|
76
|
+
# make a watch, recurse false. There should only be one watch
|
77
|
+
notifier = Sinotify::Notifier.new(@test_root_dir, :recurse => false).watch!
|
78
|
+
|
79
|
+
tiny_pause!
|
80
|
+
notifier.all_directories_being_watched.should be_eql([@test_root_dir])
|
81
|
+
|
82
|
+
# make a watch, recurse TRUE. There should only be 27 watches (a-z, and @test_root_dir)
|
83
|
+
notifier = Sinotify::Notifier.new(@test_root_dir,
|
84
|
+
:etypes => [:all_events],
|
85
|
+
:recurse => true).watch!
|
86
|
+
# notifier.spy!(:logger => Logger.new('/tmp/spy.log'))
|
87
|
+
|
88
|
+
pause!
|
89
|
+
notifier.all_directories_being_watched.size.should be_eql(27)
|
90
|
+
|
91
|
+
# check a single announcement on a file in a subdir
|
92
|
+
events = []
|
93
|
+
test_fn = File.join(@test_root_dir, 'a', 'hi')
|
94
|
+
notifier.on_event { |event| events << event }
|
95
|
+
FileUtils.touch test_fn
|
96
|
+
pause!
|
97
|
+
#puts events.map{|e|e.to_s}.join("\n")
|
98
|
+
events.detect{|e| e.path.eql?(test_fn) && e.etypes.include?(:create) }.should_not be_nil
|
99
|
+
events.detect{|e| e.path.eql?(test_fn) && e.etypes.include?(:open) }.should_not be_nil
|
100
|
+
events.detect{|e| e.path.eql?(test_fn) && e.etypes.include?(:close_write) }.should_not be_nil
|
101
|
+
events.detect{|e| e.path.eql?(test_fn) && e.etypes.include?(:close) }.should_not be_nil
|
102
|
+
|
103
|
+
events = []
|
104
|
+
File.open(test_fn, 'a'){|f| f << 'ho'}
|
105
|
+
pause!
|
106
|
+
events.detect{|e| e.path.eql?(test_fn) && e.etypes.include?(:open) }.should_not be_nil
|
107
|
+
events.detect{|e| e.path.eql?(test_fn) && e.etypes.include?(:modify) }.should_not be_nil
|
108
|
+
events.detect{|e| e.path.eql?(test_fn) && e.etypes.include?(:close_write) }.should_not be_nil
|
109
|
+
events.detect{|e| e.path.eql?(test_fn) && e.etypes.include?(:close) }.should_not be_nil
|
110
|
+
|
111
|
+
# quickly create and delete the file
|
112
|
+
events = []
|
113
|
+
FileUtils.rm test_fn
|
114
|
+
tiny_pause!
|
115
|
+
FileUtils.touch test_fn
|
116
|
+
pause!
|
117
|
+
events.detect{|e| e.path.eql?(test_fn) && e.etypes.include?(:delete) }.should_not be_nil
|
118
|
+
events.detect{|e| e.path.eql?(test_fn) && e.etypes.include?(:create) }.should_not be_nil
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should add a watch when a new subdirectory is created" do
|
122
|
+
# setup
|
123
|
+
reset_test_dir! # creates 27 directories, the root dir and 'a'...'z'
|
124
|
+
subdir_a = File.join(@test_root_dir, 'a')
|
125
|
+
events = []
|
126
|
+
notifier = Sinotify::Notifier.new(@test_root_dir, :recurse => true).watch!
|
127
|
+
#notifier.spy!(:logger => spylog = Logger.new(STDOUT))
|
128
|
+
notifier.on_event { |event| events << event }
|
129
|
+
|
130
|
+
# one watch for the root and the 26 subdirs 'a'..'z'
|
131
|
+
pause!
|
132
|
+
notifier.all_directories_being_watched.size.should be_eql(27)
|
133
|
+
|
134
|
+
# create a new subdir
|
135
|
+
subdir_abc = File.join(@test_root_dir, 'a', 'abc')
|
136
|
+
FileUtils.mkdir subdir_abc
|
137
|
+
pause! # takes a moment to sink in because the watch is added in a bg thread
|
138
|
+
notifier.all_directories_being_watched.size.should be_eql(28)
|
139
|
+
pause!
|
140
|
+
|
141
|
+
# create a file in the new subdir, it should send and event
|
142
|
+
file_in_subdir_abc = File.join(subdir_abc, 'new_file')
|
143
|
+
events_before = events.size
|
144
|
+
FileUtils.touch file_in_subdir_abc
|
145
|
+
pause!
|
146
|
+
events.size.should be_eql(events_before + 1)
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should delete watches for on subdirectories when a parent directory is deleted" do
|
150
|
+
|
151
|
+
# Setup (create the usual test dir and 26 subdirs, and an additional sub-subdir, and a file
|
152
|
+
reset_test_dir! # creates the root dir and 'a'...'z'
|
153
|
+
subdir_a = File.join(@test_root_dir, 'a')
|
154
|
+
FileUtils.mkdir File.join(@test_root_dir, 'a', 'def')
|
155
|
+
test_fn = File.join(subdir_a, 'hi')
|
156
|
+
FileUtils.touch test_fn
|
157
|
+
|
158
|
+
# Setup: create the notifier
|
159
|
+
events = []
|
160
|
+
notifier = Sinotify::Notifier.new(@test_root_dir, :recurse => true).watch!
|
161
|
+
#notifier.spy!(:logger => Logger.new('/tmp/spy.log'))
|
162
|
+
notifier.on_event { |event| events << event }
|
163
|
+
|
164
|
+
# first assert: all directories should have a watch
|
165
|
+
pause!
|
166
|
+
notifier.all_directories_being_watched.size.should be_eql(28) # all the directories should have watches
|
167
|
+
|
168
|
+
|
169
|
+
# Should get delete events for the subdir_a and its file 'hi' when removing subdir_a.
|
170
|
+
# There should be 26 watches left (after removing watches for subdir_a and its sub-subdir)
|
171
|
+
FileUtils.rm_rf subdir_a
|
172
|
+
pause!
|
173
|
+
events.detect{|e| e.path.eql?(subdir_a) && e.directory? && e.etypes.include?(:delete) }.should_not be_nil
|
174
|
+
events.detect{|e| e.path.eql?(test_fn) && !e.directory? && e.etypes.include?(:delete) }.should_not be_nil
|
175
|
+
notifier.all_directories_being_watched.size.should be_eql(26)
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should exit and nil out watch_thread when closed" do
|
179
|
+
# really need this?
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should close children when closed if recursive" do
|
183
|
+
# Setup (create the usual test dir and 26 subdirs, and an additional sub-subdir, and a file
|
184
|
+
reset_test_dir! # creates the root dir and 'a'...'z'
|
185
|
+
FileUtils.mkdir File.join(@test_root_dir, 'a', 'def')
|
186
|
+
|
187
|
+
# Setup: create the notifier
|
188
|
+
events = []
|
189
|
+
notifier = Sinotify::Notifier.new(@test_root_dir, :recurse => true).watch!
|
190
|
+
#notifier.spy!(:logger => Logger.new('/tmp/spy.log'))
|
191
|
+
notifier.on_event { |event| events << event }
|
192
|
+
|
193
|
+
# first assert: all directories should have a watch
|
194
|
+
pause!
|
195
|
+
notifier.all_directories_being_watched.size.should be_eql(28) # all the directories should have watches
|
196
|
+
|
197
|
+
notifier.close!
|
198
|
+
notifier.all_directories_being_watched.size.should be_eql(0) # all watches should have been deleted
|
199
|
+
end
|
200
|
+
|
201
|
+
it "pound it" do
|
202
|
+
# Setup (create the usual test dir and 26 subdirs, and an additional sub-subdir, and a file
|
203
|
+
reset_test_dir! # creates the root dir and 'a'...'z'
|
204
|
+
|
205
|
+
a_z = ('a'..'z').collect{|x|x}
|
206
|
+
|
207
|
+
# Setup: create the notifier
|
208
|
+
notifier = Sinotify::Notifier.new(@test_root_dir,
|
209
|
+
:announcements_sleep_time => 0.01,
|
210
|
+
:announcement_throttle => 10000,
|
211
|
+
:etypes => [:create, :modify, :delete, :close],
|
212
|
+
:recurse => true).watch!
|
213
|
+
#notifier.spy!(:logger => Logger.new('/tmp/spy.log'))
|
214
|
+
creates = deletes = modifies = closes = 0
|
215
|
+
notifier.on_event do |event|
|
216
|
+
creates += 1 if event.etypes.include?(:create)
|
217
|
+
deletes += 1 if event.etypes.include?(:delete)
|
218
|
+
modifies += 1 if event.etypes.include?(:modify)
|
219
|
+
closes += 1 if event.etypes.include?(:close)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Create, append to, and then delete a bunch of random files
|
223
|
+
tiny_pause!
|
224
|
+
total_iterations = 1000
|
225
|
+
total_iterations.times do
|
226
|
+
sub_dir = File.join(@test_root_dir, a_z[rand(a_z.size)])
|
227
|
+
test_fn = File.join(sub_dir, "zzz#{rand(10000)}")
|
228
|
+
FileUtils.touch test_fn
|
229
|
+
File.open(test_fn, 'a'){|f| f << rand(1000).to_s }
|
230
|
+
FileUtils.rm test_fn
|
231
|
+
end
|
232
|
+
puts "created and modified and deleted #{total_iterations} files in sub directories of #{@test_root_dir}"
|
233
|
+
|
234
|
+
start_wait = Time.now
|
235
|
+
|
236
|
+
# wait up to 15 seconds for all the create events to come through
|
237
|
+
waits = 0
|
238
|
+
puts "Waiting for events, will wait for up to 30 sec"
|
239
|
+
while(creates < total_iterations) do
|
240
|
+
sleep 1
|
241
|
+
waits += 1
|
242
|
+
break if waits > 30
|
243
|
+
#raise "Tired of waiting for create events to reach #{total_iterations}, it is only at #{creates}" if waits > 30
|
244
|
+
end
|
245
|
+
|
246
|
+
puts "It took #{Time.now - start_wait} seconds for all the create/modify/delete/close events to come through"
|
247
|
+
|
248
|
+
# give it a tiny bit longer to let any remaining modify/delete/close stragglers to come through
|
249
|
+
5.times{tiny_pause!}
|
250
|
+
|
251
|
+
puts "Ceates detected: #{creates}"
|
252
|
+
puts "Deletes: #{deletes}"
|
253
|
+
puts "Modifies: #{modifies}"
|
254
|
+
puts "Closes: #{closes}"
|
255
|
+
|
256
|
+
creates.should be_eql(total_iterations)
|
257
|
+
deletes.should be_eql(total_iterations)
|
258
|
+
modifies.should be_eql(total_iterations)
|
259
|
+
closes.should be_eql(2 * total_iterations) # should get a close both after the create and the modify
|
260
|
+
|
261
|
+
end
|
262
|
+
|
263
|
+
end
|
264
|
+
|
265
|
+
# EOF
|