eventmachine-maglev- 1.0.0.beta.4 → 1.0.0.rc.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -31,38 +31,38 @@ module EventMachine
31
31
  # Simple SMTP client
32
32
  #
33
33
  # @example
34
- # email = EM::Protocols::SmtpClient.send(
35
- # :domain=>"example.com",
36
- # :host=>'localhost',
37
- # :port=>25, # optional, defaults 25
38
- # :starttls=>true, # use ssl
39
- # :from=>"sender@example.com",
40
- # :to=> ["to_1@example.com", "to_2@example.com"],
41
- # :header=> {"Subject" => "This is a subject line"},
42
- # :body=> "This is the body of the email"
43
- # )
44
- # email.callback{
45
- # puts 'Email sent!'
46
- # }
47
- # email.errback{ |e|
48
- # puts 'Email failed!'
49
- # }
34
+ # email = EM::Protocols::SmtpClient.send(
35
+ # :domain=>"example.com",
36
+ # :host=>'localhost',
37
+ # :port=>25, # optional, defaults 25
38
+ # :starttls=>true, # use ssl
39
+ # :from=>"sender@example.com",
40
+ # :to=> ["to_1@example.com", "to_2@example.com"],
41
+ # :header=> {"Subject" => "This is a subject line"},
42
+ # :body=> "This is the body of the email"
43
+ # )
44
+ # email.callback{
45
+ # puts 'Email sent!'
46
+ # }
47
+ # email.errback{ |e|
48
+ # puts 'Email failed!'
49
+ # }
50
50
  #
51
51
  # Sending generated emails (using mailfactory)
52
52
  #
53
- # mail = MailFactory.new
54
- # mail.to = 'someone@site.co'
55
- # mail.from = 'me@site.com'
56
- # mail.subject = 'hi!'
57
- # mail.text = 'hello world'
58
- # mail.html = '<h1>hello world</h1>'
53
+ # mail = MailFactory.new
54
+ # mail.to = 'someone@site.co'
55
+ # mail.from = 'me@site.com'
56
+ # mail.subject = 'hi!'
57
+ # mail.text = 'hello world'
58
+ # mail.html = '<h1>hello world</h1>'
59
59
  #
60
- # email = EM::P::SmtpClient.send(
61
- # :domain=>'site.com',
62
- # :from=>mail.from,
63
- # :to=>mail.to,
64
- # :content=>"#{mail.to_s}\r\n.\r\n"
65
- # )
60
+ # email = EM::P::SmtpClient.send(
61
+ # :domain=>'site.com',
62
+ # :from=>mail.from,
63
+ # :to=>mail.to,
64
+ # :content=>"#{mail.to_s}\r\n.\r\n"
65
+ # )
66
66
  #
67
67
  class SmtpClient < Connection
68
68
  include EventMachine::Deferrable
data/lib/em/pure_ruby.rb CHANGED
@@ -177,6 +177,18 @@ module EventMachine
177
177
  def set_max_timer_count n
178
178
  end
179
179
 
180
+ # @private
181
+ def get_sock_opt signature, level, optname
182
+ selectable = Reactor.instance.get_selectable( signature ) or raise "unknown get_peername target"
183
+ selectable.getsockopt level, optname
184
+ end
185
+
186
+ # @private
187
+ def set_sock_opt signature, level, optname, optval
188
+ selectable = Reactor.instance.get_selectable( signature ) or raise "unknown get_peername target"
189
+ selectable.setsockopt level, optname, optval
190
+ end
191
+
180
192
  # @private
181
193
  def send_file_data sig, filename
182
194
  sz = File.size(filename)
@@ -29,7 +29,7 @@ module EventMachine
29
29
  #
30
30
  # # If we don't care about the result:
31
31
  # pool.perform do |dispatcher|
32
- # # The following blcok executes inside a dedicated thread, and should not
32
+ # # The following block executes inside a dedicated thread, and should not
33
33
  # # access EventMachine things:
34
34
  # dispatcher.dispatch do |cassandra|
35
35
  # cassandra.insert(:Things, '10', 'stuff' => 'things')
