nio4r 1.2.1-java → 2.0.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.rubocop.yml +31 -38
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +15 -14
  6. data/CHANGES.md +75 -42
  7. data/Gemfile +10 -5
  8. data/Guardfile +10 -0
  9. data/LICENSE.txt +1 -1
  10. data/README.md +57 -161
  11. data/Rakefile +2 -1
  12. data/examples/echo_server.rb +1 -0
  13. data/ext/libev/Changes +4 -13
  14. data/ext/libev/ev.c +101 -74
  15. data/ext/libev/ev.h +3 -3
  16. data/ext/libev/ev_epoll.c +6 -3
  17. data/ext/libev/ev_kqueue.c +8 -4
  18. data/ext/libev/ev_poll.c +6 -3
  19. data/ext/libev/ev_port.c +8 -4
  20. data/ext/libev/ev_select.c +4 -2
  21. data/ext/nio4r/bytebuffer.c +421 -0
  22. data/ext/nio4r/extconf.rb +2 -10
  23. data/ext/nio4r/monitor.c +93 -46
  24. data/ext/nio4r/nio4r.h +11 -13
  25. data/ext/nio4r/org/nio4r/ByteBuffer.java +295 -0
  26. data/ext/nio4r/org/nio4r/Monitor.java +164 -0
  27. data/ext/nio4r/org/nio4r/Nio4r.java +22 -391
  28. data/ext/nio4r/org/nio4r/Selector.java +278 -0
  29. data/ext/nio4r/selector.c +55 -53
  30. data/lib/nio.rb +4 -3
  31. data/lib/nio/bytebuffer.rb +222 -0
  32. data/lib/nio/monitor.rb +64 -4
  33. data/lib/nio/selector.rb +52 -20
  34. data/lib/nio/version.rb +1 -1
  35. data/nio4r.gemspec +25 -19
  36. data/spec/nio/acceptables_spec.rb +6 -4
  37. data/spec/nio/bytebuffer_spec.rb +349 -0
  38. data/spec/nio/monitor_spec.rb +122 -79
  39. data/spec/nio/selectables/pipe_spec.rb +5 -1
  40. data/spec/nio/selectables/ssl_socket_spec.rb +15 -12
  41. data/spec/nio/selectables/tcp_socket_spec.rb +42 -31
  42. data/spec/nio/selectables/udp_socket_spec.rb +2 -0
  43. data/spec/nio/selector_spec.rb +10 -4
  44. data/spec/spec_helper.rb +24 -3
  45. data/spec/support/selectable_examples.rb +7 -5
  46. data/tasks/extension.rake +2 -0
  47. data/tasks/rspec.rake +2 -0
  48. data/tasks/rubocop.rake +2 -0
  49. metadata +21 -14
  50. data/.rubocop_todo.yml +0 -35
data/lib/nio/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NIO
4
- VERSION = "1.2.1".freeze
4
+ VERSION = "2.0.0"
5
5
  end
data/nio4r.gemspec CHANGED
@@ -1,28 +1,34 @@
1
- # -*- encoding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  require File.expand_path("../lib/nio/version", __FILE__)
3
4
 
4
- Gem::Specification.new do |gem|
5
- gem.authors = ["Tony Arcieri"]
6
- gem.email = ["tony.arcieri@gmail.com"]
7
- gem.description = "New IO for Ruby"
8
- gem.summary = "NIO provides a high performance selector API for monitoring IO objects"
9
- gem.homepage = "https://github.com/celluloid/nio4r"
10
- gem.license = "MIT"
5
+ Gem::Specification.new do |spec|
6
+ spec.authors = ["Tony Arcieri"]
7
+ spec.email = ["bascule@gmail.com"]
8
+ spec.homepage = "https://github.com/socketry/nio4r"
9
+ spec.license = "MIT"
10
+ spec.summary = "New IO for Ruby"
11
+ spec.description = <<-DESCRIPTION.strip.gsub(/\s+/, " ")
12
+ Cross-platform asynchronous I/O primitives for scalable network clients
13
+ and servers. Inspired by the Java NIO API, but simplified for ease-of-use.
14
+ DESCRIPTION
15
+
16
+ spec.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
17
+ spec.files = `git ls-files`.split("\n")
18
+ spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ spec.name = "nio4r"
20
+ spec.require_paths = ["lib"]
21
+ spec.version = NIO::VERSION
11
22
 
