celluloid 0.16.0 → 0.17.3

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