ASS 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+ # class Topic
2
+ # def initialize(name,opts={})
3
+ # @exchange = MQ.topic(name,opts)
4
+ # end
5
+
6
+ # def publish(key,payload,opts={})
7
+ # @exchange.publish(::Marshal.dump(payload),opts.merge(:routing_key => key))
8
+ # end
9
+
10
+ # def subscribe(matcher,opts={},&block)
11
+ # ack = opts.delete(:ack)
12
+ # uid = "#{@exchange.name}.topic.#{rand 999_999_999_999}"
13
+ # q = MQ.queue(uid,opts)
14
+ # q.bind(@exchange.name,:key => matcher)
15
+ # q.subscribe(:ack => ack) { |info,payload|
16
+ # payload = ::Marshal.load(payload)
17
+ # if block.arity == 2
18
+ # block.call(info,payload)
19
+ # else
20
+ # block.call(payload)
21
+ # end
22
+ # }
23
+ # q
24
+ # end
25
+ # end
@@ -0,0 +1,84 @@
1
+ require 'rubygems'
2
+ require "lib/ass"
3
+ require "spec"
4
+ require 'rant/spec'
5
+ require 'thread'
6
+
7
+ require 'eventmachine'
8
+ EM.threadpool_size = 1
9
+
10
+ describe "Actor" do
11
+ before do
12
+ q = Queue.new
13
+ @server = nil
14
+ @thread = Thread.new {ASS.start {
15
+ q << :ready
16
+ }}
17
+ @thread.abort_on_exception = true
18
+ q.pop.should == :ready
19
+ end
20
+
21
+ after do
22
+ ASS.stop
23
+ @thread.join
24
+ end
25
+
26
+ it "should respond by dispatch to methods" do
27
+ q = Queue.new
28
+ actor = ASS.actor("spec") {
29
+ define_method(:foo) do |i|
30
+ r = [:foo,i]
31
+ q << r
32
+ r
33
+ end
34
+
35
+ define_method(:bar) do |i|
36
+ r = [:bar,i]
37
+ q << r
38
+ r
39
+ end
40
+ }
41
+ #a.cast("spec",:foo,1)
42
+ actor.cast("spec",:foo,1)
43
+ q.pop.should == [:foo,1]
44
+ actor.cast("spec",:bar,2)
45
+ q.pop.should == [:bar,2]
46
+
47
+ actor.call("spec",:bar,3) # calling to self
48
+ q.pop.should == [:bar,3] # input
49
+ q.pop.should == [:bar,[:bar,3]] # output
50
+ end
51
+
52
+ it "should handle error by calling on_error" do
53
+ q = Queue.new
54
+ actor = ASS.actor("spec") {
55
+ define_method(:on_error) do |e,data|
56
+ q << [e,data]
57
+ end
58
+ }
59
+ actor.cast("spec",:foo,1)
60
+ e, data = q.pop
61
+ e.should be_a(NoMethodError)
62
+ data.should == 1
63
+ end
64
+
65
+ it "should be able to make service calls" do
66
+ q = Queue.new
67
+ a1 = ASS.actor("spec") {
68
+ define_method(:foo) do |i|
69
+ q << [:foo,i]
70
+ cast("spec2",:bar,i+1)
71
+ end
72
+ }
73
+ a2 = ASS.actor("spec2") do
74
+ define_method(:bar) do |i|
75
+ q << [:bar,i]
76
+ end
77
+ end
78
+ a1.cast("spec",:foo,1)
79
+ q.pop.should == [:foo,1]
80
+ q.pop.should == [:bar,2]
81
+ end
82
+
83
+
84
+ end
@@ -0,0 +1,309 @@
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 "ASS" do
10
+ def server(*args,&block)
11
+ name = args.grep(String).first || "spec"
12
+ opts = args.grep(Hash).first || {}
13
+ ASS.server(name,opts) {
14
+ define_method(:on_call,&block)
15
+ }
16
+ end
17
+
18
+ def call(data,opts={},meta=nil)
19
+ ASS.call("spec",nil,data,{
20
+ :reply_to => "spec"
21
+ }.merge(opts),meta)
22
+ end
23
+
24
+ def cast(data,opts={},meta=nil)
25
+ ASS.cast("spec",nil,data,opts,meta)
26
+ end
27
+
28
+
29
+ it "should start and stop" do
30
+ 30.times do
31
+ begin
32
+ q = Queue.new
33
+ s = nil
34
+ thread = Thread.new {
35
+ ASS.start {
36
+ s = server("spec") do |i|
37
+ q << :ok
38
+ end
39
+ q << :ready
40
+ }
41
+ q << :done
42
+ }
43
+ thread.abort_on_exception = true
44
+ q.pop.should == :ready
45
+ cast(0)
46
+ q.pop.should == :ok
47
+ ASS.stop
48
+ q.pop.should == :done
49
+ rescue => e
50
+ ASS.stop
51
+ raise e
52
+ end
53
+ end
54
+ end
55
+
56
+ it "should stop ASS if server does not respond to on_error" do
57
+ begin
58
+ old_stderr = $stderr
59
+ error_output = StringIO.new
60
+ $stderr = error_output
61
+ q = Queue.new
62
+ s = nil
63
+ t = Thread.new {
64
+ ASS.start {
65
+ s = server("spec") do |i|
66
+ raise "ouch" if i == :die
67
+ q << :ok
68
+ i
69
+ end
70
+ q << :ready
71
+ }
72
+ q << :died
73
+ }
74
+ q.pop.should == :ready
75
+ cast(1)
76
+ q.pop.should == :ok
77
+ call(:die)
78
+ q.pop.should == :died
79
+ $stderr.string.should_not be_empty
80
+ EM.reactor_running?.should == false
81
+ rescue => e
82
+ ASS.stop
83
+ raise e
84
+ ensure
85
+ $stderr = old_stderr
86
+ end
87
+ end
88
+
89
+
90
+ it "should resend a message if server did not ack a message" do
91
+ begin
92
+ old_stderr = $stderr
93
+ error_output = StringIO.new
94
+ $stderr = error_output
95
+ q = Queue.new
96
+ s = nil
97
+ t1 = Thread.new {
98
+ ASS.start {
99
+ s = ASS.server("spec").react(:ack => true) do
100
+ define_method(:on_call) do |i|
101
+ raise "ouch" if i == :die
102
+ q << [header,i]
103
+ i
104
+ end
105
+ end
106
+ q << :ready
107
+ }
108
+ q << :died
109
+ }
110
+ q.pop.should == :ready
111
+ cast(1)
112
+ header, msg = q.pop
113
+ msg.should == 1
114
+ header.redelivered != true
115
+ cast(:die)
116
+ q.pop.should == :died
117
+ t1.join
118
+ $stderr.string.should_not be_empty
119
+ EM.reactor_running?.should == false
120
+
121
+ # restart server to get have the message resent
122
+ t2 = Thread.new {
123
+ ASS.start {
124
+ s = server { |msg|
125
+ q << [header,msg]
126
+ }
127
+ }
128
+ }
129
+ header, msg = q.pop
130
+ msg.should == :die
131
+ header.redelivered == true
132
+ ASS.stop
133
+ t2.join
134
+ ensure
135
+ $stderr = old_stderr
136
+ ASS.stop
137
+ end
138
+ end
139
+
140
+ describe "server" do
141
+ before do
142
+ q = Queue.new
143
+ @server = nil
144
+ @thread = Thread.new {ASS.start {
145
+ q << :ready
146
+ }}
147
+ @thread.abort_on_exception = true
148
+ q.pop.should == :ready
149
+ end
150
+
151
+ after do
152
+ ASS.stop
153
+ @thread.join
154
+ end
155
+
156
+ it "should process message with on_call" do
157
+ input = Queue.new
158
+ output = Queue.new
159
+ s = server("spec") do |i|
160
+ input << i if i == 0
161
+ output << i if i == 1 # this is the response to self
162
+ i + 1
163
+ end
164
+ # note that when calling self we have the
165
+ # wierdness of the response sending back to
166
+ # self.
167
+ 10.times { call(0) }
168
+ 10.times.map { input.pop }.uniq.should == [0]
169
+ 10.times.map { output.pop }.uniq.should == [1]
170
+ end
171
+
172
+ it "should handle error with on_error" do
173
+ errors = Queue.new
174
+ msgs = Queue.new
175
+ s = ASS.server("spec") {
176
+ def on_call(i)
177
+ raise "aieee"
178
+ end
179
+
180
+ define_method(:on_error) do |e,msg|
181
+ msgs << msg
182
+ errors << e
183
+ end
184
+ }
185
+ 10.times { call(0) }
186
+ 10.times {
187
+ msgs.pop.should == 0
188
+ #errors.pop.is_a?(Exception).should == true
189
+ errors.pop.should be_a(RuntimeError)
190
+ }
191
+ end
192
+
193
+ it "should have access to magic service methods" do
194
+ q = Queue.new
195
+ s = server { |i|
196
+ q << [header,method,data,meta]
197
+ }
198
+ cast(1,{},:meta)
199
+ header,method,data,meta = q.pop
200
+ header.should be_an(MQ::Header)
201
+ method.should be_nil
202
+ data.should == 1
203
+ meta.should == :meta
204
+ end
205
+
206
+
207
+ it "should use a new callback instance to process each request" do
208
+ q = Queue.new
209
+ s = server { |i|
210
+ q << self
211
+ i
212
+ }
213
+ 2000.times {|i|
214
+ begin
215
+ cast(1)
216
+ rescue => e
217
+ p "broken at #{i}"
218
+ raise e
219
+ end
220
+ }
221
+ # we need to hold on to the saved pointers,
222
+ # otherwise objects would get garbage
223
+ # collected and their object_ids
224
+ # reused. This is the case with C-ruby.
225
+ saved_pointers = []
226
+ ids = (0...2000).map {
227
+ o = q.pop
228
+ saved_pointers << o
229
+ o.object_id
230
+ }
231
+ #pp s.objs
232
+ ids.uniq.length.should == ids.length
233
+ end
234
+
235
+ it "should receive messages in order from a connection" do
236
+ q = Queue.new
237
+ s = server do |i|
238
+ q << i
239
+ i
240
+ end
241
+ 100.times { |i|
242
+ cast(i)
243
+ }
244
+ 100.times { |i|
245
+ q.pop == i
246
+ }
247
+ end
248
+
249
+ it "should resend message" do
250
+ pending
251
+ i = 0
252
+ q = Queue.new
253
+ s = server do |data|
254
+ q << [header,method,data,meta]
255
+ # requeue the first 100 messages
256
+ i += 1
257
+ if i <= 100
258
+ resend
259
+ end
260
+ true
261
+ end
262
+ 100.times { |i|
263
+ cast(i,{ :message_id => i }, i)
264
+ }
265
+ # the first 100 should be the same message as the messages to 200
266
+
267
+ msgs100 = 100.times.map { q.pop }
268
+ msgs200 = 100.times.map { q.pop }
269
+ msgs100.zip(msgs200) { |m1,m2|
270
+ header1,method1,data1,meta1 = m1
271
+ header2,method2,data2,meta2 = m2
272
+
273
+ # everything should be the same except the delivery_tag
274
+ header2.delivery_tag.should_not == header1.delivery_tag
275
+ # requeue is different from AMQP's redelivery
276
+ ## it should resend the message as another one.
277
+ header1.redelivered.should_not == true
278
+ header2.redelivered.should_not == true
279
+
280
+ header2.message_id.should == header2.message_id
281
+ method2.should == method1
282
+ data2.should == data1
283
+ meta1.should == meta2
284
+ }
285
+ end
286
+
287
+ it "unsubscribes from queue" do
288
+ pending
289
+ q1 = Queue.new
290
+ s1 = server do |data|
291
+ q1 << data
292
+ end
293
+ (1..10).map { |i| cast(i) }
294
+ (1..10).map { q1.pop }.should == (1..10).to_a
295
+ # s1.stop { q1 << :unsubscribed } # lame, this somehow causes blocking.
296
+ # q1.pop.should == :unsubscribed
297
+ sleep(1)
298
+ (1..10).map { |i| cast(i) } # these should be queued until another server comes up
299
+ q1.should be_empty
300
+ q2 = Queue.new
301
+ s2 = server do |data|
302
+ q2 << data
303
+ end
304
+ (1..10).map { q2.pop }.should == (1..10).to_a
305
+ end
306
+
307
+ end
308
+
309
+ end
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require "lib/ass"
3
+ require "spec"
4
+ require 'rant/spec'
5
+ require 'thread'
6
+
7
+ require 'eventmachine'
8
+ EM.threadpool_size = 1
9
+
10
+ describe "RPC" do
11
+ before do
12
+ q = Queue.new
13
+ @server = nil
14
+ @thread = Thread.new {ASS.start(:logging => false) {
15
+ q << :ready
16
+ }}
17
+ @thread.abort_on_exception = true
18
+ q.pop.should == :ready
19
+ end
20
+
21
+ after do
22
+ ASS.stop
23
+ @thread.join
24
+ end
25
+
26
+ it "should make synchronized rpc call" do
27
+ ASS.actor("spec") do
28
+ def foo(i)
29
+ i
30
+ end
31
+ end
32
+ c = ASS.client
33
+ c.call("spec",:foo,1).wait.should == 1
34
+ end
35
+
36
+ it "should do cast" do
37
+ q = Queue.new
38
+ ASS.actor("spec") do
39
+ define_method(:foo) do |i|
40
+ q << i
41
+ i
42
+ end
43
+ end
44
+ c = ASS.client
45
+ c.cast("spec",:foo,1)
46
+ q.pop.should == 1
47
+ end
48
+ end
49
+
50
+