-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathevent_machine_helper.rb
161 lines (134 loc) · 4.61 KB
/
event_machine_helper.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
require 'eventmachine'
require 'rspec'
require 'timeout'
module RSpec
module EventMachine
extend self
DEFAULT_TIMEOUT = 15
def run_reactor(timeout = DEFAULT_TIMEOUT)
@reactor_stopping = false
Timeout::timeout(timeout + 0.5) do
::EventMachine.run do
yield
end
end
end
def reactor_stopping?
@reactor_stopping
end
def stop_reactor
mark_reactor_stopping
unless realtime_clients.empty?
realtime_clients.shift.tap do |client|
# Ensure close appens outside of the caller as this can cause errbacks on Deferrables
# e.g. connection.connect { connection.close } => # Error as calling close within the connected callback
::EventMachine.add_timer(0.05) do
client.close if client.connection.can_transition_to?(:closing)
::EventMachine.add_timer(0.1) { stop_reactor }
end
end
return
end
::EventMachine.next_tick do
::EventMachine.stop
end
end
# Ensures that any clients used in tests will have their connections
# explicitly closed when stop_reactor is called
def auto_close(realtime_client)
realtime_clients << realtime_client
realtime_client
end
def realtime_clients
@realtime_clients ||= []
end
def mark_reactor_stopping
@reactor_stopping = true
end
# Allows multiple Deferrables to be passed in and calls the provided block when
# all success callbacks have completed
def when_all(*deferrables)
raise ArgumentError, 'Block required' unless block_given?
options = if deferrables.last.kind_of?(Hash)
deferrables.pop
else
{}
end
successful_deferrables = {}
deferrables.each do |deferrable|
deferrable.callback do
successful_deferrables[deferrable.object_id] = true
if successful_deferrables.keys.sort == deferrables.map(&:object_id).sort
if options[:and_wait]
::EventMachine.add_timer(options[:and_wait]) { yield }
else
yield
end
end
end
deferrable.errback do |error|
raise RuntimeError, "Error: Deferrable failed: #{error}"
end
end
end
def wait_until(condition_block, &block)
raise ArgumentError, 'Block required' unless block_given?
if condition_block.call
yield
else
::EventMachine.add_timer(0.1) do
wait_until condition_block, &block
end
end
end
end
end
RSpec.configure do |config|
config.before(:context, :event_machine) do |context|
context.class.class_eval do
include RSpec::EventMachine
end
end
# Run the test block wrapped in an EventMachine reactor that has a configured timeout.
# As RSpec does not provide an API to wrap blocks, accessing the instance variables is required.
# Note, if you start a reactor and simply run the example with example#run then the example
# will run and not wait for the reactor to stop thus triggering after callbacks prematurely.
#
config.around(:example, :event_machine) do |example|
timeout = if example.metadata[:em_timeout].is_a?(Numeric)
example.metadata[:em_timeout]
else
RSpec::EventMachine::DEFAULT_TIMEOUT
end
example_block = example.example.instance_variable_get('@example_block')
example_group_instance = example.example.instance_variable_get('@example_group_instance')
event_machine_block = lambda do |*args|
RSpec::EventMachine.run_reactor(timeout) do
example_group_instance.instance_exec(example, &example_block)
end
end
example.example.instance_variable_set('@example_block', event_machine_block)
example.run
end
config.before(:example, :event_machine) do
# Ensure EventMachine shutdown hooks are deregistered for every test
EventMachine.instance_variable_set '@tails', []
end
end
module RSpec
module Expectations
module ExpectationHelper
class << self
# This is very hacky and ties into the internals of RSpec which is likely to break in future versions
# However, this is just a convenience to reduce log noise when the reactor is stopping
# i.e. debug_failure_helper logs the verbose messages generated by the libraries, however it also often
# catches all the shutdown messages which is unnecessary
alias_method :orig_handle_failure, :handle_failure
def handle_failure(*args, &block)
RSpec::EventMachine.mark_reactor_stopping
orig_handle_failure(*args, &block)
end
end
end
end
end