io-reactor 0.05 → 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +482 -217
- data/LICENSE +27 -0
- data/README +1 -1
- data/Rakefile +257 -0
- data/examples/chatserver.rb +1 -1
- data/examples/simple.rb +59 -0
- data/examples/simpleserver.rb +44 -0
- data/lib/io/reactor.rb +114 -87
- data/rake/dependencies.rb +62 -0
- data/rake/helpers.rb +384 -0
- data/rake/manual.rb +384 -0
- data/rake/packaging.rb +112 -0
- data/rake/publishing.rb +302 -0
- data/rake/rdoc.rb +31 -0
- data/rake/style.rb +62 -0
- data/rake/svn.rb +468 -0
- data/rake/testing.rb +191 -0
- data/rake/verifytask.rb +64 -0
- data/spec/io/reactor_spec.rb +269 -0
- metadata +71 -45
- data/CATALOG +0 -10
- data/install.rb +0 -85
- data/io-reactor.gemspec +0 -21
- data/makedocs.rb +0 -79
- data/test.rb +0 -212
- data/utils.rb +0 -484
data/rake/testing.rb
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
#
|
2
|
+
# Rake tasklib for testing tasks
|
3
|
+
# $Id: testing.rb 14 2008-07-23 23:16:30Z deveiant $
|
4
|
+
#
|
5
|
+
# Authors:
|
6
|
+
# * Michael Granger <ged@FaerieMUD.org>
|
7
|
+
#
|
8
|
+
|
9
|
+
COVERAGE_MINIMUM = 85.0 unless defined?( COVERAGE_MINIMUM )
|
10
|
+
SPEC_FILES = [] unless defined?( SPEC_FILES )
|
11
|
+
TEST_FILES = [] unless defined?( TEST_FILES )
|
12
|
+
|
13
|
+
COMMON_SPEC_OPTS = ['-c', '-f', 's'] unless defined?( COMMON_SPEC_OPTS )
|
14
|
+
|
15
|
+
COVERAGE_TARGETDIR = BASEDIR + 'coverage' unless defined?( COVERAGE_TARGETDIR )
|
16
|
+
RCOV_EXCLUDES = 'spec,tests,/Library/Ruby,/var/lib,/usr/local/lib' unless
|
17
|
+
defined?( RCOV_EXCLUDES )
|
18
|
+
|
19
|
+
|
20
|
+
desc "Run all defined tests"
|
21
|
+
task :test do
|
22
|
+
unless SPEC_FILES.empty?
|
23
|
+
log "Running specs"
|
24
|
+
Rake::Task['spec:quiet'].invoke
|
25
|
+
end
|
26
|
+
|
27
|
+
unless TEST_FILES.empty?
|
28
|
+
log "Running unit tests"
|
29
|
+
Rake::Task[:unittests].invoke
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
### RSpec specifications
|
35
|
+
begin
|
36
|
+
gem 'rspec', '>= 1.1.3'
|
37
|
+
require 'spec/rake/spectask'
|
38
|
+
|
39
|
+
### Task: spec
|
40
|
+
Spec::Rake::SpecTask.new( :spec ) do |task|
|
41
|
+
task.spec_files = SPEC_FILES
|
42
|
+
task.libs += [LIBDIR]
|
43
|
+
task.spec_opts = COMMON_SPEC_OPTS
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
namespace :spec do
|
48
|
+
desc "Run rspec every time there's a change to one of the files"
|
49
|
+
task :autotest do
|
50
|
+
require 'autotest/rspec'
|
51
|
+
|
52
|
+
autotester = Autotest::Rspec.new
|
53
|
+
autotester.exceptions = %r{\.svn|\.skel}
|
54
|
+
autotester.run
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
desc "Generate quiet output"
|
59
|
+
Spec::Rake::SpecTask.new( :quiet ) do |task|
|
60
|
+
task.spec_files = SPEC_FILES
|
61
|
+
task.spec_opts = ['-f', 'p', '-D']
|
62
|
+
end
|
63
|
+
|
64
|
+
desc "Generate HTML output for a spec run"
|
65
|
+
Spec::Rake::SpecTask.new( :html ) do |task|
|
66
|
+
task.spec_files = SPEC_FILES
|
67
|
+
task.spec_opts = ['-f','h', '-D']
|
68
|
+
end
|
69
|
+
|
70
|
+
desc "Generate plain-text output for a CruiseControl.rb build"
|
71
|
+
Spec::Rake::SpecTask.new( :text ) do |task|
|
72
|
+
task.spec_files = SPEC_FILES
|
73
|
+
task.spec_opts = ['-f','p']
|
74
|
+
end
|
75
|
+
end
|
76
|
+
rescue LoadError => err
|
77
|
+
task :no_rspec do
|
78
|
+
$stderr.puts "Specification tasks not defined: %s" % [ err.message ]
|
79
|
+
end
|
80
|
+
|
81
|
+
task :spec => :no_rspec
|
82
|
+
namespace :spec do
|
83
|
+
task :autotest => :no_rspec
|
84
|
+
task :quiet => :no_rspec
|
85
|
+
task :html => :no_rspec
|
86
|
+
task :text => :no_rspec
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
### Test::Unit tests
|
92
|
+
begin
|
93
|
+
require 'rake/testtask'
|
94
|
+
|
95
|
+
Rake::TestTask.new( :unittests ) do |task|
|
96
|
+
task.libs += [LIBDIR]
|
97
|
+
task.test_files = TEST_FILES
|
98
|
+
task.verbose = true
|
99
|
+
end
|
100
|
+
|
101
|
+
rescue LoadError => err
|
102
|
+
task :no_test do
|
103
|
+
$stderr.puts "Test tasks not defined: %s" % [ err.message ]
|
104
|
+
end
|
105
|
+
|
106
|
+
task :unittests => :no_rspec
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
### RCov (via RSpec) tasks
|
111
|
+
begin
|
112
|
+
gem 'rcov'
|
113
|
+
gem 'rspec', '>= 1.1.3'
|
114
|
+
|
115
|
+
### Task: coverage (via RCov)
|
116
|
+
### Task: rcov
|
117
|
+
desc "Build test coverage reports"
|
118
|
+
unless SPEC_FILES.empty?
|
119
|
+
Spec::Rake::SpecTask.new( :coverage ) do |task|
|
120
|
+
task.spec_files = SPEC_FILES
|
121
|
+
task.libs += [LIBDIR]
|
122
|
+
task.spec_opts = ['-f', 'p', '-b']
|
123
|
+
task.rcov_opts = RCOV_OPTS
|
124
|
+
task.rcov = true
|
125
|
+
end
|
126
|
+
end
|
127
|
+
unless TEST_FILES.empty?
|
128
|
+
require 'rcov/rcovtask'
|
129
|
+
|
130
|
+
Rcov::RcovTask.new do |task|
|
131
|
+
task.libs += [LIBDIR]
|
132
|
+
task.test_files = TEST_FILES
|
133
|
+
task.verbose = true
|
134
|
+
task.rcov_opts = RCOV_OPTS
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
task :rcov => [:coverage] do; end
|
140
|
+
|
141
|
+
### Other coverage tasks
|
142
|
+
namespace :coverage do
|
143
|
+
desc "Generate a detailed text coverage report"
|
144
|
+
Spec::Rake::SpecTask.new( :text ) do |task|
|
145
|
+
task.spec_files = SPEC_FILES
|
146
|
+
task.rcov_opts = RCOV_OPTS + ['--text-report']
|
147
|
+
task.rcov = true
|
148
|
+
end
|
149
|
+
|
150
|
+
desc "Show differences in coverage from last run"
|
151
|
+
Spec::Rake::SpecTask.new( :diff ) do |task|
|
152
|
+
task.spec_files = SPEC_FILES
|
153
|
+
task.rcov_opts = ['--text-coverage-diff']
|
154
|
+
task.rcov = true
|
155
|
+
end
|
156
|
+
|
157
|
+
### Task: verify coverage
|
158
|
+
desc "Build coverage statistics"
|
159
|
+
VerifyTask.new( :verify => :rcov ) do |task|
|
160
|
+
task.threshold = COVERAGE_MINIMUM
|
161
|
+
end
|
162
|
+
|
163
|
+
desc "Run RCov in 'spec-only' mode to check coverage from specs"
|
164
|
+
Spec::Rake::SpecTask.new( :speconly ) do |task|
|
165
|
+
task.spec_files = SPEC_FILES
|
166
|
+
task.rcov_opts = ['--exclude', RCOV_EXCLUDES, '--text-report', '--save']
|
167
|
+
task.rcov = true
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
task :clobber_coverage do
|
172
|
+
rmtree( COVERAGE_TARGETDIR )
|
173
|
+
end
|
174
|
+
|
175
|
+
rescue LoadError => err
|
176
|
+
task :no_rcov do
|
177
|
+
$stderr.puts "Coverage tasks not defined: RSpec+RCov tasklib not available: %s" %
|
178
|
+
[ err.message ]
|
179
|
+
end
|
180
|
+
|
181
|
+
task :coverage => :no_rcov
|
182
|
+
task :clobber_coverage
|
183
|
+
task :rcov => :no_rcov
|
184
|
+
namespace :coverage do
|
185
|
+
task :text => :no_rcov
|
186
|
+
task :diff => :no_rcov
|
187
|
+
end
|
188
|
+
task :verify => :no_rcov
|
189
|
+
end
|
190
|
+
|
191
|
+
|
data/rake/verifytask.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
#####################################################################
|
2
|
+
### S U B V E R S I O N T A S K S A N D H E L P E R S
|
3
|
+
#####################################################################
|
4
|
+
|
5
|
+
require 'rake/tasklib'
|
6
|
+
|
7
|
+
#
|
8
|
+
# Work around the inexplicable behaviour of the original RDoc::VerifyTask, which
|
9
|
+
# errors if your coverage isn't *exactly* the threshold.
|
10
|
+
#
|
11
|
+
|
12
|
+
# A task that can verify that the RCov coverage doesn't
|
13
|
+
# drop below a certain threshold. It should be run after
|
14
|
+
# running Spec::Rake::SpecTask.
|
15
|
+
class VerifyTask < Rake::TaskLib
|
16
|
+
|
17
|
+
COVERAGE_PERCENTAGE_PATTERN =
|
18
|
+
%r{<tt class='coverage_code'>(\d+\.\d+)%</tt>}
|
19
|
+
|
20
|
+
# Name of the task. Defaults to :verify_rcov
|
21
|
+
attr_accessor :name
|
22
|
+
|
23
|
+
# Path to the index.html file generated by RCov, which
|
24
|
+
# is the file containing the total coverage.
|
25
|
+
# Defaults to 'coverage/index.html'
|
26
|
+
attr_accessor :index_html
|
27
|
+
|
28
|
+
# Whether or not to output details. Defaults to true.
|
29
|
+
attr_accessor :verbose
|
30
|
+
|
31
|
+
# The threshold value (in percent) for coverage. If the
|
32
|
+
# actual coverage is not equal to this value, the task will raise an
|
33
|
+
# exception.
|
34
|
+
attr_accessor :threshold
|
35
|
+
|
36
|
+
def initialize( name=:verify )
|
37
|
+
@name = name
|
38
|
+
@index_html = 'coverage/index.html'
|
39
|
+
@verbose = true
|
40
|
+
yield self if block_given?
|
41
|
+
raise "Threshold must be set" if @threshold.nil?
|
42
|
+
define
|
43
|
+
end
|
44
|
+
|
45
|
+
def define
|
46
|
+
desc "Verify that rcov coverage is at least #{threshold}%"
|
47
|
+
|
48
|
+
task @name do
|
49
|
+
total_coverage = nil
|
50
|
+
if match = File.read( index_html ).match( COVERAGE_PERCENTAGE_PATTERN )
|
51
|
+
total_coverage = Float( match[1] )
|
52
|
+
else
|
53
|
+
raise "Couldn't find the coverage percentage in #{index_html}"
|
54
|
+
end
|
55
|
+
|
56
|
+
puts "Coverage: #{total_coverage}% (threshold: #{threshold}%)" if verbose
|
57
|
+
if total_coverage < threshold
|
58
|
+
raise "Coverage must be at least #{threshold}% but was #{total_coverage}%"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# vim: set nosta noet ts=4 sw=4:
|
@@ -0,0 +1,269 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
BEGIN {
|
4
|
+
require 'pathname'
|
5
|
+
basedir = Pathname.new( __FILE__ ).dirname.parent.parent
|
6
|
+
|
7
|
+
libdir = basedir + "lib"
|
8
|
+
|
9
|
+
$LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
|
10
|
+
}
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'tmpdir'
|
14
|
+
require 'spec/runner'
|
15
|
+
require 'io/reactor'
|
16
|
+
require 'socket'
|
17
|
+
rescue LoadError
|
18
|
+
unless Object.const_defined?( :Gem )
|
19
|
+
require 'rubygems'
|
20
|
+
retry
|
21
|
+
end
|
22
|
+
raise
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
TMPFILE = Pathname.new( Dir.tmpdir ) + "ioreactor-spec.#{Process.pid}"
|
27
|
+
HOST = 'localhost'
|
28
|
+
PORT = 5656
|
29
|
+
TEST_DATA = File.read( __FILE__ )
|
30
|
+
|
31
|
+
$stderr.sync = $stdout.sync = true
|
32
|
+
|
33
|
+
|
34
|
+
describe IO::Reactor do
|
35
|
+
|
36
|
+
before( :each ) do
|
37
|
+
@reactor = IO::Reactor.new
|
38
|
+
@reader, @writer = IO.pipe
|
39
|
+
end
|
40
|
+
|
41
|
+
after( :each ) do
|
42
|
+
@reactor.clear
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
### #register/#registered? with no block
|
47
|
+
it "allows registration of an IO for write events" do
|
48
|
+
@reactor.register( @writer, :write )
|
49
|
+
|
50
|
+
@reactor.should be_registered( @writer )
|
51
|
+
@reactor.handles[ @writer ][ :events ].should == [:write]
|
52
|
+
end
|
53
|
+
|
54
|
+
it "allows registration of an IO for read events" do
|
55
|
+
@reactor.register( @reader, :read )
|
56
|
+
|
57
|
+
@reactor.should be_registered( @reader )
|
58
|
+
@reactor.handles[ @reader ][ :events ].should == [:read]
|
59
|
+
end
|
60
|
+
|
61
|
+
it "allows registration of an IO for error events" do
|
62
|
+
@reactor.register( @writer, :error )
|
63
|
+
|
64
|
+
@reactor.should be_registered( @writer )
|
65
|
+
@reactor.handles[ @writer ][ :events ].should == [:error]
|
66
|
+
end
|
67
|
+
|
68
|
+
it "allows registration of an IO for multiple events" do
|
69
|
+
@reactor.register( @writer, :read, :write )
|
70
|
+
|
71
|
+
@reactor.should be_registered( @writer )
|
72
|
+
@reactor.handles[ @writer ][ :events ].should have(2).members
|
73
|
+
@reactor.handles[ @writer ][ :events ].should include( :read )
|
74
|
+
@reactor.handles[ @writer ][ :events ].should include( :write )
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
it "allows registration of a handler with an IO for selected events" do
|
79
|
+
@reactor.register( @writer, :write ) {|io, event| }
|
80
|
+
|
81
|
+
@reactor.should be_registered( @writer )
|
82
|
+
@reactor.handles[ @writer ][ :events ].should == [:write]
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
it "allows registration of an argument list with a handler" do
|
87
|
+
@reactor.register( @writer, :write, "foo", :bar, :something ) {|io, event| }
|
88
|
+
|
89
|
+
@reactor.should be_registered( @writer )
|
90
|
+
@reactor.handles[ @writer ][ :events ].should == [:write]
|
91
|
+
@reactor.handles[ @writer ][ :args ].should == ["foo", :bar, :something]
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
it "allows all registered handles to be cleared" do
|
96
|
+
@reactor.register( @reader, :read )
|
97
|
+
@reactor.register( @writer, :write )
|
98
|
+
|
99
|
+
@reactor.clear
|
100
|
+
|
101
|
+
@reactor.handles.should be_empty()
|
102
|
+
@reactor.should_not be_registered( @writer )
|
103
|
+
@reactor.should_not be_registered( @reader )
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
it "calls registered handlers for events on an IO when they occur" do
|
108
|
+
data_to_send = TEST_DATA.dup
|
109
|
+
received_data = ''
|
110
|
+
|
111
|
+
@reactor.register( @reader, :read ) do |io, event|
|
112
|
+
if io.eof?
|
113
|
+
@reactor.unregister( io )
|
114
|
+
io.close
|
115
|
+
else
|
116
|
+
received_data << io.read( 256 )
|
117
|
+
end
|
118
|
+
end
|
119
|
+
@reactor.register( @writer, :write ) do |io, event|
|
120
|
+
if data_to_send.empty?
|
121
|
+
@reactor.unregister( io )
|
122
|
+
io.close
|
123
|
+
else
|
124
|
+
bytes = io.write( data_to_send )
|
125
|
+
data_to_send.slice!( 0, bytes )
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
@reactor.poll until @reactor.empty?
|
130
|
+
|
131
|
+
received_data.should == TEST_DATA
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
it "calls a provided fallback handler if there is no handler registered for an " +
|
136
|
+
"event when it occurs" do
|
137
|
+
|
138
|
+
data_to_send = TEST_DATA.dup
|
139
|
+
received_data = ''
|
140
|
+
|
141
|
+
@reactor.register( @reader, :read )
|
142
|
+
@reactor.register( @writer, :write )
|
143
|
+
|
144
|
+
until @reactor.empty?
|
145
|
+
@reactor.poll do |io, event|
|
146
|
+
case event
|
147
|
+
when :read
|
148
|
+
if io.eof?
|
149
|
+
@reactor.unregister( io )
|
150
|
+
io.close
|
151
|
+
else
|
152
|
+
received_data << io.read( 256 )
|
153
|
+
end
|
154
|
+
|
155
|
+
when :write
|
156
|
+
if data_to_send.empty?
|
157
|
+
@reactor.unregister( io )
|
158
|
+
io.close
|
159
|
+
else
|
160
|
+
bytes = io.write( data_to_send )
|
161
|
+
data_to_send.slice!( 0, bytes )
|
162
|
+
end
|
163
|
+
|
164
|
+
when :error
|
165
|
+
@reactor.unregister( io )
|
166
|
+
io.close
|
167
|
+
else
|
168
|
+
fail "Reactor got unexpected event %p on %p" % [ event, io ]
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
received_data.should == TEST_DATA
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
### A class that will encapsulate how we want to read or write from the Reactor
|
178
|
+
class IOStrategy
|
179
|
+
def initialize( reactor, buffer='' )
|
180
|
+
@reactor = reactor
|
181
|
+
@buffer = buffer
|
182
|
+
end
|
183
|
+
|
184
|
+
attr_reader :buffer
|
185
|
+
|
186
|
+
def read_from( io, event )
|
187
|
+
raise ArgumentError, "expected to read, not #{event}" unless event == :read
|
188
|
+
if io.eof?
|
189
|
+
@reactor.unregister( io )
|
190
|
+
io.close
|
191
|
+
else
|
192
|
+
@buffer << io.read( 256 )
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def write_to( io, event )
|
197
|
+
raise ArgumentError, "expected to write, not #{event}" unless event == :write
|
198
|
+
if @buffer.empty?
|
199
|
+
@reactor.unregister( io )
|
200
|
+
io.close
|
201
|
+
else
|
202
|
+
bytes = io.write( @buffer )
|
203
|
+
@buffer.slice!( 0, bytes )
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
it "uses method or proc handlers if those are used instead of blocks" do
|
209
|
+
reader = IOStrategy.new( @reactor )
|
210
|
+
writer = IOStrategy.new( @reactor, TEST_DATA.dup )
|
211
|
+
|
212
|
+
@reactor.register( @reader, :read, &reader.method(:read_from) )
|
213
|
+
@reactor.register( @writer, :write, &writer.method(:write_to) )
|
214
|
+
@reactor.poll until @reactor.empty?
|
215
|
+
|
216
|
+
reader.buffer.should == TEST_DATA
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
it "saves pending events if there is no handler registered for them and no fallback " +
|
221
|
+
"handler provided" do
|
222
|
+
|
223
|
+
@reactor.register( $stdout, :write )
|
224
|
+
@reactor.poll( 1.0 ).should == 1
|
225
|
+
|
226
|
+
@reactor.pending_events.should have(1).members
|
227
|
+
@reactor.pending_events.keys.should include( $stdout )
|
228
|
+
@reactor.pending_events[ $stdout ].should == [ :write ]
|
229
|
+
end
|
230
|
+
|
231
|
+
it "knows what events are enabled for which handles" do
|
232
|
+
@reactor.register( @writer )
|
233
|
+
@reactor.should_not have_event_enabled( @writer, :read )
|
234
|
+
@reactor.enable_events( @writer, :read, :write )
|
235
|
+
@reactor.should have_event_enabled( @writer, :read )
|
236
|
+
@reactor.should have_event_enabled( @writer, :write )
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
it "allows events to be unregistered for a handle" do
|
241
|
+
@reactor.register( @writer, :write, :read )
|
242
|
+
@reactor.disable_events( @writer, :read )
|
243
|
+
@reactor.should_not have_event_enabled( @writer, :read )
|
244
|
+
end
|
245
|
+
|
246
|
+
|
247
|
+
it "doesn't allow the error event to be disabled for a handle" do
|
248
|
+
@reactor.register( @writer, :write )
|
249
|
+
lambda {
|
250
|
+
@reactor.disable_events( @writer, :error )
|
251
|
+
}.should raise_error
|
252
|
+
end
|
253
|
+
|
254
|
+
|
255
|
+
it "can remove the handler for a handle" do
|
256
|
+
handler = lambda { }
|
257
|
+
@reactor.register( @writer, :write, &handler )
|
258
|
+
@reactor.remove_handler( @writer ).should == handler
|
259
|
+
end
|
260
|
+
|
261
|
+
|
262
|
+
it "can clear the handler arguments for a handle" do
|
263
|
+
@reactor.register( @writer, :write, :an_arg )
|
264
|
+
@reactor.remove_args( @writer )
|
265
|
+
@reactor.handles[ @writer ][:args].should be_empty()
|
266
|
+
end
|
267
|
+
|
268
|
+
end # class PollTestCase
|
269
|
+
|