exceptional_synchrony 1.0.1 → 1.3.0

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.
@@ -1,4 +1,3 @@
1
- require 'hobo_support'
2
1
  require 'em-synchrony'
3
2
  require 'em-synchrony/em-http'
4
3
  require 'exception_handling'
@@ -21,13 +21,13 @@ module ExceptionalSynchrony
21
21
  result
22
22
  end
23
23
  when :failed
24
- if result.respond_to?(:error) && result.error =~ /timeout/i
25
- raise Timeout::Error
24
+ if result.respond_to?(:error)
25
+ handle_result_error(result)
26
26
  else
27
- raise Failure, result.inspect
27
+ raise_failure_for_result(result)
28
28
  end
29
29
  else
30
- raise ArgumentError, "No deferred status set yet: #{deferred_status.inspect} #{result.inspect}"
30
+ raise ArgumentError, "No deferred status set yet: #{deferred_status.inspect} #{truncated_inspect(result)}"
31
31
  end
32
32
  end
33
33
 
@@ -38,6 +38,38 @@ module ExceptionalSynchrony
38
38
  ex
39
39
  end
40
40
  end
41
+
42
+ private
43
+
44
+ def handle_result_error(result)
45
+ error = result.error
46
+ if error_is_a_timeout?(error)
47
+ raise Timeout::Error
48
+ else
49
+ raise_failure_for_result(result, error: error)
50
+ end
51
+ end
52
+
53
+ def raise_failure_for_result(result, error: nil)
54
+ result_string = truncated_inspect(result)
55
+ error_string = if error
56
+ "ERROR = #{truncated_inspect(error)}; "
57
+ end
58
+ raise Failure, "#{error_string}RESULT = #{result_string}"
59
+ end
60
+
61
+ def truncated_inspect(obj)
62
+ inspection = obj.inspect[0, 101]
63
+ if inspection.length > 100
64
+ inspection[0, 92] + '...TRUNC'
65
+ else
66
+ inspection
67
+ end
68
+ end
69
+
70
+ def error_is_a_timeout?(error)
71
+ error =~ /timeout/i || error == Errno::ETIMEDOUT
72
+ end
41
73
  end
42
74
  end
43
75
  end
@@ -5,6 +5,10 @@ require 'em-http'
5
5
  require 'em-synchrony/em-http'
6
6
 
7
7
  module ExceptionalSynchrony
8
+ # It is important for this exception to be inherited from Exception so that
9
+ # when thrown it does not get caught by the EventMachine.error_handler.
10
+ class FatalRunError < Exception; end
11
+
8
12
  class EventMachineProxy
9
13
 
10
14
  attr_reader :connection
@@ -41,6 +45,12 @@ module ExceptionalSynchrony
41
45
  @synchrony.sleep(seconds)
42
46
  end
43
47
 
48
+ def yield_to_reactor
49
+ if reactor_running?
50
+ @synchrony.sleep(0)
51
+ end
52
+ end
53
+
44
54
  def next_tick(&block)
45
55
  @synchrony.next_tick do
46
56
  ensure_completely_safe("next_tick") do
@@ -54,17 +64,39 @@ module ExceptionalSynchrony
54
64
  @proxy_class.next_tick { } #Fake out EventMachine's epoll mechanism so we don't block until timers fire
55
65
  end
56
66
 
67
+ def defers_finished?
68
+ @proxy_class.defers_finished?
69
+ end
70
+
57
71
  def connect(server, port = nil, handler = nil, *args, &block)
58
72
  @proxy_class.connect(server, port, handler, *args, &block)
59
73
  end
60
74
 
