smparkes-watchr 0.5.7
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 +6 -0
- data/History.txt +32 -0
- data/LICENSE +19 -0
- data/Manifest +30 -0
- data/README.rdoc +82 -0
- data/Rakefile +51 -0
- data/TODO.txt +40 -0
- data/bin/watchr +77 -0
- data/docs.watchr +26 -0
- data/gem.watchr +32 -0
- data/lib/watchr.rb +101 -0
- data/lib/watchr/controller.rb +93 -0
- data/lib/watchr/event_handlers/base.rb +48 -0
- data/lib/watchr/event_handlers/em.rb +147 -0
- data/lib/watchr/event_handlers/portable.rb +60 -0
- data/lib/watchr/event_handlers/rev.rb +104 -0
- data/lib/watchr/event_handlers/unix.rb +25 -0
- data/lib/watchr/script.rb +230 -0
- data/manifest.watchr +70 -0
- data/specs.watchr +38 -0
- data/test/README +11 -0
- data/test/event_handlers/test_base.rb +24 -0
- data/test/event_handlers/test_em.rb +162 -0
- data/test/event_handlers/test_portable.rb +142 -0
- data/test/event_handlers/test_rev.rb +162 -0
- data/test/test_controller.rb +130 -0
- data/test/test_helper.rb +60 -0
- data/test/test_script.rb +124 -0
- data/test/test_watchr.rb +60 -0
- data/watchr.gemspec +64 -0
- metadata +141 -0
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'test/test_helper'
|
2
|
+
|
3
|
+
class Watchr::EventHandler::Portable
|
4
|
+
attr_accessor :monitored_paths
|
5
|
+
end
|
6
|
+
|
7
|
+
class PortableEventHandlerTest < Test::Unit::TestCase
|
8
|
+
include Watchr
|
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
|
+
@handler.monitored_paths.should include(@foo)
|
38
|
+
@handler.monitored_paths.should include(@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
|
+
@handler.monitored_paths.should include(@foo)
|
122
|
+
@handler.monitored_paths.should include(@bar)
|
123
|
+
|
124
|
+
@handler.refresh [ @baz, @bax ]
|
125
|
+
@handler.monitored_paths.should include(@baz)
|
126
|
+
@handler.monitored_paths.should include(@bax)
|
127
|
+
@handler.monitored_paths.should exclude(@foo)
|
128
|
+
@handler.monitored_paths.should exclude(@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 HAVE_REV
|
4
|
+
|
5
|
+
class Watchr::EventHandler::Rev::SingleFileWatcher
|
6
|
+
public :type
|
7
|
+
end
|
8
|
+
|
9
|
+
class RevEventHandlerTest < Test::Unit::TestCase
|
10
|
+
include Watchr
|
11
|
+
|
12
|
+
SingleFileWatcher = EventHandler::Rev::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::Rev.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
|
+
@watcher.pathname.should be_kind_of(Pathname)
|
43
|
+
@watcher.pathname.to_s.should be(@watcher.path)
|
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
|
+
@watcher.instance_variable_get(:@reference_atime).should be(:time)
|
53
|
+
@watcher.instance_variable_get(:@reference_mtime).should be(:time)
|
54
|
+
@watcher.instance_variable_get(:@reference_ctime).should be(:time)
|
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
|
+
@watcher.type.should be(:accessed)
|
70
|
+
|
71
|
+
trigger_event @watcher, @now, :mtime
|
72
|
+
@watcher.type.should be(:modified)
|
73
|
+
|
74
|
+
trigger_event @watcher, @now, :ctime
|
75
|
+
@watcher.type.should be(:changed)
|
76
|
+
|
77
|
+
trigger_event @watcher, @now, :atime, :mtime
|
78
|
+
@watcher.type.should be(:modified)
|
79
|
+
|
80
|
+
trigger_event @watcher, @now, :mtime, :ctime
|
81
|
+
@watcher.type.should be(:modified)
|
82
|
+
|
83
|
+
trigger_event @watcher, @now, :atime, :ctime
|
84
|
+
@watcher.type.should be(:accessed)
|
85
|
+
|
86
|
+
trigger_event @watcher, @now, :atime, :mtime, :ctime
|
87
|
+
@watcher.type.should be(:modified)
|
88
|
+
|
89
|
+
@watcher.pathname.stubs(:exist?).returns(false)
|
90
|
+
@watcher.type.should be(:deleted)
|
91
|
+
end
|
92
|
+
|
93
|
+
## monitoring file events
|
94
|
+
|
95
|
+
test "listens for events on monitored files" do
|
96
|
+
@handler.listen %w( foo bar )
|
97
|
+
@loop.watchers.size.should be(2)
|
98
|
+
@loop.watchers.every.path.should include('foo', 'bar')
|
99
|
+
@loop.watchers.every.class.uniq.should be([SingleFileWatcher])
|
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
|
+
@loop.watchers.size.should be(2)
|
135
|
+
@loop.watchers.every.path.should include('foo')
|
136
|
+
@loop.watchers.every.path.should include('bar')
|
137
|
+
|
138
|
+
@handler.refresh %w( baz bax )
|
139
|
+
@loop.watchers.size.should be(2)
|
140
|
+
@loop.watchers.every.path.should include('baz')
|
141
|
+
@loop.watchers.every.path.should include('bax')
|
142
|
+
@loop.watchers.every.path.should exclude('foo')
|
143
|
+
@loop.watchers.every.path.should exclude('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 # HAVE_REV
|
@@ -0,0 +1,130 @@
|
|
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 < Test::Unit::TestCase
|
11
|
+
include Watchr
|
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
|
+
Watchr.stubs(:handler).returns(MockHandler)
|
22
|
+
MockHandler.stubs(:new).returns(@handler)
|
23
|
+
@controller = Controller.new(@script)
|
24
|
+
end
|
25
|
+
|
26
|
+
test "triggers listening state on run" do
|
27
|
+
@controller.stubs(:monitored_paths).returns %w( foo bar )
|
28
|
+
@handler.expects(:listen).with %w( foo bar )
|
29
|
+
@controller.run
|
30
|
+
end
|
31
|
+
|
32
|
+
test "parses the script on #run" do
|
33
|
+
@script.expects(:parse!)
|
34
|
+
@controller.run
|
35
|
+
end
|
36
|
+
|
37
|
+
test "adds itself as handler observer" do
|
38
|
+
@controller.handler
|
39
|
+
@handler.count_observers.should be(1)
|
40
|
+
@handler.delete_observer(@controller)
|
41
|
+
@handler.count_observers.should be(0)
|
42
|
+
end
|
43
|
+
|
44
|
+
## monitored paths list
|
45
|
+
|
46
|
+
test "fetches monitored paths" do
|
47
|
+
Dir.expects(:[]).at_least_once.with('**/*').returns(%w(
|
48
|
+
a
|
49
|
+
b/x.z
|
50
|
+
b/c
|
51
|
+
b/c/y.z
|
52
|
+
))
|
53
|
+
@script.watch('.\.z') { :x }
|
54
|
+
|
55
|
+
contrl = Controller.new(@script)
|
56
|
+
contrl.monitored_paths.should include(to_p('b/x.z'))
|
57
|
+
contrl.monitored_paths.should include(to_p('b/c/y.z'))
|
58
|
+
end
|
59
|
+
|
60
|
+
test "doesn't fetch unmonitored paths" do
|
61
|
+
Dir.expects(:[]).at_least_once.with('**/*').returns(%w(
|
62
|
+
a
|
63
|
+
b/x.z
|
64
|
+
b/c
|
65
|
+
b/c/y.z
|
66
|
+
))
|
67
|
+
@script.watch('.\.z') { :x }
|
68
|
+
|
69
|
+
contrl = Controller.new(@script)
|
70
|
+
contrl.monitored_paths.should exclude(to_p('a'))
|
71
|
+
contrl.monitored_paths.should exclude(to_p('b/c'))
|
72
|
+
contrl.monitored_paths.should exclude(to_p('p/q.z'))
|
73
|
+
end
|
74
|
+
|
75
|
+
test "monitored paths include script" do
|
76
|
+
Dir.expects(:[]).at_least_once.with('**/*').returns(%w( a ))
|
77
|
+
Script.any_instance.stubs(:parse!)
|
78
|
+
|
79
|
+
path = to_p('some/file')
|
80
|
+
script = Script.new(path)
|
81
|
+
contrl = Controller.new(script)
|
82
|
+
contrl.monitored_paths.should include(path)
|
83
|
+
end
|
84
|
+
|
85
|
+
## on update
|
86
|
+
|
87
|
+
test "calls action for path" do
|
88
|
+
path = to_p('abc')
|
89
|
+
@script.expects(:call_action_for).with(path, :modified).returns(nil)
|
90
|
+
|
91
|
+
@controller.update('abc', :modified)
|
92
|
+
end
|
93
|
+
|
94
|
+
test "parses script on script file update" do
|
95
|
+
path = to_p('abc')
|
96
|
+
@script.stubs(:path).returns(path)
|
97
|
+
@script.expects(:parse!)
|
98
|
+
|
99
|
+
@controller.update('abc')
|
100
|
+
end
|
101
|
+
|
102
|
+
test "refreshes handler on script file update" do
|
103
|
+
path = to_p('abc')
|
104
|
+
@script.stubs(:path).returns(path)
|
105
|
+
@controller.stubs(:monitored_paths).returns %w( foo bar )
|
106
|
+
|
107
|
+
@handler.expects(:refresh).with %w( foo bar )
|
108
|
+
@controller.update(path)
|
109
|
+
end
|
110
|
+
|
111
|
+
test "refreshes handler on script action exception" do
|
112
|
+
path = to_p('abc')
|
113
|
+
@script.stubs(:path).returns(path)
|
114
|
+
|
115
|
+
file = to_p('012')
|
116
|
+
@script.expects(:call_action_for).with(file,nil).raises(Watchr::Refresh)
|
117
|
+
|
118
|
+
@controller.stubs(:monitored_paths).returns %w( foo bar )
|
119
|
+
|
120
|
+
@handler.expects(:refresh).with %w( foo bar )
|
121
|
+
|
122
|
+
@controller.update(file)
|
123
|
+
end
|
124
|
+
|
125
|
+
test "exits gracefully when Interrupted" do
|
126
|
+
@handler.stubs(:listen).raises(Interrupt)
|
127
|
+
@controller.run
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
require 'matchy'
|
6
|
+
require 'mocha'
|
7
|
+
require 'every'
|
8
|
+
require 'pending'
|
9
|
+
begin
|
10
|
+
require 'redgreen'
|
11
|
+
require 'phocus'
|
12
|
+
require 'ruby-debug'
|
13
|
+
rescue LoadError, RuntimeError
|
14
|
+
end
|
15
|
+
|
16
|
+
root = Pathname(__FILE__).dirname.parent.expand_path
|
17
|
+
$:.unshift(root.join('lib').to_s).uniq!
|
18
|
+
|
19
|
+
require 'watchr'
|
20
|
+
|
21
|
+
class Test::Unit::TestCase
|
22
|
+
class << self
|
23
|
+
def test(name, &block)
|
24
|
+
name = :"test_#{name.gsub(/\s/,'_')}"
|
25
|
+
define_method(name, &block)
|
26
|
+
end
|
27
|
+
alias :should :test
|
28
|
+
|
29
|
+
# noop
|
30
|
+
def xtest(*args) end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# taken from minitest/unit.rb
|
35
|
+
# (with modifications)
|
36
|
+
def capture_io
|
37
|
+
require 'stringio'
|
38
|
+
|
39
|
+
orig_stdout, orig_stderr = $stdout, $stderr
|
40
|
+
captured_stdout, captured_stderr = StringIO.new, StringIO.new
|
41
|
+
$stdout, $stderr = captured_stdout, captured_stderr
|
42
|
+
|
43
|
+
yield
|
44
|
+
|
45
|
+
return Struct.new(:stdout, :stderr).new(
|
46
|
+
captured_stdout.string,
|
47
|
+
captured_stderr.string
|
48
|
+
)
|
49
|
+
ensure
|
50
|
+
$stdout = orig_stdout
|
51
|
+
$stderr = orig_stderr
|
52
|
+
end
|
53
|
+
|
54
|
+
begin
|
55
|
+
require "watchr/event_handlers/rev"
|
56
|
+
HAVE_REV = true
|
57
|
+
rescue LoadError
|
58
|
+
HAVE_REV = false
|
59
|
+
end
|
60
|
+
|