ione 1.0.0.pre1 → 1.0.0.pre2

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: 5daae8dfb48089c7dbff04dfc53bb454b5153e5d
4
- data.tar.gz: 770290ce9dd80b0633d21a5674ae6e497a6e5e9c
3
+ metadata.gz: 72e65e6b94938777a27d3baf72acda5bb56dc206
4
+ data.tar.gz: ecfaf4523b57f11061d0548be4c4649d54ab1e8d
5
5
  SHA512:
6
- metadata.gz: 7dce6e221b06510f9551a00df5461bd179500f51c3332b55fb6530f7385ca87fe9f8db2fdaeea516b1534400aa1f5701435abc20aa34ca46287476beb9dbb8c9
7
- data.tar.gz: e341558ccaf6cbafc59846793d542eb162726927f3499acd6ad950fbd4a40d3e99a69455d501cbae07997b18ed474913f7612dda6e70bdfdcb145244ecfc72bd
6
+ metadata.gz: 8576c5a7b533d37597d874bbca5dd92ff9385afbdfdb730328307d4dd8eac8be17efddf1445655367e422ada2215aeec5814f4c11505a5db5ea9d1deb061ff39
7
+ data.tar.gz: 8de121c515e8a25ec2208d47da67eeb5b7771036c8358f92c3e087e4862b5667408d5c1256f8777765ba0ec03d3eebf0cac868e62bc09e8cea92661cd3606104
data/README.md CHANGED
@@ -1,12 +1,35 @@
1
1
  # Ione
2
2
 