data/lib/em/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module EventMachine
2
- VERSION = "1.0.0.beta.4"
2
+ VERSION = "1.0.0.rc.4"
3
3
  end
data/lib/eventmachine.rb CHANGED
@@ -82,7 +82,8 @@ module EventMachine
82
82
  @reactor_running = false
83
83
  @next_tick_queue = []
84
84
  @tails = []
85
- @threadpool = nil
85
+ @threadpool = @threadqueue = @resultqueue = nil
86
+ @all_threads_spawned = false
86
87
 
87
88
  # System errnos
88
89
  # @private
@@ -157,7 +158,13 @@ module EventMachine
157
158
  # will start without release_machine being called and will immediately throw
158
159
 
159
160
  #
160
-
161
+ if reactor_running? and @reactor_pid != Process.pid
162
+ # Reactor was started in a different parent, meaning we have forked.
163
+ # Clean up reactor state so a new reactor boots up in this child.
164
+ stop_event_loop
165
+ release_machine
166
+ @reactor_running = false
167
+ end
161
168
 
162
169
  tail and @tails.unshift(tail)
163
170
 
@@ -171,6 +178,7 @@ module EventMachine
171
178
  @next_tick_queue ||= []
172
179
  @tails ||= []
173
180
  begin
181
+ @reactor_pid = Process.pid
174
182
  @reactor_running = true
175
183
  initialize_event_machine
176
184
  (b = blk || block) and add_timer(0, b)
@@ -203,6 +211,7 @@ module EventMachine
203
211
  @threadqueue = nil
204
212
  @resultqueue = nil
205
213
  @threadpool = nil
214
+ @all_threads_spawned = false
206
215
  end
207
216
 
208
217
  @next_tick_queue = []
@@ -254,7 +263,7 @@ module EventMachine
254
263
  if self.reactor_running?
255
264
  self.stop_event_loop
256
265
  self.release_machine
257
- self.instance_variable_set( '@reactor_running', false )
266
+ @reactor_running = false
258
267
  end
259
268
  self.run block
260
269
  end
@@ -427,7 +436,8 @@ module EventMachine
427
436
  # that you must define. When the network server that is started by
428
437
  # start_server accepts a new connection, it instantiates a new
429
438
  # object of an anonymous class that is inherited from {EventMachine::Connection},
430
- # *into which your handler module have been included*.
439
+ # *into which your handler module have been included*. Arguments passed into start_server
440
+ # after the class name are passed into the constructor during the instantiation.
431
441
  #
432
442
  # Your handler module may override any of the methods in {EventMachine::Connection},
433
443
  # such as {EventMachine::Connection#receive_data}, in order to implement the specific behavior
@@ -738,6 +748,7 @@ module EventMachine
738
748
  c = klass.new s, *args
739
749
 
740
750
  c.instance_variable_set(:@io, io)
751
+ c.instance_variable_set(:@watch_mode, watch_mode)
741
752
  c.instance_variable_set(:@fd, fd)
742
753
 
743
754
  @conns[s] = c
@@ -763,7 +774,11 @@ module EventMachine
763
774
  #raise "still connected" if @conns.has_key?(handler.signature)
764
775
  return handler if @conns.has_key?(handler.signature)
765
776
 
766
- s = connect_server server, port
777
+ s = if port
778
+ connect_server server, port
779
+ else
780
+ connect_unix_server server
781
+ end
767
782
  handler.signature = s
768
783
  @conns[s] = handler
769
784
  block_given? and yield handler
@@ -938,10 +953,21 @@ module EventMachine
938
953
  cback.call result if cback
939
954
  end
940
955
 
941
- @next_tick_mutex.synchronize do
942
- jobs, @next_tick_queue = @next_tick_queue, []
943
- jobs
944
- end.each { |j| j.call }
956
+ # Capture the size at the start of this tick...
957
+ size = @next_tick_mutex.synchronize { @next_tick_queue.size }
958
+ size.times do |i|
959
+ callback = @next_tick_mutex.synchronize { @next_tick_queue.shift }
960
+ begin
961
+ callback.call
962
+ ensure
963
+ # This is a little nasty. The problem is, if an exception occurs during
964
+ # the callback, then we need to send a signal to the reactor to actually
965
+ # do some work during the next_tick. The only mechanism we have from the
966
+ # ruby side is next_tick itself, although ideally, we'd just drop a byte
967
+ # on the loopback descriptor.
968
+ EM.next_tick {} if $!
969
+ end
970
+ end
945
971
  end
