io-reactor 0.05 → 1.0.4
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/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
|
+
|