12
- gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
13
- gem.files = `git ls-files`.split("\n")
14
- gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
- gem.name = "nio4r"
16
- gem.require_paths = ["lib"]
17
- gem.version = NIO::VERSION
23
+ spec.required_ruby_version = ">= 2.2.2"
18
24
 
19
25
  if defined? JRUBY_VERSION
20
- gem.files << "lib/nio4r_ext.jar"
21
- gem.platform = "java"
26
+ spec.files << "lib/nio4r_ext.jar"
27
+ spec.platform = "java"
22
28
  else
23
- gem.extensions = ["ext/nio4r/extconf.rb"]
29
+ spec.extensions = ["ext/nio4r/extconf.rb"]
24
30
  end
25
31
 
26
- gem.add_development_dependency "rake"
27
- gem.add_development_dependency "bundler"
32
+ spec.add_development_dependency "rake"
33
+ spec.add_development_dependency "bundler"
28
34
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "spec_helper"
2
4
 
3
5
  RSpec.describe "NIO acceptables" do
@@ -15,16 +17,16 @@ RSpec.describe "NIO acceptables" do
15
17
  end
16
18
 
17
19
  describe TCPServer do
18
- let(:tcp_port) { 23_456 }
20
+ let(:port) { next_available_tcp_port }
19
21
 
20
22
  let :acceptable_subject do
21
- server = TCPServer.new("localhost", tcp_port)
22
- TCPSocket.open("localhost", tcp_port)
23
+ server = TCPServer.new("localhost", port)
24
+ TCPSocket.open("localhost", port)
23
25
  server
24
26
  end
25
27
 
26
28
  let :unacceptable_subject do
27
- TCPServer.new("localhost", tcp_port + 1)
29
+ TCPServer.new("localhost", port + 1)
28
30
  end
29
31
 
30
32
  it_behaves_like "an NIO acceptable"
