simrpc 0.2 → 0.4

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.
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ require 'spec/rake/spectask'
8
8
  require 'rake/gempackagetask'
9
9
 
10
10
  GEM_NAME="simrpc"
11
- PKG_VERSION=0.2
11
+ PKG_VERSION=0.4
12
12
 
13
13
  desc "Run all specs"
14
14
  Spec::Rake::SpecTask.new('spec') do |t|
@@ -30,11 +30,14 @@ SPEC = Gem::Specification.new do |s|
30
30
 
31
31
  s.required_ruby_version = '>= 1.8.1'
32
32
  s.required_rubygems_version = Gem::Requirement.new(">= 1.3.3")
33
- # FIXME require qpid
33
+ s.add_development_dependency('rspec', '~> 1.3.0')
34
+ s.add_dependency('uuid', '~> 2.3.1')
35
+ s.add_dependency('activesupport', '~> 2.3.8')
36
+ # !!! we also need the ruby qpid wrapper. which is not currently packaged in gem form
34
37
 
35
38
  s.author = "Mohammed Morsi"
36
39
  s.email = "movitto@yahoo.com"
37
- s.date = %q{2010-03-11}
40
+ s.date = %q{2010-09-04}
38
41
  s.description = %q{simrpc is a simple Ruby module for rpc communication, using Apache QPID as the transport mechanism.}
39
42
  s.summary = %q{simrpc is a simple Ruby module for rpc communication, using Apache QPID as the transport mechanism.}