946
972
 
947
973
 
@@ -993,7 +1019,6 @@ module EventMachine
993
1019
  # has no constructor.
994
1020
 
995
1021
  unless @threadpool
996
- require 'thread'
997
1022
  @threadpool = []
998
1023
  @threadqueue = ::Queue.new
999
1024
  @resultqueue = ::Queue.new
@@ -1018,6 +1043,19 @@ module EventMachine
1018
1043
  end
1019
1044
  @threadpool << thread
1020
1045
  end
1046
+ @all_threads_spawned = true
1047
+ end
1048
+
1049
+ ##
1050
+ # Returns +true+ if all deferred actions are done executing and their
1051
+ # callbacks have been fired.
1052
+ #
1053
+ def self.defers_finished?
1054
+ return false if @threadpool and !@all_threads_spawned
1055
+ return false if @threadqueue and not @threadqueue.empty?
1056
+ return false if @resultqueue and not @resultqueue.empty?
1057
+ return false if @threadpool and @threadqueue.num_waiting != @threadpool.size
1058
+ return true
1021
1059
  end
1022
1060
 
1023
1061
  class << self
@@ -1393,11 +1431,19 @@ module EventMachine
1393
1431
  if opcode == ConnectionUnbound
1394
1432
  if c = @conns.delete( conn_binding )
1395
1433
  begin
1396
- if c.original_method(:unbind).arity == 1
1434
+ if c.original_method(:unbind).arity != 0
1397
1435
  c.unbind(data == 0 ? nil : EventMachine::ERRNOS[data])
1398
1436
  else
1399
1437
  c.unbind
1400
1438
  end
1439
+ # If this is an attached (but not watched) connection, close the underlying io object.
1440
+ if c.instance_variable_defined?(:@io) and !c.instance_variable_get(:@watch_mode)
1441
+ io = c.instance_variable_get(:@io)
1442
+ begin
1443
+ io.close
1444
+ rescue Errno::EBADF, IOError
1445
+ end
1446
+ end
1401
1447
  rescue
1402
1448
  @wrapped_exception = $!
1403
1449
  stop
data/lib/jeventmachine.rb CHANGED
@@ -203,6 +203,11 @@ module EventMachine
203
203
  Socket.pack_sockaddr_in(*peer)
204
204
  end
205
205
  end
206
+ def self.get_sockname sig
207
+ if sockName = @em.getSockName(sig)
208
+ Socket.pack_sockaddr_in(*sockName)
209
+ end
210
+ end
206
211
  # @private
207
212
  def self.attach_fd fileno, watch_mode
208
213
  # 3Aug09: We could pass in the actual SocketChannel, but then it would be modified (set as non-blocking), and
File without changes
File without changes
File without changes
data/tests/test_basic.rb CHANGED
@@ -224,4 +224,71 @@ class TestBasic < Test::Unit::TestCase
224
224
  end
225
225
  end
226
226
  end
