kulesa-celluloid 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,565 @@
1
+ shared_context "a Celluloid Actor" do |included_module|
2
+ class ExampleCrash < StandardError
3
+ attr_accessor :foo
4
+ end
5
+
6
+ let :actor_class do
7
+ Class.new do
8
+ include included_module
9
+ attr_reader :name
10
+
11
+ def initialize(name)
12
+ @name = name
13
+ @delegate = [:bar]
14
+ end
15
+
16
+ def change_name(new_name)
17
+ @name = new_name
18
+ end
19
+
20
+ def change_name_async(new_name)
21
+ change_name! new_name
22
+ end
23
+
24
+ def greet
25
+ "Hi, I'm #{@name}"
26
+ end
27
+
28
+ def run(*args)
29
+ yield(*args)
30
+ end
31
+
32
+ def crash
33
+ raise ExampleCrash, "the spec purposely crashed me :("
34
+ end
35
+
36
+ def crash_with_abort(reason, foo = nil)
37
+ example_crash = ExampleCrash.new(reason)
38
+ example_crash.foo = foo
39
+ abort example_crash
40
+ end
41
+
42
+ def internal_hello
43
+ external_hello
44
+ end
45
+
46
+ def external_hello
47
+ "Hello"
48
+ end
49
+
50
+ def method_missing(method_name, *args, &block)
51
+ if delegates?(method_name)
52
+ @delegate.send method_name, *args, &block
53
+ else
54
+ super
55
+ end
56
+ end
57
+
58
+ def respond_to?(method_name)
59
+ super || delegates?(method_name)
60
+ end
61
+
62
+ def call_private
63
+ zomg_private!
64
+ end
65
+
66
+ def zomg_private
67
+ @private_called = true
68
+ end
69
+ private :zomg_private
70
+ attr_reader :private_called
71
+
72
+ private
73
+
74
+ def delegates?(method_name)
75
+ @delegate.respond_to?(method_name)
76
+ end
77
+ end
78
+ end
79
+
80
+ it "returns the actor's class, not the proxy's" do
81
+ actor = actor_class.new "Troy McClure"
82
+ actor.class.should == actor_class
83
+ end
84
+
85
+ it "compares with the actor's class in a case statement" do
86
+ case actor_class.new("Troy McClure")
87
+ when actor_class
88
+ true
89
+ else
90
+ false
91
+ end.should be_true
92
+ end
93
+
94
+ it "handles synchronous calls" do
95
+ actor = actor_class.new "Troy McClure"
96
+ actor.greet.should == "Hi, I'm Troy McClure"
97
+ end
98
+
99
+ it "handles futures for synchronous calls" do
100
+ actor = actor_class.new "Troy McClure"
101
+ future = actor.future :greet
102
+ future.value.should == "Hi, I'm Troy McClure"
103
+ end
104
+
105
+ it "handles circular synchronous calls" do
106
+ klass = Class.new do
107
+ include included_module
108
+
109
+ def greet_by_proxy(actor)
110
+ actor.greet
111
+ end
112
+
113
+ def to_s
114
+ "a ponycopter!"
115
+ end
116
+ end
117
+
118
+ ponycopter = klass.new
119
+ actor = actor_class.new ponycopter
120
+ ponycopter.greet_by_proxy(actor).should == "Hi, I'm a ponycopter!"
121
+ end
122
+
123
+ it "properly handles method_missing" do
124
+ actor = actor_class.new "Method Missing"
125
+ actor.should respond_to(:first)
126
+ actor.first.should be == :bar
127
+ end
128
+
129
+ it "raises NoMethodError when a nonexistent method is called" do
130
+ actor = actor_class.new "Billy Bob Thornton"
131
+
132
+ expect do
133
+ actor.the_method_that_wasnt_there
134
+ end.to raise_exception(NoMethodError)
135
+ end
136
+
137
+ it "reraises exceptions which occur during synchronous calls in the caller" do
138
+ actor = actor_class.new "James Dean" # is this in bad taste?
139
+
140
+ expect do
141
+ actor.crash
142
+ end.to raise_exception(ExampleCrash)
143
+ end
144
+
145
+ it "raises exceptions in the caller when abort is called, but keeps running" do
146
+ actor = actor_class.new "Al Pacino"
147
+
148
+ e = nil
149
+ line_no = nil
150
+
151
+ expect do
152
+ begin
153
+ line_no = __LINE__; actor.crash_with_abort "You die motherfucker!", :bar
154
+ rescue => ex
155
+ e = ex
156
+ raise
157
+ end
158
+ end.to raise_exception(ExampleCrash, "You die motherfucker!")
159
+
160
+ e.backtrace.any? { |line| line.include?([__FILE__, line_no].join(':')) }.should be_true # Check the backtrace is appropriate to the caller
161
+ e.foo.should be == :bar # Check the exception maintains instance variables
162
+
163
+ actor.should be_alive
164
+ end
165
+
166
+ it "raises DeadActorError if methods are synchronously called on a dead actor" do
167
+ actor = actor_class.new "James Dean"
168
+ actor.crash rescue nil
169
+
170
+ expect do
171
+ actor.greet
172
+ end.to raise_exception(Celluloid::DeadActorError)
173
+ end
174
+
175
+ it "handles asynchronous calls" do
176
+ actor = actor_class.new "Troy McClure"
177
+ actor.change_name! "Charlie Sheen"
178
+ actor.greet.should == "Hi, I'm Charlie Sheen"
179
+ end
180
+
181
+ it "handles asynchronous calls via #async" do
182
+ actor = actor_class.new "Troy McClure"
183
+ actor.async :change_name, "Charlie Sheen"
184
+ actor.greet.should == "Hi, I'm Charlie Sheen"
185
+ end
186
+
187
+ it "handles asynchronous calls to itself" do
188
+ actor = actor_class.new "Troy McClure"
189
+ actor.change_name_async "Charlie Sheen"
190
+ actor.greet.should == "Hi, I'm Charlie Sheen"
191
+ end
192
+
193
+ it "allows an actor to call private methods asynchronously with a bang" do
194
+ actor = actor_class.new "Troy McClure"
195
+ actor.call_private
196
+ actor.private_called.should be_true
197
+ end
198
+
199
+ it "knows if it's inside actor scope" do
200
+ Celluloid.should_not be_actor
201
+ actor = actor_class.new "Troy McClure"
202
+ actor.run do
203
+ Celluloid.actor?
204
+ end.should be_true
205
+ end
206
+
207
+ it "inspects properly" do
208
+ actor = actor_class.new "Troy McClure"
209
+ actor.inspect.should match(/Celluloid::Actor\(/)
210
+ actor.inspect.should match(/#{actor_class}/)
211
+ actor.inspect.should include('@name="Troy McClure"')
212
+ actor.inspect.should_not include("@celluloid")
213
+ end
214
+
215
+ it "inspects properly when dead" do
216
+ actor = actor_class.new "Troy McClure"
217
+ actor.terminate
218
+ actor.inspect.should match(/Celluloid::Actor\(/)
219
+ actor.inspect.should match(/#{actor_class}/)
220
+ actor.inspect.should include('dead')
221
+ end
222
+
223
+ it "allows access to the wrapped object" do
224
+ actor = actor_class.new "Troy McClure"
225
+ actor.wrapped_object.should be_a actor_class
226
+ end
227
+
228
+ describe 'mocking methods' do
229
+ let(:actor) { actor_class.new "Troy McClure" }
230
+
231
+ before do
232
+ actor.wrapped_object.should_receive(:external_hello).once.and_return "World"
233
+ end
234
+
235
+ it 'works externally via the proxy' do
236
+ actor.external_hello.should == "World"
237
+ end
238
+
239
+ it 'works internally when called on self' do
240
+ actor.internal_hello.should == "World"
241
+ end
242
+ end
243
+
244
+ context :termination do
245
+ it "terminates" do
246
+ actor = actor_class.new "Arnold Schwarzenegger"
247
+ actor.should be_alive
248
+ actor.terminate
249
+ sleep 0.5 # hax
250
+ actor.should_not be_alive
251
+ end
252
+
253
+ it "raises DeadActorError if called after terminated" do
254
+ actor = actor_class.new "Arnold Schwarzenegger"
255
+ actor.terminate
256
+
257
+ expect do
258
+ actor.greet
259
+ end.to raise_exception(Celluloid::DeadActorError)
260
+ end
261
+ end
262
+
263
+ context :current_actor do
264
+ it "knows the current actor" do
265
+ actor = actor_class.new "Roger Daltrey"
266
+ actor.current_actor.should == actor
267
+ end
268
+
269
+ it "raises NotActorError if called outside an actor" do
270
+ expect do
271
+ Celluloid.current_actor
272
+ end.to raise_exception(Celluloid::NotActorError)
273
+ end
274
+ end
275
+
276
+ context :linking do
277
+ before :each do
278
+ @kevin = actor_class.new "Kevin Bacon" # Some six degrees action here
279
+ @charlie = actor_class.new "Charlie Sheen"
280
+ end
281
+
282
+ it "links to other actors" do
283
+ @kevin.link @charlie
284
+ @kevin.linked_to?(@charlie).should be_true
285
+ @charlie.linked_to?(@kevin).should be_true
286
+ end
287
+
288
+ it "unlinks from other actors" do
289
+ @kevin.link @charlie
290
+ @kevin.unlink @charlie
291
+
292
+ @kevin.linked_to?(@charlie).should be_false
293
+ @charlie.linked_to?(@kevin).should be_false
294
+ end
295
+
296
+ it "traps exit messages from other actors" do
297
+ boss = Class.new do # like a boss
298
+ include included_module
299
+ trap_exit :lambaste_subordinate
300
+
301
+ def initialize(name)
302
+ @name = name
303
+ @subordinate_lambasted = false
304
+ end
305
+
306
+ def subordinate_lambasted?; @subordinate_lambasted; end
307
+
308
+ def lambaste_subordinate(actor, reason)
309
+ @subordinate_lambasted = true
310
+ end
311
+ end
312
+
313
+ chuck = boss.new "Chuck Lorre"
314
+ chuck.link @charlie
315
+
316
+ expect do
317
+ @charlie.crash
318
+ end.to raise_exception(ExampleCrash)
319
+
320
+ sleep 0.1 # hax to prevent a race between exit handling and the next call
321
+ chuck.should be_subordinate_lambasted
322
+ end
323
+ end
324
+
325
+ context :signaling do
326
+ before do
327
+ @signaler = Class.new do
328
+ include included_module
329
+
330
+ def initialize
331
+ @waiting = false
332
+ @signaled = false
333
+ end
334
+
335
+ def wait_for_signal
336
+ raise "already signaled" if @signaled
337
+
338
+ @waiting = true
339
+ signal :future
340
+
341
+ value = wait :ponycopter
342
+
343
+ @waiting = false
344
+ @signaled = true
345
+ value
346
+ end
347
+
348
+ def wait_for_future
349
+ return true if @waiting
350
+ wait :future
351
+ end
352
+
353
+ def send_signal(value)
354
+ signal :ponycopter, value
355
+ end
356
+
357
+ def waiting?; @waiting end
358
+ def signaled?; @signaled end
359
+ end
360
+ end
361
+
362
+ it "allows methods within the same object to signal each other" do
363
+ obj = @signaler.new
364
+ obj.should_not be_signaled
365
+
366
+ obj.wait_for_signal!
367
+ obj.should_not be_signaled
368
+
369
+ obj.send_signal :foobar
370
+ obj.should be_signaled
371
+ end
372
+
373
+ # FIXME: This is deadlocking on Travis, and may still have issues
374
+ it "sends values along with signals" do
375
+ obj = @signaler.new
376
+ obj.should_not be_signaled
377
+
378
+ future = obj.future(:wait_for_signal)
379
+
380
+ obj.wait_for_future
381
+ obj.should be_waiting
382
+ obj.should_not be_signaled
383
+
384
+ obj.send_signal(:foobar).should be_true
385
+ future.value.should == :foobar
386
+ end
387
+ end
388
+
389
+ context :receiving do
390
+ before do
391
+ @receiver = Class.new do
392
+ include included_module
393
+
394
+ def signal_myself(obj, &block)
395
+ current_actor.mailbox << obj
396
+ receive(&block)
397
+ end
398
+ end
399
+ end
400
+
401
+ let(:receiver) { @receiver.new }
402
+ let(:message) { Object.new }
403
+
404
+ it "allows unconditional receive" do
405
+ receiver.signal_myself(message).should == message
406
+ end
407
+
408
+ it "allows arbitrary selective receive" do
409
+ received_obj = receiver.signal_myself(message) { |o| o == message }
410
+ received_obj.should == message
411
+ end
412
+
413
+ it "times out after the given interval" do
414
+ interval = 0.1
415
+ started_at = Time.now
416
+
417
+ receiver.receive(interval) { false }.should be_nil
418
+ (Time.now - started_at).should be_within(Celluloid::Timer::QUANTUM).of interval
419
+ end
420
+ end
421
+
422
+ context :timers do
423
+ before do
424
+ @klass = Class.new do
425
+ include included_module
426
+
427
+ def initialize
428
+ @sleeping = false
429
+ @fired = false
430
+ end
431
+
432
+ def do_sleep(n)
433
+ @sleeping = true
434
+ sleep n
435
+ @sleeping = false
436
+ end
437
+
438
+ def sleeping?; @sleeping end
439
+
440
+ def fire_after(n)
441
+ after(n) { @fired = true }
442
+ end
443
+
444
+ def fire_every(n)
445
+ @fired = 0
446
+ every(n) { @fired += 1 }
447
+ end
448
+
449
+ def fired?; !!@fired end
450
+ def fired; @fired end
451
+ end
452
+ end
453
+
454
+ it "suspends execution of a method (but not the actor) for a given time" do
455
+ actor = @klass.new
456
+
457
+ # Sleep long enough to ensure we're actually seeing behavior when asleep
458
+ # but not so long as to delay the test suite unnecessarily
459
+ interval = Celluloid::Timer::QUANTUM * 10
460
+ started_at = Time.now
461
+
462
+ future = actor.future(:do_sleep, interval)
463
+ sleep(interval / 2) # wonky! :/
464
+ actor.should be_sleeping
465
+
466
+ future.value
467
+ (Time.now - started_at).should be_within(Celluloid::Timer::QUANTUM).of interval
468
+ end
469
+
470
+ it "schedules timers which fire in the future" do
471
+ actor = @klass.new
472
+
473
+ interval = Celluloid::Timer::QUANTUM * 10
474
+ started_at = Time.now
475
+
476
+ timer = actor.fire_after(interval)
477
+ actor.should_not be_fired
478
+
479
+ sleep(interval + Celluloid::Timer::QUANTUM) # wonky! #/
480
+ actor.should be_fired
481
+ end
482
+
483
+ it "schedules recurring timers which fire in the future" do
484
+ actor = @klass.new
485
+
486
+ interval = Celluloid::Timer::QUANTUM * 10
487
+ started_at = Time.now
488
+
489
+ timer = actor.fire_every(interval)
490
+ actor.fired.should be == 0
491
+
492
+ sleep(interval + Celluloid::Timer::QUANTUM) # wonky! #/
493
+ actor.fired.should be == 1
494
+
495
+ 2.times { sleep(interval + Celluloid::Timer::QUANTUM) } # wonky! #/
496
+ actor.fired.should be == 3
497
+ end
498
+
499
+ it "cancels timers before they fire" do
500
+ actor = @klass.new
501
+
502
+ interval = Celluloid::Timer::QUANTUM * 10
503
+ started_at = Time.now
504
+
505
+ timer = actor.fire_after(interval)
506
+ actor.should_not be_fired
507
+ timer.cancel
508
+
509
+ sleep(interval + Celluloid::Timer::QUANTUM) # wonky! #/
510
+ actor.should_not be_fired
511
+ end
512
+ end
513
+
514
+ context :tasks do
515
+ before do
516
+ @klass = Class.new do
517
+ include included_module
518
+ attr_reader :blocker
519
+
520
+ def initialize
521
+ @blocker = Blocker.new
522
+ end
523
+
524
+ def blocking_call
525
+ @blocker.block
526
+ end
527
+ end
528
+
529
+ class Blocker
530
+ include Celluloid
531
+
532
+ def block
533
+ wait :unblock
534
+ end
535
+
536
+ def unblock
537
+ signal :unblock
538
+ end
539
+ end
540
+ end
541
+
542
+ it "knows which tasks are waiting on calls to other actors" do
543
+ actor = @klass.new
544
+
545
+ # an alias for Celluloid::Actor#waiting_tasks
546
+ tasks = actor.tasks
547
+ tasks.size.should == 1
548
+ tasks.first.status.should == :running
549
+
550
+ future = actor.future(:blocking_call)
551
+ sleep 0.1 # hax! waiting for ^^^ call to actually start
552
+
553
+ tasks = actor.tasks
554
+ tasks.size.should == 2
555
+
556
+ blocking_task = tasks.find { |t| t.status != :running }
557
+ blocking_task.should be_a Celluloid::Task
558
+ blocking_task.status.should == :callwait
559
+
560
+ actor.blocker.unblock
561
+ sleep 0.1 # hax again :(
562
+ actor.tasks.size.should == 1
563
+ end
564
+ end
565
+ end
@@ -0,0 +1,52 @@
1
+ shared_context "a Celluloid Mailbox" do
2
+ class TestEvent < Celluloid::SystemEvent; end
3
+
4
+ it "receives messages" do
5
+ message = :ohai
6
+
7
+ subject << message
8
+ subject.receive.should == message
9
+ end
10
+
11
+ it "raises system events when received" do
12
+ subject.system_event TestEvent.new("example")
13
+
14
+ expect do
15
+ subject.receive
16
+ end.to raise_exception(TestEvent)
17
+ end
18
+
19
+ it "prioritizes system events over other messages" do
20
+ subject << :dummy1
21
+ subject << :dummy2
22
+ subject.system_event TestEvent.new("example")
23
+
24
+ expect do
25
+ subject.receive
26
+ end.to raise_exception(TestEvent)
27
+ end
28
+
29
+ it "selectively receives messages with a block" do
30
+ class Foo; end
31
+ class Bar; end
32
+ class Baz; end
33
+
34
+ foo, bar, baz = Foo.new, Bar.new, Baz.new
35
+
36
+ subject << baz
37
+ subject << foo
38
+ subject << bar
39
+
40
+ subject.receive { |msg| msg.is_a? Foo }.should == foo
41
+ subject.receive { |msg| msg.is_a? Bar }.should == bar
42
+ subject.receive.should == baz
43
+ end
44
+
45
+ it "waits for a given timeout interval" do
46
+ interval = 0.1
47
+ started_at = Time.now
48
+
49
+ subject.receive(interval) { false }
50
+ (Time.now - started_at).should be_within(Celluloid::Timer::QUANTUM).of interval
51
+ end
52
+ end