ASS 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,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
+