227
+
228
+ def test_schedule_close
229
+ localhost, port = '127.0.0.1', 9000
230
+ timer_ran = false
231
+ num_close_scheduled = nil
232
+ EM.run do
233
+ assert_equal 0, EM.num_close_scheduled
234
+ EM.add_timer(1) { timer_ran = true; EM.stop }
235
+ EM.start_server localhost, port do |s|
236
+ s.close_connection
237
+ num_close_scheduled = EM.num_close_scheduled
238
+ end
239
+ EM.connect localhost, port do |c|
240
+ def c.unbind
241
+ EM.stop
242
+ end
243
+ end
244
+ end
245
+ assert !timer_ran
246
+ assert_equal 1, num_close_scheduled
247
+ end
248
+
249
+ def test_fork_safe
250
+ return unless cpid = fork { exit! } rescue false
251
+
252
+ read, write = IO.pipe
253
+ EM.run do
254
+ cpid = fork do
255
+ write.puts "forked"
256
+ EM.run do
257
+ EM.next_tick do
258
+ write.puts "EM ran"
259
+ exit!
260
+ end
261
+ end
262
+ end
263
+ EM.stop
264
+ end
265
+ Process.waitall
266
+ assert_equal "forked\n", read.readline
267
+ assert_equal "EM ran\n", read.readline
268
+ ensure
269
+ read.close rescue nil
270
+ write.close rescue nil
271
+ end
272
+
273
+ def test_error_handler_idempotent # issue 185
274
+ errors = []
275
+ ticks = []
276
+ EM.error_handler do |e|
277
+ errors << e
278
+ end
279
+
280
+ EM.run do
281
+ EM.next_tick do
282
+ ticks << :first
283
+ raise
284
+ end
285
+ EM.next_tick do
286
+ ticks << :second
287
+ end
288
+ EM.add_timer(0.001) { EM.stop }
289
+ end
290
+
291
+ assert_equal 1, errors.size
292
+ assert_equal [:first, :second], ticks
293
+ end
227
294
  end
@@ -0,0 +1,23 @@
1
+ require 'em_test_helper'
2
+
3
+ class TestIdleConnection < Test::Unit::TestCase
4
+ if EM.respond_to?(:get_idle_time)
5
+ def test_idle_time
6
+ EM.run{
7
+ conn = EM.connect 'www.google.com', 80
8
+ EM.add_timer(3){
9
+ $idle_time = conn.get_idle_time
10
+ conn.send_data "GET / HTTP/1.0\r\n\r\n"
11
+ EM.next_tick{
12
+ $idle_time_after_send = conn.get_idle_time
13
+ conn.close_connection
14
+ EM.stop
15
+ }
16
+ }
17
+ }
18
+
19
+ assert_in_delta 3, $idle_time, 0.2
20
+ assert_equal 0, $idle_time_after_send
21
+ end
22
+ end
23
+ end
data/tests/test_pool.rb CHANGED
@@ -115,6 +115,41 @@ class TestPool < Test::Unit::TestCase
115
115
  assert_equal [:res], pool.contents
116
116
  end
117
117
 
118
+ def test_contents_when_perform_errors_and_on_error_is_not_set
119
+ pool.add :res
120
+ assert_equal [:res], pool.contents
121
+
122
+ pool.perform do |r|
123
+ d = EM::DefaultDeferrable.new
124
+ d.fail
125
+ d
126
+ end
127
+
128
+ EM.run { EM.next_tick { EM.stop } }
129
+
130
+ assert_equal [:res], pool.contents
131
+ end
132
+
133
+ def test_contents_when_perform_errors_and_on_error_is_set
134
+ pool.add :res
135
+ res = nil
136
+ pool.on_error do |r|
137
+ res = r
138
+ end
139
+ assert_equal [:res], pool.contents
140
+
141
+ pool.perform do |r|
142
+ d = EM::DefaultDeferrable.new
143
+ d.fail 'foo'
144
+ d
145
+ end
146
+
147
+ EM.run { EM.next_tick { EM.stop } }
148
+
149
+ assert_equal :res, res
150
+ assert_equal [], pool.contents
151
+ end
152
+
118
153
  def test_num_waiting
119
154
  pool.add :res
120
155
  assert_equal 0, pool.num_waiting
@@ -125,4 +160,35 @@ class TestPool < Test::Unit::TestCase
125
160
  assert_equal 10, pool.num_waiting
126
161
  end
127
162
 
