async-io 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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