async-io 0.1.0 → 0.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3b884696374f63d12ec25bbeddbb32bdb6fc9dda
4
- data.tar.gz: 43be6b24df7e4e244afaac328fdea0f95ecf612b
3
+ metadata.gz: cc74a7c6901edc284210da8593a505e0eb87ff89
4
+ data.tar.gz: d5c4e46d022296613e94dfbcc6409727dc33dcb2
5
5
  SHA512:
6
- metadata.gz: 5d4a4c420cff06424564949249ee0c5c442f02c581eb94268902dd98f39d7187c1af4525ba0483ec9c438a9395182141838dab42bb7ca498d5d994f014ee3001
7
- data.tar.gz: 1a75fce2c71dc1ad463ece49508068e9aff94529578b8c468b65fd679fc7857d15a2cc3c3f6b58d2dbd61f2aec06ad8940307daea6095965aeb5c008cfaf7a2b
6
+ metadata.gz: 79274376a216fce20ee3a1d8f56f2767f9a9c6cc107d20bc9780ceb4ce29c5e32f7e5a34a7ac84b6d57d55eb28cd00d4d6fd8ee32208a7e1a7015b146f5e10b4
7
+ data.tar.gz: dc4e658c5411145c90359ef91dc565027b8c1ed36e22abd92e4907b20958f2e3b03e9cccb3a503c443c0cdfbceb58a4204226cd6d735681e3df6cec60c4317b6
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.has_rdoc = "yard"
18
18
 
19
19
  spec.add_dependency "async", "~> 0.14"
20
- spec.add_development_dependency "async-rspec", "~> 1.0"
20
+ spec.add_development_dependency "async-rspec", "~> 1.1"
21
21
 
22
22
  spec.add_development_dependency "bundler", "~> 1.13"
23
23
  spec.add_development_dependency "rake", "~> 10.0"
@@ -42,7 +42,7 @@ module Async
42
42
  def each(specifications, &block)
43
43
  specifications.each do |specification|
44
44
  if specification.is_a? self
45
- yield self
45
+ yield specification
46
46
  else
47
47
  # Perhaps detect options here?
48
48
  yield self.new(specification)
@@ -84,6 +84,18 @@ module Async
84
84
  end
85
85
  end
86
86
 
87
+ def self.build(*args, task: Task.current)
88
+ socket = ::Socket.new(*args)
89
+
90
+ yield socket
91
+
92
+ return self.new(socket, task.reactor)
93
+ rescue Exception
94
+ socket.close if socket
95
+
96
+ raise
97
+ end
98
+
87
99
  # Establish a connection to a given `remote_address`.
88
100
  # @example
89
101
  # socket = Async::IO::Socket.connect(Async::IO::Address.tcp("8.8.8.8", 53))
@@ -93,21 +105,26 @@ module Async
93
105
  def self.connect(remote_address, local_address = nil, protocol: 0, task: Task.current)
94
106
  task.annotate "connecting to #{remote_address.inspect}"
95
107
 
96
- socket = ::Socket.new(remote_address.afamily, remote_address.socktype, protocol)
97
-
98
- if local_address
99
- socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, true)
100
- socket.bind(local_address.to_sockaddr) if local_address
108
+ wrapper = build(remote_address.afamily, remote_address.socktype, protocol) do |socket|
109
+ if local_address
110
+ socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, true)
111
+ socket.bind(local_address.to_sockaddr) if local_address
112
+ end
113
+
114
+ self.new(socket, task.reactor)
101
115
  end
102
116
 
103
- wrapper = self.new(socket, task.reactor)
104
- wrapper.connect(remote_address.to_sockaddr)
105
-
106
- task.annotate "connected to #{remote_address.inspect}"
117
+ begin
118
+ wrapper.connect(remote_address.to_sockaddr)
119
+ task.annotate "connected to #{remote_address.inspect}"
120
+ rescue
121
+ wrapper.close
122
+ raise
123
+ end
107
124
 
108
125
  if block_given?
109
126
  begin
110
- return yield(wrapper)
127
+ yield wrapper, task
111
128
  ensure
112
129
  wrapper.close
113
130
  end
@@ -120,20 +137,20 @@ module Async
120
137
  # @example
121
138
  # socket = Async::IO::Socket.bind(Async::IO::Address.tcp("0.0.0.0", 9090))
122
139
  # @param local_address [Address] The local address to bind to.
123
- # @option protcol [Integer] The socket protocol to use.
124
- def self.bind(local_address, protocol: 0, task: Task.current, &block)
140
+ # @option protocol [Integer] The socket protocol to use.
141
+ # @option reuse_port [Boolean] Allow this port to be bound in multiple processes.
142
+ def self.bind(local_address, protocol: 0, reuse_port: false, task: Task.current, &block)
125
143
  task.annotate "binding to #{local_address.inspect}"
126
144
 
127
- socket = ::Socket.new(local_address.afamily, local_address.socktype, protocol)
128
-
129
- socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, true)
130
- socket.bind(local_address.to_sockaddr)
131
-
132
- wrapper = self.new(socket, task.reactor)
145
+ wrapper = build(local_address.afamily, local_address.socktype, protocol) do |socket|
146
+ socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, true)
147
+ socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEPORT, true) if reuse_port
148
+ socket.bind(local_address.to_sockaddr)
149
+ end
133
150
 
134
151
  if block_given?
135
152
  begin
136
- return yield(wrapper, task)
153
+ yield wrapper, task
137
154
  ensure
138
155
  wrapper.close
139
156
  end