128
- end
163
+ def test_exceptions_in_the_work_block_bubble_up_raise_and_fail_the_resource
164
+ pool.add :res
165
+
166
+ res = nil
167
+ pool.on_error { |r| res = r }
168
+ pool.perform { raise 'boom' }
169
+
170
+ assert_raises(RuntimeError) do
171
+ EM.run { EM.next_tick { EM.stop } }
172
+ end
173
+
174
+ assert_equal [], pool.contents
175
+ assert_equal :res, res
176
+ end
177
+
178
+ def test_removed_list_does_not_leak_on_errors
179
+ pool.add :res
180
+
181
+ pool.on_error do |r|
182
+ # This is actually the wrong thing to do, and not required, but some users
183
+ # might do it. When they do, they would find that @removed would cause a
184
+ # slow leak.
185
+ pool.remove r
186
+ end
187
+
188
+ pool.perform { d = EM::DefaultDeferrable.new; d.fail; d }
189
+
190
+ EM.run { EM.next_tick { EM.stop } }
191
+ assert_equal [], pool.instance_variable_get(:@removed)
192
+ end
193
+
194
+ end
@@ -99,6 +99,24 @@ class TestProcesses < Test::Unit::TestCase
99
99
 
100
100
  assert_equal("hello\n", $out)
101
101
  end
102
+
103
+ def test_em_popen_pause_resume
104
+ c_rx = 0
105
+
106
+ test_client = Module.new do
107
+ define_method :receive_data do |data|
108
+ c_rx += 1
109
+ pause
110
+ EM.add_timer(0.5) { EM.stop }
111
+ end
112
+ end
113
+
114
+ EM.run{
115
+ EM.popen('cat /dev/random', test_client)
116
+ }
117
+
118
+ assert_equal 1, c_rx
119
+ end
102
120
  else
103
121
  warn "EM.popen not implemented, skipping tests in #{__FILE__}"
104
122
 
@@ -24,6 +24,7 @@ class TestProxyConnection < Test::Unit::TestCase
24
24
  end
25
25
 
26
26
  def unbind
27
+ $proxied_bytes = self.get_proxied_bytes
27
28
  @client.close_connection_after_writing
28
29
  end
29
30
  end
@@ -94,7 +95,7 @@ class TestProxyConnection < Test::Unit::TestCase
94
95
  end
95
96
 
96
97
  def receive_data(data)
97
- EM.connect("127.0.0.1", @port, ProxyConnection, self, data)
98
+ @proxy = EM.connect("127.0.0.1", @port, ProxyConnection, self, data)
98
99
  end
99
100
  end
100
101
 
@@ -134,6 +135,17 @@ class TestProxyConnection < Test::Unit::TestCase
134
135
  assert_equal("I know!", $client_data)
135
136
  end
136
137
 
138
+ def test_proxied_bytes
139
+ EM.run {
140
+ EM.start_server("127.0.0.1", @port, Server)
141
+ EM.start_server("127.0.0.1", @proxy_port, ProxyServer, @port)
142
+ EM.connect("127.0.0.1", @proxy_port, Client)
143
+ }
144
+
145
+ assert_equal("I know!", $client_data)
146
+ assert_equal("I know!".bytesize, $proxied_bytes)
147
+ end
148
+
137
149
  def test_partial_proxy_connection
138
150
  EM.run {
139
151
  EM.start_server("127.0.0.1", @port, Server)
@@ -0,0 +1,37 @@
1
+ require 'em_test_helper'
2
+ require 'socket'
3
+
4
+ class TestSetSockOpt < Test::Unit::TestCase
5
+
6
+ if EM.respond_to? :set_sock_opt
7
+ def setup
8
+ assert(!EM.reactor_running?)
9
+ end
10
+
11
+ def teardown
12
+ assert(!EM.reactor_running?)
13
+ end
14
+
15
+ #-------------------------------------
16
+
17
+ def test_set_sock_opt
18
+ test = self
19
+ EM.run do
20
+ EM.connect 'google.com', 80, Module.new {
21
+ define_method :post_init do
22
+ val = set_sock_opt Socket::SOL_SOCKET, Socket::SO_DEBUG, true
23
+ test.assert_equal 0, val
24
+ EM.stop
25
+ end
26
+ }
27
+ end
28
+ end
29
+ else
30
+ warn "EM.set_sock_opt not implemented, skipping tests in #{__FILE__}"
31
+
32
+ # Because some rubies will complain if a TestCase class has no tests
33
+ def test_em_set_sock_opt_unsupported
34
+ assert true
35
+ end
36
+ end
37
+ end
@@ -2,6 +2,15 @@ require 'em_test_helper'
2
2
  require 'socket'
3
3
 
4
4
  class TestUnbindReason < Test::Unit::TestCase
5
+
6
+ class StubConnection < EM::Connection
7
+ attr_reader :error
8
+ def unbind(reason = nil)
9
+ @error = reason
10
+ EM.stop
11
+ end
12
+ end
13
+
5
14
  def test_connect_timeout
6
15
  error = nil
7
16
  EM.run {
@@ -13,7 +22,7 @@ class TestUnbindReason < Test::Unit::TestCase
13
22
  }
14
23
  conn.pending_connect_timeout = 0.1
15
24
  }
