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