@@ -0,0 +1,349 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe NIO::ByteBuffer do
6
+ let(:capacity) { 256 }
7
+ let(:example_string) { "Testing 1 2 3..." }
8
+ subject(:bytebuffer) { described_class.new(capacity) }
9
+
10
+ describe "#initialize" do
11
+ it "raises TypeError if given a bogus argument" do
12
+ expect { described_class.new(:symbols_are_bogus) }.to raise_error(TypeError)
13
+ end
14
+ end
15
+
16
+ describe "#clear" do
17
+ it "clears the buffer" do
18
+ bytebuffer << example_string
19
+ bytebuffer.clear
20
+
21
+ expect(bytebuffer.remaining).to eq capacity
22
+ end
23
+ end
24
+
25
+ describe "#position" do
26
+ it "defaults to zero" do
27
+ expect(bytebuffer.position).to be_zero
28
+ end
29
+ end
30
+
31
+ describe "#position=" do
32
+ let(:example_position) { 42 }
33
+
34
+ it "sets the buffer's position to a valid value" do
35
+ expect(bytebuffer.position).to be_zero
36
+ bytebuffer.position = example_position
37
+ expect(bytebuffer.position).to eq example_position
38
+ end
39
+
40
+ it "raises ArgumentError if the specified position is less than zero" do
41
+ expect { bytebuffer.position = -1 }.to raise_error(ArgumentError)
42
+ end
43
+
44
+ it "raises ArgumentError if the specified position exceeds the limit" do
45
+ expect { bytebuffer.position = capacity + 1 }.to raise_error(ArgumentError)
46
+ end
47
+ end
48
+
49
+ describe "#limit" do
50
+ it "defaults to the buffer's capacity" do
51
+ expect(bytebuffer.limit).to eq capacity
52
+ end
53
+ end
54
+
55
+ describe "#limit=" do
56
+ it "sets the buffer's limit to a valid value" do
57
+ bytebuffer.flip
58
+ expect(bytebuffer.limit).to be_zero
59
+
60
+ new_limit = capacity / 2
61
+ bytebuffer.limit = new_limit
62
+ expect(bytebuffer.limit).to eq new_limit
63
+ end
64
+
65
+ it "preserves position and mark if they're less than the new limit" do
66
+ bytebuffer << "four"
67
+ bytebuffer.mark
68
+ bytebuffer << "more"
69
+
70
+ bytebuffer.limit = capacity / 2
71
+ expect(bytebuffer.position).to eq 8
72
+ bytebuffer.reset
73
+ expect(bytebuffer.position).to eq 4
74
+ end
75
+
76
+ it "sets position to the new limit if the previous position is beyond the limit" do
77
+ bytebuffer << "four"
78
+ bytebuffer.limit = 2
79
+ expect(bytebuffer.position).to eq 2
80
+ end
81
+
82
+ it "clears the mark if the new limit is before the current mark" do
83
+ bytebuffer << "four"
84
+ bytebuffer.mark
85
+ bytebuffer.limit = 2
86
+ expect { bytebuffer.reset }.to raise_error(NIO::ByteBuffer::MarkUnsetError)
87
+ end
88
+
89
+ it "raises ArgumentError if specified limit is less than zero" do
90
+ expect { bytebuffer.limit = -1 }.to raise_error(ArgumentError)
91
+ end
92
+
93
+ it "raises ArgumentError if specified limit exceeds capacity" do
94
+ expect { bytebuffer.limit = capacity }.not_to raise_error
95
+ expect { bytebuffer.limit = capacity + 1 }.to raise_error(ArgumentError)
96
+ end
97
+ end
98
+
99
+ describe "#capacity" do
100
+ it "has the requested capacity" do
101
+ expect(bytebuffer.capacity).to eq capacity
102
+ end
103
+ end
104
+
105
+ describe "#remaining" do
106
+ it "calculates the number of bytes remaining" do
107
+ expect(bytebuffer.remaining).to eq capacity
108
+ bytebuffer << example_string
109
+ expect(bytebuffer.remaining).to eq(capacity - example_string.length)
110
+ end
111
+ end
112
+
113
+ describe "#full?" do
114
+ it "returns false when there is space remaining in the buffer" do
115
+ expect(bytebuffer).not_to be_full
116
+ end
117
+
118
+ it "returns true when the buffer is full" do
119
+ bytebuffer << "X" * capacity
120
+ expect(bytebuffer).to be_full
121
+ end
122
+ end
123
+
124
+ describe "#get" do
125
+ it "reads all remaining data if no length is given" do
126
+ bytebuffer << example_string
127
+ bytebuffer.flip
128
+
129
+ expect(bytebuffer.get).to eq example_string
130
+ end
131
+
132
+ it "reads zeroes from a newly initialized buffer" do
133
+ expect(bytebuffer.get(capacity)).to eq("\0" * capacity)
134
+ end
135
+
136
+ it "advances position as data is read" do
137
+ bytebuffer << "First"
138
+ bytebuffer << "Second"
139
+ bytebuffer << "Third"
140
+ bytebuffer.flip
141
+
142
+ expect(bytebuffer.position).to be_zero
143
+ expect(bytebuffer.get(10)).to eq "FirstSecon"
144
+ expect(bytebuffer.position).to eq 10
145
+ end
146
+
147
+ it "raises NIO::ByteBuffer::UnderflowError if there is not enough data in the buffer" do
148
+ bytebuffer << example_string
149
+ bytebuffer.flip
150
+
151
+ expect { bytebuffer.get(example_string.length + 1) }.to raise_error(NIO::ByteBuffer::UnderflowError)
152
+ expect(bytebuffer.get(example_string.length)).to eq example_string
153
+ end
154
+ end
155
+
156
+ describe "#[]" do
157
+ it "obtains bytes at a given index without altering position" do
158
+ bytebuffer << example_string
159
+ expect(bytebuffer[7]).to eq example_string.bytes[7]
160
+ expect(bytebuffer.position).to eq example_string.length
161
+ end
162
+
163
+ it "raises ArgumentError if the index is less than zero" do
164
+ expect { bytebuffer[-1] }.to raise_error(ArgumentError)
165
+ end
166
+
167
+ it "raises ArgumentError if the index exceeds the limit" do
168
+ bytebuffer << example_string
169
+ bytebuffer.flip
170
+ expect(bytebuffer[bytebuffer.limit - 1]).to eq example_string.bytes.last
171
+ expect { bytebuffer[bytebuffer.limit] }.to raise_error(ArgumentError)
172
+ end
173
+ end
174
+
175
+ describe "#<<" do
176
+ it "adds strings to the buffer" do
177
+ bytebuffer << example_string
178
+ expect(bytebuffer.position).to eq example_string.length
179
+ expect(bytebuffer.limit).to eq capacity
180
+ end
181
+
182
+ it "raises NIO::ByteBuffer::OverflowError if the buffer is full" do
183
+ bytebuffer << "X" * (capacity - 1)
184
+ expect { bytebuffer << "X" }.not_to raise_error
185
+ expect { bytebuffer << "X" }.to raise_error(NIO::ByteBuffer::OverflowError)
186
+ end
187
+ end
188
+
189
+ describe "#flip" do
190
+ it "flips the bytebuffer" do
191
+ bytebuffer << example_string
192
+ expect(bytebuffer.position).to eql example_string.length
193
+
194
+ expect(bytebuffer.flip).to eq bytebuffer
195
+
196
+ expect(bytebuffer.position).to be_zero
197
+ expect(bytebuffer.limit).to eq example_string.length
198
+ expect(bytebuffer.get).to eq example_string
199
+ end
200
+
201
+ it "sets remaining to the previous position" do
202
+ bytebuffer << example_string
203
+ previous_position = bytebuffer.position
204
+ expect(bytebuffer.remaining).to eq(capacity - previous_position)
205
+ expect(bytebuffer.flip.remaining).to eq previous_position
206
+ end
207
+
208
+ it "sets limit to the previous position" do
209
+ bytebuffer << example_string
210
+ expect(bytebuffer.limit).to eql(capacity)
211
+
212
+ previous_position = bytebuffer.position
213
+ expect(bytebuffer.flip.limit).to eql previous_position
214
+ end
215
+ end
216
+
217
+ describe "#rewind" do
218
+ it "rewinds the buffer leaving the limit intact" do
219
+ bytebuffer << example_string
220
+ expect(bytebuffer.rewind).to eq bytebuffer
221
+
222
+ expect(bytebuffer.position).to be_zero
223
+ expect(bytebuffer.limit).to eq capacity
224
+ end
225
+ end
226
+
227
+ describe "#mark" do
228
+ it "returns self" do
229
+ expect(bytebuffer.mark).to eql bytebuffer
230
+ end
231
+ end
232
+
233
+ describe "#reset" do
234
+ it "returns to a previously marked position" do
235
+ bytebuffer << "First"
236
+ expected_position = bytebuffer.position
237
+
238
+ expect(bytebuffer.mark).to eq bytebuffer
239
+ bytebuffer << "Second"
240
+ expect(bytebuffer.position).not_to eq expected_position
241
+ expect(bytebuffer.reset.position).to eq expected_position
242
+ end
243
+
244
+ it "raises NIO::ByteBuffer::MarkUnsetError unless mark has been set" do
245
+ expect { bytebuffer.reset }.to raise_error(NIO::ByteBuffer::MarkUnsetError)
246
+ end
247
+ end
248
+
249
+ describe "#compact" do
250
+ let(:first_string) { "CompactMe" }
251
+ let(:second_string) { "Leftover" }
252
+
253
+ it "copies data from the current position to the beginning of the buffer" do
254
+ bytebuffer << first_string << second_string
255
+ bytebuffer.position = first_string.length
256
+ bytebuffer.limit = first_string.length + second_string.length
257
+ bytebuffer.compact
258
+
259
+ expect(bytebuffer.position).to eq second_string.length
260
+ expect(bytebuffer.limit).to eq capacity
261
+ expect(bytebuffer.flip.get).to eq second_string
262
+ end
263
+ end
264
+
265
+ describe "#each" do
266
+ it "iterates over data in the buffer" do
267
+ bytebuffer << example_string
268
+ bytebuffer.flip
269
+
270
+ bytes = []
271
+ bytebuffer.each { |byte| bytes << byte }
272
+ expect(bytes).to eq example_string.bytes
273
+ end
274
+ end
275
+
276
+ describe "#inspect" do
277
+ it "inspects the buffer offsets" do
278
+ regex = /\A#<NIO::ByteBuffer:.*? @position=0 @limit=#{capacity} @capacity=#{capacity}>\z/
279
+ expect(bytebuffer.inspect).to match(regex)
280
+ end
281
+ end
282
+
283
+ context "I/O" do
284
+ let(:addr) { "localhost" }
285
+ let(:port) { next_available_tcp_port }
286
+ let(:server) { TCPServer.new(addr, port) }
287
+ let(:client) { TCPSocket.new(addr, port) }
288
+ let(:peer) { server_thread.value }
289
+
290
+ let(:server_thread) do
291
+ server
292
+
293
+ thread = Thread.new { server.accept }
294
+ Thread.pass while thread.status && thread.status != "sleep"
295
+
296
+ thread
297
+ end
298
+
299
+ before do
300
+ server_thread
301
+ client
302
+ end
303
+
304
+ after do
305
+ server_thread.kill if server_thread.alive?
306
+
307
+ server.close rescue nil
308
+ client.close rescue nil
309
+ peer.close rescue nil
310
+ end
311
+
312
+ describe "#read_from" do
313
+ it "reads data into the buffer" do
314
+ client.write(example_string)
315
+ expect(bytebuffer.read_from(peer)).to eq example_string.length
316
+ bytebuffer.flip
317
+
318
+ expect(bytebuffer.get).to eq example_string
319
+ end
320
+
321
+ it "raises NIO::ByteBuffer::OverflowError if the buffer is already full" do
322
+ client.write(example_string)
323
+ bytebuffer << "X" * capacity
324
+ expect { bytebuffer.read_from(peer) }.to raise_error(NIO::ByteBuffer::OverflowError)
325
+ end
326
+
327
+ it "returns 0 if no data is available" do
328
+ expect(bytebuffer.read_from(peer)).to eq 0
329
+ end
330
+ end
331
+
332
+ describe "#write_to" do
333
+ it "writes data from the buffer" do
334
+ bytebuffer << example_string
335
+ bytebuffer.flip
336
+
337
+ expect(bytebuffer.write_to(client)).to eq example_string.length
338
+ client.close
339
+
340
+ expect(peer.read(example_string.length)).to eq example_string
341
+ end
342
+
343
+ it "raises NIO::ByteBuffer::UnderflowError if the buffer is out of data" do
344
+ bytebuffer.flip
345
+ expect { bytebuffer.write_to(peer) }.to raise_error(NIO::ByteBuffer::UnderflowError)
346
+ end
347
+ end
348
+ end
349
+ end
@@ -1,113 +1,156 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "spec_helper"
2
4
  require "socket"
3
5
 
4
6
  RSpec.describe NIO::Monitor do
5
- let(:example_peers) do
6
- address = "127.0.0.1"
7
- base_port = 12_345
8
- tries = 10
9
-
10
- server = tries.times do |n|
11
- begin
12
- break TCPServer.new(address, base_port + n)
13
- rescue Errno::EADDRINUSE
14
- retry
15
- end
16
- end
17
-
18
- fail Errno::EADDRINUSE, "couldn't find an open port" unless server
19
- client = TCPSocket.new(address, server.addr[1])
20
- [server, client]
21
- end
7
+ let(:addr) { "localhost" }
8
+ let(:port) { next_available_tcp_port }
22
9
 
23
- let(:reader) { example_peers.first }
24
- let(:writer) { example_peers.last }
10
+ let(:reader) { TCPServer.new(addr, port) }
11
+ let(:writer) { TCPSocket.new(addr, port) }
25
12
 
26
13
  let(:selector) { NIO::Selector.new }
27
14
 
28
- subject { selector.register(reader, :r) }
29
- let(:peer) { selector.register(writer, :rw) }
30
- after { selector.close }
15
+ subject(:monitor) { selector.register(writer, :rw) }
16
+ subject(:peer) { selector.register(reader, :r) }
31
17
 
