eventmachine-tail 0.0.1 → 0.6.1

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.
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ $:.unshift "#{File.dirname(__FILE__)}/../lib"
5
+ require 'eventmachine-tail'
6
+ require 'tempfile'
7
+ require 'test/unit'
8
+ require 'timeout'
9
+ require 'testcase_helpers.rb'
10
+
11
+
12
+ # Generate some data
13
+ DATA = (1..10).collect { |i| rand.to_s }
14
+ SLEEPMAX = 1
15
+
16
+ class Reader < EventMachine::FileTail
17
+ def initialize(path, startpos=-1, testobj=nil)
18
+ super(path, startpos)
19
+ @data = DATA.clone
20
+ @buffer = BufferedTokenizer.new
21
+ @testobj = testobj
22
+ @lineno = 0
23
+ end # def initialize
24
+
25
+ def receive_data(data)
26
+ @buffer.extract(data).each do |line|
27
+ @lineno += 1
28
+ expected = @data.shift
29
+ @testobj.assert_equal(expected, line,
30
+ "Expected '#{expected}' on line #{@lineno}, but got '#{line}'")
31
+ end # @buffer.extract
32
+ end # def receive_data
33
+
34
+ # This effectively tests EOF handling by requiring it to work in order
35
+ # for the tests to pass.
36
+ def eof
37
+ if @data.length == 0
38
+ close
39
+ @testobj.finish
40
+ end
41
+ end # def eof
42
+ end # class Reader
43
+
44
+ class TestFileTail < Test::Unit::TestCase
45
+ include EventMachineTailTestHelpers
46
+
47
+ # This test should run slow. We are trying to ensure that
48
+ # our file_tail correctly reads data slowly fed into the file
49
+ # as 'tail -f' would.
50
+ def test_filetail
51
+ tmp = Tempfile.new("testfiletail")
52
+ data = DATA.clone
53
+ EM.run do
54
+ abort_after_timeout(DATA.length * SLEEPMAX + 10)
55
+
56
+ EM::file_tail(tmp.path, Reader, -1, self)
57
+ timer = EM::PeriodicTimer.new(0.2) do
58
+ tmp.puts data.shift
59
+ tmp.flush
60
+ sleep(rand * SLEEPMAX)
61
+ timer.cancel if data.length == 0
62
+ end
63
+ end # EM.run
64
+ end # def test_filetail
65
+
66
+ def test_filetail_with_seek
67
+ tmp = Tempfile.new("testfiletail")
68
+ data = DATA.clone
69
+ data.each { |i| tmp.puts i }
70
+ tmp.flush
71
+ EM.run do
72
+ abort_after_timeout(2)
73
+
74
+ # Set startpos of 0 (beginning of file)
75
+ EM::file_tail(tmp.path, Reader, 0, self)
76
+ end # EM.run
77
+ end # def test_filetail
78
+
79
+ def test_filetail_with_block
80
+ tmp = Tempfile.new("testfiletail")
81
+ data = DATA.clone
82
+ EM.run do
83
+ abort_after_timeout(DATA.length * SLEEPMAX + 10)
84
+
85
+ lineno = 0
86
+ EM::file_tail(tmp.path) do |filetail, line|
87
+ lineno += 1
88
+ expected = data.shift
89
+ assert_equal(expected, line,
90
+ "Expected '#{expected}' on line #{@lineno}, but got '#{line}'")
91
+ finish if data.length == 0
92
+ end
93
+
94
+ data_copy = data.clone
95
+ timer = EM::PeriodicTimer.new(0.2) do
96
+ tmp.puts data_copy.shift
97
+ tmp.flush
98
+ sleep(rand * SLEEPMAX)
99
+ timer.cancel if data_copy.length == 0
100
+ end
101
+ end # EM.run
102
+ end # def test_filetail_with_block
103
+
104
+ def test_filetail_tracks_renames
105
+ tmp = Tempfile.new("testfiletail")
106
+ data = DATA.clone
107
+ filename = tmp.path
108
+
109
+ data_copy = data.clone
110
+
111
+ # Write first so the first read happens immediately
112
+ tmp.puts data_copy.shift
113
+ tmp.flush
114
+ EM.run do
115
+ abort_after_timeout(DATA.length * SLEEPMAX + 10)
116
+
117
+ lineno = 0
118
+ # Start at file position 0.
119
+ EM::file_tail(tmp.path, nil, 0) do |filetail, line|
120
+ lineno += 1
121
+ expected = data.shift
122
+ #puts "Got #{lineno}: #{line}"
123
+ assert_equal(expected, line,
124
+ "Expected '#{expected}' on line #{lineno}, but got '#{line}'")
125
+ finish if data.length == 0
126
+
127
+ # Start a timer on the first read.
128
+ # This is to ensure we have the file tailing before
129
+ # we try to rename.
130
+ if lineno == 1
131
+ timer = EM::PeriodicTimer.new(0.2) do
132
+ value = data_copy.shift
133
+ tmp.puts value
134
+ tmp.flush
135
+ sleep(rand * SLEEPMAX)
136
+
137
+ # Rename the file, create a new one in it's place.
138
+ # This is to simulate log rotation, etc.
139
+ path_newname = "#{filename}_#{value}"
140
+ File.rename(filename, path_newname)
141
+ File.delete(path_newname)
142
+ tmp = File.open(filename, "w")
143
+ timer.cancel if data_copy.length == 0
144
+ end # timer
145
+ end # if lineno == 1
146
+ end # EM::filetail(...)
147
+ end # EM.run
148
+
149
+ File.delete(filename)
150
+ end # def test_filetail_tracks_renames
151
+
152
+ def test_filetail_tracks_symlink_changes
153
+ to_delete = []
154
+ link = Tempfile.new("testlink")
155
+ File.delete(link.path)
156
+ to_delete << link
157
+ tmp = Tempfile.new("testfiletail")
158
+ to_delete << tmp
159
+ data = DATA.clone
160
+ File.symlink(tmp.path, link.path)
161
+
162
+ data_copy = data.clone
163
+
164
+ # Write first so the first read happens immediately
165
+ tmp.puts data_copy.shift
166
+ tmp.flush
167
+ EM.run do
168
+ abort_after_timeout(DATA.length * SLEEPMAX + 10)
169
+
170
+ lineno = 0
171
+ # Start at file position 0.
172
+ EM::file_tail(link.path, nil, 0) do |filetail, line|
173
+ # This needs to be less than the interval at which we are changing symlinks.
174
+ filetail.symlink_check_interval = 0.1
175
+
176
+ lineno += 1
177
+ expected = data.shift
178
+ puts "Got #{lineno}: #{line}" if $debug
179
+ assert_equal(expected, line,
180
+ "Expected '#{expected}' on line #{lineno}, but got '#{line}'")
181
+ finish if data.length == 0
182
+
183
+ # Start a timer on the first read.
184
+ # This is to ensure we have the file tailing before
185
+ # we try to rename.
186
+ if lineno == 1
187
+ timer = EM::PeriodicTimer.new(0.2) do
188
+ value = data_copy.shift
189
+ tmp.puts value
190
+ tmp.flush
191
+ sleep(rand * SLEEPMAX)
192
+
193
+ # Make a new file and update the symlink to point to it.
194
+ # This is to simulate log rotation, etc.
195
+ tmp.close
196
+ tmp = Tempfile.new("testfiletail")
197
+ to_delete << tmp
198
+ File.delete(link.path)
199
+ File.symlink(tmp.path, link.path)
200
+ puts "#{tmp.path} => #{link.path}" if $debug
201
+ timer.cancel if data_copy.length == 0
202
+ end # timer
203
+ end # if lineno == 1
204
+ end # EM::filetail(...)
205
+ end # EM.run
206
+
207
+ to_delete.each do |f|
208
+ File.delete(f.path)
209
+ end
210
+ end # def test_filetail_tracks_renames
211
+
212
+ def test_encoding
213
+ return if RUBY_VERSION < '1.9.0'
214
+ tmp = Tempfile.new("testfiletail")
215
+ data = DATA.clone
216
+ EM.run do
217
+ abort_after_timeout(1)
218
+
219
+ EM::file_tail(tmp.path) do |filetail, line|
220
+ assert_equal(Encoding.default_external, line.encoding,
221
+ "Expected the read data to have the encoding specified in Encoding.default_external (#{Encoding.default_external}, but was #{line.encoding})")
222
+ finish
223
+ end
224
+
225
+ EM.next_tick do
226
+ tmp.puts(data.shift)
227
+ tmp.flush
228
+ end
229
+ end # EM.run
230
+ end # def test_encoding
231
+ end # class TestFileTail
232
+
data/test/test_glob.rb ADDED
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ $:.unshift "#{File.dirname(__FILE__)}/../lib"
5
+ require 'eventmachine-tail'
6
+ require 'tempfile'
7
+ require 'test/unit'
8
+ require 'timeout'
9
+ require 'tmpdir'
10
+
11
+ require 'testcase_helpers'
12
+
13
+ class Watcher < EventMachine::FileGlobWatch
14
+ def initialize(path, interval, data, testobj)
15
+ super(path, interval)
16
+ @data = data
17
+ @testobj = testobj
18
+ end # def initialize
19
+
20
+ def file_found(path)
21
+ # Use .include? here because files aren't going to be found in any
22
+ # particular order.
23
+ @testobj.assert(@data.include?(path), "Expected #{path} in \n#{@data.join("\n")}")
24
+ @data.delete(path)
25
+ @testobj.finish if @data.length == 0
26
+ end
27
+
28
+ def file_deleted(path)
29
+ @testobj.assert(@data.include?(path), "Expected #{path} in \n#{@data.join("\n")}")
30
+ @data.delete(path)
31
+ @testobj.finish if @data.length == 0
32
+ end
33
+ end # class Reader
34
+
35
+ class TestGlobWatcher < Test::Unit::TestCase
36
+ include EventMachineTailTestHelpers
37
+ SLEEPMAX = 1
38
+
39
+ def setup
40
+ @watchinterval = 0.2
41
+ @dir = Dir.mktmpdir
42
+ @data = []
43
+ @data << "#{@dir}/#{rand}"
44
+ @data << "#{@dir}/#{rand}"
45
+ @data << "#{@dir}/#{rand}"
46
+ @data << "#{@dir}/#{rand}"
47
+ @data << "#{@dir}/#{rand}"
48
+ @data << "#{@dir}/#{rand}"
49
+ @data << "#{@dir}/#{rand}"
50
+ @data << "#{@dir}/#{rand}.gz"
51
+ @data << "#{@dir}/#{rand}.gz"
52
+ @data << "#{@dir}/#{rand}.tar.gz"
53
+ end # def setup
54
+
55
+ def teardown
56
+ @data.each do |file|
57
+ #puts "Deleting #{file}"
58
+ File.delete(file) rescue nil
59
+ end
60
+ Dir.delete(@dir)
61
+ end # def teardown
62
+
63
+ def finish
64
+ EM.stop_event_loop
65
+ end
66
+
67
+ def test_glob_finds_existing_files
68
+ EM.run do
69
+ abort_after_timeout(SLEEPMAX * @data.length + 10)
70
+
71
+ @data.each do |path|
72
+ File.new(path, "w").close
73
+ end
74
+ EM::watch_glob("#{@dir}/*", Watcher, @watchinterval, @data.clone, self)
75
+ end # EM.run
76
+ end # def test_glob_finds_existing_files
77
+
78
+ # This test should run slow. We are trying to ensure that
79
+ # our file_tail correctly reads data slowly fed into the file
80
+ # as 'tail -f' would.
81
+ def test_glob_finds_newly_created_files_at_runtime
82
+ EM.run do
83
+ abort_after_timeout(SLEEPMAX * @data.length + 10)
84
+
85
+ EM::watch_glob("#{@dir}/*", Watcher, @watchinterval, @data.clone, self)
86
+ datacopy = @data.clone
87
+ timer = EM::PeriodicTimer.new(0.2) do
88
+ #puts "Creating: #{datacopy.first}"
89
+ File.new(datacopy.shift, "w").close
90
+ sleep(rand * SLEEPMAX)
91
+ timer.cancel if datacopy.length == 0
92
+ end
93
+ end # EM.run
94
+ end # def test_glob_finds_newly_created_files_at_runtime
95
+
96
+ def test_glob_ignores_file_renames
97
+ EM.run do
98
+ abort_after_timeout(SLEEPMAX * @data.length + 10)
99
+
100
+ EM::watch_glob("#{@dir}/*", Watcher, @watchinterval, @data.clone, self)
101
+
102
+ datacopy = @data.clone
103
+ timer = EM::PeriodicTimer.new(0.2) do
104
+ filename = datacopy.shift
105
+ File.new(filename, "w").close
106
+ sleep(rand * SLEEPMAX)
107
+
108
+ # This file rename should be ignored.
109
+ EM::Timer.new(2) do
110
+ newname = "#{filename}.renamed"
111
+ File.rename(filename, newname)
112
+
113
+ # Track the new filename so teardown removes it.
114
+ @data << newname
115
+ end
116
+ timer.cancel if datacopy.length == 0
117
+ end
118
+ end
119
+ end # def test_glob_ignores_file_renames
120
+
121
+ def test_glob_ignores_duplicate_hardlinks
122
+ EM.run do
123
+ abort_after_timeout(SLEEPMAX * @data.length + 10)
124
+
125
+ EM::watch_glob("#{@dir}/*", Watcher, @watchinterval, @data.clone, self)
126
+
127
+ datacopy = @data.clone
128
+ timer = EM::PeriodicTimer.new(0.2) do
129
+ filename = datacopy.shift
130
+ File.new(filename, "w").close
131
+ sleep(rand * SLEEPMAX)
132
+
133
+ # This file rename should be ignored.
134
+ EM::Timer.new(2) do
135
+ newname = "#{filename}.renamed"
136
+ File.link(filename, newname)
137
+
138
+ # Track the new filename so teardown removes it.
139
+ @data << newname
140
+ end
141
+ timer.cancel if datacopy.length == 0
142
+ end
143
+ end
144
+ end # def test_glob_ignores_file_renames
145
+ end # class TestGlobWatcher
146
+
@@ -0,0 +1,13 @@
1
+
2
+ module EventMachineTailTestHelpers
3
+ def abort_after_timeout(seconds)
4
+ EM::Timer.new(seconds) do
5
+ EM.stop_event_loop
6
+ flunk("Timeout (#{seconds} seconds) while running tests. Failing.")
7
+ end
8
+ end
9
+
10
+ def finish
11
+ EventMachine.stop_event_loop
12
+ end
13
+ end # module EventMachineTailTestHelpers
metadata CHANGED
@@ -1,7 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eventmachine-tail
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ hash: 5
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 6
9
+ - 1
10
+ version: 0.6.1
5
11
  platform: ruby