@@ -0,0 +1,307 @@
1
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'socket'
22
+ require_relative 'generic'
23
+
24
+ module Async
25
+ module IO
26
+ class BinaryString < String
27
+ def initialize(*args)
28
+ super
29
+
30
+ force_encoding(Encoding::BINARY)
31
+ end
32
+
33
+ def << string
34
+ super
35
+
36
+ force_encoding(Encoding::BINARY)
37
+ end
38
+
39
+ alias concat <<
40
+ end
41
+
42
+ class Stream
43
+ include Enumerable
44
+
45
+ def initialize(io, block_size: 1024, eol: $/)
46
+ @io = io
47
+ @eof = false
48
+ @sync = false
49
+
50
+ @block_size = block_size
51
+ @eol = eol
52
+
53
+ @read_buffer = BinaryString.new
54
+ @write_buffer = BinaryString.new
55
+ end
56
+
57
+ attr :io
58
+
59
+ # The "sync mode" of the stream. See IO#sync for full details.
60
+ attr_accessor :sync
61
+
62
+ # Reads `size` bytes from the stream. If `buffer` is provided it must
63
+ # reference a string which will receive the data.
64
+ #
65
+ # See IO#read for full details.
66
+ def read(size = nil, output_buffer = nil)
67
+ if size == 0
68
+ if output_buffer
69
+ output_buffer.clear
70
+ return output_buffer
71
+ else
72
+ return ""
73
+ end
74
+ end
75
+
76
+ until @eof
77
+ break if size && size <= @read_buffer.size
78
+ fill_read_buffer
79
+ break unless size
80
+ end
81
+
82
+ buffer = consume_read_buffer(size)
83
+
84
+ if buffer && output_buffer
85
+ output_buffer.replace(buffer)
86
+ buffer = output_buffer
87
+ end
88
+
89
+ if size
90
+ return buffer
91
+ else
92
+ return buffer || ""
93
+ end
94
+ end
95
+
96
+ # Writes `string` to the buffer. When the buffer is full or #sync is true the
97
+ # buffer is flushed to the underlying `io`.
98
+ # @param string the string to write to the buffer.
99
+ # @return the number of bytes appended to the buffer.
100
+ def write(string)
101
+ @write_buffer << string
102
+
103
+ if @sync || @write_buffer.size > @block_size
104
+ flush
105
+ end
106
+
107
+ return string.bytesize
108
+ end
109
+
110
+ # Flushes buffered data to the stream.
111
+ def flush
112
+ syswrite(@write_buffer)
113
+ end
114
+
115
+ # Closes the stream and flushes any unwritten data.
116
+ def close
117
+ flush rescue nil
118
+
119
+ @io.close
120
+ end
121
+
122
+ # Reads the next line from the stream. Lines are separated by +eol+. If
123
+ # +limit+ is provided the result will not be longer than the given number of
124
+ # bytes.
125
+ #
126
+ # +eol+ may be a String or Regexp.
127
+ #
128
+ # Unlike IO#gets the line read will not be assigned to +$_+.
129
+ #
130
+ # Unlike IO#gets the separator must be provided if a limit is provided.
131
+ def gets(eol = @eol, limit = nil)
132
+ index = @read_buffer.index(eol)
133
+
134
+ until index || @eof
135
+ fill_read_buffer
136
+ index = @read_buffer.index(eol)
137
+ end
138
+
139
+ if eol.is_a?(Regexp)
140
+ size = index ? index+$&.bytesize : nil
141
+ else
142
+ size = index ? index+eol.bytesize : nil
143
+ end
144
+
145
+ if limit && limit >= 0
146
+ size = [size, limit].min
147
+ end
148
+
149
+ consume_read_buffer(size)
150
+ end
151
+
152
+ # Executes the block for every line in the stream where lines are separated
153
+ # by +eol+.
154
+ #
155
+ # See also #gets
156
+ def each(eol = @eol)
157
+ while line = self.gets(eol)
158
+ yield line
159
+ end
160
+ end
161
+ alias each_line each
162
+
163
+ # Reads lines from the stream which are separated by +eol+.
164
+ #
165
+ # See also #gets
166
+ def readlines(eol = @eol)
167
+ lines = []
168
+
169
+ while line = self.gets(eol)
170
+ lines << line
171
+ end
172
+
173
+ lines
174
+ end
175
+
176
+ # Reads a line from the stream which is separated by +eol+.
177
+ #
178
+ # Raises EOFError if at end of file.
179
+ def readline(eol=@eol)
180
+ gets(eol) or raise EOFError
181
+ end
182
+
183
+ # Reads one character from the stream. Returns nil if called at end of
184
+ # file.
185
+ def getc
186
+ read(1)
187
+ end
188
+
189
+ # Calls the given block once for each byte in the stream.
190
+ def each_byte # :yields: byte
191
+ while c = getc
192
+ yield(c.ord)
193
+ end
194
+ end
195
+
196
+ # Reads a one-character string from the stream. Raises an EOFError at end
197
+ # of file.
198
+ def readchar
199
+ getc or raise EOFError
200
+ end
201
+
202
+ # Returns true if the stream is at file which means there is no more data to be read.
203
+ def eof?
204
+ fill_read_buffer if !@eof && @read_buffer.empty?
205
+
206
+ @eof && @read_buffer.empty?
207
+ end
208
+ alias eof eof?
209
+
210
+ # Writes `string` to the stream and returns self.
211
+ def <<(string)
212
+ write(string)
213
+
214
+ return self
215
+ end
216
+
217
+ # Writes `args` to the stream along with a record separator. See `IO#puts` for full details.
218
+ def puts(*args, eol: @eol)
219
+ if args.empty?
220
+ write(eol)
221
+ else
222
+ args.each do |arg|
223
+ string = arg.to_s
224
+ if string.end_with? eol
225
+ write(string)
226
+ else
227
+ write(string)
228
+ write(eol)
229
+ end
230
+ end
231
+ end
232
+
233
+ return nil
234
+ end
235
+
236
+ # Writes `args` to the stream. See `IO#print` for full details.
237
+ def print(*args)
238
+ args.each do |arg|
239
+ write(arg.to_s)
240
+ end
241
+
242
+ return nil
243
+ end
244
+
245
+ # Formats and writes to the stream converting parameters under control of the format string. See `Kernel#sprintf` for format string details.
246
+ def printf(s, *args)
247
+ write(s % args)
248
+
249
+ return nil
250
+ end
251
+
252
+ private
253
+
254
+ # Write a buffer to the underlying stream.
255
+ # @param buffer [String] The string to write, any encoding is okay.
256
+ def syswrite(buffer)
257
+ remaining = buffer.bytesize
258
+
259
+ # Fast path:
260
+ written = @io.write(buffer)
261
+ return if written == remaining
262
+
263
+ # Slow path:
264
+ remaining -= written
265
+
266
+ while remaining > 0
267
+ wrote = @io.write(buffer.byteslice(written, remaining))
268
+
269
+ remaining -= wrote
270
+ written += wrote
271
+ end
272
+
273
+ return written
274
+ end
275
+
276
+ # Fills the buffer from the underlying stream.
277
+ def fill_read_buffer
278
+ if buffer = @io.read(@block_size)
279
+ # We guarantee that the read_buffer remains ASCII-8BIT because read should always return ASCII-8BIT
280
+ @read_buffer << buffer
281
+ else
282
+ @eof = true
283
+ end
284
+ end
285
+
286
+ # Consumes `size` bytes from the buffer.
287
+ # @param size [Integer|nil] The amount of data to consume. If nil, consume entire buffer.
288
+ def consume_read_buffer(size = nil)
289
+ # If we are at eof, and the read buffer is empty, we can't consume anything.
290
+ return nil if @eof && @read_buffer.empty?
291
+
292
+ result = nil
293
+
294
+ if size == nil || size == @read_buffer.size
295
+ # Consume the entire read buffer:
296
+ result = @read_buffer.dup
297
+ @read_buffer.clear
298
+ else
299
+ # Consume only part of the read buffer:
300
+ result = @read_buffer.slice!(0, size)
301
+ end
302
+
303
+ return result
304
+ end
305
+ end
306
+ end
307
+ end
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Async
22
22
  module IO
