kulesa-celluloid 0.10.2

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.
@@ -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