32
- before { example_peers } # open server and client automatically
33
- after { reader.close }
34
- after { writer.close }
18
+ before { reader }
19
+ before { writer }
20
+ after { reader.close }
21
+ after { writer.close }
22
+ after { selector.close }
35
23
 
36
- it "knows its interests" do
37
- expect(subject.interests).to eq(:r)
38
- expect(peer.interests).to eq(:rw)
24
+ describe "#interests" do
25
+ it "knows its interests" do
26
+ expect(peer.interests).to eq(:r)
27
+ expect(monitor.interests).to eq(:rw)
28
+ end
39
29
  end
40
30
 
41
- it "changes the interest set" do
42
- expect(peer.interests).not_to eq(:w)
43
- peer.interests = :w
44
- expect(peer.interests).to eq(:w)
45
- end
31
+ describe "#interests=" do
32
+ it "changes the interest set" do
33
+ expect(monitor.interests).not_to eq(:w)
34
+ monitor.interests = :w
35
+ expect(monitor.interests).to eq(:w)
36
+ end
46
37
 
47
- it "knows its IO object" do
48
- expect(subject.io).to eq(reader)
38
+ it "raises EOFError if interests are changed after the monitor is closed" do
39
+ monitor.close
40
+ expect { monitor.interests = :rw }.to raise_error(EOFError)
41
+ end
49
42
  end
