fssm 0.2.2 → 0.2.3
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/VERSION.yml +2 -2
- data/fssm.gemspec +4 -2
- data/lib/fssm/state/file.rb +1 -1
- data/spec/count_down_latch.rb +151 -0
- data/spec/monitor_spec.rb +91 -78
- metadata +5 -7
data/VERSION.yml
CHANGED
data/fssm.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{fssm}
|
8
|
-
s.version = "0.2.
|
8
|
+
s.version = "0.2.3"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Travis Tilley"]
|
12
|
-
s.date = %q{2010-12-
|
12
|
+
s.date = %q{2010-12-18}
|
13
13
|
s.description = %q{file system state monitor}
|
14
14
|
s.email = %q{ttilley@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -42,6 +42,7 @@ Gem::Specification.new do |s|
|
|
42
42
|
"profile/prof-pathname.rb",
|
43
43
|
"profile/prof-plain-pathname.html",
|
44
44
|
"profile/prof.html",
|
45
|
+
"spec/count_down_latch.rb",
|
45
46
|
"spec/monitor_spec.rb",
|
46
47
|
"spec/path_spec.rb",
|
47
48
|
"spec/root/duck/quack.txt",
|
@@ -56,6 +57,7 @@ Gem::Specification.new do |s|
|
|
56
57
|
s.rubygems_version = %q{1.3.7}
|
57
58
|
s.summary = %q{file system state monitor}
|
58
59
|
s.test_files = [
|
60
|
+
"spec/count_down_latch.rb",
|
59
61
|
"spec/monitor_spec.rb",
|
60
62
|
"spec/path_spec.rb",
|
61
63
|
"spec/root/file.rb",
|
data/lib/fssm/state/file.rb
CHANGED
@@ -8,7 +8,7 @@ module FSSM::State
|
|
8
8
|
|
9
9
|
def refresh(base=nil, skip_callbacks=false)
|
10
10
|
base ||= @path.to_pathname
|
11
|
-
used_to_exist, @exists = @exists, base.
|
11
|
+
used_to_exist, @exists = @exists, base.exist?
|
12
12
|
# this handles bad symlinks without failing. why handle bad symlinks at
|
13
13
|
# all? well, we could still be interested in their creation and deletion.
|
14
14
|
old_mtime, @mtime = @mtime, base.symlink? ? Time.at(0) : base.mtime if @exists
|
@@ -0,0 +1,151 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
require 'thread'
|
5
|
+
|
6
|
+
class CountDownLatch
|
7
|
+
attr_reader :count
|
8
|
+
|
9
|
+
def initialize(to)
|
10
|
+
@count = to.to_i
|
11
|
+
raise ArgumentError, "cannot count down from negative integer" unless @count >= 0
|
12
|
+
@lock = Mutex.new
|
13
|
+
@condition = ConditionVariable.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def count_down
|
17
|
+
@lock.synchronize do
|
18
|
+
@count -= 1 if @count > 0
|
19
|
+
@condition.broadcast if @count == 0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def wait
|
24
|
+
@lock.synchronize do
|
25
|
+
@condition.wait(@lock) while @count > 0
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
if $0 == __FILE__
|
31
|
+
require 'test/unit'
|
32
|
+
|
33
|
+
class CountDownLatchTest < Test::Unit::TestCase
|
34
|
+
def test_requires_positive_count
|
35
|
+
assert_raise(ArgumentError) { CountDownLatch.new(-1) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_basic_latch_usage
|
39
|
+
latch = CountDownLatch.new(1)
|
40
|
+
name = "foo"
|
41
|
+
Thread.new do
|
42
|
+
name = "bar"
|
43
|
+
latch.count_down
|
44
|
+
end
|
45
|
+
latch.wait
|
46
|
+
assert_equal(0, latch.count)
|
47
|
+
assert_equal("bar", name)
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_basic_latch_usage_inverted
|
51
|
+
latch = CountDownLatch.new(1)
|
52
|
+
name = "foo"
|
53
|
+
Thread.new do
|
54
|
+
latch.wait
|
55
|
+
assert_equal(0, latch.count)
|
56
|
+
assert_equal("bar", name)
|
57
|
+
end
|
58
|
+
name = "bar"
|
59
|
+
latch.count_down
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_count_down_from_zero_skips_wait
|
63
|
+
latch = CountDownLatch.new(0)
|
64
|
+
latch.wait
|
65
|
+
assert_equal(0, latch.count)
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_count_down_twice_with_thread
|
69
|
+
latch = CountDownLatch.new(2)
|
70
|
+
name = "foo"
|
71
|
+
Thread.new do
|
72
|
+
latch.count_down
|
73
|
+
name = "bar"
|
74
|
+
latch.count_down
|
75
|
+
end
|
76
|
+
latch.wait
|
77
|
+
assert_equal(0, latch.count)
|
78
|
+
assert_equal("bar", name)
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_count_down_twice_with_two_parallel_threads
|
82
|
+
latch = CountDownLatch.new(2)
|
83
|
+
name = "foo"
|
84
|
+
Thread.new { latch.count_down }
|
85
|
+
Thread.new do
|
86
|
+
name = "bar"
|
87
|
+
latch.count_down
|
88
|
+
end
|
89
|
+
latch.wait
|
90
|
+
assert_equal(0, latch.count)
|
91
|
+
assert_equal("bar", name)
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_count_down_twice_with_two_chained_threads
|
95
|
+
latch = CountDownLatch.new(2)
|
96
|
+
name = "foo"
|
97
|
+
Thread.new do
|
98
|
+
latch.count_down
|
99
|
+
Thread.new do
|
100
|
+
name = "bar"
|
101
|
+
latch.count_down
|
102
|
+
end
|
103
|
+
end
|
104
|
+
latch.wait
|
105
|
+
assert_equal(0, latch.count)
|
106
|
+
assert_equal("bar", name)
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_count_down_with_multiple_waiters
|
110
|
+
proceed_latch = CountDownLatch.new(2)
|
111
|
+
check_latch = CountDownLatch.new(2)
|
112
|
+
results = {}
|
113
|
+
Thread.new do
|
114
|
+
proceed_latch.wait
|
115
|
+
results[:first] = 1
|
116
|
+
check_latch.count_down
|
117
|
+
end
|
118
|
+
Thread.new do
|
119
|
+
proceed_latch.wait
|
120
|
+
results[:second] = 2
|
121
|
+
check_latch.count_down
|
122
|
+
end
|
123
|
+
assert_equal({}, results)
|
124
|
+
proceed_latch.count_down
|
125
|
+
proceed_latch.count_down
|
126
|
+
check_latch.wait
|
127
|
+
assert_equal(0, proceed_latch.count)
|
128
|
+
assert_equal(0, check_latch.count)
|
129
|
+
assert_equal({:first => 1, :second => 2}, results)
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_interleaved_latches
|
133
|
+
change_1_latch = CountDownLatch.new(1)
|
134
|
+
check_latch = CountDownLatch.new(1)
|
135
|
+
change_2_latch = CountDownLatch.new(1)
|
136
|
+
name = "foo"
|
137
|
+
Thread.new do
|
138
|
+
name = "bar"
|
139
|
+
change_1_latch.count_down
|
140
|
+
check_latch.wait
|
141
|
+
name = "man"
|
142
|
+
change_2_latch.count_down
|
143
|
+
end
|
144
|
+
change_1_latch.wait
|
145
|
+
assert_equal("bar", name)
|
146
|
+
check_latch.count_down
|
147
|
+
change_2_latch.wait
|
148
|
+
assert_equal("man", name)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
data/spec/monitor_spec.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require 'count_down_latch'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'tempfile'
|
5
6
|
|
6
7
|
module FSSM::MonitorSpecHelpers
|
7
8
|
def create_tmp_dir
|
@@ -16,24 +17,28 @@ module FSSM::MonitorSpecHelpers
|
|
16
17
|
FileUtils.remove_entry_secure @tmp_dir
|
17
18
|
end
|
18
19
|
|
19
|
-
def create_handler(type)
|
20
|
-
lambda
|
20
|
+
def create_handler(type, latch)
|
21
|
+
lambda do |*args|
|
22
|
+
@handler_results[type] << args
|
23
|
+
latch.count_down
|
24
|
+
end
|
21
25
|
end
|
22
26
|
|
23
|
-
def
|
27
|
+
def run_monitor(num_events_to_expect=0, options={})
|
28
|
+
event_latch = CountDownLatch.new(num_events_to_expect)
|
24
29
|
@handler_results = Hash.new {|hash, key| hash[key] = []}
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
+
thread = Thread.new do
|
31
|
+
monitor = FSSM::Monitor.new(options)
|
32
|
+
monitor.path(@tmp_dir) do |p|
|
33
|
+
p.create(&create_handler(:create, event_latch))
|
34
|
+
p.update(&create_handler(:update, event_latch))
|
35
|
+
p.delete(&create_handler(:delete, event_latch))
|
36
|
+
end
|
37
|
+
monitor.run
|
30
38
|
end
|
31
|
-
sleep 1
|
32
|
-
|
33
|
-
|
34
|
-
def run_monitor
|
35
|
-
thread = Thread.new {@monitor.run}
|
36
|
-
sleep 2 # give time for monitor to see changes
|
39
|
+
sleep 1 # give time for monitor to start up
|
40
|
+
yield if block_given?
|
41
|
+
event_latch.wait
|
37
42
|
thread.kill
|
38
43
|
end
|
39
44
|
end
|
@@ -51,136 +56,144 @@ describe "The File System State Monitor" do
|
|
51
56
|
end
|
52
57
|
|
53
58
|
describe "with default options" do
|
54
|
-
before do
|
55
|
-
create_monitor
|
56
|
-
end
|
57
|
-
|
58
59
|
it "should call create callback upon file creation" do
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
60
|
+
run_monitor(1) do
|
61
|
+
file = @tmp_dir + "/newfile.rb"
|
62
|
+
File.exists?(file).should be_false
|
63
|
+
FileUtils.touch file
|
64
|
+
end
|
63
65
|
@handler_results[:create].should == [[@tmp_dir, 'newfile.rb']]
|
64
66
|
end
|
65
67
|
|
66
68
|
it "should call update callback upon file modification" do
|
67
|
-
|
68
|
-
|
69
|
+
run_monitor(1) do
|
70
|
+
FileUtils.touch @tmp_dir + '/root/file.rb'
|
71
|
+
end
|
69
72
|
@handler_results[:update].should == [[@tmp_dir, 'root/file.rb']]
|
70
73
|
end
|
71
74
|
|
72
75
|
it "should call delete callback upon file deletion" do
|
73
|
-
|
74
|
-
|
76
|
+
run_monitor(1) do
|
77
|
+
FileUtils.rm @tmp_dir + "/root/file.rb"
|
78
|
+
end
|
75
79
|
@handler_results[:delete].should == [[@tmp_dir, 'root/file.rb']]
|
76
80
|
end
|
77
81
|
|
78
82
|
it "should call create and delete callbacks upon file renaming in the same directory" do
|
79
|
-
|
80
|
-
|
83
|
+
run_monitor(2) do
|
84
|
+
FileUtils.mv @tmp_dir + "/root/file.rb", @tmp_dir + "/root/old_file.rb"
|
85
|
+
end
|
81
86
|
@handler_results[:create].should == [[@tmp_dir, 'root/old_file.rb']]
|
82
87
|
@handler_results[:delete].should == [[@tmp_dir, 'root/file.rb']]
|
83
88
|
@handler_results[:update].should == []
|
84
89
|
end
|
85
90
|
|
86
91
|
it "should call create and delete callbacks upon file moving to another directory" do
|
87
|
-
|
88
|
-
|
92
|
+
run_monitor(2) do
|
93
|
+
FileUtils.mv @tmp_dir + "/root/file.rb", @tmp_dir + "/old_file.rb"
|
94
|
+
end
|
89
95
|
@handler_results[:create].should == [[@tmp_dir, 'old_file.rb']]
|
90
96
|
@handler_results[:delete].should == [[@tmp_dir, 'root/file.rb']]
|
91
97
|
@handler_results[:update].should == []
|
92
98
|
end
|
93
99
|
|
94
100
|
it "should not call callbacks upon directory operations" do
|
95
|
-
|
96
|
-
|
97
|
-
|
101
|
+
run_monitor do
|
102
|
+
FileUtils.mkdir @tmp_dir + "/another_yawn"
|
103
|
+
FileUtils.rmdir @tmp_dir + "/root/yawn"
|
104
|
+
end
|
98
105
|
@handler_results[:create].should == []
|
99
106
|
@handler_results[:delete].should == []
|
100
107
|
end
|
101
108
|
end
|
102
109
|
|
103
110
|
describe "when configured to consider files and directories" do
|
104
|
-
before do
|
105
|
-
create_monitor(:directories => true)
|
106
|
-
end
|
107
|
-
|
108
111
|
it "should call create callback upon directory creation" do
|
109
|
-
|
110
|
-
|
112
|
+
run_monitor(1, :directories => true) do
|
113
|
+
FileUtils.mkdir @tmp_dir + "/another_yawn"
|
114
|
+
end
|
111
115
|
@handler_results[:create].should == [[@tmp_dir, 'another_yawn', :directory]]
|
112
116
|
end
|
113
117
|
|
114
118
|
it "should call delete callback upon directory deletion" do
|
115
|
-
|
116
|
-
|
119
|
+
run_monitor(1, :directories => true) do
|
120
|
+
FileUtils.rmdir @tmp_dir + "/root/yawn"
|
121
|
+
end
|
117
122
|
@handler_results[:delete].should == [[@tmp_dir, 'root/yawn', :directory]]
|
118
123
|
end
|
119
124
|
|
120
125
|
it "should call create, update, and delete callbacks upon directory renaming in the same directory" do
|
121
|
-
|
122
|
-
|
126
|
+
run_monitor(3, :directories => true) do
|
127
|
+
FileUtils.mv @tmp_dir + "/root/yawn", @tmp_dir + "/root/old_yawn"
|
128
|
+
end
|
123
129
|
@handler_results[:create].should == [[@tmp_dir, 'root/old_yawn', :directory]]
|
124
130
|
@handler_results[:delete].should == [[@tmp_dir, 'root/yawn', :directory]]
|
125
131
|
@handler_results[:update].should == [[@tmp_dir, 'root', :directory]]
|
126
132
|
end
|
127
133
|
|
128
134
|
it "should call create, update, and delete callbacks upon directory moving to another directory" do
|
129
|
-
|
130
|
-
|
135
|
+
run_monitor(3, :directories => true) do
|
136
|
+
FileUtils.mv @tmp_dir + "/root/yawn", @tmp_dir + "/old_yawn"
|
137
|
+
end
|
131
138
|
@handler_results[:create].should == [[@tmp_dir, 'old_yawn', :directory]]
|
132
139
|
@handler_results[:delete].should == [[@tmp_dir, 'root/yawn', :directory]]
|
133
140
|
@handler_results[:update].should == [[@tmp_dir, 'root', :directory]]
|
134
141
|
end
|
135
142
|
|
136
143
|
it "should call create, update, and delete callbacks upon file renaming in the same directory" do
|
137
|
-
|
138
|
-
|
144
|
+
run_monitor(3, :directories => true) do
|
145
|
+
FileUtils.mv @tmp_dir + "/root/file.rb", @tmp_dir + "/root/old_file.rb"
|
146
|
+
end
|
139
147
|
@handler_results[:create].should == [[@tmp_dir, 'root/old_file.rb', :file]]
|
140
148
|
@handler_results[:delete].should == [[@tmp_dir, 'root/file.rb', :file]]
|
141
149
|
@handler_results[:update].should == [[@tmp_dir, 'root', :directory]]
|
142
150
|
end
|
143
151
|
|
144
152
|
it "should call create, update, and delete callbacks upon file moving to another directory" do
|
145
|
-
|
146
|
-
|
153
|
+
run_monitor(3, :directories => true) do
|
154
|
+
FileUtils.mv @tmp_dir + "/root/file.rb", @tmp_dir + "/old_file.rb"
|
155
|
+
end
|
147
156
|
@handler_results[:create].should == [[@tmp_dir, 'old_file.rb', :file]]
|
148
157
|
@handler_results[:delete].should == [[@tmp_dir, 'root/file.rb', :file]]
|
149
158
|
@handler_results[:update].should == [[@tmp_dir, 'root', :directory]]
|
150
159
|
end
|
151
160
|
|
152
161
|
it "should call delete callbacks upon directory structure deletion, in reverse order" do
|
153
|
-
|
154
|
-
|
162
|
+
expected_delete_events = [
|
163
|
+
['root/yawn', :directory],
|
164
|
+
['root/moo/cow.txt', :file],
|
165
|
+
['root/moo', :directory],
|
166
|
+
['root/file.yml', :file],
|
167
|
+
['root/file.rb', :file],
|
168
|
+
['root/file.css', :file],
|
169
|
+
['root/duck/quack.txt', :file],
|
170
|
+
['root/duck', :directory],
|
171
|
+
['root', :directory]
|
172
|
+
]
|
173
|
+
run_monitor(expected_delete_events.size, :directories => true) do
|
174
|
+
FileUtils.rm_rf @tmp_dir + '/.'
|
175
|
+
end
|
155
176
|
@handler_results[:create].should == []
|
156
|
-
@handler_results[:delete].should == [
|
157
|
-
['root/yawn', :directory],
|
158
|
-
['root/moo/cow.txt', :file],
|
159
|
-
['root/moo', :directory],
|
160
|
-
['root/file.yml', :file],
|
161
|
-
['root/file.rb', :file],
|
162
|
-
['root/file.css', :file],
|
163
|
-
['root/duck/quack.txt', :file],
|
164
|
-
['root/duck', :directory],
|
165
|
-
['root', :directory]
|
166
|
-
].map {|(file, type)| [@tmp_dir, file, type]}
|
177
|
+
@handler_results[:delete].should == expected_delete_events.map {|(file, type)| [@tmp_dir, file, type]}
|
167
178
|
@handler_results[:update].should == []
|
168
179
|
end
|
169
180
|
|
170
181
|
it "should call create callbacks upon directory structure creation, in order" do
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
182
|
+
expected_create_events = [
|
183
|
+
['new_root', :directory],
|
184
|
+
['new_root/duck', :directory],
|
185
|
+
['new_root/duck/quack.txt', :file],
|
186
|
+
['new_root/file.css', :file],
|
187
|
+
['new_root/file.rb', :file],
|
188
|
+
['new_root/file.yml', :file],
|
189
|
+
['new_root/moo', :directory],
|
190
|
+
['new_root/moo/cow.txt', :file],
|
191
|
+
['new_root/yawn', :directory]
|
192
|
+
]
|
193
|
+
run_monitor(expected_create_events.size, :directories => true) do
|
194
|
+
FileUtils.cp_r @tmp_dir + '/root/.', @tmp_dir + '/new_root'
|
195
|
+
end
|
196
|
+
@handler_results[:create].should == expected_create_events.map {|(file, type)| [@tmp_dir, file, type]}
|
184
197
|
@handler_results[:delete].should == []
|
185
198
|
@handler_results[:update].should == []
|
186
199
|
end
|
metadata
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fssm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 19
|
5
4
|
prerelease: false
|
6
5
|
segments:
|
7
6
|
- 0
|
8
7
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
8
|
+
- 3
|
9
|
+
version: 0.2.3
|
11
10
|
platform: ruby
|
12
11
|
authors:
|
13
12
|
- Travis Tilley
|
@@ -15,7 +14,7 @@ autorequire:
|
|
15
14
|
bindir: bin
|
16
15
|
cert_chain: []
|
17
16
|
|
18
|
-
date: 2010-12-
|
17
|
+
date: 2010-12-18 00:00:00 -05:00
|
19
18
|
default_executable:
|
20
19
|
dependencies:
|
21
20
|
- !ruby/object:Gem::Dependency
|
@@ -26,7 +25,6 @@ dependencies:
|
|
26
25
|
requirements:
|
27
26
|
- - ">="
|
28
27
|
- !ruby/object:Gem::Version
|
29
|
-
hash: 3
|
30
28
|
segments:
|
31
29
|
- 0
|
32
30
|
version: "0"
|
@@ -67,6 +65,7 @@ files:
|
|
67
65
|
- profile/prof-pathname.rb
|
68
66
|
- profile/prof-plain-pathname.html
|
69
67
|
- profile/prof.html
|
68
|
+
- spec/count_down_latch.rb
|
70
69
|
- spec/monitor_spec.rb
|
71
70
|
- spec/path_spec.rb
|
72
71
|
- spec/root/duck/quack.txt
|
@@ -89,7 +88,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
89
88
|
requirements:
|
90
89
|
- - ">="
|
91
90
|
- !ruby/object:Gem::Version
|
92
|
-
hash: 3
|
93
91
|
segments:
|
94
92
|
- 0
|
95
93
|
version: "0"
|
@@ -98,7 +96,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
98
96
|
requirements:
|
99
97
|
- - ">="
|
100
98
|
- !ruby/object:Gem::Version
|
101
|
-
hash: 3
|
102
99
|
segments:
|
103
100
|
- 0
|
104
101
|
version: "0"
|
@@ -110,6 +107,7 @@ signing_key:
|
|
110
107
|
specification_version: 3
|
111
108
|
summary: file system state monitor
|
112
109
|
test_files:
|
110
|
+
- spec/count_down_latch.rb
|
113
111
|
- spec/monitor_spec.rb
|
114
112
|
- spec/path_spec.rb
|
115
113
|
- spec/root/file.rb
|