61
- def run(&block)
62
- ensure_completely_safe("run") do
63
- if @proxy_class.respond_to?(:synchrony)
64
- @proxy_class.synchrony(&block)
65
- else
66
- @proxy_class.run(&block)
67
- end
75
+ # The on_error option has these possible values:
76
+ # :log - log any rescued StandardError exceptions and continue
77
+ # :raise - raise FatalRunError for any rescued StandardError exceptions
78
+ def run(on_error: :log, &block)
79
+ case on_error
80
+ when :log then run_with_error_logging(&block)
81
+ when :raise then run_with_error_raising(&block)
82
+ else raise ArgumentError, "Invalid on_error: #{on_error.inspect}, must be :log or :raise"
83
+ end
84
+ end
85
+
86
+ # This method will execute the block on the background thread pool
87
+ # By default, it will block the caller until the background thread has finished, so that the result can be returned
88
+ # :wait_for_result - setting this to false will prevent the caller from being blocked by this deferred work
89
+ def defer(context, wait_for_result: true, &block)
90
+ if wait_for_result
91
+ deferrable = EventMachine::DefaultDeferrable.new
92
+ callback = -> (result) { deferrable.succeed(result) }
93
+
94
+ EventMachine.defer(nil, callback) { CallbackExceptions.return_exception(&block) }
95
+ EventMachine::Synchrony.sync(deferrable)
96
+ CallbackExceptions.map_deferred_result(deferrable)
97
+ else
98
+ EventMachine.defer { ExceptionHandling.ensure_completely_safe("defer", &block) }
99
+ nil
68
100
  end
69
101
  end
70
102
 
@@ -90,6 +122,38 @@ module ExceptionalSynchrony
90
122
  yield
91
123
  end
92
124
  end
125
+
126
+ def rescue_exceptions_and_ensure_exit(context)
127
+ yield
128
+ rescue StandardError => ex
129
+ # Raise a non-StandardError so that not caught by EM.error_handler.
130
+ # Expecting rescued exception to be stored in this new exception's cause.
131
+ raise FatalRunError, "Fatal EventMachine #{context} error\n#{ex.class.name}: #{ex.message}"
132
+ end
133
+
134
+ private
135
+
136
+ def run_with_error_logging(&block)
137
+ ensure_completely_safe("run_with_error_logging") do
138
+ if @proxy_class.respond_to?(:synchrony)
139
+ @proxy_class.synchrony(&block)
140
+ else
141
+ @proxy_class.run(&block)
142
+ end
143
+ end
144
+ end
145
+
146
+ def run_with_error_raising(&block)
147
+ run_block = -> { rescue_exceptions_and_ensure_exit("run_with_error_raising", &block) }
148
+
149
+ rescue_exceptions_and_ensure_exit("run_with_error_raising") do
150
+ if @proxy_class.respond_to?(:synchrony)
151
+ @proxy_class.synchrony(&run_block)
152
+ else
153
+ @proxy_class.run(&run_block)
154
+ end
155
+ end
156
+ end
93
157
  end
94
158
 
95
159
  EMP = EventMachineProxy.new(EventMachine, EventMachine::HttpRequest)
@@ -1,15 +1,17 @@
1
1
  module ExceptionalSynchrony
2
2
  class LimitedWorkQueue
3
+
3
4
  def initialize(em, limit)
4
5
  @em = em
5
6
  limit > 0 or raise ArgumentError, "limit must be positive"
6
7
  @limit = limit
7
8
  @worker_count = 0
8
9
  @job_procs = []
10
+ @paused = false
9
11
  end
10
12
 
11
13
  # Adds a job_proc to work.
12
- def add(proc = nil, &block)
14
+ def add!(proc = nil, &block)
13
15
  job = proc || block
14
16
  job.respond_to?(:call) or raise "Must respond_to?(:call)! #{job.inspect}"
15
17
  if @job_procs.any? && job.respond_to?(:merge) && (merged_queue = job.merge(@job_procs))
@@ -17,8 +19,9 @@ module ExceptionalSynchrony
17
19
  else
18
20
  @job_procs << job
19
21
  end
20
- work!
22
+ work! unless paused?
21
23
  end
24
+ alias_method :add, :add!
22
25
 
23
26
  def workers_empty?
24
27
  @worker_count.zero?
@@ -32,18 +35,39 @@ module ExceptionalSynchrony
32
35
  @job_procs.empty?
33
36
  end
34
37
 
35
- private
38
+ def paused?
39
+ @paused
40
+ end
41
+
42
+ def pause!
43
+ @paused = true
44
+ end
45
+
46
+ def unpause!
47
+ @paused = false
48
+ end
49
+
50
+ def items
51
+ @job_procs
52
+ end
53
+
36
54
  def work!
37
55
  until queue_empty? || workers_full?
38
56
  job_proc = @job_procs.shift
39
57
  @worker_count += 1
40
58
  Fiber.new do
