eventkit 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 379de97a4cfe31a23cbfb2a470c01fca1226e56f
4
+ data.tar.gz: 80491f27ba56b4f1b26490b3e6e30e89a15eaf80
5
+ SHA512:
6
+ metadata.gz: 658fa33c95c9ff69057b5ba3dec554290925e38256e080a06a012910e6e76f11971a38b3beb6995f876528a960afb379d84beaeba5e09f03b532f53ecb777127
7
+ data.tar.gz: 800f9561598b169c3093f5caebab3cb24e848b0efab3cd888dd801f371a663d9d56abd103438ce4f50b48ff39c6b889c6eb288d812dcce811728308f08e38058
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.0
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0
5
+ - 2.1
6
+ - 2.2
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in eventkit.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'rake', '~> 10.4'
8
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Oliver Martell
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # Eventkit
2
+
3
+ A basic toolkit for asynchronous event driven applications. The
4
+ current version includes an Event Loop to perform non blocking IO and
5
+ a promises A+ implementation to coordinate asychronous tasks.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'eventkit'
13
+ ```
14
+
15
+ ## Event Loop Usage
16
+
17
+ Eventkit provides a basic Event Loop on top of Ruby's IO.select to perform non blocking IO.
18
+ Callbacks can be registered to monitor the readability or writability of IO objects.
19
+ These callbacks are executed when the IO object is ready to be read or written to.
20
+
21
+ Another feature is timers, which allows you to execute code at some point in the future.
22
+
23
+ ```ruby
24
+ require 'eventkit'
25
+
26
+ # Getting notified when an IO object is ready to be read or written
27
+ event_loop = Eventkit::EventLoop.new
28
+
29
+ server = TCPServer.new('localhost', 9595)
30
+
31
+ client = TCPSocket.new('localhost', 9595)
32
+
33
+ event_loop.register_read(server) do |server|
34
+ # This will be executed every time a new connection is ready to be accepted
35
+ connection, _ = server.accept_nonblock
36
+ event_loop.register_write(connection) do |connection|
37
+ bytes_written = connection.write_nonblock('hello world')
38
+ end
39
+ end
40
+
41
+ event_loop.start
42
+
43
+
44
+ # Unsubscribing from notifications
45
+ # A single read
46
+ event_loop.deregister_read(io_object, handler)
47
+
48
+ # A single write
49
+ event_loop.deregister_write(io_object, handler)
50
+
51
+ # All handlers
52
+ event_loop.deregister_write(io_object)
53
+ event_loop.deregister_read(io_object)
54
+
55
+
56
+ # Registering a handler to be run on the next tick
57
+ event_loop = Eventkit::EventLoop.new
58
+
59
+ event_loop.on_next_tick do
60
+ puts 'hello world'
61
+ event_loop.stop
62
+ end
63
+
64
+ event_loop.start
65
+
66
+
67
+ # Registering timers
68
+
69
+ event_loop = Eventkit::EventLoop.new
70
+
71
+ event_loop.register_timer(run_in: 5) do
72
+ # Block executes after 5 seconds have passed
73
+ puts 'hello world'
74
+ event_loop.stop
75
+ end
76
+
77
+ event_loop.start
78
+ ```
79
+
80
+ ## Promises Usage
81
+
82
+ Eventkit also provides an implementation of the [Promise A+ specification](https://promisesaplus.com/),
83
+ which allows you coordinate different asynchronous tasks while still programming with values.
84
+
85
+ If you're only interested in promises, then eventkit-promise is also available as a [separate gem] (https://rubygems.org/gems/eventkit-promise).
86
+
87
+ ```ruby
88
+ require 'eventkit/promise'
89
+
90
+ # Resolving a promise
91
+
92
+ promise = Eventkit::Promise.new
93
+
94
+ promise.then(->(value) { value + 1 })
95
+
96
+ promise.resolve(1)
97
+
98
+ promise.value # => 1
99
+
100
+ # Rejecting a promise
101
+
102
+ promise = Eventkit::Promise.new
103
+
104
+ promise.then(
105
+ ->(value) {
106
+ value + 1
107
+ },
108
+ ->(error) {
109
+ log(error.message)
110
+ }
111
+ )
112
+
113
+ promise.reject(NoMethodError.new('Undefined method #call'))
114
+
115
+ promise.reason # => <NoMethodError: undefined method #call>
116
+
117
+ # Chaining promises
118
+
119
+ promise_a = Eventkit::Promise.new
120
+
121
+ promise_b = promise_a
122
+ .then(->(v) { v + 1 })
123
+ .then(->(v) { v + 1 })
124
+ .then(->(v) { v + 1 })
125
+
126
+ promise_b.catch { |error|
127
+ # Handle errors raised by any of the previous handlers
128
+ }
129
+
130
+ promise_a.resolve(1)
131
+
132
+ promise_a.value # => 1
133
+ promise_b.value # => 4
134
+
135
+ # Resolving and fullfiling with another promise
136
+
137
+ promise_a = Eventkit::Promise.new
138
+ promise_b = Eventkit::Promise.new
139
+
140
+ promise_a.resolve(promise_b)
141
+
142
+ promise_b.resolve('foobar')
143
+
144
+ promise_a.value # => foobar
145
+
146
+ # Resolving and rejecting with another promise
147
+
148
+ promise_a = Eventkit::Promise.new
149
+ promise_b = Eventkit::Promise.new
150
+
151
+ promise_a.resolve(promise_b)
152
+
153
+ promise_b.reject('Ooops can not continue')
154
+
155
+ promise_a.reason # => 'Ooops can not continue'
156
+
157
+ # Initializing with a block
158
+
159
+ promise = Promise.new do |p|
160
+ p.resolve('foobar')
161
+ end
162
+
163
+ promise.value # => 'foobar'
164
+
165
+ ```
166
+
167
+ ## Contributing
168
+
169
+ 1. Fork it ( https://github.com/[my-github-username]/eventkit/fork )
170
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
171
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
172
+ 4. Push to the branch (`git push origin my-new-feature`)
173
+ 5. Create a new Pull Request
174
+ 6. Happy Hacking!
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ RSpec::Core::RakeTask.new(:spec)
4
+ task :default => :spec
data/eventkit.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'eventkit/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'eventkit'
8
+ spec.version = Eventkit::VERSION
9
+ spec.authors = ['Oliver Martell']
10
+ spec.email = ['oliver.martell@gmail.com']
11
+ spec.summary = 'Experimental toolkit for asynchronous event driven applications'
12
+ spec.description =
13
+ 'Experimental toolkit for asynchronous event driven applications, which includes
14
+ an event loop to perform non blocking IO and a promises A+ implementation.'
15
+ spec.homepage = ''
16
+ spec.license = 'MIT'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0")
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_development_dependency 'rake', '~> 10.0'
24
+ spec.add_development_dependency 'rspec', '~> 3.2.0'
25
+ spec.add_development_dependency 'pry', '~> 0.10.1'
26
+ spec.add_development_dependency 'pry-doc', '~> 0.6.0'
27
+ spec.add_development_dependency 'method_source', '~> 0.8.2'
28
+ spec.add_runtime_dependency 'eventkit-promise', '~> 0.1.0'
29
+ end
@@ -0,0 +1,90 @@
1
+ require 'set'
2
+ require 'eventkit/timer'
3
+
4
+ module Eventkit
5
+ class EventLoopAlreadyStartedError < StandardError; end
6
+
7
+ class EventLoop
8
+ attr_reader :select_interval
9
+ private :select_interval
10
+
11
+ def initialize(config = {})
12
+ @read_handlers = Hash.new { |h, k| h[k] = [] }
13
+ @write_handlers = Hash.new { |h, k| h[k] = [] }
14
+ @select_interval = config.fetch(:select_interval, 1 / 100_000)
15
+ @timers = SortedSet.new
16
+ @stopped = false
17
+ @started = false
18
+ end
19
+
20
+ def start(&_block)
21
+ if @started
22
+ fail EventLoopAlreadyStartedError, 'This event loop instance has already started running'
23
+ else
24
+ @started = true
25
+ end
26
+
27
+ loop do
28
+ break if stopped?
29
+ tick
30
+ end
31
+ end
32
+
33
+ def stop
34
+ @stopped = true
35
+ @started = false
36
+ end
37
+
38
+ def stopped?
39
+ @stopped
40
+ end
41
+
42
+ def tick
43
+ ready_read, ready_write, _ = IO.select(@read_handlers.keys, @write_handlers.keys, [], select_interval)
44
+ ready_read.each do |io|
45
+ @read_handlers.fetch(io).each { |handler| handler.call(io) }
46
+ end if ready_read
47
+
48
+ ready_write.each do |io|
49
+ @write_handlers.fetch(io).each { |handler| handler.call(io) }
50
+ end if ready_write
51
+
52
+ @timers.each { |timer| timer.handler.call if timer.expired? }
53
+ @timers = @timers.reject(&:expired?)
54
+ nil
55
+ end
56
+
57
+ def on_next_tick(&handler)
58
+ register_timer(run_in: 0, &handler)
59
+ end
60
+
61
+ def register_timer(run_in:, &handler)
62
+ @timers << Timer.new(run_in, handler)
63
+ end
64
+
65
+ def register_read(io, &listener)
66
+ @read_handlers[io] += [listener]
67
+ end
68
+
69
+ def deregister_read(io, listener = nil)
70
+ if listener
71
+ @read_handlers[io] -= [listener]
72
+ else
73
+ @read_handlers[io] = []
74
+ end
75
+ end
76
+
77
+ def register_write(io, &listener)
78
+ @write_handlers[io] += [listener]
79
+ end
80
+
81
+ def deregister_write(io, listener = nil)
82
+ if listener
83
+ @write_handlers[io] -= [listener]
84
+ else
85
+ @write_handlers[io] = []
86
+ end
87
+ end
88
+ end
89
+ end
90
+
@@ -0,0 +1,15 @@
1
+ module Eventkit
2
+ Timer = Struct.new(:expires_in, :handler) do
3
+ def initialize(seconds, handler)
4
+ super(Time.now.to_f + seconds, handler)
5
+ end
6
+
7
+ def <=>(other)
8
+ expires_in <=> other.expires_in
9
+ end
10
+
11
+ def expired?
12
+ expires_in <= Time.now.to_f
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module Eventkit
2
+ VERSION = '0.0.1'
3
+ end
data/lib/eventkit.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'eventkit/version'
2
+ require 'eventkit/event_loop'
3
+ require 'eventkit/promise'
4
+
5
+ module Eventkit
6
+ # Your code goes here...
7
+ end
@@ -0,0 +1,277 @@
1
+ require 'support/async_helper'
2
+ require 'socket'
3
+ require 'eventkit'
4
+
5
+ module Eventkit
6
+ RSpec.describe EventLoop do
7
+ include AsyncHelper
8
+
9
+ let!(:event_loop) { EventLoop.new(select_interval: 1 / 100_000) }
10
+
11
+ let!(:tcp_server) { TCPServer.new('localhost', 9595) }
12
+
13
+ let!(:another_tcp_server) { TCPServer.new('localhost', 9494) }
14
+
15
+ let!(:tcp_socket) { TCPSocket.new('localhost', 9595) }
16
+
17
+ let!(:another_tcp_socket) { TCPSocket.new('localhost', 9494) }
18
+
19
+ after do
20
+ tcp_server.close
21
+ another_tcp_server.close
22
+ tcp_socket.close
23
+ another_tcp_socket.close
24
+ end
25
+
26
+ it 'allows to start and stop the event loop' do
27
+ listener = double(ready_to_write: nil)
28
+
29
+ handler = listener.method(:ready_to_write).to_proc
30
+
31
+ event_loop.register_write(tcp_socket, &handler)
32
+
33
+ Thread.new { event_loop.stop }
34
+
35
+ event_loop.start
36
+
37
+ eventually do
38
+ expect(listener).to have_received(:ready_to_write)
39
+ end
40
+ end
41
+
42
+ it 'does not allow to start the event loop once it has started' do
43
+ expect do
44
+ listener = double(:listener)
45
+
46
+ allow(listener).to receive(:handle_event) do |_io|
47
+ event_loop.start
48
+ end
49
+
50
+ event_loop.register_write(tcp_socket, &listener.method(:handle_event))
51
+
52
+ event_loop.start
53
+ end.to raise_error(EventLoopAlreadyStartedError)
54
+ end
55
+
56
+ it 'allows to restart the event loop' do
57
+ expect do
58
+ listener = double(:listener)
59
+
60
+ allow(listener).to receive(:handle_event) do |_io|
61
+ event_loop.stop
62
+ event_loop.start
63
+ end
64
+
65
+ event_loop.register_write(tcp_socket, &listener.method(:handle_event))
66
+
67
+ event_loop.start
68
+ end.not_to raise_error
69
+ end
70
+
71
+ it 'notifies when a single read operation is ready' do
72
+ fake_server = double(to_io: tcp_server, connection_read_ready: nil)
73
+
74
+ event_loop.register_read(fake_server, &fake_server.method(:connection_read_ready))
75
+
76
+ event_loop.tick
77
+
78
+ expect(fake_server).to have_received(:connection_read_ready).once.with(fake_server)
79
+ end
80
+
81
+ it 'notifies when a single write operation is ready' do
82
+ fake_socket = double(to_io: tcp_socket, connection_write_ready: nil)
83
+
84
+ event_loop.register_write(fake_socket, &fake_socket.method(:connection_write_ready))
85
+
86
+ event_loop.tick
87
+
88
+ expect(fake_socket).to have_received(:connection_write_ready).once.with(fake_socket)
89
+ end
90
+
91
+ it 'notifies when multiple write operations are ready' do
92
+ fake_socket = double(to_io: tcp_socket, connection_write_ready: nil)
93
+ another_fake_socket = double(to_io: another_tcp_socket, ready_to_write: nil)
94
+
95
+ event_loop.register_write(fake_socket, &fake_socket.method(:connection_write_ready))
96
+ event_loop.register_write(another_fake_socket, &another_fake_socket.method(:ready_to_write))
97
+
98
+ event_loop.tick
99
+
100
+ expect(fake_socket).to have_received(:connection_write_ready).once.with(fake_socket)
101
+ expect(another_fake_socket).to have_received(:ready_to_write).once.with(another_fake_socket)
102
+ end
103
+
104
+ it 'notifies when multiple read operations are ready' do
105
+ fake_server = double(to_io: tcp_server, connection_read_ready: nil)
106
+ another_fake_server = double(to_io: another_tcp_server, new_connection: nil)
107
+
108
+ event_loop.register_read(fake_server, &fake_server.method(:connection_read_ready))
109
+ event_loop.register_read(another_fake_server, &another_fake_server.method(:new_connection))
110
+
111
+ event_loop.tick
112
+
113
+ expect(fake_server).to have_received(:connection_read_ready).once.with(fake_server)
114
+ expect(another_fake_server).to have_received(:new_connection).once.with(another_fake_server)
115
+ end
116
+
117
+ it 'allows an object to register reads on multiple io objects' do
118
+ listener = double(connection_read_ready: nil, another_connection: nil)
119
+
120
+ event_loop.register_read(tcp_server, &listener.method(:connection_read_ready))
121
+ event_loop.register_read(another_tcp_server, &listener.method(:another_connection))
122
+
123
+ expect(listener).to receive(:connection_read_ready).once.with(tcp_server)
124
+ expect(listener).to receive(:another_connection).once.with(another_tcp_server)
125
+
126
+ event_loop.tick
127
+ end
128
+
129
+ it 'allows an object to register writes on multiple io objects' do
130
+ listener = double(one_ready_to_write: nil, another_ready_to_write: nil)
131
+
132
+ event_loop.register_write(tcp_socket, &listener.method(:one_ready_to_write))
133
+ event_loop.register_write(another_tcp_socket, &listener.method(:another_ready_to_write))
134
+
135
+ expect(listener).to receive(:one_ready_to_write).once.with(tcp_socket)
136
+ expect(listener).to receive(:another_ready_to_write).once.with(another_tcp_socket)
137
+
138
+ event_loop.tick
139
+ end
140
+
141
+ it 'allows to register multiple read handlers on a single io object' do
142
+ listener = double(connection_read_ready: nil)
143
+ another_listener = double(new_connection: nil)
144
+
145
+ event_loop.register_read(tcp_server, &listener.method(:connection_read_ready))
146
+ event_loop.register_read(tcp_server, &another_listener.method(:new_connection))
147
+
148
+ expect(listener).to receive(:connection_read_ready).once.with(tcp_server)
149
+ expect(another_listener).to receive(:new_connection).once.with(tcp_server)
150
+
151
+ event_loop.tick
152
+ end
153
+
154
+ it 'allows to register multiple write handlers on a single io object' do
155
+ listener = double(connection_write_ready: nil)
156
+ another_listener = double(ready_to_write: nil)
157
+
158
+ event_loop.register_write(tcp_socket, &listener.method(:connection_write_ready))
159
+ event_loop.register_write(tcp_socket, &another_listener.method(:ready_to_write))
160
+
161
+ expect(listener).to receive(:connection_write_ready).once.with(tcp_socket)
162
+ expect(another_listener).to receive(:ready_to_write).once.with(tcp_socket)
163
+
164
+ event_loop.tick
165
+ end
166
+
167
+ it 'allows to deregister read handlers' do
168
+ listener = double(connection_read_ready: nil)
169
+ handler = listener.method(:connection_read_ready).to_proc
170
+
171
+ event_loop.register_read(tcp_server, &handler)
172
+ event_loop.deregister_read(tcp_server, &handler)
173
+
174
+ expect(listener).not_to receive(:connection_read_ready)
175
+
176
+ event_loop.tick
177
+ end
178
+
179
+ it 'allows to deregister write handlers' do
180
+ listener = double(connection_write_ready: nil)
181
+
182
+ handler = listener.method(:connection_write_ready).to_proc
183
+
184
+ event_loop.register_write(tcp_socket, &handler)
185
+ event_loop.deregister_write(tcp_socket, handler)
186
+
187
+ expect(listener).not_to receive(:connection_write_ready)
188
+
189
+ event_loop.tick
190
+ end
191
+
192
+ it 'deregisters all write handlers for an io object' do
193
+ listener = double(connection_write_ready: nil, another_write_event: nil)
194
+
195
+ first_handler = listener.method(:connection_write_ready).to_proc
196
+ second_handler = listener.method(:another_write_event).to_proc
197
+
198
+ event_loop.register_write(tcp_socket, &first_handler)
199
+ event_loop.register_write(tcp_socket, &second_handler)
200
+
201
+ event_loop.deregister_write(tcp_socket)
202
+
203
+ expect(listener).not_to receive(:connection_write_ready)
204
+ expect(listener).not_to receive(:another_event)
205
+
206
+ event_loop.tick
207
+ end
208
+
209
+ it 'deregisters all read handlers for an io object' do
210
+ listener = double(connection_read_ready: nil, another_read_event: nil)
211
+
212
+ first_handler = listener.method(:connection_read_ready).to_proc
213
+ second_handler = listener.method(:another_read_event).to_proc
214
+
215
+ connection = tcp_server.accept
216
+ connection.write('hello world')
217
+
218
+ event_loop.register_read(tcp_socket, &first_handler)
219
+ event_loop.register_read(tcp_socket, &second_handler)
220
+
221
+ event_loop.deregister_read(tcp_socket)
222
+
223
+ expect(listener).not_to receive(:connection_read_ready)
224
+ expect(listener).not_to receive(:another_event)
225
+
226
+ event_loop.tick
227
+ end
228
+
229
+ it 'allows to register timers which will executed in order' do
230
+ listener = double(timer_expired_a: nil,
231
+ timer_expired_b: nil,
232
+ timer_expired_c: nil,
233
+ timer_expired_d: nil)
234
+
235
+ event_loop.register_timer(run_in: 2, &listener.method(:timer_expired_a))
236
+
237
+ event_loop.register_timer(run_in: 3, &listener.method(:timer_expired_b))
238
+
239
+ event_loop.register_timer(run_in: 1, &listener.method(:timer_expired_c))
240
+
241
+ event_loop.register_timer(run_in: 5, &listener.method(:timer_expired_d))
242
+
243
+ sleep(3.1)
244
+
245
+ event_loop.tick
246
+
247
+ expect(listener).to have_received(:timer_expired_c).ordered.once
248
+ expect(listener).to have_received(:timer_expired_a).ordered.once
249
+ expect(listener).to have_received(:timer_expired_b).ordered.once
250
+ expect(listener).to_not have_received(:timer_expired_d)
251
+ end
252
+
253
+ it 'deregister timers as soon as they have expired' do
254
+ listener = double(timer_expired: nil)
255
+
256
+ event_loop.register_timer(run_in: 1, &listener.method(:timer_expired))
257
+
258
+ sleep(2)
259
+
260
+ event_loop.tick
261
+ event_loop.tick
262
+
263
+ expect(listener).to have_received(:timer_expired).once
264
+ end
265
+
266
+ it 'allows to schedule code to be run on the next tick' do
267
+ listener = double(on_next_tick: nil)
268
+
269
+ event_loop.on_next_tick(&listener.method(:on_next_tick))
270
+
271
+ event_loop.tick
272
+
273
+ expect(listener).to have_received(:on_next_tick).once
274
+ end
275
+ end
276
+ end
277
+
@@ -0,0 +1,30 @@
1
+ RSpec.configure do |config|
2
+ # rspec-expectations config goes here. You can use an alternate
3
+ # assertion/expectation library such as wrong or the stdlib/minitest
4
+ # assertions if you prefer.
5
+ config.expect_with :rspec do |expectations|
6
+ # This option will default to `true` in RSpec 4. It makes the `description`
7
+ # and `failure_message` of custom matchers include text for helper methods
8
+ # defined using `chain`, e.g.:
9
+ # be_bigger_than(2).and_smaller_than(4).description
10
+ # # => "be bigger than 2 and smaller than 4"
11
+ # ...rather than:
12
+ # # => "be bigger than 2"
13
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
14
+ end
15
+
16
+ # rspec-mocks config goes here. You can use an alternate test double
17
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
18
+ config.mock_with :rspec do |mocks|
19
+ # Prevents you from mocking or stubbing a method that does not exist on
20
+ # a real object. This is generally recommended, and will default to
21
+ # `true` in RSpec 4.
22
+ mocks.verify_partial_doubles = true
23
+ end
24
+
25
+ config.filter_run :focus
26
+ config.run_all_when_everything_filtered = true
27
+ config.disable_monkey_patching!
28
+ config.order = :random
29
+ config.warnings = true
30
+ end
@@ -0,0 +1,40 @@
1
+ module AsyncHelper
2
+ # Instead of waiting an specific an amount of time and then
3
+ # asserting for a certain behaviour, this test helper polls for an assertion
4
+ # success every X number of seconds (configurable as interval).
5
+ # The test will fail if the assertion doesn't pass after Y number of
6
+ # seconds (configurable as timeout).
7
+ def eventually(options = {})
8
+ timeout = options[:timeout] || 1 # seconds
9
+ interval = options[:interval] || 0.0001 # seconds
10
+ time_limit = Time.now + timeout
11
+
12
+ loop do
13
+ begin
14
+ yield
15
+ rescue RSpec::Expectations::ExpectationNotMetError => error
16
+
17
+ end
18
+
19
+ return if error.nil?
20
+
21
+ fail error if Time.now >= time_limit
22
+ sleep interval
23
+ end
24
+ end
25
+
26
+ # Same behaviour as eventually, but decided to change the method name to make
27
+ # it more clear that this is focused on 'synchronizing' the test with the application
28
+ def wait_until(options = {}, &block)
29
+ eventually(options, &block)
30
+ end
31
+
32
+ # Small wrapper around sleep
33
+ def on_timeout(options = {})
34
+ timeout = options[:timeout] || 0.5
35
+
36
+ sleep timeout
37
+
38
+ yield
39
+ end
40
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: eventkit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Oliver Martell
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-05-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '10.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '10.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 3.2.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 3.2.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.10.1
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.10.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry-doc
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.6.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.6.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: method_source
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.8.2
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.8.2
83
+ - !ruby/object:Gem::Dependency
84
+ name: eventkit-promise
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.1.0
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.1.0
97
+ description: |-
98
+ Experimental toolkit for asynchronous event driven applications, which includes
99
+ an event loop to perform non blocking IO and a promises A+ implementation.
100
+ email:
101
+ - oliver.martell@gmail.com
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - ".gitignore"
107
+ - ".rspec"
108
+ - ".ruby-version"
109
+ - ".travis.yml"
110
+ - Gemfile
111
+ - LICENSE.txt
112
+ - README.md
113
+ - Rakefile
114
+ - eventkit.gemspec
115
+ - lib/eventkit.rb
116
+ - lib/eventkit/event_loop.rb
117
+ - lib/eventkit/timer.rb
118
+ - lib/eventkit/version.rb
119
+ - spec/eventkit/event_loop_spec.rb
120
+ - spec/spec_helper.rb
121
+ - spec/support/async_helper.rb
122
+ homepage: ''
123
+ licenses:
124
+ - MIT
125
+ metadata: {}
126
+ post_install_message:
127
+ rdoc_options: []
128
+ require_paths:
129
+ - lib
130
+ required_ruby_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ required_rubygems_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ requirements: []
141
+ rubyforge_project:
142
+ rubygems_version: 2.4.5
143
+ signing_key:
144
+ specification_version: 4
145
+ summary: Experimental toolkit for asynchronous event driven applications
146
+ test_files:
147
+ - spec/eventkit/event_loop_spec.rb
148
+ - spec/spec_helper.rb
149
+ - spec/support/async_helper.rb
150
+ has_rdoc: