donkey 0.1.0

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