23
- VERSION = "0.1.0"
23
+ VERSION = "0.3.0"
24
24
  end
25
25
  end
@@ -0,0 +1,129 @@
1
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'async/io'
22
+ require 'benchmark'
23
+
24
+ RSpec.describe "echo client/server" do
25
+ let(:repeats) {10000}
26
+ let(:server_address) {Async::IO::Address.tcp('0.0.0.0', 9000)}
27
+
28
+ def echo_server(server_address)
29
+ Async::Reactor.run do |task|
30
+ connection_count = 0
31
+
32
+ connections_complete = task.async do
33
+ last_count = 0
34
+
35
+ while connection_count < repeats
36
+ if connection_count != last_count
37
+ puts "#{connection_count}/#{repeats} simultaneous connections."
38
+ last_count = connection_count
39
+ end
40
+
41
+ task.sleep(1.0)
42
+ end
43
+
44
+ puts "Releasing all connections..."
45
+ end
46
+
47
+ # This is a synchronous block within the current task:
48
+ Async::IO::Socket.accept(server_address) do |client|
49
+ connection_count += 1
50
+
51
+ # Wait until we've got all the connections:
52
+ connections_complete.wait
53
+
54
+ # This is an asynchronous block within the current reactor:
55
+ data = client.read(512)
56
+ client.write(data)
57
+ end
58
+ end
59
+ end
60
+
61
+ def echo_client(server_address, data, responses)
62
+ Async::Reactor.run do |task|
63
+ begin
64
+ Async::IO::Socket.connect(server_address) do |peer|
65
+ result = peer.write(data)
66
+
67
+ message = peer.read(512)
68
+
69
+ responses << message
70
+ end
71
+ rescue Errno::ECONNREFUSED
72
+ puts "Connection refused..."
73
+ # If the connection was refused, it means the server probably can't accept connections any faster than it currently is, so we simply retry.
74
+ retry
75
+ end
76
+ end
77
+ end
78
+
79
+ def fork_server
80
+ pid = fork do
81
+ echo_server(server_address)
82
+ end
83
+
84
+ yield
85
+ ensure
86
+ Process.kill(:KILL, pid)
87
+ Process.wait(pid)
88
+ end
89
+
90
+ around(:each) do |example|
91
+ duration = Benchmark.realtime do
92
+ example.run
93
+ end
94
+
95
+ example.reporter.message "Handled #{repeats} connections in #{duration}: #{repeats/duration}req/s"
96
+ end
97
+
98
+ around(:each) do |example|
99
+ previous_level = Async.logger.level
100
+ # Supress logging:
101
+ Async.logger.level = Logger::WARN
102
+
103
+ begin
104
+ example.run
105
+ ensure
106
+ Async.logger.level = previous_level
107
+ end
108
+ end
109
+
110
+ it "should send/receive 10,000 messages" do
111
+ fork_server do
112
+ Async::Reactor.run do |task|
113
+ responses = []
114
+
115
+ tasks = repeats.times.collect do |i|
116
+ # puts "Starting client #{i} on #{task}..." if (i % 1000) == 0
117
+
118
+ echo_client(server_address, "Hello World #{i}", responses)
119
+ end
120
+
121
+ # task.reactor.print_hierarchy
122
+
123
+ tasks.each(&:wait)
124
+
125
+ expect(responses.count).to be repeats
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,45 @@
1
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'async/io/socket'
22
+
23
+ RSpec.describe Async::IO::Socket do
24
+ include_context Async::RSpec::Reactor
25
+
26
+ describe '#connect' do
27
+ let(:address) {Async::IO::Address.tcp('127.0.0.1', 12345)}
28
+
29
+ it "should fail to connect if no listening server" do
30
+ expect do
31
+ Async::IO::Socket.connect(address)
32
+ end.to raise_error(Errno::ECONNREFUSED)
33
+ end
34
+ end
35
+
36
+ describe '#bind' do
37
+ let(:address) {Async::IO::Address.tcp('127.0.0.1', 1)}
38
+
39
+ it "should fail to bind to port < 1024" do
40
+ expect do
41
+ Async::IO::Socket.bind(address)
42
+ end.to raise_error(Errno::EACCES)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,65 @@
1
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'async/io/stream'
22
+
23
+ RSpec.describe Async::IO::Stream do
24
+ let(:io) {StringIO.new}
25
+ let(:stream) {Async::IO::Stream.new(io, eol: "\n")}
26
+
27
+ describe '#puts' do
28
+ it "should write line" do
29
+ stream.puts "Hello World"
30
+ stream.flush
31
+
32
+ expect(io.string).to be == "Hello World\n"
33
+ end
34
+ end
35
+
36
+ describe '#readline' do
37
+ before(:each) do
38
+ io.puts "Hello World"
39
+ io.seek(0)
40
+ end
41
+
42
+ it "should read one line" do
43
+ expect(stream.readline).to be == "Hello World\n"
44
+ end
45
+
46
+ it "should be binary encoding" do
47
+ expect(stream.readline.encoding).to be == Encoding::BINARY
48
+ end
49
+ end
50
+
51
+ describe '#readlines' do
52
+ before(:each) do
53
+ io << "Hello\nWorld\n"
54
+ io.seek(0)
55
+ end
56
+
57
+ it "should read multiple lines" do
58
+ expect(stream.readlines).to be == ["Hello\n", "World\n"]
59
+ end
60
+
61
+ it "should be binary encoding" do
62
+ expect(stream.readlines.first.encoding).to be == Encoding::BINARY
63
+ end
64
+ end
65
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-io
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-05-24 00:00:00.000000000 Z
11
+ date: 2017-06-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.0'
33
+ version: '1.1'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '1.0'
40
+ version: '1.1'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -98,14 +98,18 @@ files:
98
98
  - lib/async/io/address.rb