50
43
 
51
- it "knows its selector" do
52
- expect(subject.selector).to eq(selector)
53
- end
44
+ describe "#add_interest" do
45
+ it "sets a new interest if it isn't currently registered" do
46
+ monitor.interests = :r
47
+ expect(monitor.interests).to eq(:r)
48
+
49
+ expect(monitor.add_interest(:w)).to eq(:rw)
50
+ expect(monitor.interests).to eq(:rw)
51
+ end
52
+
53
+ it "acts idempotently" do
54
+ monitor.interests = :r
55
+ expect(monitor.interests).to eq(:r)
56
+
57
+ expect(monitor.add_interest(:r)).to eq(:r)
58
+ expect(monitor.interests).to eq(:r)
59
+ end
54
60
 
55
- it "stores arbitrary values" do
56
- subject.value = 42
57
- expect(subject.value).to eq(42)
61
+ it "raises ArgumentError if given a bogus option" do
62
+ expect { monitor.add_interest(:derp) }.to raise_error(ArgumentError)
63
+ end
58
64
  end
59
65
 
60
- it "knows what operations IO objects are ready for" do
61
- # For whatever odd reason this breaks unless we eagerly evaluate subject
62
- reader_monitor = subject
63
- writer_monitor = peer
66
+ describe "#remove_interest" do
67
+ it "removes an interest from the set" do
68
+ expect(monitor.interests).to eq(:rw)
69
+
70
+ expect(monitor.remove_interest(:r)).to eq(:w)
71
+ expect(monitor.interests).to eq(:w)
72
+ end
64
73
 
65
- selected = selector.select(0)
66
- expect(selected).to include(writer_monitor)
74
+ it "can clear the last interest" do
75
+ monitor.interests = :w
76
+ expect(monitor.interests).to eq(:w)
67
77
 
68
- expect(writer_monitor.readiness).to eq(:w)
69
- expect(writer_monitor).not_to be_readable
70
- expect(writer_monitor).to be_writable
78
+ expect(monitor.remove_interest(:w)).to be_nil
79
+ expect(monitor.interests).to be_nil
80
+ end
71
81
 