41
- job_proc.call
42
- worker_done
59
+ begin
60
+ job_proc.call
61
+ rescue => ex
62
+ ExceptionHandling.log_error(ex, "LimitedWorkQueue encountered an exception")
63
+ ensure
64
+ worker_done
65
+ end
43
66
  end.resume
44
67
  end
45
68
  end
46
69
 
70
+ private
47
71
  def worker_done
48
72
  @worker_count -= 1
49
73
  work!
@@ -1,5 +1,6 @@
1
1
  require 'fiber'
2
2
  require 'set'
3
+ require 'invoca/utils/hash'
3
4
 
4
5
  module ExceptionalSynchrony
5
6
  class ParallelSync
@@ -1,3 +1,3 @@
1
1
  module ExceptionalSynchrony
2
- VERSION = '1.0.1'
2
+ VERSION = '1.3.0'
3
3
  end
data/test/test_helper.rb CHANGED
@@ -1,11 +1,23 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+
3
+ require 'bundler'
4
+ Bundler.setup(:default, :development)
5
+
1
6
  require_relative '../lib/exceptional_synchrony.rb'
2
7
 
3
8
  require 'minitest/autorun' or raise "Already loaded minitest?"
4
9
  require 'minitest/pride'
10
+ require 'minitest/reporters'
11
+ Minitest::Reporters.use! [
12
+ Minitest::Reporters::DefaultReporter.new,
13
+ Minitest::Reporters::JUnitReporter.new
14
+ ]
5
15
  require 'webmock'
6
16
  require 'webmock/minitest'
7
17
  require 'rr'
8
18
 
19
+ ActiveSupport::TestCase.test_order = :sorted
20
+
9
21
  module TestHelper
10
22
  @@constant_overrides = []
11
23
 
@@ -1,4 +1,5 @@
1
1
  require_relative '../test_helper'
2
+ require 'pry'
2
3
 
3
4
  describe ExceptionalSynchrony::CallbackExceptions do
4
5
  describe "ensure_callback" do
@@ -60,14 +61,14 @@ describe ExceptionalSynchrony::CallbackExceptions do
60
61
  deferrable = EM::DefaultDeferrable.new
61
62
  deferrable.succeed(12)
62
63
  result = ExceptionalSynchrony::CallbackExceptions.map_deferred_result(deferrable)
63
- result.must_equal 12
64
+ expect(result).must_equal(12)
64
65
  end
65
66
 
66
67
  it "should map success values to an array" do
67
68
  deferrable = EM::DefaultDeferrable.new
68
69
  deferrable.succeed(12, 13, 14)
69
70
  result = ExceptionalSynchrony::CallbackExceptions.map_deferred_result(deferrable)
70
- result.must_equal [12, 13, 14]
71
+ expect(result).must_equal([12, 13, 14])
71
72
  end
72
73
 
73
74
  it "should map success exception values to raise" do
@@ -77,7 +78,7 @@ describe ExceptionalSynchrony::CallbackExceptions do
77
78
  result = assert_raises(ArgumentError) do
78
79
  ExceptionalSynchrony::CallbackExceptions.map_deferred_result(deferrable)
79
80
  end
80
- result.must_equal exception
81
+ expect(result).must_equal(exception)
81
82
  end
82
83
  end
83
84
 
@@ -87,8 +88,18 @@ describe ExceptionalSynchrony::CallbackExceptions do
87
88
  deferrable.fail(first: "a", last: "b")
88
89
  result = assert_raises(ExceptionalSynchrony::CallbackExceptions::Failure) do
89
90
  ExceptionalSynchrony::CallbackExceptions.map_deferred_result(deferrable)
90
- end
91
- result.message.must_equal "{:first=>\"a\", :last=>\"b\"}"
91
+ end.message
92
+ expect(result).must_equal("RESULT = {:first=>\"a\", :last=>\"b\"}")
93
+ end
94
+
95
+ it "should truncate long failures" do
96
+ deferrable = EM::DefaultDeferrable.new
97
+ deferrable.fail('a'*75 + 'b'*75)
98
+ result = assert_raises(ExceptionalSynchrony::CallbackExceptions::Failure) do
99
+ ExceptionalSynchrony::CallbackExceptions.map_deferred_result(deferrable)
100
+ end.message
101
+ expected_message = "RESULT = \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbb...TRUNC"
102
+ expect(result).must_equal(expected_message)
92
103
  end
93
104
 
94
105
  it "should map failure exceptions to raise" do
@@ -97,9 +108,9 @@ describe ExceptionalSynchrony::CallbackExceptions do
97
108
  deferrable.fail(exception)
98
109
  result = assert_raises(ExceptionalSynchrony::CallbackExceptions::Failure) do
99
110
  ExceptionalSynchrony::CallbackExceptions.map_deferred_result(deferrable)
100
- end
101
- result.message.must_match /ArgumentError/
102
- result.message.must_match /Wrong argument!/
111
+ end.message
112
+ expect(result).must_match(/ArgumentError/)
113
+ expect(result).must_match(/Wrong argument!/)
103
114
  end
104
115
 
105
116
  it "should map timeout failure to raise TimeoutError" do
@@ -114,6 +125,47 @@ describe ExceptionalSynchrony::CallbackExceptions do
114
125
  ExceptionalSynchrony::CallbackExceptions.map_deferred_result(deferrable)
115
126
  end
116
127
  end
128
+
129
+ it "should map Errno::ETIMEDOUT to TimeoutError" do
130
+ deferrable = EM::DefaultDeferrable.new
131
+
132
+ def deferrable.error
133
+ Errno::ETIMEDOUT
134
+ end
135
+
136
+ deferrable.fail(deferrable)
137
+ assert_raises(Timeout::Error) do
138
+ ExceptionalSynchrony::CallbackExceptions.map_deferred_result(deferrable)
139
+ end
140
+ end
141
+
142
+ it "should map other SystemCallError exceptions to Failures with the error in the message" do
143
+ deferrable = EM::DefaultDeferrable.new
144
+
145
+ def deferrable.error
146
+ Errno::ECONNREFUSED
147
+ end
148
+
149
+ deferrable.fail(deferrable)
150
+ result = assert_raises(ExceptionalSynchrony::CallbackExceptions::Failure) do
151
+ ExceptionalSynchrony::CallbackExceptions.map_deferred_result(deferrable)
152
+ end
153
+ expect(result.message).must_match(/\AERROR = Errno::ECONNREFUSED; RESULT = #<EventMachine::DefaultDeferrable/)
154
+ end
155
+
156
+ it "should map any other errors to Failure with the error in the message" do
157
+ deferrable = EM::DefaultDeferrable.new
158
+
159
+ def deferrable.error
160
+ ArgumentError.new("Some errror")
161
+ end
162
+
163
+ deferrable.fail(deferrable)
164
+ result = assert_raises(ExceptionalSynchrony::CallbackExceptions::Failure) do
165
+ ExceptionalSynchrony::CallbackExceptions.map_deferred_result(deferrable)
166
+ end
167
+ expect(result.message).must_match(/\AERROR = #<ArgumentError: Some errror>; RESULT = #<EventMachine::DefaultDeferrable/)
168
+ end
117
169
  end
118
170
 
119
171
  describe "no status" do
@@ -122,29 +174,29 @@ describe ExceptionalSynchrony::CallbackExceptions do
122
174
  result = assert_raises(ArgumentError) do
123
175
  ExceptionalSynchrony::CallbackExceptions.map_deferred_result(deferrable)
124
176
  end
125
- result.message.must_match /no deferred status set yet/i
177
+ expect(result.message).must_match(/no deferred status set yet/i)
126
178
  end
127
179
  end
128
180
  end
129
181
 
130
182
  describe "return_exception" do
131
183
  it "should return the value if no exception" do
132
- ExceptionalSynchrony::CallbackExceptions.return_exception do
184
+ expect(ExceptionalSynchrony::CallbackExceptions.return_exception do
133
185
  14
134
- end.must_equal 14
186
+ end).must_equal(14)
135
187
  end
136
188
 
137
189
  it "should yield its args" do
138
- ExceptionalSynchrony::CallbackExceptions.return_exception(0, 1) do |a, b|
190
+ expect(ExceptionalSynchrony::CallbackExceptions.return_exception(0, 1) do |a, b|
139
191
  assert_equal [0, 1], [a, b]
140
192
  14
141
- end.must_equal 14
193
+ end).must_equal(14)
142
194
  end
143
195
 
144
196
  it "should rescue any exception that was raised and return it" do
145
- ExceptionalSynchrony::CallbackExceptions.return_exception do
197
+ expect(ExceptionalSynchrony::CallbackExceptions.return_exception do
146
198
  raise ArgumentError, "An argument error occurred"
147
- end.inspect.must_equal ArgumentError.new("An argument error occurred").inspect
199
+ end.inspect).must_equal(ArgumentError.new("An argument error occurred").inspect)
148
200
  end
149
201
  end
150
202
  end
@@ -3,10 +3,39 @@ require_relative '../test_helper'
3
3
  describe ExceptionalSynchrony::EventMachineProxy do
4
4
  include TestHelper
5
5
 
6
+ class RunProxyMock
7
+ class << self
8
+ def run(&block)
9
+ block.call
10
+ :run
11
+ end
12
+
13
+ def error_handler
14
+ end
15
+ end
16
+ end
17
+
18
+ class SynchronyProxyMock < RunProxyMock
19
+ class << self
20
+ def synchrony(&block)
21
+ block.call
22
+ :synchrony
23
+ end
24
+ end
25
+ end
26
+
27
+ def stop_em_after_defers_finish!(em)
28
+ check_finished_counter = 0
29
+ em.add_periodic_timer(0.1) do
30
+ (check_finished_counter += 1) > 20 and raise "defer never finished!"
31
+ em.defers_finished? and em.stop
32
+ end
33
+ end
34
+
6
35
  before do
7
36
  @em = ExceptionalSynchrony::EventMachineProxy.new(EventMachine, nil)
8
37
  @yielded_value = nil
9
- @block = lambda { |value| @yielded_value = value }
38
+ @block = -> (value) { @yielded_value = value }
10
39
  end
11
40
 
12
41
  it "should proxy add_timer" do
@@ -39,7 +68,68 @@ describe ExceptionalSynchrony::EventMachineProxy do
39
68
  ServerClass = Class.new
40
69
  mock(EventMachine).connect(ServerClass, 8080, :handler, :extra_arg).yields(:called)
41
70
  @em.connect(ServerClass, 8080, :handler, :extra_arg, &@block)
42
- @yielded_value.must_equal :called
71
+ expect(@yielded_value).must_equal(:called)
72
+ end
73
+
74
+ describe "#yield_to_reactor" do
75
+ it "should give control to other threads when the reactor is running" do
76
+ mock(@em).reactor_running? { true }
77
+ mock(EventMachine::Synchrony).sleep(0)
78
+ @em.yield_to_reactor
79
+ end
80
+
81
+ it "should be a no-op if the reactor is not running" do
82
+ mock(@em).reactor_running? { false }
83
+ stub(EventMachine::Synchrony).sleep(0) { raise "Should not sleep!" }
84
+ @em.yield_to_reactor
85
+ end
86
+ end
87
+
88
+ describe "#defer" do
89
+ before do
90
+ logger = Logger.new(STDERR)
91
+ logger.extend ContextualLogger::LoggerMixin
92
+ ExceptionHandling.logger = logger
93
+ end
94
+
95
+ it "should output its block's output when it doesn't raise an error, by default" do
96
+ @em.run do
97
+ assert_equal 12, @em.defer("#defer success") { 12 }
98
+ @em.stop
99
+ end
100
+ end
101
+
102
+ it "should not wait for its block to run if option is passed" do
103
+ @block_ran = false
104
+
105
+ @em.run do
106
+ assert_nil @em.defer("#defer success", wait_for_result: false) { @block_ran = true; 12 }
107
+ refute @block_ran
108
+ stop_em_after_defers_finish!(@em)
109
+ end
110
+
111
+ assert @block_ran
112
+ end
113
+
114
+ it "should handle exceptions when not waiting for its block to run" do
115
+ mock(ExceptionHandling).log_error(is_a(RuntimeError), "defer", {})
116
+
117
+ @em.run do
118
+ assert_nil @em.defer("#defer success", wait_for_result: false) { raise RuntimeError, "error in defer" }
119
+ stop_em_after_defers_finish!(@em)
120
+ end
121
+ end
122
+
123
+ it "should raise an error when its block raises an error" do
124
+ @em.run do
125
+ ex = assert_raises(ArgumentError) do
126
+ @em.defer("#defer raising an error") { raise ArgumentError, "!!!" }
127
+ end
128
+
129
+ assert_equal "!!!", ex.message
130
+ @em.stop
131
+ end
132
+ end
43
133
  end
44
134
 
45
135
  EXCEPTION = ArgumentError.new('in block')
@@ -50,45 +140,68 @@ describe ExceptionalSynchrony::EventMachineProxy do
50
140
  end
51
141
 
52
142
  it "add_timer" do
53
- mock(ExceptionHandling).log_error(EXCEPTION, "add_timer")
143
+ mock(ExceptionHandling).log_error(EXCEPTION, "add_timer", {})
54
144
  mock(EventMachine::Synchrony).add_timer(10) { |duration, *args| args.first.call }
55
145
  @em.add_timer(10) { raise EXCEPTION }
56
146
  end
57
147
 
58
148
  it "add_periodic_timer" do
59
- mock(ExceptionHandling).log_error(EXCEPTION, "add_periodic_timer")
149
+ mock(ExceptionHandling).log_error(EXCEPTION, "add_periodic_timer", {})
60
150
  mock(EventMachine::Synchrony).add_periodic_timer(10) { |duration, *args| args.first.call }
61
151
  @em.add_periodic_timer(10) { raise EXCEPTION }
62
152
  end
63
153
 
64
154
  it "next_tick" do
65
- mock(ExceptionHandling).log_error(EXCEPTION, "next_tick")
155
+ mock(ExceptionHandling).log_error(EXCEPTION, "next_tick", {})
66
156
  mock(EventMachine::Synchrony).next_tick { |*args| args.first.call }
67
157
  @em.next_tick { raise EXCEPTION }
68
158
  end
69
159
  end
70
160
 
71
- [false, true].each do |synchrony|
72
- describe "synchrony = #{synchrony}" do
73
- it "should dispatch to the proxy's synchrony method instead of run iff synchrony" do
74
- proxy_mock = Struct.new(:proxy, :class_connection) do
75
- if synchrony
76
- def self.synchrony(&block)
77
- block.(:synchrony)
161
+ { synchrony: SynchronyProxyMock, run: RunProxyMock }.each do |method, proxy_mock|
162
+ describe "run" do
163
+ before do
164
+ @proxy = ExceptionalSynchrony::EventMachineProxy.new(proxy_mock, nil)
165
+ end
166
+
167
+ it "should raise ArgumentError if on_error has invalid value" do
168
+ assert_raises(ArgumentError, "Invalid on_error: :ignore, must be :log or :raise") do
169
+ @proxy.run(on_error: :ignore)
170
+ end
171
+ end
172
+
173
+ describe "without error" do
174
+ [:log, :raise].each do |on_error|
175
+ describe "when using #{method} and on_error = #{on_error}" do
176
+ it "should dispatch to the proxy's synchrony method instead of run iff synchrony" do
177
+ dispatched = false
178
+ assert_equal method, (@proxy.run(on_error: on_error) { dispatched = true })
179
+ assert_equal true, dispatched
78
180
  end
79
181
  end
182
+ end
183
+ end
80
184
 
81
- def self.run(&block)
82
- block.(:run)
83
- end
185
+ describe "with error" do
186
+ before do
187
+ set_test_const('ExceptionalSynchrony::EventMachineProxy::WRAP_WITH_ENSURE_COMPLETELY_SAFE', true)
84
188
  end
85
189
 
86
- mock(proxy_mock).error_handler
190
+ describe "when using #{method} and on_error = :log" do
191
+ it "should rescue any exceptions and log them" do
192
+ mock(ExceptionHandling).log_error(EXCEPTION, "run_with_error_logging", {})
87
193
 
88
- proxy = ExceptionalSynchrony::EventMachineProxy.new(proxy_mock, nil)
194
+ @proxy.run(on_error: :log) { raise EXCEPTION }
195
+ end
196
+ end
89
197
 
90
- proxy.run(&@block)
91
- @yielded_value.must_equal synchrony ? :synchrony : :run
198
+ describe "when using #{method} and on_error = :raise" do
199
+ it "should rescue any exceptions and raise FatalRunError" do
200
+ assert_raises(ExceptionalSynchrony::FatalRunError, "Fatal EventMachine run error") do
201
+ @proxy.run(on_error: :raise) { raise EXCEPTION }
202
+ end
203
+ end
204
+ end
92
205
  end
93
206
  end
94
207
  end