40
43
  s.homepage = %q{http://projects.morsi.org/Simrpc}
data/lib/simrpc.rb CHANGED
@@ -30,9 +30,10 @@ lib = File.dirname(__FILE__)
30
30
 
31
31
  require lib + '/simrpc/common'
32
32
  require lib + '/simrpc/exceptions'
33
+ require lib + '/simrpc/thread_pool'
33
34
  require lib + '/simrpc/schema'
34
35
  require lib + '/simrpc/message'
35
36
  require lib + '/simrpc/qpid_adapter'
36
37
  require lib + '/simrpc/node'
37
38
 
38
- require 'activesupport' # for inflector
39
+ require 'active_support' # for inflector
data/lib/simrpc/common.rb CHANGED
@@ -24,6 +24,7 @@
24
24
  # OTHER DEALINGS IN THE SOFTWARE.
25
25
 
26
26
  require 'logger'
27
+ require 'uuid'
27
28
 
28
29
  module Simrpc
29
30
 
@@ -43,4 +44,12 @@ class Logger
43
44
  end
44
45
  end
45
46
 
47
+ # ID bank, generates unique ids
48
+ class IDBank
49
+ # FIXME right now generates uuid and grabs first 8 chars for message ids, not enough for tru randomness
50
+ def self.generate
51
+ UUID.new.generate[0...8]
52
+ end
46
53
  end
54
+
55
+ end # module Simrpc
@@ -33,15 +33,15 @@ module Message
33
33
  # Simrpc::Message formatter helper module
34
34
  class Formatter
35
35
 
36
- # helper method to format a data field,
37
- # prepending a fixed size to it
36
+ # Helper method to format a data field,
37
+ # prepending a size to it
38
38
  def self.format_with_size(data)
39
39
  # currently size is set to a 8 digit int
40
40
  len = "%08d" % data.to_s.size
41
41
  len + data.to_s
42
42
  end
43
43
 
44
- # helper method to parse a data field
44
+ # Helper method to parse a data field
45
45
  # off the front of a data sequence, using the
46
46
  # formatted size. Returns parsed data field
47
47
  # and remaining data sequence. If optional
@@ -56,6 +56,24 @@ class Formatter
56
56
  return parsed, remaining if data_class.nil?
57
57
  return data_class.from_s(parsed), remaining
58
58
  end
59
+
60
+ # Helper to format a data field of a fixed size,
61
+ def self.format_with_fixed_size(size, data)
62
+ # we will only accept the first <size> characters of data
63
+ # FIXME throw exception if data.size < size
64
+ data.to_s[0..size-1]
65
+ end
66
+
67
+ # Helper to parse data of a fixed size and return it and the remaining data.
68
+ # If optional class is given, the from_s method will be invoked w/ the parsed
69
+ # data field and returned w/ the remaining data sequence instead
70
+ def self.parse_from_formatted_with_fixed_size(size, data, data_class = nil)
71
+ # FIXME throw exception if data.size < size
72
+ parsed = data[0..size-1]
73
+ remaining = data[size...data.size]
74
+ return parsed, remaining if data_class.nil?
75
+ return data_class.from_s(parsed), remaining
76
+ end
59
77
  end
60
78
 
61
79
  # a single field trasnmitted via a message,
@@ -83,19 +101,21 @@ end
83
101
  # header contains various descriptive properies
84
102
  # about a message
85
103
  class Header
86
- attr_accessor :type, :target
104
+ attr_accessor :id, :type, :target
87
105
 
88
106
  def initialize(args = {})
107
+ @id = args[:id].nil? ? "" : args[:id]
89
108
  @type = args[:type].nil? ? "" : args[:type]
90
109
  @target = args[:target].nil? ? "" : args[:target]
91
110
  end
92
111
 
93
112
  def to_s
94
- Formatter::format_with_size(@type) + Formatter::format_with_size(@target)
113
+ Formatter::format_with_fixed_size(8, @id) + Formatter::format_with_size(@type) + Formatter::format_with_size(@target)
95
114
  end
96
115
 
97
116
  def self.from_s(data)
98
117
  header = Header.new
118
+ header.id, data = Formatter::parse_from_formatted_with_fixed_size(8, data)
99
119
  header.type, data = Formatter::parse_from_formatted(data)
100
120
  header.target, data = Formatter::parse_from_formatted(data)
101
121
  return header
@@ -106,7 +126,7 @@ end
106
126
  class Body
107
127
  attr_accessor :fields
108
128
 
109
- def initialize
129
+ def initialize(args = {})
110
130
  @fields = []
111
131
  end
112
132
 
@@ -134,9 +154,9 @@ end
134
154
  class Message
135
155
  attr_accessor :header, :body
136
156
 
137
- def initialize
138
- @header = Header.new
139
- @body = Body.new
157
+ def initialize(args = {})
158
+ @header = Header.new(args.has_key?(:header) ? args[:header] : {})
159
+ @body = Body.new(args.has_key?(:body) ? args[:body] : {})
140
160
  end
141
161
 
142
162
  def to_s
data/lib/simrpc/node.rb CHANGED
@@ -33,89 +33,71 @@ class MethodMessageController
33
33
  @schema_def = schema_def
34
34
  end
35
35
 
36
- # generate new new method message, setting the message
36
+ # Generate new new method message, setting the message
37
37
  # target to the specified method name, and setting the fields
38
38
  # on the message to the method arguments
39
39
  def generate(method_name, args)
40
- @schema_def.methods.each { |method|
41
- if method.name == method_name
42
- msg = Message::Message.new
43
- msg.header.type = 'request'
44
- msg.header.target = method.name
45
-
46
- # loop through each param, convering corresponding
47
- # argument to message field and adding it to msg
48
- i = 0
49
- method.parameters.each { |param|
50
- field = Message::Field.new
51
- field.name = param.name
52
- field.value = param.to_s(args[i], @schema_def)
53
- msg.body.fields.push field
54
- i += 1
55
- }
56
-
57
- return msg
58
- end
59
- }
60
- return nil
40
+ mmethod = @schema_def.methods.find { |method| method.name == method_name }
41
+ return nil if mmethod.nil?
42
+
43
+ msg = Message::Message.new :header => {:id => IDBank.generate, :type => 'request', :target => mmethod.name }
44
+
45
+ # if we have too few arguments, fill rest in w/ default values
46
+ if mmethod.parameters.size > args.size
47
+ mmethod.parameters[args.size...mmethod.parameters.size].each { |param| args << param.default }
48
+ end
49
+
50
+ msg.body.fields = (0...mmethod.parameters.size).collect { |i|
51
+ Message::Field.new(:name => mmethod.parameters[i].name,
52
+ :value => mmethod.parameters[i].to_s(args[i], @schema_def))
53
+ }
54
+
55
+ return msg
61
56
  end
62
57
 
63
58
  # should be invoked when a message is received,
64
59
  # takes a message, converts it into a method call, and calls the corresponding
65
60
  # handler in the provided schema. Takes return arguments and sends back to caller
66
61
  def message_received(node, message, reply_to)
67
- message = Message::Message::from_s(message)
68
- @schema_def.methods.each { |method|
69
-
70
- if method.name == message.header.target
71
- Logger.info "received method #{method.name} message "
72
-
73
- # for request messages, dispatch to method handler
74
- if message.header.type != 'response' && method.handler != nil
75
- # order the params
76
- params = []
77
- method.parameters.each { |data_field|
78
- value_field = message.body.fields.find { |f| f.name == data_field.name }
79
- params.push data_field.from_s(value_field.value, @schema_def) unless value_field.nil? # TODO what if value_field is nil
80
- }
81
-
82
- Logger.info "invoking #{method.name} handler "
83
-
84
- # invoke method handler
85
- return_values = method.handler.call(*params) # FIXME handlers can't use 'return' as this will fall through here
86
- # FIXME throw a catch block around this call to catch all handler exceptions
87
- return_values = [return_values] unless return_values.is_a? Array
88
-
89
- # if method returns no values, do not return response
90
- unless method.return_values.size == 0
91
-
92
- # consruct and send response message using return values
93
- response = Message::Message.new
94
- response.header.type = 'response'
95
- response.header.target = method.name
96
- (0...method.return_values.size).each { |rvi|
97
- field = Message::Field.new
98
- field.name = method.return_values[rvi].name
99
- field_def = method.return_values.find { |rv| rv.name == field.name }
100
- field.value = field_def.to_s(return_values[rvi], @schema_def) unless field_def.nil? # TODO what if field_def is nil
101
- response.body.fields.push field
102
- }
103
- Logger.info "responding to #{reply_to}"
104
- node.send_message(reply_to, response)
105
-
106
- end
107
-
108
- # for response values just return converted return values
109
- else
110
- results = []
111
- method.return_values.each { |data_field|
112
- value_field = message.body.fields.find { |f| f.name == data_field.name }
113
- results.push data_field.from_s(value_field.value, @schema_def) unless value_field.nil? # TODO what if value_field is nil
114
- }
115
- return results
116
- end
117
- end
118
- }
62
+ message = Message::Message::from_s(message)
63
+ mmethod = @schema_def.methods.find { |method| method.name == message.header.target }
64
+ return nil if mmethod.nil?
65
+
66
+ Logger.info "received method #{mmethod.name} message "
67
+
68
+ # For request messages, dispatch to method handler
69
+ if message.header.type == 'request'
70
+ return nil if mmethod.handler.nil?
71
+
72
+ # collect the method params
73
+ params = (0...mmethod.parameters.size).collect { |i| mmethod.parameters[i].from_s(message.body.fields[i].value, @schema_def) }
74
+
75
+ Logger.info "invoking #{mmethod.name} handler "
76
+
77
+ # invoke method handler
78
+ return_values = mmethod.handler.call(*params) # FIXME handlers can't use 'return' as this will fall through here
79
+ # FIXME throw a catch block around this call to catch all handler exceptions
80
+ return_values = [return_values] unless return_values.is_a? Array
81
+
82
+ # consruct and send response message using return values
83
+ response = Message::Message.new :header => {:id => message.header.id, :type => 'response', :target => mmethod.name }
84
+ response.body.fields = (0...mmethod.return_values.size).collect { |i|
85
+ field_def = mmethod.return_values[i]
86
+ Message::Field.new :name => field_def.name, :value => field_def.to_s(return_values[i], @schema_def)
87
+ # TODO if mmethod.return_values.size > return_values.size, fill in w/ default values
88
+ }
89
+ Logger.info "responding to #{reply_to}"
90
+ node.send_message(reply_to, response)
91
+ return message.header.id, params
92
+
93
+ # For response values just return converted return values
94
+ else
95
+ results = (0...mmethod.return_values.size).collect { |i|
96
+ mmethod.return_values[i].from_s(message.body.fields[i].value, @schema_def)
97
+ }
98
+ return message.header.id, results
99
+
100
+ end
119
101
  end
120
102
  end
121
103
 
@@ -140,15 +122,17 @@ class Node
140
122
  end
141
123
  raise ArgumentError, "schema_def cannot be nil" if @schema_def.nil?
142
124
  @mmc = MethodMessageController.new(@schema_def)
143
- @message_lock = Semaphore.new(1)
144
- @message_lock.wait
125
+
126
+ # hash of message id's -> response locks
127
+ @message_locks = {}
145
128
 
146
129
  # FIXME currently not allowing for any other params to be passed into
147
130
  # QpidAdapter::Node such as broker ip or port, NEED TO FIX THIS
131
+ # NOTE see QpidAdapter::Node for limitation on using 'inspect'
148
132
  @qpid_node = QpidAdapter::Node.new(:id => @id)
149
133
  @qpid_node.async_accept { |node, msg, reply_to|
150
- results = @mmc.message_received(node, msg, reply_to)
151
- message_received(results)
134
+ mid, results = @mmc.message_received(node, msg, reply_to)
135
+ message_received(mid, results)
152
136
  }
153
137
  end
154
138
 
@@ -158,9 +142,14 @@ class Node
158
142
  end
159
143
 
160
144
  # implements, message_received callback to be notified when qpid receives a message
161
- def message_received(results)
162
- @message_results = results
163
- @message_lock.signal
145
+ def message_received(mid, results)
146
+ @message_results = results unless results.nil?
147
+ @message_locks[mid].signal unless mid.nil? || !@message_locks.has_key?(mid)
148
+ end
149
+
150
+ # Terminate node operations
151
+ def terminate
152
+ @qpid_node.terminate
164
153
  end
165
154
 
166
155
  # wait until the node is no longer accepting messages
@@ -188,12 +177,16 @@ class Node
188
177
 
189
178
  # block if we are expecting return values
190
179
  if @schema_def.methods.find{|m| m.name == method_name}.return_values.size != 0
191
- @message_lock.wait # block until response received
180
+ @message_locks[msg.header.id] = Semaphore.new(1)
181
+ @message_locks[msg.header.id].wait
182
+ @message_locks[msg.header.id].wait # block until response received
183
+ @message_locks.delete(msg.header.id)
192
184
 
193
185
  # return return values
194
186
  #@message_received.body.fields.collect { |f| f.value }
195
187
  return *@message_results
196
188
  end
189
+ return nil
197
190
  end
198
191
 
199
192
  # can invoke schema methods directly on Node instances, this will catch
@@ -37,7 +37,8 @@ module QpidAdapter
37
37
  # network which has its own exchange and queue which it listens on
38
38
  class Node
39
39
  private
40
- # helper method to generate a random id
40
+ # Helper method to generate a random id
41
+ # TODO use uuid since we're pulling in that gem anyways for message ids
41
42
  def gen_uuid
42
43
  ["%02x"*4, "%02x"*2, "%02x"*2, "%02x"*2, "%02x"*6].join("-") %
43
44
  Array.new(16) {|x| rand(0xff) }
@@ -77,6 +78,9 @@ class Node
77
78
  end
78
79
 
79
80
  ### create underlying tcp connection
81
+ # NOTE pretty big bug in ruby/qpid preventing us from using 'inspect'
82
+ # on this class (causes segfault)
83
+ # https://issues.apache.org/jira/browse/QPID-2405
80
84
  @conn = Qpid::Connection.new(TCPSocket.new(broker,port))
81
85
  @conn.start
82
86
 
@@ -85,6 +89,10 @@ class Node
85
89
 
86
90
  @children = {}
87
91
 
92
+ # threads pool to handle incoming requests
93
+ # FIXME make the # of threads and timeout configurable)
94
+ @thread_pool = ThreadPool.new(10, :timeout => 5)
95
+
88
96
  @accept_lock = Semaphore.new(1)
89
97
 
90
98
  # qpid constructs that will be created for node
@@ -126,28 +134,34 @@ class Node
126
134
 
127
135
  # start receiving messages
128
136
  @incoming.listen{ |msg|
129
- Logger.info "queue #{@queue} received message #{msg.body.to_s.size} #{msg.body}"
137
+ Logger.info "queue #{@queue} received message #{msg.body}"
130
138
  reply_to = msg.get(:message_properties).reply_to.routing_key
131
- handler.call(self, msg.body, reply_to)
139
+ # FIXME should delete handler threads as they complete
140
+ # FIXME handler timeout
141
+ job = ThreadPoolJob.new { handler.call(self, msg.body, reply_to) }
142
+ @thread_pool << job
132
143
  }
133
144
  end
134
145
 
135
146
  # block until accept operation is complete
136
147
  def join
137
148
  @accept_lock.wait
149
+ #FIXME @thread_pool.join
138
150
  end
139
151
 
140
152
  # instructs QpidServer to stop accepting, blocking
141
153
  # untill all accepting operations have terminated
142
154
  def terminate
143
155
  Logger.warn "terminating qpid session"
156
+ @thread_pool.stop
144
157
  unless @incoming.nil?
145
158
  @incoming.stop
146
159
  @incoming.close
147
160
  @accept_lock.signal
148
161
  end
162
+ @ssn.queue_delete(@queue)
163
+ @ssn.exchange_delete(@exchange)
149
164
  @ssn.close
150
- # TODO undefine the @queue/@exchange
151
165
  end
152
166
 
153
167
  # send a message to the specified routing_key
data/lib/simrpc/schema.rb CHANGED
@@ -66,9 +66,12 @@ module_function :primitive_from_str
66
66
  class DataFieldDef
67
67
  attr_accessor :type, :name, :associated
68
68
 
69
- # indicates this data field should be ignored if it has a null value
69
+ # Indicates this data field should be ignored if it has a null value
70
70
  attr_accessor :ignore_null
71
71
 
72
+ # Indicates the default value of the field
73
+ attr_accessor :default
74
+
72
75
  def initialize(args = {})
73
76
  @type = args[:type] unless args[:type].nil?
74
77
  @name = args[:name] unless args[:name].nil?
@@ -138,7 +141,7 @@ class DataFieldDef
138
141
  # Provide schema_def for :obj or :array data fields associated w/ non-primitive types
139
142
  # # coverted_classes is a recursive helper array used/maintained internally
140
143
  def from_s(str, schema_def = nil, converted_classes = [])
141
- if str == ""
144
+ if str == "" # || str == "nil" # FIXME uncomment & test
142
145
  return nil
143
146
 
144
147
  elsif Schema::is_primitive?(@type)
@@ -390,8 +393,10 @@ class Parser
390
393
  data_field = DataFieldDef.new
391
394
  data_field.type = element.attributes["type"].intern
392
395
  data_field.name = element.attributes["name"]
396
+ data_field.default = data_field.from_s(element.attributes["default"]) unless element.attributes["default"].nil?
393
397
  data_field.associated = element.attributes["associated"].intern unless element.attributes["associated"].nil?
394
398
  data_field.ignore_null = true if element.attributes.include? "ignore_null"
399
+ data_field
395
400
  return data_field
396
401
  end
397
402
  end
@@ -0,0 +1,137 @@
1
+ # Thread Pool
2
+ #
3
+ # Copyright (C) 2010 Mohammed Morsi <movitto@yahoo.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person
6
+ # obtaining a copy of this software and associated documentation
7
+ # files (the "Software"), to deal in the Software without
8
+ # restriction, including without limitation the rights to use,
9
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the
11
+ # Software is furnished to do so, subject to the following
12
+ # conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24
+ # OTHER DEALINGS IN THE SOFTWARE.
25
+
26
+ require 'thread'
27
+
28
+ module Simrpc
29
+
30
+ # Work item to be executed in thread pool
31
+ class ThreadPoolJob
32
+ attr_accessor :handler
33
+ attr_accessor :params
34
+
35
+ def initialize(*params, &block)
36
+ @params = params
37
+ @handler = block
38
+ end
39
+ end
40
+
41
+ # ActiveObject pattern, encapsulate each thread pool thread in object
42
+ class ThreadPoolJobRunner
43
+ attr_accessor :time_started
44
+
45
+ def initialize(thread_pool)
46
+ @thread_pool = thread_pool
47
+ end
48
+
49
+ def run
50
+ @thread = Thread.new {
51
+ until @thread_pool.terminate
52
+ work = @thread_pool.next_job
53
+ @time_started = Time.now
54
+ work.handler.call *work.params unless work.nil?
55
+ @time_started = nil
56
+ end
57
+ }
58
+ end
59
+
60
+ def stop
61
+ @thread.kill
62
+ @thread.join
63
+ end
64
+
65
+ def join
66
+ @thread.join
67
+ end
68
+ end
69
+
70
+ # Launches a specified number of threads on instantiation,
71
+ # assigning work to them as it arrives
72
+ class ThreadPool
73
+ attr_accessor :terminate
74
+
75
+ # Create a thread pool with a specified number of threads
76
+ def initialize(num_threads, args = {})
77
+ @num_threads = num_threads
78
+ @timeout = args[:timeout]
79
+ @job_runners = []
80
+ @work_queue = []
81
+ @work_queue_lock = Mutex.new
82
+ @work_queue_cv = ConditionVariable.new
83
+
84
+ @terminate = false
85
+
86
+ 0.upto(@num_threads) { |i|
87
+ runner = ThreadPoolJobRunner.new(self)
88
+ @job_runners << runner
89
+ runner.run
90
+ }
91
+
92
+ # optional timeout thread
93
+ unless @timeout.nil?
94
+ @timeout_thread = Thread.new {
95
+ until @terminate
96
+ sleep @timeout
97
+ @job_runners.each { |jr|
98
+ if !jr.time_started.nil? && (Time.now - jr.time_started > @timeout)
99
+ jr.stop
100
+ jr.run
101
+ end
102
+ }
103
+ end
104
+ }
105
+ end
106
+ end
107
+
108
+ # Add work to the pool
109
+ def <<(work)
110
+ @work_queue_lock.synchronize {
111
+ @work_queue << work
112
+ @work_queue_cv.signal
113
+ }
114
+ end
115
+
116
+ # Return the next job queued up, blocking until one is received
117
+ # if none are present
118
+ def next_job
119
+ work = nil
120
+ @work_queue_lock.synchronize {
121
+ # wait until we have work
122
+ @work_queue_cv.wait(@work_queue_lock) if @work_queue.empty?
123
+ work = @work_queue.shift
124
+ }
125
+ work
126
+ end
127
+
128
+ # Terminate the thread pool
129
+ def stop
130
+ @terminate = true
131
+ @work_queue_lock.synchronize {
132
+ @work_queue.clear
133
+ }
134
+ @job_runners.each { |jr| jr.stop }
135
+ end
136
+ end
137
+ end
data/spec/common_spec.rb CHANGED
@@ -24,3 +24,13 @@
24
24
  # OTHER DEALINGS IN THE SOFTWARE.
25
25
 
26
26
  require File.dirname(__FILE__) + '/spec_helper'
27
+
28
+ describe "Simrpc" do
29
+
30
+ it "should generate unique ids" do
31
+ id1 = IDBank.generate
32
+ id2 = IDBank.generate
33
+ id1.should_not == id2
34
+ end
35
+
36
+ end
data/spec/message_spec.rb CHANGED
@@ -35,6 +35,12 @@ describe "Simrpc::Message" do
35
35
  assert_equal ["foobar", ""], Message::Formatter::parse_from_formatted(formatted)
36
36
  end
37
37
 
38
+ it "should format and parsed a data field with a fixed size" do
39
+ formatted = Message::Formatter::format_with_fixed_size(3, 123456)
40
+ formatted.should == "123"
41
+ assert_equal ["123", ""], Message::Formatter::parse_from_formatted_with_fixed_size(3, formatted)
42
+ end
43
+
38
44
  it "should convert a field to and from a string" do
39
45
  field = Message::Field.new(:name => "foo", :value => "bar")
40
46
  field_s = field.to_s
@@ -72,13 +78,14 @@ describe "Simrpc::Message" do
72
78
 
73
79
  it "should convert a message to and from a string" do
74
80
  msg = Message::Message.new
81
+ msg.header.id = "12345678"
75
82
  msg.header.type = 'request'
76
83
  msg.header.target = 'method'
77
84
  msg.body.fields.push Message::Field.new(:name => "foo", :value => "bar")
78
85
  msg.body.fields.push Message::Field.new(:name => "money", :value => "lotsof")
79
86
  msg_s = msg.to_s
80
87
 
81
- msg_s.should == "0000002900000007request00000006method000000650000002200000003foo00000003bar0000002700000005money00000006lotsof"
88
+ msg_s.should == "000000371234567800000007request00000006method000000650000002200000003foo00000003bar0000002700000005money00000006lotsof"
82
89
 
83
90
  msg_o = Message::Message.from_s(msg_s)
84
91
  msg_o.header.type.should == 'request'
data/spec/node_spec.rb CHANGED
@@ -70,9 +70,43 @@ describe "Simrpc::Node" do
70
70
  client.send_message("server-queue", msg)
71
71
  finished_lock.wait()
72
72
 
73
+ server.terminate
74
+ client.terminate
75
+
73
76
  # FIXME test method that doesn't have any return values
74
77
  end
75
78
 
79
+ it "should handle default values" do
80
+ schema_def = Schema::Parser.parse(:schema => TEST_SCHEMA)
81
+ mmc = MethodMessageController.new(schema_def)
82
+ msg = mmc.generate('foo_method', [2])
83
+
84
+ finished_lock = Semaphore.new(1)
85
+ finished_lock.wait()
86
+
87
+ schema_def.methods[0].handler = lambda { |some_int, floating_point_number|
88
+ some_int.to_i.should == 2
89
+ floating_point_number.to_f.should == 5.6 # test default value
90
+ return "yo", MyClass.new
91
+ }
92
+
93
+ server = QpidAdapter::Node.new :id => "server"
94
+ server.async_accept { |node, msg, reply_to|
95
+ mmc.message_received(node, msg, reply_to)
96
+ }
97
+
98
+ client = QpidAdapter::Node.new :id => 'client'
99
+ client.async_accept { |node, msg, reply_to|
100
+ #msg = Message::Message.from_s(msg)
101
+ finished_lock.signal()
102
+ }
103
+ client.send_message("server-queue", msg)
104
+ finished_lock.wait()
105
+
106
+ server.terminate
107
+ client.terminate
108
+ end
109
+
76
110
  it "it should run properly" do
77
111
  server = Node.new(:id => "server3", :schema => TEST_SCHEMA)
78
112
  client = Node.new(:id => "client3", :schema => TEST_SCHEMA, :destination => "server3")
@@ -94,6 +128,9 @@ describe "Simrpc::Node" do
94
128
  my_class_instance.float_member.should == 4.2
95
129
 
96
130
  # FIXME test method that doesn't have any return values
131
+
132
+ client.terminate
133
+ server.terminate
97
134
  end
98
135
 
99
136
  end
data/spec/qpid_spec.rb CHANGED
@@ -38,6 +38,7 @@ describe "Simrpc::QpidAdapter" do
38
38
  ssn.error?.should == false
39
39
  #ssn.closed.should == false
40
40
  id.should == "test1"
41
+ qpid.terminate
41
42
  end
42
43
 
43
44
  # ensure we define a queue and exchange
@@ -67,6 +68,21 @@ describe "Simrpc::QpidAdapter" do
67
68
  #assert !binding_result.queue_not_found?
68
69
  #assert !binding_result.queue_not_matched?
69
70
  #assert !binding_result.key_not_found?
71
+
72
+ node.terminate
73
+ end
74
+
75
+ it "should terminate queue and exchange" do
76
+ node = QpidAdapter::Node.new :id => "qpid_test3"
77
+ node.terminate
78
+
79
+ # need to start a new session since terminate closes the previous one
80
+ conn = Qpid::Connection.new(TCPSocket.new('localhost', 5672))
81
+ conn.start
82
+ ssn = conn.session(UUID.new.generate)
83
+
84
+ assert ssn.queue_query("qpid_test3-queue").queue.nil?
85
+ assert ssn.exchange_query("qpid_test3-exchange").not_found
70
86
  end
71
87
 
72
88
  # test sending/receiving a message
@@ -87,6 +103,9 @@ describe "Simrpc::QpidAdapter" do
87
103
  }