6
12
  authors:
7
13
  - Jordan Sissel
@@ -9,32 +15,49 @@ autorequire:
9
15
  bindir: bin
10
16
  cert_chain: []
11
17
 
12
- date: 2010-04-11 00:00:00 -07:00
18
+ date: 2011-02-22 00:00:00 -08:00
13
19
  default_executable:
14
20
  dependencies:
15
21
  - !ruby/object:Gem::Dependency
16
22
  name: eventmachine
17
- type: :runtime
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
20
26
  requirements:
21
27
  - - ">="
22
28
  - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
23
32
  version: "0"
24
- version:
25
- description: Add file 'tail' implemented with EventMachine
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description: Add file 'tail' implemented with EventMachine. Also includes a 'glob watch' class for watching a directory pattern for new matches, like /var/log/*.log
26
36
  email: jls@semicomplete.com
27
- executables: []
28
-
37
+ executables:
38
+ - rtail
39
+ - emtail
29
40
  extensions: []
30
41
 
31
42
  extra_rdoc_files: []
32
43
 
33
44
  files:
45
+ - lib/eventmachine-tail.rb
34
46
  - lib/em/filetail.rb
35
47
  - lib/em/globwatcher.rb
48
+ - samples/tail.rb
49
+ - samples/glob-tail.rb
50
+ - samples/globwatch.rb
51
+ - samples/tail-with-block.rb
52
+ - test/test_filetail.rb
53
+ - test/logrotate.conf
54
+ - test/test_glob.rb
55
+ - test/alltests.rb
56
+ - test/testcase_helpers.rb
57
+ - bin/rtail
58
+ - bin/emtail
36
59
  has_rdoc: true
37
- homepage: http://code.google.com/p/semicomplete/wiki/EventMachine-Tail
60
+ homepage: http://code.google.com/p/semicomplete/wiki/EventMachineTail
38
61
  licenses: []
39
62
 
40
63
  post_install_message:
@@ -44,23 +67,29 @@ require_paths:
44
67
  - lib
45
68
  - lib
46
69
  required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
47
71
  requirements:
48
72
  - - ">="
49
73
  - !ruby/object:Gem::Version
74
+ hash: 3
75
+ segments:
76
+ - 0
50
77
  version: "0"
51
- version:
52
78
  required_rubygems_version: !ruby/object:Gem::Requirement
79
+ none: false
53
80
  requirements:
54
81
  - - ">="
55
82
  - !ruby/object:Gem::Version
83
+ hash: 3
84
+ segments:
85
+ - 0
56
86
  version: "0"
57
- version:
58
87
  requirements: []
59
88
 
60
89
  rubyforge_project:
61
- rubygems_version: 1.3.5
90
+ rubygems_version: 1.5.0
62
91
  signing_key:
63
92
  specification_version: 3
64
- summary: eventmachine tail - a file tail implementation
93
+ summary: eventmachine tail - a file tail implementation with glob support
65
94
  test_files: []
66
95