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.
@@ -1,5 +1,5 @@
1
1
  ---
2
- :patch: 2
3
- :build:
4
2
  :major: 0
5
3
  :minor: 2
4
+ :patch: 3
5
+ :build:
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{fssm}
8
- s.version = "0.2.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-11}
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",
@@ -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.exists?
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
@@ -1,7 +1,8 @@
1
- require "spec_helper"
1
+ require 'spec_helper'
2
2
 
3
- require "fileutils"
4
- require "tempfile"
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 {|*args| @handler_results[type] << args}
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 create_monitor(options={})
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
- @monitor = FSSM::Monitor.new(options)
26
- @monitor.path(@tmp_dir) do |p|
27
- p.create(&create_handler(:create))
28
- p.update(&create_handler(:update))
29
- p.delete(&create_handler(:delete))
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 # give time for monitor to preload
32
- end
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
- file = @tmp_dir + "/newfile.rb"
60
- File.exists?(file).should be_false
61
- FileUtils.touch file
62
- run_monitor
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
- FileUtils.touch @tmp_dir + '/root/file.rb'
68
- run_monitor
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
- FileUtils.rm @tmp_dir + "/root/file.rb"
74
- run_monitor
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
- FileUtils.mv @tmp_dir + "/root/file.rb", @tmp_dir + "/root/old_file.rb"
80
- run_monitor
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
- FileUtils.mv @tmp_dir + "/root/file.rb", @tmp_dir + "/old_file.rb"
88
- run_monitor
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
- FileUtils.mkdir @tmp_dir + "/another_yawn"
96
- FileUtils.rmdir @tmp_dir + "/root/yawn"
97
- run_monitor
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
- FileUtils.mkdir @tmp_dir + "/another_yawn"
110
- run_monitor
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
- FileUtils.rmdir @tmp_dir + "/root/yawn"
116
- run_monitor
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
- FileUtils.mv @tmp_dir + "/root/yawn", @tmp_dir + "/root/old_yawn"
122
- run_monitor
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
- FileUtils.mv @tmp_dir + "/root/yawn", @tmp_dir + "/old_yawn"
130
- run_monitor
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
- FileUtils.mv @tmp_dir + "/root/file.rb", @tmp_dir + "/root/old_file.rb"
138
- run_monitor
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
- FileUtils.mv @tmp_dir + "/root/file.rb", @tmp_dir + "/old_file.rb"
146
- run_monitor
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
- FileUtils.rm_rf @tmp_dir + '/.'
154
- run_monitor
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
- FileUtils.cp_r @tmp_dir + '/root/.', @tmp_dir + '/new_root'
172
- run_monitor
173
- @handler_results[:create].should == [
174
- ['new_root', :directory],
175
- ['new_root/duck', :directory],
176
- ['new_root/duck/quack.txt', :file],
177
- ['new_root/file.css', :file],
178
- ['new_root/file.rb', :file],
179
- ['new_root/file.yml', :file],
180
- ['new_root/moo', :directory],
181
- ['new_root/moo/cow.txt', :file],
182
- ['new_root/yawn', :directory]
183
- ].map {|(file, type)| [@tmp_dir, file, type]}
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
- - 2
10
- version: 0.2.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-11 00:00:00 -05:00
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