celluloid 0.6.2 → 0.7.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,4 @@
1
1
  module Celluloid
2
- VERSION = '0.6.2'
2
+ VERSION = '0.7.0'
3
3
  def self.version; VERSION; end
4
4
  end
@@ -0,0 +1,453 @@
1
+ shared_context "a Celluloid Actor" do |included_module|
2
+ class ExampleCrash < StandardError; end
3
+
4
+ let :actor_class do
5
+ Class.new do
6
+ include included_module
7
+ attr_reader :name
8
+
9
+ def initialize(name)
10
+ @name = name
11
+ end
12
+
13
+ def change_name(new_name)
14
+ @name = new_name
15
+ end
16
+
17
+ def change_name_async(new_name)
18
+ change_name! new_name
19
+ end
20
+
21
+ def greet
22
+ "Hi, I'm #{@name}"
23
+ end
24
+
25
+ def run(*args)
26
+ yield(*args)
27
+ end
28
+
29
+ def crash
30
+ raise ExampleCrash, "the spec purposely crashed me :("
31
+ end
32
+
33
+ def crash_with_abort(reason)
34
+ abort ExampleCrash.new(reason)
35
+ end
36
+
37
+ def internal_hello
38
+ external_hello
39
+ end
40
+
41
+ def external_hello
42
+ "Hello"
43
+ end
44
+ end
45
+ end
46
+
47
+ it "returns the actor's class, not the proxy's" do
48
+ actor = actor_class.new "Troy McClure"
49
+ actor.class.should == actor_class
50
+ end
51
+
52
+ it "handles synchronous calls" do
53
+ actor = actor_class.new "Troy McClure"
54
+ actor.greet.should == "Hi, I'm Troy McClure"
55
+ end
56
+
57
+ it "handles futures for synchronous calls" do
58
+ actor = actor_class.new "Troy McClure"
59
+ future = actor.future :greet
60
+ future.value.should == "Hi, I'm Troy McClure"
61
+ end
62
+
63
+ it "handles circular synchronous calls" do
64
+ klass = Class.new do
65
+ include included_module
66
+
67
+ def greet_by_proxy(actor)
68
+ actor.greet
69
+ end
70
+
71
+ def to_s
72
+ "a ponycopter!"
73
+ end
74
+ end
75
+
76
+ ponycopter = klass.new
77
+ actor = actor_class.new ponycopter
78
+ ponycopter.greet_by_proxy(actor).should == "Hi, I'm a ponycopter!"
79
+ end
80
+
81
+ it "raises NoMethodError when a nonexistent method is called" do
82
+ actor = actor_class.new "Billy Bob Thornton"
83
+
84
+ expect do
85
+ actor.the_method_that_wasnt_there
86
+ end.to raise_exception(NoMethodError)
87
+ end
88
+
89
+ it "reraises exceptions which occur during synchronous calls in the caller" do
90
+ actor = actor_class.new "James Dean" # is this in bad taste?
91
+
92
+ expect do
93
+ actor.crash
94
+ end.to raise_exception(ExampleCrash)
95
+ end
96
+
97
+ it "raises exceptions in the caller when abort is called, but keeps running" do
98
+ actor = actor_class.new "Al Pacino"
99
+
100
+ expect do
101
+ actor.crash_with_abort ExampleCrash.new("You die motherfucker!")
102
+ end.to raise_exception(ExampleCrash)
103
+
104
+ actor.should be_alive
105
+ end
106
+
107
+ it "raises DeadActorError if methods are synchronously called on a dead actor" do
108
+ actor = actor_class.new "James Dean"
109
+ actor.crash rescue nil
110
+
111
+ expect do
112
+ actor.greet
113
+ end.to raise_exception(Celluloid::DeadActorError)
114
+ end
115
+
116
+ it "handles asynchronous calls" do
117
+ actor = actor_class.new "Troy McClure"
118
+ actor.change_name! "Charlie Sheen"
119
+ actor.greet.should == "Hi, I'm Charlie Sheen"
120
+ end
121
+
122
+ it "handles asynchronous calls to itself" do
123
+ actor = actor_class.new "Troy McClure"
124
+ actor.change_name_async "Charlie Sheen"
125
+ actor.greet.should == "Hi, I'm Charlie Sheen"
126
+ end
127
+
128
+ it "knows if it's inside actor scope" do
129
+ Celluloid.should_not be_actor
130
+ actor = actor_class.new "Troy McClure"
131
+ actor.run do
132
+ Celluloid.actor?
133
+ end.should be_true
134
+ end
135
+
136
+ it "inspects properly" do
137
+ actor = actor_class.new "Troy McClure"
138
+ actor.inspect.should match(/Celluloid::Actor\(/)
139
+ actor.inspect.should include('@name="Troy McClure"')
140
+ actor.inspect.should_not include("@celluloid")
141
+ end
142
+
143
+ it "inspects properly when dead" do
144
+ actor = actor_class.new "Troy McClure"
145
+ actor.terminate
146
+ actor.inspect.should match(/Celluloid::Actor\(/)
147
+ actor.inspect.should include('dead')
148
+ end
149
+
150
+ it "allows access to the wrapped object" do
151
+ actor = actor_class.new "Troy McClure"
152
+ actor.wrapped_object.should be_a actor_class
153
+ end
154
+
155
+ describe 'mocking methods' do
156
+ let(:actor) { actor_class.new "Troy McClure" }
157
+
158
+ before do
159
+ actor.wrapped_object.should_receive(:external_hello).once.and_return "World"
160
+ end
161
+
162
+ it 'works externally via the proxy' do
163
+ actor.external_hello.should == "World"
164
+ end
165
+
166
+ it 'works internally when called on self' do
167
+ actor.internal_hello.should == "World"
168
+ end
169
+ end
170
+
171
+ context :termination do
172
+ it "terminates" do
173
+ actor = actor_class.new "Arnold Schwarzenegger"
174
+ actor.should be_alive
175
+ actor.terminate
176
+ sleep 0.5 # hax
177
+ actor.should_not be_alive
178
+ end
179
+
180
+ it "raises DeadActorError if called after terminated" do
181
+ actor = actor_class.new "Arnold Schwarzenegger"
182
+ actor.terminate
183
+
184
+ expect do
185
+ actor.greet
186
+ end.to raise_exception(Celluloid::DeadActorError)
187
+ end
188
+ end
189
+
190
+ context :current_actor do
191
+ it "knows the current actor" do
192
+ actor = actor_class.new "Roger Daltrey"
193
+ actor.current_actor.should == actor
194
+ end
195
+
196
+ it "raises NotActorError if called outside an actor" do
197
+ expect do
198
+ Celluloid.current_actor
199
+ end.to raise_exception(Celluloid::NotActorError)
200
+ end
201
+ end
202
+
203
+ context :linking do
204
+ before :each do
205
+ @kevin = actor_class.new "Kevin Bacon" # Some six degrees action here
206
+ @charlie = actor_class.new "Charlie Sheen"
207
+ end
208
+
209
+ it "links to other actors" do
210
+ @kevin.link @charlie
211
+ @kevin.linked_to?(@charlie).should be_true
212
+ @charlie.linked_to?(@kevin).should be_true
213
+ end
214
+
215
+ it "unlinks from other actors" do
216
+ @kevin.link @charlie
217
+ @kevin.unlink @charlie
218
+
219
+ @kevin.linked_to?(@charlie).should be_false
220
+ @charlie.linked_to?(@kevin).should be_false
221
+ end
222
+
223
+ it "traps exit messages from other actors" do
224
+ boss = Class.new do # like a boss
225
+ include included_module
226
+ trap_exit :lambaste_subordinate
227
+
228
+ def initialize(name)
229
+ @name = name
230
+ @subordinate_lambasted = false
231
+ end
232
+
233
+ def subordinate_lambasted?; @subordinate_lambasted; end
234
+
235
+ def lambaste_subordinate(actor, reason)
236
+ @subordinate_lambasted = true
237
+ end
238
+ end
239
+
240
+ chuck = boss.new "Chuck Lorre"
241
+ chuck.link @charlie
242
+
243
+ expect do
244
+ @charlie.crash
245
+ end.to raise_exception(ExampleCrash)
246
+
247
+ sleep 0.1 # hax to prevent a race between exit handling and the next call
248
+ chuck.should be_subordinate_lambasted
249
+ end
250
+ end
251
+
252
+ context :signaling do
253
+ before do
254
+ @signaler = Class.new do
255
+ include included_module
256
+ attr_reader :signaled
257
+
258
+ def initialize
259
+ @signaled = false
260
+ end
261
+
262
+ def wait_for_signal
263
+ value = wait :ponycopter
264
+ @signaled = true
265
+ value
266
+ end
267
+
268
+ def send_signal(value)
269
+ signal :ponycopter, value
270
+ end
271
+ end
272
+ end
273
+
274
+ it "allows methods within the same object to signal each other" do
275
+ obj = @signaler.new
276
+ obj.signaled.should be_false
277
+
278
+ obj.wait_for_signal!
279
+ obj.signaled.should be_false
280
+
281
+ obj.send_signal :foobar
282
+ obj.signaled.should be_true
283
+ end
284
+
285
+ # FIXME: This is deadlocking on Travis, and may still have issues
286
+ it "sends values along with signals", :pending => ENV['CI'] do
287
+ obj = @signaler.new
288
+ obj.signaled.should be_false
289
+
290
+ future = Celluloid::Future.new { obj.wait_for_signal }
291
+ obj.signaled.should be_false
292
+
293
+ obj.send_signal :foobar
294
+ future.value.should == :foobar
295
+ end
296
+ end
297
+
298
+ context :receiving do
299
+ before do
300
+ @receiver = Class.new do
301
+ include included_module
302
+
303
+ def signal_myself(obj, &block)
304
+ current_actor.mailbox << obj
305
+ receive(&block)
306
+ end
307
+ end
308
+ end
309
+
310
+ it "allows arbitrary selective receive" do
311
+ obj = Object.new
312
+ receiver = @receiver.new
313
+ received_obj = receiver.signal_myself(obj) { |o| o == obj }
314
+ received_obj.should == obj
315
+ end
316
+
317
+ it "times out after the given interval" do
318
+ interval = 0.1
319
+ started_at = Time.now
320
+ receiver = @receiver.new
321
+
322
+ receiver.receive(interval) { false }.should be_nil
323
+ (Time.now - started_at).should be_within(Celluloid::Timer::QUANTUM).of interval
324
+ end
325
+ end
326
+
327
+ context :timers do
328
+ before do
329
+ @klass = Class.new do
330
+ include included_module
331
+
332
+ def initialize
333
+ @sleeping = false
334
+ @fired = false
335
+ end
336
+
337
+ def do_sleep(n)
338
+ @sleeping = true
339
+ sleep n
340
+ @sleeping = false
341
+ end
342
+
343
+ def sleeping?; @sleeping end
344
+
345
+ def fire_after(n)
346
+ after(n) { @fired = true }
347
+ end
348
+
349
+ def fired?; @fired end
350
+ end
351
+ end
352
+
353
+ it "suspends execution of a method (but not the actor) for a given time" do
354
+ actor = @klass.new
355
+
356
+ # Sleep long enough to ensure we're actually seeing behavior when asleep
357
+ # but not so long as to delay the test suite unnecessarily
358
+ interval = Celluloid::Timer::QUANTUM * 10
359
+ started_at = Time.now
360
+
361
+ future = actor.future(:do_sleep, interval)
362
+ sleep(interval / 2) # wonky! :/
363
+ actor.should be_sleeping
364
+
365
+ future.value
366
+ (Time.now - started_at).should be_within(Celluloid::Timer::QUANTUM).of interval
367
+ end
368
+
369
+ it "schedules timers which fire in the future" do
370
+ actor = @klass.new
371
+
372
+ interval = Celluloid::Timer::QUANTUM * 10
373
+ started_at = Time.now
374
+
375
+ timer = actor.fire_after(interval)
376
+ actor.should_not be_fired
377
+
378
+ sleep(interval + Celluloid::Timer::QUANTUM) # wonky! #/
379
+ actor.should be_fired
380
+ end
381
+
382
+ it "cancels timers before they fire" do
383
+ actor = @klass.new
384
+
385
+ interval = Celluloid::Timer::QUANTUM * 10
386
+ started_at = Time.now
387
+
388
+ timer = actor.fire_after(interval)
389
+ actor.should_not be_fired
390
+ timer.cancel
391
+
392
+ sleep(interval + Celluloid::Timer::QUANTUM) # wonky! #/
393
+ actor.should_not be_fired
394
+ end
395
+ end
396
+
397
+ context :tasks do
398
+ before do
399
+ @klass = Class.new do
400
+ include included_module
401
+ attr_reader :blocker
402
+
403
+ def initialize
404
+ @blocker = Blocker.new
405
+ end
406
+
407
+ def blocking_call
408
+ @blocker.block
409
+ end
410
+ end
411
+
412
+ class Blocker
413
+ include Celluloid
414
+
415
+ def block
416
+ wait :unblock
417
+ end
418
+
419
+ def unblock
420
+ signal :unblock
421
+ end
422
+ end
423
+ end
424
+
425
+ it "knows which tasks are waiting on calls to other actors" do
426
+ actor = @klass.new
427
+
428
+ # an alias for Celluloid::Actor#waiting_tasks
429
+ tasks = actor.tasks
430
+ tasks.size.should == 1
431
+ tasks.values.first.should == :running
432
+
433
+ future = actor.future(:blocking_call)
434
+ sleep 0.1 # hax! waiting for ^^^ call to actually start
435
+
436
+ tasks = actor.tasks
437
+ tasks.size.should == 2
438
+
439
+ blocking_task = nil
440
+ tasks.each do |task, waitable|
441
+ next if waitable == :running
442
+ blocking_task = task
443
+ break
444
+ end
445
+
446
+ tasks[blocking_task].first.should == :call
447
+
448
+ actor.blocker.unblock
449
+ sleep 0.1 # hax again :(
450
+ actor.tasks.size.should == 1
451
+ end
452
+ end
453
+ end