donkey 0.1.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.
@@ -0,0 +1,83 @@
1
+ require 'rubygems'
2
+ require "lib/ass"
3
+ require "spec"
4
+ require 'thread'
5
+
6
+ require 'eventmachine'
7
+ EM.threadpool_size = 1
8
+
9
+ describe "Actor" do
10
+ before do
11
+ q = Queue.new
12
+ @server = nil
13
+ @thread = Thread.new {ASS.start {
14
+ q << :ready
15
+ }}
16
+ @thread.abort_on_exception = true
17
+ q.pop.should == :ready
18
+ end
19
+
20
+ after do
21
+ ASS.stop
22
+ @thread.join
23
+ end
24
+
25
+ it "should respond by dispatch to methods" do
26
+ q = Queue.new
27
+ actor = ASS.actor("spec") {
28
+ define_method(:foo) do |i|
29
+ r = [:foo,i]
30
+ q << r
31
+ r
32
+ end
33
+
34
+ define_method(:bar) do |i|
35
+ r = [:bar,i]
36
+ q << r
37
+ r
38
+ end
39
+ }
40
+ #a.cast("spec",:foo,1)
41
+ actor.cast("spec",:foo,1)
42
+ q.pop.should == [:foo,1]
43
+ actor.cast("spec",:bar,2)
44
+ q.pop.should == [:bar,2]
45
+
46
+ actor.call("spec",:bar,3) # calling to self
47
+ q.pop.should == [:bar,3] # input
48
+ q.pop.should == [:bar,[:bar,3]] # output
49
+ end
50
+
51
+ it "should handle error by calling on_error" do
52
+ q = Queue.new
53
+ actor = ASS.actor("spec") {
54
+ define_method(:on_error) do |e,data|
55
+ q << [e,data]
56
+ end
57
+ }
58
+ actor.cast("spec",:foo,1)
59
+ e, data = q.pop
60
+ e.should be_a(NoMethodError)
61
+ data.should == 1
62
+ end
63
+
64
+ it "should be able to make service calls" do
65
+ q = Queue.new
66
+ a1 = ASS.actor("spec") {
67
+ define_method(:foo) do |i|
68
+ q << [:foo,i]
69
+ cast("spec2",:bar,i+1)
70
+ end
71
+ }
72
+ a2 = ASS.actor("spec2") do
73
+ define_method(:bar) do |i|
74
+ q << [:bar,i]
75
+ end
76
+ end
77
+ a1.cast("spec",:foo,1)
78
+ q.pop.should == [:foo,1]
79
+ q.pop.should == [:bar,2]
80
+ end
81
+
82
+
83
+ end
@@ -0,0 +1,425 @@
1
+ require 'rubygems'
2
+ require "lib/ass"
3
+ require "spec"
4
+ require 'thread'
5
+
6
+ require 'eventmachine'
7
+ EM.threadpool_size = 1
8
+
9
+ require 'mocha'
10
+ Spec::Runner.configure do |config|
11
+ config.mock_with :mocha
12
+ end
13
+
14
+
15
+ describe "ASS" do
16
+ def server(*args,&block)
17
+ name = args.grep(String).first || "spec"
18
+ opts = args.grep(Hash).first || {}
19
+ ASS.server(name,opts) {
20
+ define_method(:on_call,&block)
21
+ }
22
+ end
23
+
24
+ def call(data,opts={},meta=nil)
25
+ ASS.call("spec",nil,data,{
26
+ :reply_to => "spec"
27
+ }.merge(opts),meta)
28
+ end
29
+
30
+ def cast(data,opts={},meta=nil)
31
+ ASS.cast("spec",nil,data,opts,meta)
32
+ end
33
+
34
+ def start_ass
35
+ q = Queue.new
36
+ @thread = Thread.new {ASS.start(:logging => (ENV["trace"]=="true")) {
37
+ q << :ready
38
+ }}
39
+ @thread.abort_on_exception = true
40
+ q.pop.should == :ready
41
+ end
42
+
43
+ def stop_ass
44
+ ASS.stop
45
+ @thread.join
46
+ end
47
+
48
+ it "should start and stop" do
49
+ 30.times do
50
+ begin
51
+ q = Queue.new
52
+ s = nil
53
+ thread = Thread.new {
54
+ ASS.start {
55
+ s = server("spec") do |i|
56
+ q << :ok
57
+ end
58
+ q << :ready
59
+ }
60
+ q << :done
61
+ }
62
+ thread.abort_on_exception = true
63
+ q.pop.should == :ready
64
+ cast(0)
65
+ q.pop.should == :ok
66
+ ASS.stop
67
+ q.pop.should == :done
68
+ rescue => e
69
+ ASS.stop
70
+ raise e
71
+ end
72
+ end
73
+ end
74
+
75
+ it "should stop ASS if server does not respond to on_error" do
76
+ begin
77
+ old_stderr = $stderr
78
+ error_output = StringIO.new
79
+ $stderr = error_output
80
+ q = Queue.new
81
+ s = nil
82
+ t = Thread.new {
83
+ ASS.start {
84
+ s = server("spec") do |i|
85
+ raise "ouch" if i == :die
86
+ q << :ok
87
+ i
88
+ end
89
+ q << :ready
90
+ }
91
+ q << :died
92
+ }
93
+ q.pop.should == :ready
94
+ cast(1)
95
+ q.pop.should == :ok
96
+ call(:die)
97
+ q.pop.should == :died
98
+ $stderr.string.should_not be_empty
99
+ EM.reactor_running?.should == false
100
+ rescue => e
101
+ ASS.stop
102
+ raise e
103
+ ensure
104
+ $stderr = old_stderr
105
+ end
106
+ end
107
+
108
+
109
+ it "should resend a message if server did not ack a message" do
110
+ begin
111
+ old_stderr = $stderr
112
+ error_output = StringIO.new
113
+ $stderr = error_output
114
+ q = Queue.new
115
+ s = nil
116
+ t1 = Thread.new {
117
+ ASS.start {
118
+ s = ASS.server("spec").react(:ack => true) do
119
+ define_method(:on_call) do |i|
120
+ raise "ouch" if i == :die
121
+ q << [header,i]
122
+ i
123
+ end
124
+ end
125
+ q << :ready
126
+ }
127
+ q << :died
128
+ }
129
+ q.pop.should == :ready
130
+ cast(1)
131
+ header, msg = q.pop
132
+ msg.should == 1
133
+ header.redelivered != true
134
+ cast(:die)
135
+ q.pop.should == :died
136
+ t1.join
137
+ $stderr.string.should_not be_empty
138
+ EM.reactor_running?.should == false
139
+
140
+ # restart server to get have the message resent
141
+ t2 = Thread.new {
142
+ ASS.start {
143
+ s = server { |msg|
144
+ q << [header,msg]
145
+ }
146
+ }
147
+ }
148
+ header, msg = q.pop
149
+ msg.should == :die
150
+ header.redelivered == true
151
+ ASS.stop
152
+ t2.join
153
+ ensure
154
+ $stderr = old_stderr
155
+ ASS.stop
156
+ end
157
+ end
158
+
159
+ describe "serialization formats" do
160
+ def test_format(format)
161
+ q = Queue.new
162
+ @thread = Thread.new {ASS.start(:format => format
163
+ #:logging => true
164
+ ) {
165
+ q << :ready
166
+ }}
167
+ @thread.abort_on_exception = true
168
+ q.pop.should == :ready
169
+
170
+ server { |*args|
171
+ # do nothing
172
+ q << :got_msg
173
+ }
174
+ cast(:whatever)
175
+ q.pop.should == :got_msg
176
+
177
+ ASS.stop
178
+ @thread.join
179
+ end
180
+
181
+ it "uses Marshal" do
182
+ ASS::Marshal.expects(:dump).returns("whatever")
183
+ ASS::Marshal.expects(:load).returns({})
184
+ test_format(ASS::Marshal)
185
+ end
186
+
187
+ it "uses JSON" do
188
+ ASS::JSON.expects(:dump).returns("whatever")
189
+ ASS::JSON.expects(:load).returns({})
190
+ test_format(ASS::JSON)
191
+ end
192
+ end
193
+
194
+ describe "server" do
195
+ before do
196
+ q = Queue.new
197
+ @server = nil
198
+ @thread = Thread.new {ASS.start {
199
+ q << :ready
200
+ }}
201
+ @thread.abort_on_exception = true
202
+ q.pop.should == :ready
203
+ end
204
+
205
+ after do
206
+ ASS.stop
207
+ @thread.join
208
+ end
209
+
210
+ it "should process message with on_call" do
211
+ input = Queue.new
212
+ output = Queue.new
213
+ s = server("spec") do |i|
214
+ input << i if i == 0
215
+ output << i if i == 1 # this is the response to self
216
+ i + 1
217
+ end
218
+ # note that when calling self we have the
219
+ # wierdness of the response sending back to
220
+ # self.
221
+ 10.times { call(0) }
222
+ 10.times.map { input.pop }.uniq.should == [0]
223
+ 10.times.map { output.pop }.uniq.should == [1]
224
+ end
225
+
226
+
227
+ it "should handle error with on_error" do
228
+ errors = Queue.new
229
+ msgs = Queue.new
230
+ s = ASS.server("spec") {
231
+ def on_call(i)
232
+ raise "aieee"
233
+ end
234
+
235
+ define_method(:on_error) do |e,msg|
236
+ msgs << msg
237
+ errors << e
238
+ end
239
+ }
240
+ 10.times { call(0) }
241
+ 10.times {
242
+ msgs.pop.should == 0
243
+ #errors.pop.is_a?(Exception).should == true
244
+ errors.pop.should be_a(RuntimeError)
245
+ }
246
+ end
247
+
248
+ it "should have access to magic service methods" do
249
+ q = Queue.new
250
+ s = server { |i|
251
+ q << [header,method,data,meta]
252
+ }
253
+ cast(1,{},:meta)
254
+ header,method,data,meta = q.pop
255
+ header.should be_an(MQ::Header)
256
+ method.should be_nil
257
+ data.should == 1
258
+ meta.should == :meta
259
+ end
260
+
261
+
262
+ it "should use a new callback instance to process each request" do
263
+ q = Queue.new
264
+ s = server { |i|
265
+ q << self
266
+ i
267
+ }
268
+ 2000.times {|i|
269
+ begin
270
+ cast(1)
271
+ rescue => e
272
+ p "broken at #{i}"
273
+ raise e
274
+ end
275
+ }
276
+ # we need to hold on to the saved pointers,
277
+ # otherwise objects would get garbage
278
+ # collected and their object_ids
279
+ # reused. This is the case with C-ruby.
280
+ saved_pointers = []
281
+ ids = (0...2000).map {
282
+ o = q.pop
283
+ saved_pointers << o
284
+ o.object_id
285
+ }
286
+ #pp s.objs
287
+ ids.uniq.length.should == ids.length
288
+ end
289
+
290
+ it "should receive messages in order from a connection" do
291
+ q = Queue.new
292
+ s = server do |i|
293
+ q << i
294
+ i
295
+ end
296
+ 100.times { |i|
297
+ cast(i)
298
+ }
299
+ 100.times { |i|
300
+ q.pop == i
301
+ }
302
+ end
303
+
304
+ it "should resend message" do
305
+ pending
306
+ i = 0
307
+ q = Queue.new
308
+ s = server do |data|
309
+ q << [header,method,data,meta]
310
+ # requeue the first 100 messages
311
+ i += 1
312
+ if i <= 100
313
+ resend
314
+ end
315
+ true
316
+ end
317
+ 100.times { |i|
318
+ cast(i,{ :message_id => i }, i)
319
+ }
320
+ # the first 100 should be the same message as the messages to 200
321
+
322
+ msgs100 = 100.times.map { q.pop }
323
+ msgs200 = 100.times.map { q.pop }
324
+ msgs100.zip(msgs200) { |m1,m2|
325
+ header1,method1,data1,meta1 = m1
326
+ header2,method2,data2,meta2 = m2
327
+
328
+ # everything should be the same except the delivery_tag
329
+ header2.delivery_tag.should_not == header1.delivery_tag
330
+ # requeue is different from AMQP's redelivery
331
+ ## it should resend the message as another one.
332
+ header1.redelivered.should_not == true
333
+ header2.redelivered.should_not == true
334
+
335
+ header2.message_id.should == header2.message_id
336
+ method2.should == method1
337
+ data2.should == data1
338
+ meta1.should == meta2
339
+ }
340
+ end
341
+
342
+ it "unsubscribes from queue" do
343
+ pending
344
+ q1 = Queue.new
345
+ s1 = server do |data|
346
+ q1 << data
347
+ end
348
+ (1..10).map { |i| cast(i) }
349
+ (1..10).map { q1.pop }.should == (1..10).to_a
350
+ # s1.stop { q1 << :unsubscribed } # lame, this somehow causes blocking.
351
+ # q1.pop.should == :unsubscribed
352
+ sleep(1)
353
+ (1..10).map { |i| cast(i) } # these should be queued until another server comes up
354
+ q1.should be_empty
355
+ q2 = Queue.new
356
+ s2 = server do |data|
357
+ q2 << data
358
+ end
359
+ (1..10).map { q2.pop }.should == (1..10).to_a
360
+ end
361
+
362
+ end
363
+
364
+ describe "funnel and tunnel" do
365
+ before do
366
+ start_ass
367
+ end
368
+
369
+ after do
370
+ stop_ass
371
+ end
372
+
373
+ def funnel(i,match)
374
+ q = Queue.new
375
+ ASS::Topic.funnel("spec-tunnel","spec-funnel-#{i}",match) {
376
+ define_method(:on_event) do |key,data|
377
+ q << [key,data]
378
+ end
379
+ }
380
+ q
381
+ end
382
+
383
+ def tunnel
384
+ ASS::Topic.tunnel("spec-tunnel")
385
+ end
386
+
387
+ def event(key,data)
388
+ ASS::Topic.event("spec-tunnel",key,data)
389
+ end
390
+
391
+ it "event notification from tunnel to funnel" do
392
+ tunnel
393
+
394
+ all = funnel(0,"*")
395
+ evens = funnel(1,"even")
396
+ odds = funnel(2,"odd")
397
+
398
+ sleep(0.1) # yield
399
+
400
+ 10.times { |i|
401
+ key = i.even? ? "even" : "odd"
402
+ event(key,i)
403
+ }
404
+
405
+ all = 10.times.map { all.pop }
406
+ all.each do |(key,data)|
407
+ key.match(/^even|odd$/).should_not be_nil
408
+ end
409
+
410
+ evens = 5.times.map { evens.pop }
411
+ evens.each do |(key,data)|
412
+ key.should == "even"
413
+ data.should be_even
414
+ end
415
+
416
+ odds = 5.times.map { odds.pop }
417
+ odds.each do |(key,data)|
418
+ key.should == "odd"
419
+ data.should be_odd
420
+ end
421
+ end
422
+
423
+ end
424
+
425
+ end