72
- writer << "testing 1 2 3"
82
+ it "acts idempotently" do
83
+ monitor.interests = :w
84
+ expect(monitor.interests).to eq(:w)
73
85
 
74
- selected = selector.select(0)
75
- expect(selected).to include(reader_monitor)
86
+ expect(monitor.remove_interest(:r)).to eq(:w)
87
+ expect(monitor.interests).to eq(:w)
88
+ end
76
89
 
77
- expect(reader_monitor.readiness).to eq(:r)
78
- expect(reader_monitor).to be_readable
79
- expect(reader_monitor).not_to be_writable
90
+ it "raises ArgumentError if given a bogus option" do
91
+ expect { monitor.add_interest(:derp) }.to raise_error(ArgumentError)
92
+ end
80
93
  end
81
94
 
82
- it "changes current interests with #interests=" do
83
- client = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
84
- monitor = selector.register(client, :r)
85
- expect(monitor.interests).to eq(:r)
86
- monitor.interests = :w
87
- expect(monitor.interests).to eq(:w)
95
+ describe "#io" do
96
+ it "knows its IO object" do
97
+ expect(monitor.io).to eq(writer)
98
+ end
88
99
  end
89
100
 
90
- it "closes" do
91
- expect(subject).not_to be_closed
92
- expect(selector.registered?(reader)).to be_truthy
101
+ describe "#selector" do
102
+ it "knows its selector" do
103
+ expect(monitor.selector).to eq(selector)
104
+ end
105
+ end
93
106
 
94
- subject.close
95
- expect(subject).to be_closed
96
- expect(selector.registered?(reader)).to be_falsey
107
+ describe "#value=" do
108
+ it "stores arbitrary values" do
109
+ monitor.value = 42
110
+ expect(monitor.value).to eq(42)
111
+ end
97
112
  end
98
113
 
99
- it "closes even if the selector has been shutdown" do
100
- expect(subject).not_to be_closed
101
- selector.close # forces shutdown
102
- expect(subject).not_to be_closed
103
- subject.close
104
- expect(subject).to be_closed
114
+ describe "#readiness" do
115
+ it "knows what operations IO objects are ready for" do
116
+ # For whatever odd reason this breaks unless we eagerly evaluate monitor
117
+ reader_peer = peer
118
+ writer_peer = monitor
119
+
120
+ selected = selector.select(0)
121
+ expect(selected).to include(writer_peer)
122
+
123
+ expect(writer_peer.readiness).to eq(:w)
124
+ expect(writer_peer).not_to be_readable
125
+ expect(writer_peer).to be_writable
126
+
127
+ writer << "testing 1 2 3"
128
+
129
+ selected = selector.select(0)
130
+ expect(selected).to include(reader_peer)
131
+
132
+ expect(reader_peer.readiness).to eq(:r)
133
+ expect(reader_peer).to be_readable
134
+ expect(reader_peer).not_to be_writable
135
+ end
105
136
  end
106
137
 
107
- it "changes the interest set after monitor closed" do
108
- # check for changing the interests on the go after closed expected to fail
109
- expect(subject.interests).not_to eq(:rw)
110
- subject.close # forced shutdown
111
- expect { subject.interests = :rw }.to raise_error(TypeError)
138
+ describe "#close" do
139
+ it "closes" do
140
+ expect(monitor).not_to be_closed
141
+ expect(selector.registered?(writer)).to be_truthy
142
+
143
+ monitor.close
144
+ expect(monitor).to be_closed
145
+ expect(selector.registered?(writer)).to be_falsey
146
+ end
147
+
148
+ it "closes even if the selector has been shutdown" do
149
+ expect(monitor).not_to be_closed
150
+ selector.close # forces shutdown
151
+ expect(monitor).not_to be_closed
152
+ monitor.close
153
+ expect(monitor).to be_closed
154
+ end
112
155
  end
113
156
  end