88
104
  client.send_message("server1-queue", "test-data")
89
105
  finished_lock.wait()
106
+
107
+ server.terminate
108
+ client.terminate
90
109
  end
91
110
 
92
111
  end
data/spec/schema_spec.rb CHANGED
@@ -49,6 +49,7 @@ describe "Simrpc::Schema" do
49
49
  schema_def.methods[0].parameters[0].name.should == 'some_int'
50
50
  schema_def.methods[0].parameters[1].type.should == :float
51
51
  schema_def.methods[0].parameters[1].name.should == 'floating_point_number'
52
+ schema_def.methods[0].parameters[1].default.should == 5.6
52
53
  schema_def.methods[0].return_values.size.should == 2
53
54
  schema_def.methods[0].return_values[0].type.should == :str
54
55
  schema_def.methods[0].return_values[0].name.should == 'a_string'
data/spec/spec_helper.rb CHANGED
@@ -40,7 +40,7 @@ TEST_SCHEMA =
40
40
  "<schema>"+
41
41
  " <method name='foo_method'>" +
42
42
  " <param type='int' name='some_int'/>"+
43
- " <param type='float' name='floating_point_number'/>"+
43
+ " <param type='float' name='floating_point_number' default='5.6' />"+
44
44
  " <return_value type='str' name='a_string' />" +
