eventkit 0.0.1

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 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: