celluloid 0.16.0 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of celluloid might be problematic. Click here for more details.

Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +333 -0
  3. data/README.md +1 -1
  4. data/culture/CODE_OF_CONDUCT.md +28 -0
  5. data/culture/Gemfile +9 -0
  6. data/culture/README.md +22 -0
  7. data/culture/Rakefile +5 -0
  8. data/culture/SYNC.md +70 -0
  9. data/culture/celluloid-culture.gemspec +18 -0
  10. data/culture/gems/README.md +39 -0
  11. data/culture/gems/dependencies.yml +78 -0
  12. data/culture/gems/loader.rb +101 -0
  13. data/culture/rubocop/README.md +38 -0
  14. data/culture/rubocop/lint.yml +8 -0
  15. data/culture/rubocop/metrics.yml +15 -0
  16. data/culture/rubocop/rubocop.yml +4 -0
  17. data/culture/rubocop/style.yml +48 -0
  18. data/culture/spec/gems_spec.rb +2 -0
  19. data/culture/spec/spec_helper.rb +0 -0
  20. data/culture/spec/sync_spec.rb +2 -0
  21. data/culture/sync.rb +56 -0
  22. data/culture/tasks/rspec.rake +5 -0
  23. data/culture/tasks/rubocop.rake +2 -0
  24. data/examples/basic_usage.rb +49 -0
  25. data/examples/futures.rb +38 -0
  26. data/examples/ring.rb +61 -0
  27. data/examples/simple_pmap.rb +14 -0
  28. data/examples/timers.rb +72 -0
  29. data/lib/celluloid.rb +142 -127
  30. data/lib/celluloid/actor.rb +47 -41
  31. data/lib/celluloid/actor_system.rb +75 -22
  32. data/lib/celluloid/autostart.rb +1 -1
  33. data/lib/celluloid/backported.rb +2 -0
  34. data/lib/celluloid/call/async.rb +16 -0
  35. data/lib/celluloid/call/block.rb +22 -0
  36. data/lib/celluloid/call/sync.rb +70 -0
  37. data/lib/celluloid/calls.rb +25 -114
  38. data/lib/celluloid/cell.rb +32 -20
  39. data/lib/celluloid/condition.rb +3 -3
  40. data/lib/celluloid/core_ext.rb +1 -1
  41. data/lib/celluloid/current.rb +2 -0
  42. data/lib/celluloid/deprecate.rb +18 -0
  43. data/lib/celluloid/exceptions.rb +1 -1
  44. data/lib/celluloid/fiber.rb +3 -3
  45. data/lib/celluloid/future.rb +7 -6
  46. data/lib/celluloid/group.rb +65 -0
  47. data/lib/celluloid/group/manager.rb +27 -0
  48. data/lib/celluloid/group/pool.rb +125 -0
  49. data/lib/celluloid/group/spawner.rb +71 -0
  50. data/lib/celluloid/logging.rb +5 -5
  51. data/lib/celluloid/mailbox.rb +14 -13
  52. data/lib/celluloid/mailbox/evented.rb +76 -0
  53. data/lib/celluloid/notices.rb +15 -0
  54. data/lib/celluloid/proxies.rb +12 -0
  55. data/lib/celluloid/proxy/abstract.rb +24 -0
  56. data/lib/celluloid/proxy/actor.rb +46 -0
  57. data/lib/celluloid/proxy/async.rb +36 -0
  58. data/lib/celluloid/proxy/block.rb +31 -0
  59. data/lib/celluloid/proxy/cell.rb +76 -0
  60. data/lib/celluloid/proxy/future.rb +40 -0
  61. data/lib/celluloid/proxy/sync.rb +44 -0
  62. data/lib/celluloid/rspec.rb +9 -10
  63. data/lib/celluloid/system_events.rb +16 -15
  64. data/lib/celluloid/{tasks.rb → task.rb} +21 -21
  65. data/lib/celluloid/task/fibered.rb +45 -0
  66. data/lib/celluloid/task/threaded.rb +59 -0
  67. data/lib/celluloid/test.rb +1 -1
  68. data/lib/celluloid/thread.rb +6 -1
  69. data/lib/celluloid/version.rb +3 -0
  70. data/spec/celluloid/actor_spec.rb +2 -2
  71. data/spec/celluloid/actor_system_spec.rb +35 -21
  72. data/spec/celluloid/block_spec.rb +3 -5
  73. data/spec/celluloid/calls_spec.rb +33 -11
  74. data/spec/celluloid/condition_spec.rb +16 -13
  75. data/spec/celluloid/evented_mailbox_spec.rb +1 -31
  76. data/spec/celluloid/future_spec.rb +13 -10
  77. data/spec/celluloid/group/elastic_spec.rb +0 -0
  78. data/spec/celluloid/group/manager_spec.rb +0 -0
  79. data/spec/celluloid/group/pool_spec.rb +8 -0
  80. data/spec/celluloid/group/spawner_spec.rb +8 -0
  81. data/spec/celluloid/mailbox/evented_spec.rb +27 -0
  82. data/spec/celluloid/mailbox_spec.rb +1 -3
  83. data/spec/celluloid/misc/leak_spec.rb +73 -0
  84. data/spec/celluloid/task/fibered_spec.rb +5 -0
  85. data/spec/celluloid/task/threaded_spec.rb +5 -0
  86. data/spec/celluloid/timer_spec.rb +14 -16
  87. data/spec/deprecate/actor_system_spec.rb +72 -0
  88. data/spec/deprecate/block_spec.rb +52 -0
  89. data/spec/deprecate/calls_spec.rb +57 -0
  90. data/spec/deprecate/evented_mailbox_spec.rb +34 -0
  91. data/spec/deprecate/future_spec.rb +32 -0
  92. data/spec/deprecate/internal_pool_spec.rb +4 -0
  93. data/spec/shared/actor_examples.rb +1237 -0
  94. data/spec/shared/group_examples.rb +121 -0
  95. data/{lib/celluloid/rspec → spec/shared}/mailbox_examples.rb +20 -17
  96. data/{lib/celluloid/rspec → spec/shared}/task_examples.rb +9 -8
  97. data/spec/spec_helper.rb +72 -16
  98. data/spec/support/coverage.rb +4 -0
  99. data/spec/support/crash_checking.rb +68 -0
  100. data/spec/support/debugging.rb +31 -0
  101. data/spec/support/env.rb +16 -0
  102. data/{lib/celluloid/rspec/example_actor_class.rb → spec/support/examples/actor_class.rb} +21 -2
  103. data/spec/support/examples/evented_mailbox_class.rb +27 -0
  104. data/spec/support/includer.rb +9 -0
  105. data/spec/support/logging.rb +63 -0
  106. data/spec/support/loose_threads.rb +65 -0
  107. data/spec/support/reset_class_variables.rb +27 -0
  108. data/spec/support/sleep_and_wait.rb +14 -0
  109. data/spec/support/split_logs.rb +1 -0
  110. data/spec/support/stubbing.rb +14 -0
  111. metadata +255 -95
  112. data/lib/celluloid/call_chain.rb +0 -13
  113. data/lib/celluloid/cpu_counter.rb +0 -34
  114. data/lib/celluloid/evented_mailbox.rb +0 -73
  115. data/lib/celluloid/fsm.rb +0 -186
  116. data/lib/celluloid/handlers.rb +0 -41
  117. data/lib/celluloid/internal_pool.rb +0 -159
  118. data/lib/celluloid/legacy.rb +0 -9
  119. data/lib/celluloid/links.rb +0 -36
  120. data/lib/celluloid/logger.rb +0 -93
  121. data/lib/celluloid/logging/incident.rb +0 -21
  122. data/lib/celluloid/logging/incident_logger.rb +0 -129
  123. data/lib/celluloid/logging/incident_reporter.rb +0 -48
  124. data/lib/celluloid/logging/log_event.rb +0 -20
  125. data/lib/celluloid/logging/ring_buffer.rb +0 -65
  126. data/lib/celluloid/method.rb +0 -32
  127. data/lib/celluloid/notifications.rb +0 -83
  128. data/lib/celluloid/pool_manager.rb +0 -146
  129. data/lib/celluloid/probe.rb +0 -73
  130. data/lib/celluloid/properties.rb +0 -24
  131. data/lib/celluloid/proxies/abstract_proxy.rb +0 -20
  132. data/lib/celluloid/proxies/actor_proxy.rb +0 -38
  133. data/lib/celluloid/proxies/async_proxy.rb +0 -31
  134. data/lib/celluloid/proxies/block_proxy.rb +0 -29
  135. data/lib/celluloid/proxies/cell_proxy.rb +0 -68
  136. data/lib/celluloid/proxies/future_proxy.rb +0 -35
  137. data/lib/celluloid/proxies/sync_proxy.rb +0 -36
  138. data/lib/celluloid/receivers.rb +0 -63
  139. data/lib/celluloid/registry.rb +0 -57
  140. data/lib/celluloid/responses.rb +0 -44
  141. data/lib/celluloid/rspec/actor_examples.rb +0 -1054
  142. data/lib/celluloid/signals.rb +0 -23
  143. data/lib/celluloid/stack_dump.rb +0 -133
  144. data/lib/celluloid/supervision_group.rb +0 -169
  145. data/lib/celluloid/supervisor.rb +0 -22
  146. data/lib/celluloid/task_set.rb +0 -49
  147. data/lib/celluloid/tasks/task_fiber.rb +0 -43
  148. data/lib/celluloid/tasks/task_thread.rb +0 -53
  149. data/lib/celluloid/thread_handle.rb +0 -50
  150. data/lib/celluloid/uuid.rb +0 -38
  151. data/spec/celluloid/cpu_counter_spec.rb +0 -82
  152. data/spec/celluloid/fsm_spec.rb +0 -107
  153. data/spec/celluloid/internal_pool_spec.rb +0 -52
  154. data/spec/celluloid/links_spec.rb +0 -45
  155. data/spec/celluloid/logging/ring_buffer_spec.rb +0 -38
  156. data/spec/celluloid/notifications_spec.rb +0 -120
  157. data/spec/celluloid/pool_spec.rb +0 -92
  158. data/spec/celluloid/probe_spec.rb +0 -121
  159. data/spec/celluloid/properties_spec.rb +0 -42
  160. data/spec/celluloid/registry_spec.rb +0 -64
  161. data/spec/celluloid/stack_dump_spec.rb +0 -64
  162. data/spec/celluloid/supervision_group_spec.rb +0 -65
  163. data/spec/celluloid/supervisor_spec.rb +0 -103
  164. data/spec/celluloid/tasks/task_fiber_spec.rb +0 -5
  165. data/spec/celluloid/tasks/task_thread_spec.rb +0 -5
  166. data/spec/celluloid/thread_handle_spec.rb +0 -26
  167. data/spec/celluloid/uuid_spec.rb +0 -11
@@ -0,0 +1,32 @@
1
+ RSpec.describe "Deprecated Celluloid::Future", actor_system: :global do
2
+ subject { Celluloid::Future.new }
3
+
4
+ it "creates future objects that can be retrieved later" do
5
+ future = Celluloid::Future.new { 40 + 2 }
6
+ expect(future.value).to eq(42)
7
+ end
8
+
9
+ it "passes arguments to future blocks" do
10
+ future = Celluloid::Future.new(40) { |n| n + 2 }
11
+ expect(future.value).to eq(42)
12
+ end
13
+
14
+ it "reraises exceptions that occur when the value is retrieved" do
15
+ class ExampleError < StandardError; end
16
+
17
+ future = Celluloid::Future.new { fail ExampleError, "oh noes crash!" }
18
+ expect { future.value }.to raise_exception(ExampleError)
19
+ end
20
+
21
+ it "knows if it's got a value yet" do
22
+ future = Celluloid::Future.new { sleep CelluloidSpecs::TIMER_QUANTUM * 5 }
23
+ expect(future).not_to be_ready
24
+ sleep CelluloidSpecs::TIMER_QUANTUM * 6
25
+ expect(future).to be_ready
26
+ end
27
+
28
+ it "raises TimeoutError when the future times out" do
29
+ future = Celluloid::Future.new { sleep 2 }
30
+ expect { future.value(1) }.to raise_exception(Celluloid::TimeoutError)
31
+ end
32
+ end unless $CELLULOID_BACKPORTED == false
@@ -0,0 +1,4 @@
1
+ # RSpec.describe Celluloid::InternalPool do
2
+ # it_behaves_like "a Celluloid Group", Celluloid::InternalPool
3
+ # end
4
+ # =
@@ -0,0 +1,1237 @@
1
+ RSpec.shared_examples "a Celluloid Actor" do
2
+ before(:each) do |example|
3
+ @fake_logger = Specs::FakeLogger.new(Celluloid.logger, example.description)
4
+ stub_const("Celluloid::Internals::Logger", @fake_logger)
5
+ end
6
+
7
+ around do |ex|
8
+ Celluloid.boot
9
+ ex.run
10
+ Celluloid.shutdown
11
+ end
12
+
13
+ let(:task_klass) { Celluloid.task_class }
14
+ let(:actor_class) { ExampleActorClass.create(CelluloidSpecs.included_module, task_klass) }
15
+ let(:actor) { actor_class.new "Troy McClure" }
16
+
17
+ let(:logger) { Specs::FakeLogger.current }
18
+
19
+ it "returns the actor's class, not the proxy's" do
20
+ expect(actor.class).to eq(actor_class)
21
+ end
22
+
23
+ it "compares with the actor's class in a case statement" do
24
+ expect(
25
+ case actor
26
+ when actor_class
27
+ true
28
+ else
29
+ false
30
+ end,
31
+ ).to be_truthy
32
+ end
33
+
34
+ it "can be stored in hashes" do
35
+ expect(actor.hash).not_to eq(Kernel.hash)
36
+ expect(actor.object_id).not_to eq(Kernel.object_id)
37
+ end
38
+
39
+ it "implements respond_to? correctly" do
40
+ expect(actor).to respond_to(:alive?)
41
+ end
42
+
43
+ it "supports synchronous calls" do
44
+ expect(actor.greet).to eq("Hi, I'm Troy McClure")
45
+ end
46
+
47
+ context "with a method accepting a block" do
48
+ let(:actor) { james_bond_role.new }
49
+ let(:james_bond_role) do
50
+ Class.new do
51
+ include CelluloidSpecs.included_module
52
+ def act
53
+ "My name is #{yield('James', 'Bond')}"
54
+ end
55
+
56
+ def give_role_to(actor, &block)
57
+ actor.act(&block)
58
+ end
59
+ end
60
+ end
61
+
62
+ it "supports synchronously passing a block to itself through a reference" do
63
+ result = actor.give_role_to(actor) { |name, surname| "#{surname}. #{name} #{surname}." }
64
+ expect(result).to eq("My name is Bond. James Bond.")
65
+ end
66
+ end
67
+
68
+ it "supports synchronous calls via #method" do
69
+ method = actor.method(:greet)
70
+ expect(method.call).to eq("Hi, I'm Troy McClure")
71
+ end
72
+
73
+ it "supports #arity calls via #method" do
74
+ method = actor.method(:greet)
75
+ expect(method.arity).to be(0)
76
+
77
+ method = actor.method(:change_name)
78
+ expect(method.arity).to be(1)
79
+ end
80
+
81
+ it "supports #name calls via #method" do
82
+ method = actor.method(:greet)
83
+ expect(method.name).to eq(:greet)
84
+ end
85
+
86
+ it "supports #parameters via #method" do
87
+ method = actor.method(:greet)
88
+ expect(method.parameters).to eq([])
89
+
90
+ method = actor.method(:change_name)
91
+ expect(method.parameters.first.last).to eq(:new_name)
92
+ end
93
+
94
+ it "supports future(:method) syntax for synchronous future calls" do
95
+ future = actor.future :greet
96
+ expect(future.value).to eq("Hi, I'm Troy McClure")
97
+ end
98
+
99
+ it "supports future.method syntax for synchronous future calls" do
100
+ future = actor.future.greet
101
+ expect(future.value).to eq("Hi, I'm Troy McClure")
102
+ end
103
+
104
+ context "when a block is passed synchronously to an actor" do
105
+ let(:actor) { actor_class.new "Blocky Ralboa" }
106
+
107
+ it "the block is called" do
108
+ block_executed = false
109
+ actor.run { block_executed = true }
110
+ expect(block_executed).to be_truthy
111
+ end
112
+ end
113
+
114
+ context "when there is a circular synchronous reference" do
115
+ let(:ponycopter) do
116
+ Class.new do
117
+ include CelluloidSpecs.included_module
118
+
119
+ def greet_by_proxy(actor)
120
+ actor.greet
121
+ end
122
+
123
+ def to_s
124
+ "a ponycopter!"
125
+ end
126
+ end.new
127
+ end
128
+
129
+ let(:actor) { actor_class.new ponycopter }
130
+
131
+ it "is called correctly" do
132
+ expect(ponycopter.greet_by_proxy(actor)).to eq("Hi, I'm a ponycopter!")
133
+ end
134
+ end
135
+
136
+ let(:recursive_klass1) do
137
+ Class.new do
138
+ include CelluloidSpecs.included_module
139
+
140
+ def recursion_test(recurse_through = nil)
141
+ if recurse_through
142
+ recurse_through.recursion_thunk(Celluloid::Actor.current)
143
+ else
144
+ Celluloid.detect_recursion
145
+ end
146
+ end
147
+ end
148
+ end
149
+
150
+ let(:recursive_klass2) do
151
+ Class.new do
152
+ include CelluloidSpecs.included_module
153
+
154
+ def recursion_thunk(other)
155
+ other.recursion_test
156
+ end
157
+ end
158
+ end
159
+
160
+ it "detects recursion" do
161
+ actor1 = recursive_klass1.new
162
+ actor2 = recursive_klass2.new
163
+
164
+ expect(actor1.recursion_test).to be_falsey
165
+ expect(actor1.recursion_test(actor2)).to be_truthy
166
+ end
167
+
168
+ describe "#respond_to?" do
169
+ context "with method_missing resolving to :first" do
170
+ specify { expect(actor).to respond_to(:first) }
171
+
172
+ context "when missing method is called" do
173
+ specify { expect(actor.first).to be :bar }
174
+ end
175
+ end
176
+
177
+ context "with a private method" do
178
+ specify { expect(actor.respond_to?(:zomg_private)).to be_falsey }
179
+
180
+ context "when :include_private is passed" do
181
+ specify { expect(actor.respond_to?(:zomg_private, true)).to be_truthy }
182
+ end
183
+ end
184
+ end
185
+
186
+ context "when initialize sleeps" do
187
+ let(:actor) do
188
+ Class.new do
189
+ include CelluloidSpecs.included_module
190
+
191
+ def initialize
192
+ sleep 0.1
193
+ end
194
+ end.new
195
+ end
196
+
197
+ it "warns about suspending the initialize" do
198
+ expect(logger).to receive(:warn).with(/Dangerously suspending task: type=:call, meta={:dangerous_suspend=>true, :method_name=>:initialize}, status=:sleeping/)
199
+
200
+ actor.terminate
201
+ Specs.sleep_and_wait_until { !actor.alive? }
202
+ end
203
+ end
204
+
205
+ context "with a user defined finalizer" do
206
+ it "calls the user defined finalizer" do
207
+ expect(actor.wrapped_object).to receive(:my_finalizer)
208
+ actor.terminate
209
+ Specs.sleep_and_wait_until { !actor.alive? }
210
+ end
211
+ end
212
+
213
+ context "when actor sleeps in finalizer" do
214
+ let(:actor) do
215
+ Class.new do
216
+ include CelluloidSpecs.included_module
217
+
218
+ finalizer :cleanup
219
+
220
+ def cleanup
221
+ sleep 0.1
222
+ end
223
+ end.new
224
+ end
225
+
226
+ it "warns about suspending the finalizer" do
227
+ allow(logger).to receive(:warn)
228
+ allow(logger).to receive(:crash).with(/finalizer crashed!/, Celluloid::Task::TerminatedError)
229
+ expect(logger).to receive(:warn).with(/Dangerously suspending task: type=:finalizer, meta={:dangerous_suspend=>true, :method_name=>:cleanup}, status=:sleeping/)
230
+ actor.terminate
231
+ Specs.sleep_and_wait_until { !actor.alive? }
232
+ end
233
+ end
234
+
235
+ it "supports async(:method) syntax for asynchronous calls" do
236
+ actor.async :change_name, "Charlie Sheen"
237
+ expect(actor.greet).to eq("Hi, I'm Charlie Sheen")
238
+ end
239
+
240
+ it "supports async.method syntax for asynchronous calls" do
241
+ actor.async.change_name "Charlie Sheen"
242
+ expect(actor.greet).to eq("Hi, I'm Charlie Sheen")
243
+ end
244
+
245
+ it "supports async.method syntax for asynchronous calls to itself" do
246
+ actor.change_name_async "Charlie Sheen"
247
+ expect(actor.greet).to eq("Hi, I'm Charlie Sheen")
248
+ end
249
+
250
+ it "allows an actor to call private methods asynchronously" do
251
+ actor.call_private
252
+ expect(actor.private_called).to be_truthy
253
+ end
254
+
255
+ it "knows if it's inside actor scope" do
256
+ expect(Celluloid).not_to be_actor
257
+ expect(actor.run do
258
+ Celluloid.actor?
259
+ end).to be_falsey
260
+ expect(actor.run_on_receiver do
261
+ Celluloid.actor?
262
+ end).to be_truthy
263
+ expect(actor).to be_actor
264
+ end
265
+
266
+ it "inspects properly" do
267
+ expect(actor.inspect).to match(/Celluloid::Proxy::Cell\(/)
268
+ expect(actor.inspect).to match(/#{actor_class}/)
269
+ expect(actor.inspect).to include('@name="Troy McClure"')
270
+ expect(actor.inspect).not_to include("@celluloid")
271
+ end
272
+
273
+ it "inspects properly when dead" do
274
+ actor.terminate
275
+ Specs.sleep_and_wait_until { !actor.alive? }
276
+ expect(actor.inspect).to match(/Celluloid::Proxy::Cell\(/)
277
+ expect(actor.inspect).to match(/#{actor_class}/)
278
+ expect(actor.inspect).to include("dead")
279
+ end
280
+
281
+ it "reports private methods properly when dead" do
282
+ actor.terminate
283
+ expect { actor.private_methods }.not_to raise_error
284
+ end
285
+
286
+ context "with actors referencing each other" do
287
+ let(:klass) do
288
+ Class.new do
289
+ include CelluloidSpecs.included_module
290
+
291
+ attr_accessor :other
292
+
293
+ def initialize(other = nil)
294
+ @other = other
295
+ end
296
+ end
297
+ end
298
+
299
+ it "supports recursive inspect" do
300
+ itchy = klass.new
301
+ scratchy = klass.new(itchy)
302
+ itchy.other = scratchy
303
+
304
+ inspection = itchy.inspect
305
+ expect(inspection).to match(/Celluloid::Proxy::Cell\(/)
306
+ expect(inspection).to include("...")
307
+ end
308
+ end
309
+
310
+ it "allows access to the wrapped object" do
311
+ expect(actor.wrapped_object).to be_a actor_class
312
+ end
313
+
314
+ it "warns about leaked wrapped objects via #inspect" do
315
+ expect(actor.inspect).not_to include Celluloid::BARE_OBJECT_WARNING_MESSAGE
316
+ expect(actor.inspect_thunk).not_to include Celluloid::BARE_OBJECT_WARNING_MESSAGE
317
+ expect(actor.wrapped_object.inspect).to include Celluloid::BARE_OBJECT_WARNING_MESSAGE
318
+ end
319
+
320
+ it "can override #send" do
321
+ expect(actor.send("foo")).to eq("oof")
322
+ end
323
+
324
+ if RUBY_PLATFORM == "java" && Celluloid.task_class != Celluloid::Task::Fibered
325
+ context "when executing under JRuby" do
326
+ let(:actor) do
327
+ Class.new do
328
+ include CelluloidSpecs.included_module
329
+
330
+ def current_java_thread
331
+ Thread.current.to_java.getNativeThread
332
+ end
333
+
334
+ def name_inside_task
335
+ Thread.current.to_java.getNativeThread.get_name
336
+ end
337
+ end.new
338
+ end
339
+
340
+ it "sets execution info" do
341
+ expect(actor.name_inside_task).to match(/\[Celluloid\] #<Class:0x[0-9a-f]+>#name_inside_task/)
342
+ end
343
+
344
+ context "when the task if finished" do
345
+ let(:jthread) { actor.current_java_thread }
346
+
347
+ before do
348
+ Specs.sleep_and_wait_until { jthread.get_name !~ /^\[Celluloid\] #<Class:0x[0-9a-f]+>#current_java_thread$/ }
349
+ end
350
+
351
+ it "unsets execution info after task completion" do
352
+ expect(jthread.get_name).to match(/^Ruby-/)
353
+ end
354
+ end
355
+ end
356
+ end
357
+
358
+ context "mocking methods" do
359
+ before do
360
+ expect(actor.wrapped_object).to receive(:external_hello).once.and_return "World"
361
+ end
362
+
363
+ it "works externally via the proxy" do
364
+ expect(actor.external_hello).to eq("World")
365
+ end
366
+
367
+ it "works internally when called on self" do
368
+ expect(actor.internal_hello).to eq("World")
369
+ end
370
+ end
371
+
372
+ context "mocking out the proxy" do
373
+ it "allows mocking return values" do
374
+ expect(actor).to receive(:name).and_return "Spiderman"
375
+ expect(actor.name).to eq "Spiderman"
376
+ end
377
+
378
+ it "allows mocking raises" do
379
+ expect(actor).to receive(:greet).and_raise ArgumentError
380
+ expect { actor.greet }.to raise_error(ArgumentError)
381
+ expect(actor).to be_alive
382
+ end
383
+
384
+ xit "allows mocking async calls via the async proxy" do
385
+ # TODO: Verify via CIA... This appears to be working, with new celluloid/rspec handler.
386
+ # pending "Fails due to RSpec's new expectation verification"
387
+ # fail "TODO: may never work in newer versions of RSpec (no method on Proxy::Async)"
388
+ expect(actor.async).to receive(:greet).once
389
+ actor.async.greet
390
+ end
391
+
392
+ it "allows mocking async calls via #async send" do
393
+ expect(actor).to receive(:async).once.with(:greet)
394
+ actor.async :greet
395
+ end
396
+ end
397
+
398
+ context :exceptions do
399
+ context "with a dead actor" do
400
+ let(:actor) { actor_class.new "James Dean" } # is this in bad taste?
401
+
402
+ it "reraises exceptions which occur during synchronous calls in the sender" do
403
+ allow(logger).to receive(:crash).with("Actor crashed!", ExampleCrash)
404
+ expect { actor.crash }.to raise_exception(ExampleCrash)
405
+ Specs.sleep_and_wait_until { !actor.alive? }
406
+ end
407
+
408
+ it "includes both sender and receiver in exception traces" do
409
+ allow(logger).to receive(:crash).with("Actor crashed!", ExampleCrash)
410
+
411
+ example_receiver = Class.new do
412
+ include CelluloidSpecs.included_module
413
+
414
+ define_method(:receiver_method) do
415
+ fail ExampleCrash, "the spec purposely crashed me :("
416
+ end
417
+ end.new
418
+
419
+ example_caller = Class.new do
420
+ include CelluloidSpecs.included_module
421
+
422
+ define_method(:sender_method) do
423
+ example_receiver.receiver_method
424
+ end
425
+ end.new
426
+
427
+ ex = example_caller.sender_method rescue $ERROR_INFO
428
+ Specs.sleep_and_wait_until { !example_caller.alive? }
429
+ Specs.sleep_and_wait_until { !example_receiver.alive? }
430
+
431
+ expect(ex).to be_a ExampleCrash
432
+ expect(ex.backtrace.grep(/`sender_method'/)).to be_truthy
433
+ expect(ex.backtrace.grep(/`receiver_method'/)).to be_truthy
434
+ end
435
+
436
+ it "raises DeadActorError if methods are synchronously called on a dead actor" do
437
+ allow(logger).to receive(:crash).with("Actor crashed!", ExampleCrash)
438
+ actor.crash rescue ExampleCrash
439
+
440
+ # TODO: avoid this somehow
441
+ sleep 0.1 # hax to prevent a race between exit handling and the next call
442
+
443
+ expect { actor.greet }.to raise_exception(Celluloid::DeadActorError)
444
+ end
445
+ end
446
+ end
447
+
448
+ context :abort do
449
+ let(:actor) { actor_class.new "Al Pacino" }
450
+
451
+ it "raises exceptions in the sender but keeps running" do
452
+ expect do
453
+ actor.crash_with_abort "You die motherfucker!", :bar
454
+ end.to raise_exception(ExampleCrash, "You die motherfucker!")
455
+
456
+ expect(actor).to be_alive
457
+ end
458
+
459
+ it "converts strings to runtime errors" do
460
+ expect do
461
+ actor.crash_with_abort_raw "foo"
462
+ end.to raise_exception(RuntimeError, "foo")
463
+ end
464
+
465
+ it "crashes the sender if we pass neither String nor Exception" do
466
+ allow(logger).to receive(:crash).with("Actor crashed!", TypeError)
467
+ expect do
468
+ actor.crash_with_abort_raw 10
469
+ end.to raise_exception(TypeError, "Exception object/String expected, but Fixnum received")
470
+
471
+ Specs.sleep_and_wait_until { !actor.alive? }
472
+ expect(actor).not_to be_alive
473
+ end
474
+ end
475
+
476
+ context :termination do
477
+ let(:actor) { actor_class.new "Arnold Schwarzenegger" }
478
+
479
+ context "when alive" do
480
+ specify { expect(actor).to be_alive }
481
+ specify { expect(actor).to_not be_dead }
482
+ end
483
+
484
+ context "when terminated" do
485
+ before do
486
+ actor.terminate
487
+ Specs.sleep_and_wait_until { !actor.alive? }
488
+ end
489
+
490
+ specify { expect(actor).not_to be_alive }
491
+ context "when terminated!" do
492
+ specify do
493
+ expect do
494
+ actor.terminate!
495
+ end.to raise_exception(Celluloid::DeadActorError, "actor already terminated")
496
+ end
497
+ end
498
+ end
499
+
500
+ context "when terminated by a Call::Sync" do
501
+ before do
502
+ actor.shutdown
503
+ Specs.sleep_and_wait_until { !actor.alive? }
504
+ end
505
+
506
+ specify { expect(actor).not_to be_alive }
507
+ end
508
+
509
+ unless RUBY_PLATFORM == "java" || RUBY_ENGINE == "rbx"
510
+ context "when killed" do
511
+ before do
512
+ Celluloid::Actor.kill(actor)
513
+ Specs.sleep_and_wait_until { !actor.alive? }
514
+ end
515
+
516
+ specify { expect(actor).not_to be_alive }
517
+ specify { expect(actor).to be_dead }
518
+
519
+ context "when called" do
520
+ specify do
521
+ expect { actor.greet }.to raise_exception(Celluloid::DeadActorError)
522
+ end
523
+ end
524
+ end
525
+
526
+ context "when celluloid is shutdown" do
527
+ before do
528
+ allow(Celluloid::Actor).to receive(:kill).and_call_original
529
+ actor
530
+ Celluloid.shutdown
531
+ end
532
+
533
+ it "terminates cleanly on Celluloid shutdown" do
534
+ expect(Celluloid::Actor).not_to have_received(:kill)
535
+ end
536
+ end
537
+ end
538
+
539
+ context "when sleeping" do
540
+ before do
541
+ actor.async.sleepy 10
542
+ actor.greet # make sure the actor has started sleeping
543
+ end
544
+
545
+ context "when terminated" do
546
+ it "logs a debug" do
547
+ expect(logger).to receive(:debug).with(/^Terminating task: type=:call, meta={:dangerous_suspend=>false, :method_name=>:sleepy}, status=:sleeping/)
548
+ actor.terminate
549
+ Specs.sleep_and_wait_until { !actor.alive? }
550
+ end
551
+ end
552
+ end
553
+ end
554
+
555
+ describe "#current_actor" do
556
+ context "when called on an actor" do
557
+ let(:actor) { actor_class.new "Roger Daltrey" }
558
+
559
+ it "knows the current actor" do
560
+ expect(actor.current_actor).to eq actor
561
+ end
562
+ end
563
+
564
+ context "when called outside an actor" do
565
+ specify { expect { Celluloid.current_actor }.to raise_exception(Celluloid::NotActorError) }
566
+ end
567
+ end
568
+
569
+ context :linking do
570
+ before :each do
571
+ @kevin = actor_class.new "Kevin Bacon" # Some six degrees action here
572
+ @charlie = actor_class.new "Charlie Sheen"
573
+ end
574
+
575
+ let(:supervisor_class) do
576
+ Class.new do # like a boss
577
+ include CelluloidSpecs.included_module
578
+ trap_exit :lambaste_subordinate
579
+
580
+ def initialize(name)
581
+ @name = name
582
+ @subordinate_lambasted = false
583
+ end
584
+
585
+ def subordinate_lambasted?
586
+ @subordinate_lambasted
587
+ end
588
+
589
+ def lambaste_subordinate(_actor, _reason)
590
+ @subordinate_lambasted = true
591
+ end
592
+ end
593
+ end
594
+
595
+ it "links to other actors" do
596
+ @kevin.link @charlie
597
+ expect(@kevin.monitoring?(@charlie)).to be_truthy
598
+ expect(@kevin.linked_to?(@charlie)).to be_truthy
599
+ expect(@charlie.monitoring?(@kevin)).to be_truthy
600
+ expect(@charlie.linked_to?(@kevin)).to be_truthy
601
+ end
602
+
603
+ it "unlinks from other actors" do
604
+ @kevin.link @charlie
605
+ @kevin.unlink @charlie
606
+
607
+ expect(@kevin.monitoring?(@charlie)).to be_falsey
608
+ expect(@kevin.linked_to?(@charlie)).to be_falsey
609
+ expect(@charlie.monitoring?(@kevin)).to be_falsey
610
+ expect(@charlie.linked_to?(@kevin)).to be_falsey
611
+ end
612
+
613
+ it "monitors other actors unidirectionally" do
614
+ @kevin.monitor @charlie
615
+
616
+ expect(@kevin.monitoring?(@charlie)).to be_truthy
617
+ expect(@kevin.linked_to?(@charlie)).to be_falsey
618
+ expect(@charlie.monitoring?(@kevin)).to be_falsey
619
+ expect(@charlie.linked_to?(@kevin)).to be_falsey
620
+ end
621
+
622
+ it "unmonitors other actors" do
623
+ @kevin.monitor @charlie
624
+ @kevin.unmonitor @charlie
625
+
626
+ expect(@kevin.monitoring?(@charlie)).to be_falsey
627
+ expect(@kevin.linked_to?(@charlie)).to be_falsey
628
+ expect(@charlie.monitoring?(@kevin)).to be_falsey
629
+ expect(@charlie.linked_to?(@kevin)).to be_falsey
630
+ end
631
+
632
+ it "traps exit messages from other actors" do
633
+ allow(logger).to receive(:crash).with("Actor crashed!", ExampleCrash)
634
+ chuck = supervisor_class.new "Chuck Lorre"
635
+ chuck.link @charlie
636
+
637
+ expect do
638
+ @charlie.crash
639
+ end.to raise_exception(ExampleCrash)
640
+
641
+ sleep 0.1 # hax to prevent a race between exit handling and the next call
642
+ expect(chuck).to be_subordinate_lambasted
643
+ end
644
+
645
+ it "traps exit messages from other actors in subclasses" do
646
+ allow(logger).to receive(:crash).with("Actor crashed!", ExampleCrash)
647
+
648
+ supervisor_subclass = Class.new(supervisor_class)
649
+ chuck = supervisor_subclass.new "Chuck Lorre"
650
+ chuck.link @charlie
651
+
652
+ expect do
653
+ @charlie.crash
654
+ end.to raise_exception(ExampleCrash)
655
+
656
+ sleep 0.1 # hax to prevent a race between exit handling and the next call
657
+ expect(chuck).to be_subordinate_lambasted
658
+ end
659
+
660
+ it "unlinks from a dead linked actor" do
661
+ allow(logger).to receive(:crash).with("Actor crashed!", ExampleCrash)
662
+ chuck = supervisor_class.new "Chuck Lorre"
663
+ chuck.link @charlie
664
+
665
+ expect do
666
+ @charlie.crash
667
+ end.to raise_exception(ExampleCrash)
668
+
669
+ sleep 0.1 # hax to prevent a race between exit handling and the next call
670
+ expect(chuck.links.count).to be(0)
671
+ end
672
+ end
673
+
674
+ context :signaling do
675
+ before do
676
+ @signaler = Class.new do
677
+ include CelluloidSpecs.included_module
678
+
679
+ def initialize
680
+ @waiting = false
681
+ @signaled = false
682
+ end
683
+
684
+ def wait_for_signal
685
+ fail "already signaled" if @signaled
686
+
687
+ @waiting = true
688
+ value = wait :ponycopter
689
+
690
+ @waiting = false
691
+ @signaled = true
692
+ value
693
+ end
694
+
695
+ def send_signal(value)
696
+ signal :ponycopter, value
697
+ end
698
+
699
+ def waiting?
700
+ @waiting
701
+ end
702
+
703
+ def signaled?
704
+ @signaled
705
+ end
706
+ end
707
+ end
708
+
709
+ it "allows methods within the same object to signal each other" do
710
+ obj = @signaler.new
711
+ expect(obj).not_to be_signaled
712
+
713
+ obj.async.wait_for_signal
714
+ expect(obj).not_to be_signaled
715
+ expect(obj).to be_waiting
716
+
717
+ obj.send_signal :foobar
718
+ expect(obj).to be_signaled
719
+ expect(obj).not_to be_waiting
720
+ end
721
+
722
+ it "sends values along with signals" do
723
+ obj = @signaler.new
724
+ expect(obj).not_to be_signaled
725
+
726
+ future = obj.future(:wait_for_signal)
727
+
728
+ expect(obj).to be_waiting
729
+ expect(obj).not_to be_signaled
730
+
731
+ expect(obj.send_signal(:foobar)).to be_truthy
732
+ expect(future.value).to be(:foobar)
733
+ end
734
+ end
735
+
736
+ context :exclusive do
737
+ subject do
738
+ Class.new do
739
+ include CelluloidSpecs.included_module
740
+
741
+ attr_reader :tasks
742
+
743
+ def initialize
744
+ @tasks = []
745
+ end
746
+
747
+ def log_task(task)
748
+ @tasks << task
749
+ end
750
+
751
+ def exclusive_with_block_log_task(task)
752
+ exclusive do
753
+ sleep CelluloidSpecs::TIMER_QUANTUM
754
+ log_task(task)
755
+ end
756
+ end
757
+
758
+ def exclusive_log_task(task)
759
+ sleep CelluloidSpecs::TIMER_QUANTUM
760
+ log_task(task)
761
+ end
762
+ exclusive :exclusive_log_task
763
+
764
+ def check_not_exclusive
765
+ Celluloid.exclusive?
766
+ end
767
+
768
+ def check_exclusive
769
+ exclusive { Celluloid.exclusive? }
770
+ end
771
+
772
+ def nested_exclusive_example
773
+ exclusive { exclusive { nil }; Celluloid.exclusive? }
774
+ end
775
+ end.new
776
+ end
777
+
778
+ it "executes methods in the proper order with block form" do
779
+ subject.async.exclusive_with_block_log_task(:one)
780
+ subject.async.log_task(:two)
781
+ sleep CelluloidSpecs::TIMER_QUANTUM * 2
782
+ expect(subject.tasks).to eq([:one, :two])
783
+ end
784
+
785
+ it "executes methods in the proper order with a class-level annotation" do
786
+ subject.async.exclusive_log_task :one
787
+ subject.async.log_task :two
788
+ sleep CelluloidSpecs::TIMER_QUANTUM * 2
789
+ expect(subject.tasks).to eq([:one, :two])
790
+ end
791
+
792
+ it "knows when it's in exclusive mode" do
793
+ expect(subject.check_not_exclusive).to be_falsey
794
+ expect(subject.check_exclusive).to be_truthy
795
+ end
796
+
797
+ it "remains in exclusive mode inside nested blocks" do
798
+ expect(subject.nested_exclusive_example).to be_truthy
799
+ end
800
+ end
801
+
802
+ context "exclusive classes" do
803
+ subject do
804
+ Class.new do
805
+ include CelluloidSpecs.included_module
806
+ exclusive
807
+
808
+ attr_reader :tasks
809
+
810
+ def initialize
811
+ @tasks = []
812
+ end
813
+
814
+ def eat_donuts
815
+ sleep CelluloidSpecs::TIMER_QUANTUM
816
+ @tasks << "donuts"
817
+ end
818
+
819
+ def drink_coffee
820
+ @tasks << "coffee"
821
+ end
822
+ end
823
+ end
824
+
825
+ context "with two async methods called" do
826
+ let(:actor) { subject.new }
827
+
828
+ before do
829
+ actor.async.eat_donuts
830
+ actor.async.drink_coffee
831
+ sleep CelluloidSpecs::TIMER_QUANTUM * 2
832
+ end
833
+
834
+ it "executes in an exclusive order" do
835
+ expect(actor.tasks).to eq(%w(donuts coffee))
836
+ end
837
+ end
838
+ end
839
+
840
+ context :receiving do
841
+ before do
842
+ @receiver = Class.new do
843
+ include CelluloidSpecs.included_module
844
+ execute_block_on_receiver :signal_myself
845
+
846
+ def signal_myself(obj, &block)
847
+ current_actor.mailbox << obj
848
+ receive(&block)
849
+ end
850
+ end
851
+ end
852
+
853
+ let(:receiver) { @receiver.new }
854
+ let(:message) { Object.new }
855
+
856
+ it "allows unconditional receive" do
857
+ expect(receiver.signal_myself(message)).to eq(message)
858
+ end
859
+
860
+ it "allows arbitrary selective receive" do
861
+ received_obj = receiver.signal_myself(message) { |o| o == message }
862
+ expect(received_obj).to eq(message)
863
+ end
864
+
865
+ context "when exceeding a given time out" do
866
+ let(:interval) { 0.1 }
867
+
868
+ it "times out" do
869
+ # Barely didn't make it once on MRI, so attempting to "unrefactor"
870
+ started_at = Time.now
871
+ result = receiver.receive(interval) { false }
872
+ ended_at = Time.now - started_at
873
+
874
+ expect(result).to_not be
875
+ expect(ended_at).to be_within(CelluloidSpecs::TIMER_QUANTUM).of interval
876
+ end
877
+ end
878
+ end
879
+
880
+ context :timers do
881
+ let(:actor) do
882
+ Class.new do
883
+ include CelluloidSpecs.included_module
884
+
885
+ def initialize
886
+ @sleeping = false
887
+ @fired = false
888
+ end
889
+
890
+ def do_sleep(n)
891
+ @sleeping = true
892
+ sleep n
893
+ @sleeping = false
894
+ end
895
+
896
+ def sleeping?
897
+ @sleeping
898
+ end
899
+
900
+ def fire_after(n)
901
+ after(n) { @fired = true }
902
+ end
903
+
904
+ def fire_every(n)
905
+ @fired = 0
906
+ every(n) { @fired += 1 }
907
+ end
908
+
909
+ def fired?
910
+ !!@fired
911
+ end
912
+
913
+ attr_reader :fired
914
+ end.new
915
+ end
916
+
917
+ let(:interval) { CelluloidSpecs::TIMER_QUANTUM * 10 }
918
+ let(:sleep_interval) { interval + CelluloidSpecs::TIMER_QUANTUM } # wonky! #/
919
+
920
+ it "suspends execution of a method (but not the actor) for a given time" do
921
+ # Sleep long enough to ensure we're actually seeing behavior when asleep
922
+ # but not so long as to delay the test suite unnecessarily
923
+ started_at = Time.now
924
+
925
+ future = actor.future(:do_sleep, interval)
926
+ sleep(interval / 2) # wonky! :/
927
+ expect(actor).to be_sleeping
928
+
929
+ future.value
930
+ # I got 0.558 (in a slighly busy MRI) which is outside 0.05 of 0.5, so let's use (0.05 * 2)
931
+ expect(Time.now - started_at).to be_within(CelluloidSpecs::TIMER_QUANTUM * 2).of interval
932
+ end
933
+
934
+ it "schedules timers which fire in the future" do
935
+ actor.fire_after(interval)
936
+ expect(actor).not_to be_fired
937
+
938
+ sleep sleep_interval
939
+ expect(actor).to be_fired
940
+ end
941
+
942
+ it "schedules recurring timers which fire in the future" do
943
+ actor.fire_every(interval)
944
+ expect(actor.fired).to be_zero
945
+
946
+ sleep sleep_interval
947
+ expect(actor.fired).to be 1
948
+
949
+ 2.times { sleep sleep_interval }
950
+ expect(actor.fired).to be 3
951
+ end
952
+
953
+ it "cancels timers before they fire" do
954
+ timer = actor.fire_after(interval)
955
+ expect(actor).not_to be_fired
956
+ timer.cancel
957
+
958
+ sleep sleep_interval
959
+ expect(actor).not_to be_fired
960
+ end
961
+
962
+ it "allows delays from outside the actor" do
963
+ fired = false
964
+
965
+ actor.after(interval) { fired = true }
966
+ expect(fired).to be_falsey
967
+
968
+ sleep sleep_interval
969
+ expect(fired).to be_truthy
970
+ end
971
+ end
972
+
973
+ context :tasks do
974
+ let(:actor) do
975
+ Class.new do
976
+ include CelluloidSpecs.included_module
977
+ attr_reader :blocker
978
+
979
+ def initialize
980
+ @blocker = Class.new do
981
+ include Celluloid
982
+
983
+ def block
984
+ wait :unblock
985
+ end
986
+
987
+ def unblock
988
+ signal :unblock
989
+ end
990
+ end.new
991
+ end
992
+
993
+ def blocking_call
994
+ @blocker.block
995
+ end
996
+ end.new
997
+ end
998
+
999
+ it "knows which tasks are waiting on calls to other actors" do
1000
+ tasks = actor.tasks
1001
+ expect(tasks.size).to be 1
1002
+
1003
+ actor.future(:blocking_call)
1004
+ sleep 0.1 # hax! waiting for ^^^ call to actually start
1005
+
1006
+ tasks = actor.tasks
1007
+ expect(tasks.size).to be 2
1008
+
1009
+ blocking_task = tasks.find { |t| t.status != :running }
1010
+ expect(blocking_task).to be_a task_klass
1011
+ expect(blocking_task.status).to be :callwait
1012
+
1013
+ actor.blocker.unblock
1014
+ sleep 0.1 # hax again :(
1015
+ expect(actor.tasks.size).to be 1
1016
+ end
1017
+ end
1018
+
1019
+ context :mailbox_class do
1020
+ class ExampleMailbox < Celluloid::Mailbox; end
1021
+
1022
+ subject do
1023
+ Class.new do
1024
+ include CelluloidSpecs.included_module
1025
+ mailbox_class ExampleMailbox
1026
+ end
1027
+ end
1028
+
1029
+ it "uses user-specified mailboxes" do
1030
+ expect(subject.new.mailbox).to be_a ExampleMailbox
1031
+ end
1032
+
1033
+ it "retains custom mailboxes when subclassed" do
1034
+ subclass = Class.new(subject)
1035
+ expect(subclass.new.mailbox).to be_a ExampleMailbox
1036
+ end
1037
+ end
1038
+
1039
+ context :mailbox_size do
1040
+ subject do
1041
+ Class.new do
1042
+ include CelluloidSpecs.included_module
1043
+ mailbox_size 100
1044
+ end
1045
+ end
1046
+
1047
+ it "configures the mailbox limit" do
1048
+ expect(subject.new.mailbox.max_size).to eq(100)
1049
+ end
1050
+ end
1051
+
1052
+ context "#proxy_class" do
1053
+ subject do
1054
+ Class.new do
1055
+ include CelluloidSpecs.included_module
1056
+
1057
+ klass = Class.new(Celluloid::Proxy::Cell) do
1058
+ def subclass_proxy?
1059
+ true
1060
+ end
1061
+ end
1062
+
1063
+ proxy_class klass
1064
+ end
1065
+ end
1066
+
1067
+ it "uses user-specified proxy" do
1068
+ expect { subject.new.subclass_proxy? }.to_not raise_error
1069
+ end
1070
+
1071
+ it "retains custom proxy when subclassed" do
1072
+ subclass = Class.new(subject)
1073
+ expect(subclass.new.subclass_proxy?).to be(true)
1074
+ end
1075
+
1076
+ context "when overriding a actor's method" do
1077
+ subject do
1078
+ Class.new do
1079
+ include CelluloidSpecs.included_module
1080
+
1081
+ klass = Class.new(Celluloid::Proxy::Cell) do
1082
+ def dividing_3_by(number)
1083
+ fail ArgumentError, "<facepalm>" if number.zero?
1084
+ super
1085
+ end
1086
+ end
1087
+
1088
+ def dividing_3_by(number)
1089
+ 3 / number
1090
+ end
1091
+
1092
+ proxy_class klass
1093
+ end.new
1094
+ end
1095
+
1096
+ context "when invoked with an invalid parameter for that method" do
1097
+ it "calls the overloaded method" do
1098
+ expect { subject.dividing_3_by(0) }.to raise_error(ArgumentError, "<facepalm>")
1099
+ end
1100
+
1101
+ it "does not crash the actor" do
1102
+ subject.dividing_3_by(0) rescue ArgumentError
1103
+ expect(subject.dividing_3_by(3)).to eq(1)
1104
+ end
1105
+ end
1106
+ end
1107
+
1108
+ context "when it includes method checking" do
1109
+ module MethodChecking
1110
+ def method_missing(meth, *args)
1111
+ return super if [:__send__, :respond_to?, :method, :class, :__class__].include? meth
1112
+
1113
+ unmet_requirement = nil
1114
+
1115
+ arity = method(meth).arity
1116
+ if arity >= 0
1117
+ unmet_requirement = arity.to_s if args.size != arity
1118
+ elsif arity < -1
1119
+ mandatory_args = -arity - 1
1120
+ unmet_requirement = "#{mandatory_args}+" if args.size < mandatory_args
1121
+ end
1122
+ fail ArgumentError, "wrong number of arguments (#{args.size} for #{unmet_requirement})" if unmet_requirement
1123
+
1124
+ super
1125
+ end
1126
+ end
1127
+
1128
+ subject do
1129
+ Class.new do
1130
+ include CelluloidSpecs.included_module
1131
+
1132
+ klass = Class.new(Celluloid::Proxy::Cell) do
1133
+ include MethodChecking
1134
+ end
1135
+
1136
+ def madness
1137
+ "This is madness!"
1138
+ end
1139
+
1140
+ def this_is_not_madness(word1, word2, word3, *_args)
1141
+ fail "This is madness!" unless [word1, word2, word3] == [:this, :is, :Sparta]
1142
+ end
1143
+
1144
+ proxy_class klass
1145
+ end.new
1146
+ end
1147
+
1148
+ context "when invoking a non-existing method" do
1149
+ it "raises a NoMethodError" do
1150
+ expect { subject.method_to_madness }.to raise_error(NoMethodError)
1151
+ end
1152
+
1153
+ it "does not crash the actor" do
1154
+ subject.method_to_madness rescue NoMethodError
1155
+ expect(subject.madness).to eq("This is madness!")
1156
+ end
1157
+ end
1158
+
1159
+ context "when invoking a existing method with incorrect args" do
1160
+ context "with too many arguments" do
1161
+ it "raises an ArgumentError" do
1162
+ expect { subject.madness(:Sparta) }.to raise_error(ArgumentError, "wrong number of arguments (1 for 0)")
1163
+ end
1164
+
1165
+ it "does not crash the actor" do
1166
+ subject.madness(:Sparta) rescue ArgumentError
1167
+ expect(subject.madness).to eq("This is madness!")
1168
+ end
1169
+ end
1170
+
1171
+ context "with not enough mandatory arguments" do
1172
+ it "raises an ArgumentError" do
1173
+ expect { subject.this_is_not_madness(:this, :is) }.to raise_error(ArgumentError, "wrong number of arguments (2 for 3+)")
1174
+ end
1175
+ end
1176
+ end
1177
+ end
1178
+ end
1179
+
1180
+ context :task_class do
1181
+ class ExampleTask < Celluloid.task_class; end
1182
+
1183
+ subject do
1184
+ Class.new do
1185
+ include CelluloidSpecs.included_module
1186
+ task_class ExampleTask
1187
+ end
1188
+ end
1189
+
1190
+ it "overrides the task class" do
1191
+ expect(subject.new.tasks.first).to be_a ExampleTask
1192
+ end
1193
+
1194
+ it "retains custom task classes when subclassed" do
1195
+ subclass = Class.new(subject)
1196
+ expect(subclass.new.tasks.first).to be_a ExampleTask
1197
+ end
1198
+ end
1199
+
1200
+ context :timeouts do
1201
+ let :actor_class do
1202
+ Class.new do
1203
+ include CelluloidSpecs.included_module
1204
+
1205
+ def name
1206
+ sleep 0.5
1207
+ :foo
1208
+ end
1209
+
1210
+ def ask_name_with_timeout(other, duration)
1211
+ timeout(duration) { other.name }
1212
+ end
1213
+ end
1214
+ end
1215
+
1216
+ let(:a1) { actor_class.new }
1217
+ let(:a2) { actor_class.new }
1218
+
1219
+ it "allows timing out tasks, raising Celluloid::Task::TimeoutError" do
1220
+ allow(logger).to receive(:crash).with("Actor crashed!", Celluloid::Task::TimeoutError)
1221
+ expect { a1.ask_name_with_timeout a2, 0.3 }.to raise_error(Celluloid::Task::TimeoutError)
1222
+ Specs.sleep_and_wait_until { !a1.alive? }
1223
+ end
1224
+
1225
+ it "does not raise when it completes in time" do
1226
+ expect(a1.ask_name_with_timeout(a2, 0.6)).to eq(:foo)
1227
+ end
1228
+ end
1229
+
1230
+ context "raw message sends" do
1231
+ it "logs on unhandled messages" do
1232
+ expect(logger).to receive(:debug).with("Discarded message (unhandled): first")
1233
+ actor.mailbox << :first
1234
+ sleep CelluloidSpecs::TIMER_QUANTUM
1235
+ end
1236
+ end
1237
+ end