observr 1.0
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.
- checksums.yaml +15 -0
- data/.gitignore +6 -0
- data/History.txt +32 -0
- data/LICENSE +19 -0
- data/README.md +119 -0
- data/Rakefile +34 -0
- data/TODO.md +31 -0
- data/bin/observr +105 -0
- data/contributions.txt +7 -0
- data/docs.observr +26 -0
- data/gem.observr +22 -0
- data/lib/observr.rb +133 -0
- data/lib/observr/controller.rb +87 -0
- data/lib/observr/event_handlers/base.rb +57 -0
- data/lib/observr/event_handlers/darwin.rb +160 -0
- data/lib/observr/event_handlers/portable.rb +75 -0
- data/lib/observr/event_handlers/unix.rb +125 -0
- data/lib/observr/script.rb +257 -0
- data/observr.gemspec +21 -0
- data/specs.watchr +37 -0
- data/test/README +11 -0
- data/test/event_handlers/test_base.rb +24 -0
- data/test/event_handlers/test_darwin.rb +111 -0
- data/test/event_handlers/test_portable.rb +142 -0
- data/test/event_handlers/test_unix.rb +162 -0
- data/test/test_controller.rb +121 -0
- data/test/test_helper.rb +37 -0
- data/test/test_script.rb +163 -0
- data/test/test_watchr.rb +76 -0
- metadata +114 -0
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'test/test_helper'
|
2
|
+
|
3
|
+
class Observr::EventHandler::Portable
|
4
|
+
attr_accessor :monitored_paths
|
5
|
+
end
|
6
|
+
|
7
|
+
class PortableEventHandlerTest < MiniTest::Unit::TestCase
|
8
|
+
include Observr
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@handler = EventHandler::Portable.new
|
12
|
+
@handler.stubs(:loop)
|
13
|
+
|
14
|
+
@foo = Pathname('foo').expand_path
|
15
|
+
@bar = Pathname('bar').expand_path
|
16
|
+
@baz = Pathname('baz').expand_path
|
17
|
+
@bax = Pathname('bax').expand_path
|
18
|
+
|
19
|
+
@now = Time.now
|
20
|
+
[@foo, @bar, @baz, @bax].each do |path|
|
21
|
+
path.stubs(:mtime ).returns(@now - 100)
|
22
|
+
path.stubs(:atime ).returns(@now - 100)
|
23
|
+
path.stubs(:ctime ).returns(@now - 100)
|
24
|
+
path.stubs(:exist?).returns(true)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
test "triggers listening state" do
|
29
|
+
@handler.expects(:loop)
|
30
|
+
@handler.listen([])
|
31
|
+
end
|
32
|
+
|
33
|
+
## monitoring file events
|
34
|
+
|
35
|
+
test "listens for events on monitored files" do
|
36
|
+
@handler.listen [ @foo, @bar ]
|
37
|
+
assert_includes @handler.monitored_paths, @foo
|
38
|
+
assert_includes @handler.monitored_paths, @bar
|
39
|
+
end
|
40
|
+
|
41
|
+
test "doesn't trigger on start" do
|
42
|
+
end
|
43
|
+
|
44
|
+
## event types
|
45
|
+
|
46
|
+
test "deleted file event" do
|
47
|
+
@foo.stubs(:exist?).returns(false)
|
48
|
+
|
49
|
+
@handler.listen [ @foo, @bar ]
|
50
|
+
@handler.expects(:notify).with(@foo, :deleted)
|
51
|
+
@handler.trigger
|
52
|
+
end
|
53
|
+
|
54
|
+
test "modified file event" do
|
55
|
+
@foo.stubs(:mtime).returns(@now + 100)
|
56
|
+
|
57
|
+
@handler.listen [ @foo, @bar ]
|
58
|
+
@handler.expects(:notify).with(@foo, :modified)
|
59
|
+
@handler.trigger
|
60
|
+
end
|
61
|
+
|
62
|
+
test "accessed file event" do
|
63
|
+
@foo.stubs(:atime).returns(@now + 100)
|
64
|
+
|
65
|
+
@handler.listen [ @foo, @bar ]
|
66
|
+
@handler.expects(:notify).with(@foo, :accessed)
|
67
|
+
@handler.trigger
|
68
|
+
end
|
69
|
+
|
70
|
+
test "changed file event" do
|
71
|
+
@foo.stubs(:ctime).returns(@now + 100)
|
72
|
+
|
73
|
+
@handler.listen [ @foo, @bar ]
|
74
|
+
@handler.expects(:notify).with(@foo, :changed)
|
75
|
+
@handler.trigger
|
76
|
+
end
|
77
|
+
|
78
|
+
## event type priorities
|
79
|
+
|
80
|
+
test "mtime > atime" do
|
81
|
+
@foo.stubs(:mtime).returns(@now + 100)
|
82
|
+
@foo.stubs(:atime).returns(@now + 100)
|
83
|
+
@foo.stubs(:ctime).returns(@now + 100)
|
84
|
+
|
85
|
+
@handler.listen [ @foo, @bar ]
|
86
|
+
@handler.expects(:notify).with(@foo, :modified)
|
87
|
+
@handler.trigger
|
88
|
+
end
|
89
|
+
|
90
|
+
test "mtime > ctime" do
|
91
|
+
@foo.stubs(:mtime).returns(@now + 100)
|
92
|
+
@foo.stubs(:ctime).returns(@now + 100)
|
93
|
+
|
94
|
+
@handler.listen [ @foo, @bar ]
|
95
|
+
@handler.expects(:notify).with(@foo, :modified)
|
96
|
+
@handler.trigger
|
97
|
+
end
|
98
|
+
|
99
|
+
test "atime > ctime" do
|
100
|
+
@foo.stubs(:atime).returns(@now + 100)
|
101
|
+
@foo.stubs(:ctime).returns(@now + 100)
|
102
|
+
|
103
|
+
@handler.listen [ @foo, @bar ]
|
104
|
+
@handler.expects(:notify).with(@foo, :accessed)
|
105
|
+
@handler.trigger
|
106
|
+
end
|
107
|
+
|
108
|
+
test "deleted > mtime" do
|
109
|
+
@foo.stubs(:exist?).returns(false)
|
110
|
+
@foo.stubs(:mtime ).returns(@now + 100)
|
111
|
+
|
112
|
+
@handler.listen [ @foo, @bar ]
|
113
|
+
@handler.expects(:notify).with(@foo, :deleted)
|
114
|
+
@handler.trigger
|
115
|
+
end
|
116
|
+
|
117
|
+
## on the fly updates of monitored files list
|
118
|
+
|
119
|
+
test "reattaches to new monitored files" do
|
120
|
+
@handler.listen [ @foo, @bar ]
|
121
|
+
assert_includes @handler.monitored_paths, @foo
|
122
|
+
assert_includes @handler.monitored_paths, @bar
|
123
|
+
|
124
|
+
@handler.refresh [ @baz, @bax ]
|
125
|
+
assert_includes @handler.monitored_paths, @baz
|
126
|
+
assert_includes @handler.monitored_paths, @bax
|
127
|
+
refute_includes @handler.monitored_paths, @foo
|
128
|
+
refute_includes @handler.monitored_paths, @bar
|
129
|
+
end
|
130
|
+
|
131
|
+
test "retries on ENOENT errors" do
|
132
|
+
@oops = Pathname('oops').expand_path
|
133
|
+
@oops.stubs(:exist?).returns(true)
|
134
|
+
@oops.stubs(:mtime).raises(Errno::ENOENT).
|
135
|
+
then.returns(Time.now + 100)
|
136
|
+
|
137
|
+
@handler.listen [ @oops ]
|
138
|
+
|
139
|
+
@handler.expects(:notify).with(@oops, :modified)
|
140
|
+
@handler.trigger
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'test/test_helper'
|
2
|
+
|
3
|
+
if Observr::HAVE_REV
|
4
|
+
|
5
|
+
class Observr::EventHandler::Unix::SingleFileWatcher
|
6
|
+
public :type
|
7
|
+
end
|
8
|
+
|
9
|
+
class UnixEventHandlerTest < MiniTest::Unit::TestCase
|
10
|
+
include Observr
|
11
|
+
|
12
|
+
SingleFileWatcher = EventHandler::Unix::SingleFileWatcher
|
13
|
+
|
14
|
+
def setup
|
15
|
+
@now = Time.now
|
16
|
+
pathname = Pathname.new('foo/bar')
|
17
|
+
pathname.stubs(:atime ).returns(@now)
|
18
|
+
pathname.stubs(:mtime ).returns(@now)
|
19
|
+
pathname.stubs(:ctime ).returns(@now)
|
20
|
+
pathname.stubs(:exist?).returns(true)
|
21
|
+
SingleFileWatcher.any_instance.stubs(:pathname).returns(pathname)
|
22
|
+
|
23
|
+
@loop = Rev::Loop.default
|
24
|
+
@handler = EventHandler::Unix.new
|
25
|
+
@watcher = SingleFileWatcher.new('foo/bar')
|
26
|
+
@loop.stubs(:run)
|
27
|
+
end
|
28
|
+
|
29
|
+
def teardown
|
30
|
+
SingleFileWatcher.handler = nil
|
31
|
+
Rev::Loop.default.watchers.every.detach
|
32
|
+
end
|
33
|
+
|
34
|
+
test "triggers listening state" do
|
35
|
+
@loop.expects(:run)
|
36
|
+
@handler.listen([])
|
37
|
+
end
|
38
|
+
|
39
|
+
## SingleFileWatcher
|
40
|
+
|
41
|
+
test "watcher pathname" do
|
42
|
+
assert_instance_of Pathname, @watcher.pathname
|
43
|
+
assert_equal @watcher.path, @watcher.pathname.to_s
|
44
|
+
end
|
45
|
+
|
46
|
+
test "stores reference times" do
|
47
|
+
@watcher.pathname.stubs(:atime).returns(:time)
|
48
|
+
@watcher.pathname.stubs(:mtime).returns(:time)
|
49
|
+
@watcher.pathname.stubs(:ctime).returns(:time)
|
50
|
+
|
51
|
+
@watcher.send(:update_reference_times)
|
52
|
+
assert_equal :time, @watcher.instance_variable_get(:@reference_atime)
|
53
|
+
assert_equal :time, @watcher.instance_variable_get(:@reference_mtime)
|
54
|
+
assert_equal :time, @watcher.instance_variable_get(:@reference_ctime)
|
55
|
+
end
|
56
|
+
|
57
|
+
test "stores initial reference times" do
|
58
|
+
SingleFileWatcher.any_instance.expects(:update_reference_times)
|
59
|
+
SingleFileWatcher.new('foo')
|
60
|
+
end
|
61
|
+
|
62
|
+
test "updates reference times on change" do
|
63
|
+
@watcher.expects(:update_reference_times)
|
64
|
+
@watcher.on_change
|
65
|
+
end
|
66
|
+
|
67
|
+
test "detects event type" do
|
68
|
+
trigger_event @watcher, @now, :atime
|
69
|
+
assert_equal :accessed, @watcher.type
|
70
|
+
|
71
|
+
trigger_event @watcher, @now, :mtime
|
72
|
+
assert_equal :modified, @watcher.type
|
73
|
+
|
74
|
+
trigger_event @watcher, @now, :ctime
|
75
|
+
assert_equal :changed, @watcher.type
|
76
|
+
|
77
|
+
trigger_event @watcher, @now, :atime, :mtime
|
78
|
+
assert_equal :modified, @watcher.type
|
79
|
+
|
80
|
+
trigger_event @watcher, @now, :mtime, :ctime
|
81
|
+
assert_equal :modified, @watcher.type
|
82
|
+
|
83
|
+
trigger_event @watcher, @now, :atime, :ctime
|
84
|
+
assert_equal :accessed, @watcher.type
|
85
|
+
|
86
|
+
trigger_event @watcher, @now, :atime, :mtime, :ctime
|
87
|
+
assert_equal :modified, @watcher.type
|
88
|
+
|
89
|
+
@watcher.pathname.stubs(:exist?).returns(false)
|
90
|
+
assert_equal :deleted, @watcher.type
|
91
|
+
end
|
92
|
+
|
93
|
+
## monitoring file events
|
94
|
+
|
95
|
+
test "listens for events on monitored files" do
|
96
|
+
@handler.listen %w( foo bar )
|
97
|
+
assert_equal 2, @loop.watchers.size
|
98
|
+
assert_equal %w( foo bar ).to_set, @loop.watchers.every.path.to_set
|
99
|
+
assert_equal [SingleFileWatcher], @loop.watchers.every.class.uniq
|
100
|
+
end
|
101
|
+
|
102
|
+
test "notifies observers on file event" do
|
103
|
+
@watcher.stubs(:path).returns('foo')
|
104
|
+
@handler.expects(:notify).with('foo', anything)
|
105
|
+
@watcher.on_change
|
106
|
+
end
|
107
|
+
|
108
|
+
test "notifies observers of event type" do
|
109
|
+
trigger_event @watcher, @now, :atime
|
110
|
+
@handler.expects(:notify).with('foo/bar', :accessed)
|
111
|
+
@watcher.on_change
|
112
|
+
|
113
|
+
trigger_event @watcher, @now, :mtime
|
114
|
+
@handler.expects(:notify).with('foo/bar', :modified)
|
115
|
+
@watcher.on_change
|
116
|
+
|
117
|
+
trigger_event @watcher, @now, :ctime
|
118
|
+
@handler.expects(:notify).with('foo/bar', :changed)
|
119
|
+
@watcher.on_change
|
120
|
+
|
121
|
+
trigger_event @watcher, @now, :atime, :mtime, :ctime
|
122
|
+
@handler.expects(:notify).with('foo/bar', :modified)
|
123
|
+
@watcher.on_change
|
124
|
+
|
125
|
+
@watcher.pathname.stubs(:exist?).returns(false)
|
126
|
+
@handler.expects(:notify).with('foo/bar', :deleted)
|
127
|
+
@watcher.on_change
|
128
|
+
end
|
129
|
+
|
130
|
+
## on the fly updates of monitored files list
|
131
|
+
|
132
|
+
test "reattaches to new monitored files" do
|
133
|
+
@handler.listen %w( foo bar )
|
134
|
+
assert_equal 2, @loop.watchers.size
|
135
|
+
assert_includes @loop.watchers.every.path, 'foo'
|
136
|
+
assert_includes @loop.watchers.every.path, 'bar'
|
137
|
+
|
138
|
+
@handler.refresh %w( baz bax )
|
139
|
+
assert_equal 2, @loop.watchers.size
|
140
|
+
assert_includes @loop.watchers.every.path, 'baz'
|
141
|
+
assert_includes @loop.watchers.every.path, 'bax'
|
142
|
+
refute_includes @loop.watchers.every.path, 'foo'
|
143
|
+
refute_includes @loop.watchers.every.path, 'bar'
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def trigger_event(watcher, now, *types)
|
149
|
+
watcher.pathname.stubs(:atime).returns(now)
|
150
|
+
watcher.pathname.stubs(:mtime).returns(now)
|
151
|
+
watcher.pathname.stubs(:ctime).returns(now)
|
152
|
+
watcher.instance_variable_set(:@reference_atime, now)
|
153
|
+
watcher.instance_variable_set(:@reference_mtime, now)
|
154
|
+
watcher.instance_variable_set(:@reference_ctime, now)
|
155
|
+
|
156
|
+
types.each do |type|
|
157
|
+
watcher.pathname.stubs(type).returns(now+10)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
end # if Observr::HAVE_REV
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'test/test_helper'
|
2
|
+
require 'observer'
|
3
|
+
|
4
|
+
class MockHandler
|
5
|
+
include Observable
|
6
|
+
def listen(paths) end
|
7
|
+
def refresh(paths) end
|
8
|
+
end
|
9
|
+
|
10
|
+
class TestController < MiniTest::Unit::TestCase
|
11
|
+
include Observr
|
12
|
+
|
13
|
+
def to_p(str)
|
14
|
+
Pathname(str).expand_path
|
15
|
+
end
|
16
|
+
|
17
|
+
def setup
|
18
|
+
tmpfile = Tempfile.new('foo')
|
19
|
+
@script = Script.new( Pathname.new( tmpfile.path ) )
|
20
|
+
@handler = MockHandler.new
|
21
|
+
@controller = Controller.new(@script, @handler)
|
22
|
+
end
|
23
|
+
|
24
|
+
test "triggers listening state on run" do
|
25
|
+
@controller.stubs(:monitored_paths).returns %w( foo bar )
|
26
|
+
@handler.expects(:listen).with %w( foo bar )
|
27
|
+
@controller.run
|
28
|
+
end
|
29
|
+
|
30
|
+
test "parses the script on #run" do
|
31
|
+
@script.expects(:parse!)
|
32
|
+
@controller.run
|
33
|
+
end
|
34
|
+
|
35
|
+
test "adds itself as handler observer" do
|
36
|
+
assert_equal 1, @handler.count_observers
|
37
|
+
@handler.delete_observer(@controller)
|
38
|
+
assert_equal 0, @handler.count_observers
|
39
|
+
end
|
40
|
+
|
41
|
+
## monitored paths list
|
42
|
+
|
43
|
+
test "fetches monitored paths" do
|
44
|
+
Dir.expects(:[]).at_least_once.with('**/*').returns(%w(
|
45
|
+
a
|
46
|
+
b/x.z
|
47
|
+
b/c
|
48
|
+
b/c/y.z
|
49
|
+
))
|
50
|
+
@script.watch('.\.z') { :x }
|
51
|
+
|
52
|
+
contrl = Controller.new(@script, MockHandler.new)
|
53
|
+
assert_includes contrl.monitored_paths, to_p('b/x.z')
|
54
|
+
assert_includes contrl.monitored_paths, to_p('b/c/y.z')
|
55
|
+
end
|
56
|
+
|
57
|
+
test "doesn't fetch unmonitored paths" do
|
58
|
+
Dir.expects(:[]).at_least_once.with('**/*').returns(%w(
|
59
|
+
a
|
60
|
+
b/x.z
|
61
|
+
b/c
|
62
|
+
b/c/y.z
|
63
|
+
))
|
64
|
+
@script.watch('.\.z') { :x }
|
65
|
+
|
66
|
+
contrl = Controller.new(@script, MockHandler.new)
|
67
|
+
refute_includes contrl.monitored_paths, to_p('a')
|
68
|
+
refute_includes contrl.monitored_paths, to_p('b/c')
|
69
|
+
refute_includes contrl.monitored_paths, to_p('p/q.z')
|
70
|
+
end
|
71
|
+
|
72
|
+
test "monitored paths include script" do
|
73
|
+
Dir.expects(:[]).at_least_once.with('**/*').returns(%w( a ))
|
74
|
+
Script.any_instance.stubs(:parse!)
|
75
|
+
|
76
|
+
path = to_p('some/file')
|
77
|
+
script = Script.new(path)
|
78
|
+
contrl = Controller.new(script, MockHandler.new)
|
79
|
+
assert_includes contrl.monitored_paths, path
|
80
|
+
end
|
81
|
+
|
82
|
+
## on update
|
83
|
+
|
84
|
+
test "calls action for path" do
|
85
|
+
path = to_p('abc')
|
86
|
+
@script.expects(:action_for).with(path, :modified).returns(lambda {})
|
87
|
+
|
88
|
+
@controller.update('abc', :modified)
|
89
|
+
end
|
90
|
+
|
91
|
+
test "parses script on script file update" do
|
92
|
+
path = to_p('abc')
|
93
|
+
@script.stubs(:path).returns(path)
|
94
|
+
@script.expects(:parse!)
|
95
|
+
|
96
|
+
@controller.update('abc')
|
97
|
+
end
|
98
|
+
|
99
|
+
test "refreshes handler on script file update" do
|
100
|
+
path = to_p('abc')
|
101
|
+
@script.stubs(:path).returns(path)
|
102
|
+
@controller.stubs(:monitored_paths).returns %w( foo bar )
|
103
|
+
|
104
|
+
@handler.expects(:refresh).with %w( foo bar )
|
105
|
+
@controller.update(path)
|
106
|
+
end
|
107
|
+
|
108
|
+
test "exits gracefully when Interrupted" do
|
109
|
+
@handler.stubs(:listen).raises(Interrupt)
|
110
|
+
@controller.run
|
111
|
+
end
|
112
|
+
|
113
|
+
test "does not parse script on mere script file access" do
|
114
|
+
path = to_p('abc')
|
115
|
+
@script.stubs(:path).returns(path)
|
116
|
+
@script.expects(:parse!).never
|
117
|
+
|
118
|
+
@controller.update('abc', :accessed)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
require 'minitest/autorun'
|
7
|
+
require 'mocha'
|
8
|
+
require 'every'
|
9
|
+
begin
|
10
|
+
require 'redgreen' #http://gemcutter.org/gems/mynyml-redgreen
|
11
|
+
require 'phocus'
|
12
|
+
require 'ruby-debug'
|
13
|
+
rescue LoadError, RuntimeError
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'observr'
|
17
|
+
|
18
|
+
class MiniTest::Unit::TestCase
|
19
|
+
class << self
|
20
|
+
def test(name, &block)
|
21
|
+
define_method("test_#{name.gsub(/\s/,'_')}", &block)
|
22
|
+
end
|
23
|
+
alias :should :test
|
24
|
+
|
25
|
+
# noop
|
26
|
+
def xtest(*args) end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
unless Observr::HAVE_REV
|
31
|
+
puts "Skipping Unix handler tests. Install Rev (gem install rev) to properly test full suite"
|
32
|
+
end
|
33
|
+
|
34
|
+
unless Observr::HAVE_FSE
|
35
|
+
puts "Skipping Darwin handler tests. Install FSEvent (gem install ruby-fsevent) to properly test full suite (osx only)"
|
36
|
+
end
|
37
|
+
|