45
45
  " <return_value type='obj' name='my_class_instance' associated='MyClass' />" +
46
46
  " </method>"+
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simrpc
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.2"
4
+ hash: 3
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 4
9
+ version: "0.4"
5
10
  platform: ruby
6
11
  authors:
7
12
  - Mohammed Morsi
@@ -9,10 +14,57 @@ autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2010-03-11 00:00:00 -05:00
17
+ date: 2010-09-04 00:00:00 -04:00
13
18
  default_executable:
14
- dependencies: []
15
-
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 27
29
+ segments:
30
+ - 1
31
+ - 3
32
+ - 0
33
+ version: 1.3.0
34
+ type: :development
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: uuid
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 1
45
+ segments:
46
+ - 2
47
+ - 3
48
+ - 1
49
+ version: 2.3.1
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: activesupport
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ hash: 19
61
+ segments:
62
+ - 2
63
+ - 3
64
+ - 8
65
+ version: 2.3.8
66
+ type: :runtime
67
+ version_requirements: *id003
16
68
  description: simrpc is a simple Ruby module for rpc communication, using Apache QPID as the transport mechanism.
17
69
  email: movitto@yahoo.com
18
70
  executables: []
@@ -25,6 +77,7 @@ files:
25
77
  - lib/simrpc/message.rb