3
3
  [![Build Status](https://travis-ci.org/iconara/ione.png?branch=master)](https://travis-ci.org/iconara/ione)
4
+ [![Coverage Status](https://coveralls.io/repos/iconara/ione/badge.png)](https://coveralls.io/r/iconara/ione)
4
5
  [![Blog](http://b.repl.ca/v1/blog-ione-ff69b4.png)](http://architecturalatrocities.com/tagged/ione)
5
6
 
6
7
  _If you're reading this on GitHub, please note that this is the readme for the development version and that some features described here might not yet have been released. You can find the readme for a specific version either through [rubydoc.info](http://rubydoc.info/find/gems?q=ione) or via the release tags ([here is an example](https://github.com/iconara/ione/tree/v1.0.0.pre0))._
7
8
 
8
9
  Ione is a framework for reactive programming in Ruby. It is based on the reactive core of [cql-rb](http://github.com/iconara/cql-rb), the Ruby driver for Cassandra.
9
10
 
11
+ # Features
12
+
13
+ ## Futures & promises
14
+
15
+ At the core of Ione is a futures API. Futures make it easy to compose asynchronous operations.
16
+
17
+ ## Evented IO
18
+
19
+ A key piece of the framework is an IO reactor with which you can easily build network clients and servers.
20
+
21
+ ### Byte buffer
22
+
23
+ Networking usually means pushing lots of bytes around and in Ruby it's easy to make the mistake of using strings as buffers. Ione provides an efficient byte buffer implementation as an alternative.
24
+
25
+ # Examples
26
+
27
+ The [examples](https://github.com/iconara/ione/tree/master/examples) directory has some examples of what you can do with Ione, for example:
28
+
29
+ * [redis_client](https://github.com/iconara/ione/tree/master/examples/redis_client) is a more or less full featured Redis client that uses most of Ione's features.
30
+ * [http_client](https://github.com/iconara/ione/tree/master/examples/http_client) is a simplistic HTTP client that uses Ione and [http_parser.rb](http://rubygems.org/gems/http_parser.rb) to make HTTP GET request.
31
+ * [cql-rb](https://github.com/iconara/cql-rb) is a high performance Cassandra driver and where Ione was originally developed.
32
+
10
33
  # How to contribute
11
34
 
12
35
  [See CONTRIBUTING.md](CONTRIBUTING.md)
@@ -15,12 +15,17 @@ module Ione
15
15
  # and reads read from the read buffer until it is empty, then a new write
16
16
  # buffer is created and the old write buffer becomes the new read buffer.
17
17
  class ByteBuffer
18
- def initialize(initial_bytes='')
18
+ def initialize(initial_bytes=nil)
19
19
  @read_buffer = ''
20
- @write_buffer = ''
21
20
  @offset = 0
22
21
  @length = 0
23
- append(initial_bytes) unless initial_bytes.empty?
22
+ if initial_bytes && !initial_bytes.empty?
23
+ @write_buffer = initial_bytes.dup
24
+ @write_buffer.force_encoding(::Encoding::BINARY)
25
+ @length = @write_buffer.bytesize
26
+ else
27
+ @write_buffer = ''
28
+ end
24
29
  end
25
30
 
26
31
  # Returns the number of bytes in the buffer.
@@ -73,6 +78,7 @@ module Ione
73
78
  # @return [Ione::ByteBuffer] itself
74
79
  # @raise RangeError when there are not enough bytes in the buffer
75
80
  def discard(n)
81
+ raise RangeError, 'Cannot discard a negative number of bytes' if n < 0
76
82
  raise RangeError, "#{n} bytes to discard but only #{@length} available" if @length < n
77
83
  @offset += n
78
84
  @length -= n
@@ -86,6 +92,7 @@ module Ione
86
92
  # with `Encoding::BINARY`.
87
93
  # @raise RangeError when there are not enough bytes in the buffer
88
94
  def read(n)
95
+ raise RangeError, 'Cannot read a negative number of bytes' if n < 0
89
96
  raise RangeError, "#{n} bytes required but only #{@length} available" if @length < n
90
97
  if @offset >= @read_buffer.bytesize
91
98
  swap_buffers
@@ -165,6 +172,20 @@ module Ione
165
172
  b
166
173
  end
167
174
 
175
+ def index(substring, start_index=0)
176
+ if @offset >= @read_buffer.bytesize
177
+ swap_buffers
178
+ end
179
+ read_buffer_length = @read_buffer.bytesize
180
+ if start_index < read_buffer_length - @offset && (index = @read_buffer.index(substring, @offset + start_index))
181
+ index - @offset
182
+ elsif (index = @write_buffer.index(substring, start_index - read_buffer_length + @offset))
183
+ index + read_buffer_length - @offset
184
+ else
185
+ nil
186
+ end
187
+ end
188
+
168
189
  # Overwrite a portion of the buffer with new bytes.
169
190
  #
170
191
  # The number of bytes that will be replaced depend on the size of the
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Ione
4
- VERSION = '1.0.0.pre1'.freeze
4
+ VERSION = '1.0.0.pre2'.freeze
5
5
  end
@@ -48,78 +48,6 @@ describe 'An IO reactor' do
48
48
  protocol_handlers.sample.data.should == 'hello world'
49
49
  end
50
50
  end
51
-
52
- context 'when talking to Redis' do
53
- let :protocol_handler do
54
- begin
55
- io_reactor.connect(ENV['SERVER_HOST'], 6379, 1).map { |c| IoSpec::RedisProtocolHandler.new(c) }.value
56
- rescue Ione::Io::ConnectionError
57
- nil
58
- end
59
- end
60
-
61
- before do
62
- io_reactor.start.value
63
- end
64
-
65
- after do
66
- io_reactor.stop.value
67
- end
68
-
69
- it 'can set a value' do
70
- pending('Redis not running', unless: protocol_handler)
71
- response = protocol_handler.send_request('SET', 'foo', 'bar').value
72
- response.should == 'OK'
73
- end
74
-
75
- it 'can get a value' do
76
- pending('Redis not running', unless: protocol_handler)
77
- f = protocol_handler.send_request('SET', 'foo', 'bar').flat_map do
78
- protocol_handler.send_request('GET', 'foo')
79
- end
80
- f.value.should == 'bar'
81
- end
82
-
83
- it 'can delete values' do
84
- pending('Redis not running', unless: protocol_handler)
85
- f = protocol_handler.send_request('SET', 'hello', 'world').flat_map do
86
- protocol_handler.send_request('DEL', 'hello')
87
- end
88
- f.value.should == 1
89
- end
90
-
91
- it 'handles nil values' do
92
- pending('Redis not running', unless: protocol_handler)
93
- f = protocol_handler.send_request('DEL', 'hello').flat_map do
94
- protocol_handler.send_request('GET', 'hello')
95
- end
96
- f.value.should be_nil
97
- end
98
-
99
- it 'handles errors' do
100
- pending('Redis not running', unless: protocol_handler)
101
- f = protocol_handler.send_request('SET', 'foo')
102
- expect { f.value }.to raise_error("ERR wrong number of arguments for 'set' command")
103
- end
104
-
105
- it 'handles replies with multiple elements' do
106
- pending('Redis not running', unless: protocol_handler)
107
- f = protocol_handler.send_request('DEL', 'stuff')
108
- f.value
109
- f = protocol_handler.send_request('RPUSH', 'stuff', 'hello', 'world')
110
- f.value.should == 2
111
- f = protocol_handler.send_request('LRANGE', 'stuff', 0, 2)
112
- f.value.should == ['hello', 'world']
113
- end
114
-
115
- it 'handles nil values when reading multiple elements' do
116
- pending('Redis not running', unless: protocol_handler)
117
- protocol_handler.send_request('DEL', 'things')
118
- protocol_handler.send_request('HSET', 'things', 'hello', 'world')
119
- f = protocol_handler.send_request('HMGET', 'things', 'hello', 'foo')
120
- f.value.should == ['world', nil]
121
- end
122
- end
123
51
  end
124
52
 
125
53
  module IoSpec
@@ -141,143 +69,4 @@ module IoSpec
141
69
  @lock.synchronize { @data << new_data }
142
70
  end
143
71
  end
144
-
145
- class LineProtocolHandler
146
- def initialize(connection)
147
- @connection = connection
148
- @connection.on_data(&method(:process_data))
149
- @lock = Mutex.new
150
- @buffer = ''
151
- @requests = []
152
- end
153
-
154
- def on_line(&listener)
155
- @line_listener = listener
156
- end
157
-
158
- def write(command_string)
159
- @connection.write(command_string)
160
- end
161
-
162
- def process_data(new_data)
163
- lines = []
164
- @lock.synchronize do
165
- @buffer << new_data
166
- while newline_index = @buffer.index("\r\n")
167
- line = @buffer.slice!(0, newline_index + 2)
168
- line.chomp!
169
- lines << line
170
- end
171
- end
172
- lines.each do |line|
173
- @line_listener.call(line) if @line_listener
174
- end
175
- end
176
- end
177
-
178
- class RedisProtocolHandler
179
- def initialize(connection)
180
- @line_protocol = LineProtocolHandler.new(connection)
181
- @line_protocol.on_line(&method(:handle_line))
182
- @lock = Mutex.new
183
- @responses = []
184
- @state = BaseState.new(method(:handle_response))
185
- end
186
-
187
- def send_request(*args)
188
- promise = Ione::Promise.new
189
- @lock.synchronize do
190
- @responses << promise
191
- end
192
- request = "*#{args.size}\r\n"
193
- args.each do |arg|
194
- arg_str = arg.to_s
195
- request << "$#{arg_str.bytesize}\r\n#{arg_str}\r\n"
196
- end
197
- @line_protocol.write(request)
198
- promise.future
199
- end
200
-
201
- def handle_response(result, error=false)
202
- promise = @lock.synchronize do
203
- @responses.shift
204
- end
205
- if error
206
- promise.fail(StandardError.new(result))
207
- else
208
- promise.fulfill(result)
209
- end
210
- end
211
-
212
- def handle_line(line)
213
- @state = @state.handle_line(line)
214
- end
215
-
216
- class State
217
- def initialize(result_handler)
218
- @result_handler = result_handler
219
- end
220
-
221
- def complete!(result)
222
- @result_handler.call(result)
223
- end
224
-
225
- def fail!(message)
226
- @result_handler.call(message, true)
227
- end
228
- end
229
-
230
- class BulkState < State
231
- def handle_line(line)
232
- complete!(line)
233
- BaseState.new(@result_handler)
234
- end
235
- end
236
-
237
- class MultiBulkState < State
238
- def initialize(result_handler, expected_elements)
239
- super(result_handler)
240
- @expected_elements = expected_elements
241
- @elements = []
242
- end
243
-
244
- def handle_line(line)
245
- if line.start_with?('$')
246
- line.slice!(0, 1)
247
- if line.to_i == -1
248
- @elements << nil
249
- end
250
- else
251
- @elements << line
252
- end
253
- if @elements.size == @expected_elements
254
- complete!(@elements)
255
- BaseState.new(@result_handler)
256
- else
257
- self
258
- end
259
- end
260
- end
261
-
262
- class BaseState < State
263
- def handle_line(line)
264
- next_state = self
265
- first_char = line.slice!(0, 1)
266
- case first_char
267
- when '+' then complete!(line)
268
- when ':' then complete!(line.to_i)
269
- when '-' then fail!(line)
270
- when '$'
271
- if line.to_i == -1
272
- complete!(nil)
273
- else
274
- next_state = BulkState.new(@result_handler)
275
- end
276
- when '*'
277
- next_state = MultiBulkState.new(@result_handler, line.to_i)
278
- end
279
- next_state
280
- end
281
- end
282
- end
283
72
  end
@@ -175,6 +175,11 @@ module Ione
175
175
  buffer.append('hello')
176
176
  expect { buffer.discard(7) }.to raise_error(RangeError)
177
177
  end
178
+
179
+ it 'raises an error when the specified number of bytes is negative' do
180
+ buffer.append('hello')
181
+ expect { buffer.discard(-7) }.to raise_error(RangeError)
182
+ end
178
183
  end
179
184
 
180
185
  describe '#read' do
@@ -196,6 +201,11 @@ module Ione
196
201
  expect { buffer.discard(5).read(1) }.to raise_error(RangeError)
197
202
  end
198
203
 
204
+ it 'raises an error when the specified number of bytes is negative' do
205
+ buffer.append('hello')
206
+ expect { buffer.read(-4) }.to raise_error(RangeError)
207
+ end
208
+
199
209
  it 'returns a string with binary encoding' do
200
210
  buffer.append('hello')
201
211
  buffer.read(4).encoding.should == ::Encoding::BINARY
@@ -322,6 +332,30 @@ module Ione
322
332
  end
323
333
  end
324
334
 
335
+ describe '#index' do
336
+ it 'returns the first index of the specified substring' do
337
+ buffer.append('fizz buzz')
338
+ buffer.index('zz').should == 2
339
+ end
340
+
341
+ it 'returns the first index of the specified substring, after the specified index' do
342
+ buffer.append('fizz buzz')
343
+ buffer.index('zz', 3).should == 7
344
+ end
345
+
346
+ it 'returns nil when the substring is not found' do
347
+ buffer.append('hello world')
348
+ buffer.index('zz').should be_nil
349
+ end
350
+
351
+ it 'returns the first index of the specified substring after the buffer has been modified' do
352
+ buffer.append('foo bar')
353
+ buffer.read(1)
354
+ buffer.append(' baz baz')
355
+ buffer.index('baz', 8).should == 11
356
+ end
357
+ end
358
+
325
359
  context 'when reading and appending' do
326
360
  it 'handles heavy churn' do
327
361
  1000.times do
@@ -696,12 +696,26 @@ module Ione
696
696
  future.should be_resolved
697
697
  end
698
698
 
699
- it 'calls callbacks immediately' do
699
+ it 'is completed' do
700
+ future.should be_completed
701
+ end
702
+
703
+ it 'is not failed' do
704
+ future.should_not be_failed
705
+ end
706
+
707
+ it 'calls its value callbacks immediately' do
700
708
  value = nil
701
709
  future.on_value { |v| value = v }
702
710
  value.should == 'hello world'
703
711
  end
704
712
 
713
+ it 'calls its complete callbacks immediately' do
714
+ f = nil
715
+ future.on_complete { |ff| f = ff }
716
+ f.should_not be_nil
717
+ end
718
+
705
719
  it 'does not block on #value' do
706
720
  future.value.should == 'hello world'
707
721
  end
@@ -718,16 +732,30 @@ module Ione
718
732
  end
719
733
 
720
734
  context 'returns a future which' do
721
- it 'is failed when created' do
735
+ it 'is failed' do
722
736
  future.should be_failed
723
737
  end
724
738
 
725
- it 'calls callbacks immediately' do
739
+ it 'is completed' do
740
+ future.should be_completed
741
+ end
742
+
743
+ it 'is not resolved' do
744
+ future.should_not be_resolved
745
+ end
746
+
747
+ it 'call its failure callbacks immediately' do
726
748
  error = nil
727
749
  future.on_failure { |e| error = e }
728
750
  error.message.should == 'bork'
729
751
  end
730
752
 
753
+ it 'calls its complete callbacks immediately' do
754
+ f = nil
755
+ future.on_complete { |ff| f = ff }
756
+ f.should_not be_nil
757
+ end
758
+
731
759
  it 'does not block on #value' do
732
760
  expect { future.value }.to raise_error('bork')
733
761
  end
@@ -1,8 +1,26 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'ione'
3
+ ENV['SERVER_HOST'] ||= '127.0.0.1'.freeze
4
+
5
+ require 'bundler/setup'
4
6
 
5
7
  require 'support/fake_server'
6
8
  require 'support/await_helper'
7
9
 
8
- ENV['SERVER_HOST'] ||= '127.0.0.1'.freeze
10
+ unless ENV['COVERAGE'] == 'no' || RUBY_ENGINE == 'rbx'
11
+ require 'coveralls'
12
+ require 'simplecov'
13
+
14
+ if ENV.include?('TRAVIS')
15
+ Coveralls.wear!
16
+ SimpleCov.formatter = Coveralls::SimpleCov::Formatter
17
+ end
18
+
19
+ SimpleCov.start do
20
+ add_group 'Source', 'lib'
21
+ add_group 'Unit tests', 'spec/cql'
22
+ add_group 'Integration tests', 'spec/integration'
23
+ end
24
+ end
25
+
26
+ require 'ione'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ione
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre1
4
+ version: 1.0.0.pre2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Theo Hultberg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-01 00:00:00.000000000 Z
11
+ date: 2014-03-15 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Reactive programming framework for Ruby, painless evented IO, futures
14
14
  and an efficient byte buffer