celluloid 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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