26
78
  - lib/simrpc/common.rb
27
79
  - lib/simrpc/semaphore.rb
80
+ - lib/simrpc/thread_pool.rb
28
81
  - lib/simrpc/qpid_adapter.rb
29
82
  - lib/simrpc/exceptions.rb
30
83
  - lib/simrpc/schema.rb
@@ -49,21 +102,31 @@ rdoc_options: []
49
102
  require_paths:
50
103
  - lib
51
104
  required_ruby_version: !ruby/object:Gem::Requirement
105
+ none: false
52
106
  requirements:
53
107
  - - ">="
54
108
  - !ruby/object:Gem::Version
109
+ hash: 53
110
+ segments:
111
+ - 1
112
+ - 8
113
+ - 1
55
114
  version: 1.8.1
56
- version:
57
115
  required_rubygems_version: !ruby/object:Gem::Requirement
116
+ none: false
58
117
  requirements:
59
118
  - - ">="
60
119
  - !ruby/object:Gem::Version
120
+ hash: 29
121
+ segments:
122
+ - 1
123
+ - 3
124
+ - 3
61
125
  version: 1.3.3
62
- version:
63
126
  requirements: []
64
127
 
65
128
  rubyforge_project:
66
- rubygems_version: 1.3.5
129
+ rubygems_version: 1.3.7
67
130
  signing_key:
68
131
  specification_version: 3
69
132
  summary: simrpc is a simple Ruby module for rpc communication, using Apache QPID as the transport mechanism.