16
- assert_equal error, Errno::ETIMEDOUT
25
+ assert_equal Errno::ETIMEDOUT, error
17
26
  end
18
27
 
19
28
  def test_connect_refused
@@ -26,6 +35,14 @@ class TestUnbindReason < Test::Unit::TestCase
26
35
  end
27
36
  }
28
37
  }
29
- assert_equal error, Errno::ECONNREFUSED
38
+ assert_equal Errno::ECONNREFUSED, error
39
+ end
40
+
41
+ def test_optional_argument
42
+ conn = nil
43
+ EM.run {
44
+ conn = EM.connect '127.0.0.1', 12388, StubConnection
45
+ }
46
+ assert_equal Errno::ECONNREFUSED, conn.error
30
47
  end
31
48
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eventmachine-maglev-
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta.4
4
+ version: 1.0.0.rc.4
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -10,22 +10,22 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-07-29 00:00:00.000000000 Z
13
+ date: 2012-08-17 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rake-compiler
17
- requirement: &69825862374080 !ruby/object:Gem::Requirement
17
+ requirement: &70169512878700 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
- - - =
20
+ - - ~>
21
21
  - !ruby/object:Gem::Version
22
- version: 0.7.6
22
+ version: 0.8.1
23
23
  type: :development
24
24
  prerelease: false
25
- version_requirements: *69825862374080
25
+ version_requirements: *70169512878700
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: yard
28
- requirement: &69825862373380 !ruby/object:Gem::Requirement
28
+ requirement: &70169512877400 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ! '>='
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: 0.7.2
34
34
  type: :development
35
35
  prerelease: false
36
- version_requirements: *69825862373380
36
+ version_requirements: *70169512877400
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: bluecloth
39
- requirement: &69825862372800 !ruby/object:Gem::Requirement
39
+ requirement: &70169512876360 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ! '>='
@@ -44,7 +44,7 @@ dependencies:
44
44
  version: '0'
45
45
  type: :development
46
46
  prerelease: false
47
- version_requirements: *69825862372800
47
+ version_requirements: *70169512876360
48
48
  description: ! 'EventMachine implements a fast, single-threaded engine for arbitrary
49
49
  network
50
50
 
@@ -193,9 +193,9 @@ files:
193
193
  - lib/em/version.rb
194
194
  - lib/eventmachine.rb
195
195
  - lib/jeventmachine.rb
196
- - tasks/cpp.rake_example
197
- - tasks/package.rake
198
- - tasks/test.rake
196
+ - rakelib/cpp.rake_example
197
+ - rakelib/package.rake
198
+ - rakelib/test.rake
199
199
  - tests/client.crt
200
200
  - tests/client.key
201
201
  - tests/em_test_helper.rb
@@ -216,6 +216,7 @@ files:
216
216
  - tests/test_hc.rb
217
217
  - tests/test_httpclient.rb
218
218
  - tests/test_httpclient2.rb
219
+ - tests/test_idle_connection.rb
219
220
  - tests/test_inactivity_timeout.rb
220
221
  - tests/test_kb.rb
221
222
  - tests/test_ltp.rb
@@ -235,6 +236,7 @@ files:
235
236
  - tests/test_sasl.rb
236
237
  - tests/test_send_file.rb
237
238
  - tests/test_servers.rb
239
+ - tests/test_set_sock_opt.rb
238
240
  - tests/test_shutdown_hooks.rb
239
241
  - tests/test_smtpclient.rb
240
242
  - tests/test_smtpserver.rb