99
99
  - lib/async/io/generic.rb
100
100
  - lib/async/io/socket.rb
101
+ - lib/async/io/stream.rb
101
102
  - lib/async/io/tcp_socket.rb
102
103
  - lib/async/io/udp_socket.rb
103
104
  - lib/async/io/unix_socket.rb
104
105
  - lib/async/io/version.rb
105
106
  - lib/async/io/wrap/tcp.rb
106
107
  - spec/async/io/address_spec.rb
108
+ - spec/async/io/c10k_spec.rb
107
109
  - spec/async/io/echo_spec.rb
108
110
  - spec/async/io/generic_spec.rb
111
+ - spec/async/io/socket_spec.rb
112
+ - spec/async/io/stream_spec.rb
109
113
  - spec/async/io/tcp_socket_spec.rb
110
114
  - spec/async/io/udp_socket_spec.rb
111
115
  - spec/async/io/unix_socket_spec.rb
@@ -136,8 +140,11 @@ specification_version: 4
136
140
  summary: Provides support for asynchonous TCP, UDP, UNIX and SSL sockets.
137
141
  test_files:
138
142
  - spec/async/io/address_spec.rb
143
+ - spec/async/io/c10k_spec.rb
139
144
  - spec/async/io/echo_spec.rb
140
145
  - spec/async/io/generic_spec.rb
146
+ - spec/async/io/socket_spec.rb
147
+ - spec/async/io/stream_spec.rb
141
148
  - spec/async/io/tcp_socket_spec.rb
142
149
  - spec/async/io/udp_socket_spec.rb
143
150
  - spec/async/io/unix_socket_spec.rb