farcall 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fed9ecbac82727b85ed5f2bf84aa8c367a166ff8
4
- data.tar.gz: a32f4158ea4c07d1cc369819bd71ea5d76e42592
3
+ metadata.gz: 28f543ad602b3ae18360d4e445c79c60f9999e89
4
+ data.tar.gz: 5c560d5137bba206c11a216e3005bb5ec7f2713c
5
5
  SHA512:
6
- metadata.gz: 929962400c2e9805d7d0fc27476f68886de4309951d9c0293cc4ca6527d827fe1546bb35880cec785294199c2bb6d61b70132228e6c328a100c6540bb3c60a0b
7
- data.tar.gz: e10d64380d80222d89736335fc75b22e3516d7dc7917452caf03e370da5576d3097ade4068234aed4f6b0d71a159ab6bfc4a55aac7913038c0656dd3b73c4b52
6
+ metadata.gz: 9696bda5293c960cb672aeb14a5d47a640d4db7673a59f1a4aa2a2bded5d9fd80cf4f1d302f83a9c404bef236b480ebb8bc52250519f86cf00d80319d17f8273
7
+ data.tar.gz: 07128d134976c41240dd64612b575f475ef5d6e2884657729cfa1a81f89f50ffaf8aba8debd87589140e449e9a682033bacc89496c80f8fc85aa433a6e8bc8e1
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/README.md CHANGED
@@ -1,9 +1,61 @@
1
1
  # Farcall
2
2
 
3
- # Important!
3
+ ## Important!
4
4
 
5
- The gem creation is under way, plaese wait for 0.1 version at least
5
+ The gem creation is under active development, current state is: beta. Only JSON format is supported.
6
6
 
7
+ ## Description
8
+
9
+ The simple and elegant cross-platform RPC protocol that uses any formatter/transport capable of
10
+ transmitting dictionary-like objects, for example, JSON, XML, BSON, BOSS and many others.
11
+
12
+ RPC is made asynchronously, each call can have any return values. While one call is waiting,
13
+ other calls can be executed. The protocol is bidirectional Call parameters could be
14
+ both arrays of arguments and keyword arguments, return value could be any object, e.g. array,
15
+ dictionary, wahtever.
16
+
17
+ Exception/errors transmitting is also supported.
18
+
19
+ The interface is very simple and rubyish:
20
+
21
+ ```ruby
22
+ # The sample class that exports all its methods to the remote callers:
23
+ #
24
+ class TestProvider < Farcall::Provider
25
+
26
+ attr :foo_calls, :a, :b
27
+
28
+ def foo a, b, optional: 'none'
29
+ @foo_calls = (@foo_calls || 0) + 1
30
+ @a, @b = a, b
31
+ return "Foo: #{a+b}, #{optional}"
32
+ end
33
+ end
34
+
35
+ # create instance and export it to some connected socket:
36
+
37
+ TestProvider.new socket: connected_socket # default format is JSON
38
+ ```
39
+
40
+ Suppose whe have some socket connected to one above, then TestProvider methods are available via
41
+ this connection:
42
+
43
+ ```ruby
44
+ i = Farcall::Interface.new socket: client_socket
45
+
46
+ # Plain arguments
47
+ i.foo(10, 20).should == 'Foo: 30, none'
48
+
49
+ # Plain and keyword arguments
50
+ i.foo(5, 6, optional: 'yes!').should == 'Foo: 11, yes!'
51
+
52
+ # the exceptions on the remote side are conveyed:
53
+ expect(-> { i.foo() }).to raise_error Farcall::RemoteError
54
+
55
+ # new we can read results from the remote side state:
56
+ i.a.should == 5
57
+ i.b.should == 6
58
+ ```
7
59
 
8
60
  ## Installation
9
61
 
@@ -23,7 +75,7 @@ Or install it yourself as:
23
75
 
24
76
  ## Usage
25
77
 
26
- TODO: Write usage instructions here
78
+ Try to get autodocs. Sorry for now.
27
79
 
28
80
  ## Contributing
29
81
 
@@ -21,4 +21,5 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.add_development_dependency "bundler", "~> 1.7"
23
23
  spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "rspec"
24
25
  end
@@ -1,4 +1,7 @@
1
- require "farcall/version"
1
+ require 'farcall/version'
2
+ require 'farcall/transport'
3
+ require 'farcall/json_transport'
4
+ require 'farcall/endpoint'
2
5
 
3
6
  module Farcall
4
7
  # Your code goes here...
@@ -0,0 +1,258 @@
1
+ module Farcall
2
+
3
+ # The protocol endpoint. Takes some transport and implements Farcall protocol over
4
+ # it. You can use it direcly or with Farcall::RemoteInterface and Farcall::LocalProvider helper
5
+ # classes.
6
+ #
7
+ # Endpoint class is thread-safe.
8
+ class Endpoint
9
+
10
+ # Set or get provider instance. When provider is set, its methods are called by the remote
11
+ # and any possible exception are passed back to caller party. You can use any ruby class instance
12
+ # everything will work, operators, indexes[] and like.
13
+ attr_accessor :provider
14
+
15
+ # Create endpoint connected to some transport
16
+ # @param [Farcall::Transport] transport
17
+ def initialize(transport)
18
+ @transport = transport
19
+ @in_serial = @out_serial = 0
20
+ @transport.on_data_received = -> (data) {
21
+ begin
22
+ _received(data)
23
+ rescue
24
+ abort :format_error, $!
25
+ end
26
+ }
27
+ @send_lock = Mutex.new
28
+ @receive_lock = Mutex.new
29
+
30
+ @waiting = {}
31
+ end
32
+
33
+ # The provided block will be called if endpoint functioning will be aborted.
34
+ # The block should take |reason, exception| parameters - latter could be nil
35
+ def on_abort &proc
36
+ @abort_hadnler = proc
37
+ end
38
+
39
+ # Add the close handler. Specified block will be called when the endpoint is been closed
40
+ def on_close &block
41
+ @close_handler = block
42
+ end
43
+
44
+ # :nodoc:
45
+ def abort reason, exception = nil
46
+ puts "*** Abort: reason #{reason || exception.to_s}"
47
+ @abort_hadnler and @abort_hadnler.call reason, exception
48
+ if exception
49
+ raise exception
50
+ end
51
+ close
52
+ end
53
+
54
+ # Close endpoint and connected transport
55
+ def close
56
+ @transport.close
57
+ @transport = nil
58
+ @close_handler and @close_handler.call
59
+ end
60
+
61
+ # Call remote party. Retruns immediately. When remote party answers, calls the specified block
62
+ # if present. The block should take |error, result| parameters. Error could be nil or
63
+ # {'class' =>, 'text' => } hash. result is always nil if error is presented.
64
+ #
65
+ # It is desirable to use Farcall::Endpoint#interface or
66
+ # Farcall::RemoteInterface rather than this low-level method.
67
+ #
68
+ # @param [String] name of the remote command
69
+ def call(name, *args, **kwargs, &block)
70
+ @send_lock.synchronize {
71
+ if block != nil
72
+ @waiting[@out_serial] = {
73
+ time: Time.new,
74
+ proc: block
75
+ }
76
+ _send(cmd: name.to_s, args: args, kwargs: kwargs)
77
+ end
78
+ }
79
+ end
80
+
81
+ # Call the remote party and wait for the return.
82
+ #
83
+ # It is desirable to use Farcall::Endpoint#interface or
84
+ # Farcall::RemoteInterface rather than this low-level method.
85
+ #
86
+ # @param [String] name of the remote command
87
+ # @return [Object] any data that remote party retruns
88
+ # @raise [Farcall::RemoteError]
89
+ #
90
+ def sync_call(name, *args, **kwargs)
91
+ mutex = Mutex.new
92
+ resource = ConditionVariable.new
93
+ error = nil
94
+ result = nil
95
+ calling_thread = Thread.current
96
+
97
+ mutex.synchronize {
98
+ same_thread = false
99
+ call(name, *args, **kwargs) { |e, r|
100
+ error, result = e, r
101
+ # Absolutly stupid wait for self situation
102
+ # When single thread is used to send and receive
103
+ # - often happens in test environments
104
+ if calling_thread == Thread.current
105
+ same_thread = true
106
+ else
107
+ resource.signal
108
+ end
109
+ }
110
+ same_thread or resource.wait(mutex)
111
+ }
112
+ if error
113
+ raise Farcall::RemoteError.new(error['class'], error['text'])
114
+ end
115
+ result
116
+ end
117
+
118
+ # Process remote commands. Not that provider have precedence at the moment.
119
+ # Provided block will be executed on every remote command taking parameters
120
+ # |name, args, kwargs|. Whatever block returns will be passed to a calling party.
121
+ # The same any exception that the block might raise would be send back to caller.
122
+ def on_remote_call &block
123
+ @on_remote_call = block
124
+ end
125
+
126
+ # Get the Farcall::RemoteInterface connnected to this endpoint. Any subsequent calls with
127
+ # return the same instance.
128
+ def remote
129
+ @remote ||= Farcall::Interface.new endpoint: self
130
+ end
131
+
132
+ private
133
+
134
+ def _send(**kwargs)
135
+ if @send_lock.locked?
136
+ kwargs[:serial] = @out_serial
137
+ @transport.send_data kwargs
138
+ @out_serial += 1
139
+ else
140
+ @send_lock.synchronize { _send(**kwargs) }
141
+ end
142
+ end
143
+
144
+ def _received(data)
145
+ # p [:r, data]
146
+ cmd, serial, args, kwargs, ref, result, error =
147
+ %w{cmd serial args kwargs ref result error}.map { |k| data[k] || data[k.to_sym] }
148
+ !serial || serial < 0 and abort 'missing or bad serial'
149
+
150
+ @receive_lock.synchronize {
151
+ serial == @in_serial or abort "bad sync"
152
+ @in_serial += 1
153
+ }
154
+
155
+ case
156
+ when cmd
157
+
158
+ begin
159
+ result = if @provider
160
+ args ||= []
161
+ if kwargs && !kwargs.empty?
162
+ # ruby thing: keyqord args must be symbols, not strings:
163
+ fixed = {}
164
+ kwargs.each { |k,v| fixed[k.to_sym] = v}
165
+ args << fixed
166
+ end
167
+ @provider.send cmd.to_sym, *args
168
+ elsif @on_remote_call
169
+ @on_remote_call.call cmd, args, kwargs
170
+ end
171
+ _send ref: serial, result: result
172
+ rescue Exception => e
173
+ _send ref: serial, error: { 'class' => e.class.name, 'text' => e.to_s }
174
+ end
175
+
176
+ when ref
177
+
178
+ ref or abort 'no reference in return'
179
+ (w = @waiting.delete ref) != nil and w[:proc].call(error, result)
180
+
181
+ else
182
+ abort 'unknown command'
183
+ end
184
+ end
185
+
186
+ end
187
+
188
+ # Could be used as a base class to export its methods to the remote. You are not limited
189
+ # to subclassing, instead, you can set any class instance as a provider setting it to
190
+ # the Farcall::Endpoint#provider. The provider has only one method^ which can not be accessed
191
+ # remotely: #far_interface, which is used locally to object interface to call remote methods
192
+ # for two-way connections.
193
+ class Provider
194
+ # Create an instance connected to the Farcall::Transport or Farcall::Endpoint - use what
195
+ # suites you better.
196
+ #
197
+ # Please remember that Farcall::Transport instance could be used with only
198
+ # one conneced object, unlike Farcall::Endpoint, which could be connected to several
199
+ # consumers.
200
+ def initialize endpoint: nil, transport: nil, **params
201
+ @endpoint = if endpoint
202
+ endpoint
203
+ else
204
+ transport ||= Farcall::Transport.create **params
205
+ Farcall::Endpoint.new transport
206
+ end
207
+ @endpoint.provider = self
208
+ end
209
+
210
+ # Get remote interface
211
+ # @return [Farcall::Interface] to call methods on the other end
212
+ def far_interface
213
+ @endpoint.remote
214
+ end
215
+ end
216
+
217
+ # Intervace to the remote provider via Farcall protocols. Works the same as if the object
218
+ # would be in local data, but slower :) The same as calling Farcall::Endpoint#interface
219
+ #
220
+ # RemoteInterface transparently creates methods as you call them to speedup subsequent
221
+ # calls.
222
+ #
223
+ # There is no way to check that the remote responds to some method other than call it and
224
+ # catch the exception
225
+ #
226
+ class Interface
227
+
228
+ # Create interface connected to some endpoint ar transpost.
229
+ #
230
+ # Please remember that Farcall::Transport instance could be used with only
231
+ # one conneced object, unlike Farcall::Endpoint, which could be connected to several
232
+ # consumers.
233
+ #
234
+ # @param [Farcall::Endpoint|Farcall::Transport] arg either endpoint or a transport
235
+ # to connect interface to
236
+ def initialize endpoint: nil, transport: nil, provider: nil, **params
237
+ @endpoint = if endpoint
238
+ endpoint
239
+ else
240
+ Farcall::Endpoint.new(transport || Farcall::Transport.create(**params))
241
+ end
242
+ provider and @endpoint.provider = provider
243
+ end
244
+
245
+ def method_missing(method_name, *arguments, **kw_arguments, &block)
246
+ instance_eval <<-End
247
+ def #{method_name} *arguments, **kw_arguments
248
+ @endpoint.sync_call '#{method_name}', *arguments, **kw_arguments
249
+ end
250
+ End
251
+ @endpoint.sync_call method_name, *arguments, **kw_arguments
252
+ end
253
+
254
+ def respond_to_missing?(method_name, include_private = false)
255
+ true
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,141 @@
1
+ require 'json'
2
+
3
+ module Farcall
4
+
5
+ # Stream-like object to wrap very strange ruby socket IO
6
+ class SocketStream
7
+
8
+ def initialize socket
9
+ @socket = socket
10
+ end
11
+
12
+ def read length=1
13
+ data = ''
14
+ while data.length < length
15
+ data << @socket.recv(length - data.length, Socket::MSG_WAITALL)
16
+ end
17
+ data
18
+ end
19
+
20
+ def write data
21
+ @socket.write data
22
+ end
23
+
24
+ def << data
25
+ write data
26
+ end
27
+
28
+ end
29
+
30
+ # The socket stream that imitates slow data reception over the slow internet connection
31
+ # use to for testing only
32
+ class DebugSocketStream < Farcall::SocketStream
33
+
34
+ # @param [float] timeout between sending individual bytes in seconds
35
+ def initialize socket, timeout
36
+ super socket
37
+ @timeout = timeout
38
+ end
39
+
40
+ def write data
41
+ data.to_s.each_char { |x|
42
+ super x
43
+ sleep @timeout
44
+ }
45
+ end
46
+ end
47
+
48
+ # :nodoc:
49
+ module TransportBase
50
+ # connect socket or use streams if any
51
+ def setup_streams input: nil, output: nil, socket: nil
52
+ if socket
53
+ @socket = socket
54
+ @input = @output = SocketStream.new(socket)
55
+ else
56
+ @input, @output = input, output
57
+ end
58
+ @input != nil && @output != nil or raise Farcall::Error, "can't setup streams"
59
+ end
60
+
61
+ # close connection (socket or streams)
62
+ def close_connection
63
+ if @socket
64
+ if !@socket.closed?
65
+ begin
66
+ @socket.flush
67
+ @socket.shutdown
68
+ rescue Errno::ENOTCONN
69
+ end
70
+ @socket.close
71
+ end
72
+ @socket = nil
73
+ else
74
+ @input.close
75
+ @output.close
76
+ end
77
+ @input = @output = nil
78
+ end
79
+ end
80
+
81
+ # The transport that uses delimited texts formatted with JSON. Delimiter should be a character
82
+ # sequence that will never appear in data, by default "\x00" is used. Also several \n\n\n can be
83
+ # used, most JSON codecs never insert several empty strings
84
+ class JsonTransport < Farcall::Transport
85
+ include TransportBase
86
+
87
+ # Create json transport, see Farcall::Transpor#create for parameters
88
+ def initialize delimiter: "\x00", **params
89
+ setup_streams **params
90
+ @delimiter = delimiter
91
+ @dlength = -delimiter.length
92
+ end
93
+
94
+ def on_data_received= block
95
+ super
96
+ if block && !@thread
97
+ @thread = Thread.start {
98
+ load_loop
99
+ }
100
+ end
101
+ end
102
+
103
+ def send_data hash
104
+ @output << JSON.unparse(hash) + @delimiter
105
+ end
106
+
107
+ def close
108
+ if !@closing
109
+ @closing = true
110
+ close_connection
111
+ @thread and @thread.join
112
+ @thread = nil
113
+ @in_close = false
114
+ end
115
+ end
116
+
117
+ private
118
+
119
+ def load_loop
120
+ buffer = ''
121
+ while true
122
+ buffer << @input.read(1)
123
+ if buffer[@dlength..-1] == @delimiter
124
+ on_data_received and on_data_received.call(JSON.parse(buffer[0...@dlength]))
125
+ buffer = ''
126
+ end
127
+ end
128
+ rescue Errno::EPIPE
129
+ close
130
+ rescue
131
+ if !@closing
132
+ STDERR.puts "Farcall::JsonTransport read loop failed: #{$!.class.name}: #$!"
133
+ connection_aborted $!
134
+ else
135
+ close
136
+ end
137
+ end
138
+
139
+ end
140
+ end
141
+
@@ -0,0 +1,112 @@
1
+
2
+ module Farcall
3
+
4
+ # Generic error in Farcall library
5
+ class Error < StandardError
6
+ end
7
+
8
+ # The error occured while executin remote method
9
+ class RemoteError < Error
10
+ attr :remote_class
11
+
12
+ def initialize remote_class, text
13
+ @remote_class = remote_class
14
+ super "#{remote_class}: #{text}"
15
+ end
16
+ end
17
+
18
+ # The transport interface. Farcall works via anything that can send and receive dictionary
19
+ # objects. The transport should only implement Transport#send_data and invoke
20
+ # Transport#on_data_received when incoming data are available
21
+ class Transport
22
+
23
+ # Create transport with a given format and parameters.
24
+ #
25
+ # format right now can be only :json
26
+ #
27
+ # creation parameters can be:
28
+ #
29
+ # - socket: connect transport to some socket (should be connected)
30
+ #
31
+ # - input and aoutput: two stream-like objects which support read(length) and write(data)
32
+ # parameters
33
+ #
34
+ def self.create format: :json, **params
35
+ case format
36
+ when :json
37
+ Farcall::JsonTransport.new **params
38
+ else
39
+ raise Farcall::Error, "unknown format: #{format}"
40
+ end
41
+ end
42
+
43
+ attr :closed
44
+
45
+ # Tansport must call this process on each incoming hash
46
+ # passing it as the only parameter, e.g. self.on_data_received(hash)
47
+ attr_accessor :on_data_received, :on_abort, :on_close
48
+
49
+ # Utility function. Calls the provided block on data reception. Resets the
50
+ # block with #on_data_received
51
+ def receive_data &block
52
+ self.on_data_received = block
53
+ end
54
+
55
+ # Transmit somehow a dictionary to the remote part
56
+ def send_data hash
57
+ raise 'not implemented'
58
+ end
59
+
60
+ # Flush and close transport
61
+ def close
62
+ @closed = true
63
+ @on_close and @on_close.call
64
+ end
65
+
66
+ protected
67
+
68
+ def connection_closed
69
+ close
70
+ end
71
+
72
+ def connection_aborted exceptoin
73
+ STDERR.puts "Farcall: connection aborted: #{$!.class.name}: #{$!}"
74
+ @on_abort and @on_abort.call $!
75
+ close
76
+ end
77
+
78
+
79
+ end
80
+
81
+ # Test connection that provides 2 interconnected transports
82
+ # TestConnection#a and TestConnection#b that could be used to connect Endpoints
83
+ class LocalConnection
84
+
85
+ # :nodoc:
86
+ class Connection < Transport
87
+
88
+ attr_accessor :other
89
+
90
+ def initialize other_loop = nil
91
+ if other_loop
92
+ other_loop.other = self
93
+ @other = other_loop
94
+ end
95
+ end
96
+
97
+ def send_data hash
98
+ @other.on_data_received.call hash
99
+ end
100
+
101
+ end
102
+
103
+ attr :a, :b
104
+
105
+ def initialize
106
+ @a = Connection.new
107
+ @b = Connection.new @a
108
+ end
109
+ end
110
+
111
+
112
+ end
@@ -1,3 +1,3 @@
1
1
  module Farcall
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+ require 'stringio'
3
+
4
+ # The sample class that exports all its methods to the remote callers:
5
+ #
6
+ class TestProvider < Farcall::Provider
7
+
8
+ attr :foo_calls, :a, :b
9
+
10
+ def foo a, b, optional: 'none'
11
+ @foo_calls = (@foo_calls || 0) + 1
12
+ @a, @b = a, b
13
+ return "Foo: #{a+b}, #{optional}"
14
+ end
15
+ end
16
+
17
+ describe 'endpoint' do
18
+ include Farcall
19
+
20
+ it 'should do RPC call with provider/interface' do
21
+ tc = Farcall::LocalConnection.new
22
+
23
+ ea = Farcall::Endpoint.new tc.a
24
+ eb = Farcall::Endpoint.new tc.b
25
+
26
+ TestProvider.new endpoint: ea
27
+ eb.provider = "Hello world"
28
+
29
+ i = Farcall::Interface.new endpoint: eb
30
+ i2 = Farcall::Interface.new endpoint: eb
31
+ ib = Farcall::Interface.new endpoint: ea
32
+
33
+ expect(-> { i.foo() }).to raise_error Farcall::RemoteError
34
+
35
+ i.foo(10, 20).should == 'Foo: 30, none'
36
+ i2.foo(5, 6, optional: 'yes!').should == 'Foo: 11, yes!'
37
+
38
+ i.a.should == 5
39
+ i.b.should == 6
40
+
41
+ ib.split.should == ['Hello', 'world']
42
+ end
43
+
44
+ it 'should connect via shortcut' do
45
+ s1, s2 = Socket.pair(:UNIX, :STREAM, 0)
46
+
47
+ tp = TestProvider.new socket: s1, format: :json
48
+ i = Farcall::Interface.new socket: s2, format: :json, provider: "Hello world"
49
+
50
+
51
+ expect(-> { i.foo() }).to raise_error Farcall::RemoteError
52
+
53
+ i.foo(10, 20).should == 'Foo: 30, none'
54
+ i.foo(5, 6, optional: 'yes!').should == 'Foo: 11, yes!'
55
+
56
+ i.a.should == 5
57
+ i.b.should == 6
58
+
59
+ tp.far_interface.split.should == ['Hello', 'world']
60
+ end
61
+
62
+ end
@@ -0,0 +1,100 @@
1
+ require 'farcall'
2
+
3
+ # This file was generated by the `rspec --init` command. Conventionally, all
4
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
5
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
6
+ # this file to always be loaded, without a need to explicitly require it in any
7
+ # files.
8
+ #
9
+ # Given that it is always loaded, you are encouraged to keep this file as
10
+ # light-weight as possible. Requiring heavyweight dependencies from this file
11
+ # will add to the boot time of your test suite on EVERY test run, even for an
12
+ # individual file that may not need all of that loaded. Instead, consider making
13
+ # a separate helper file that requires the additional dependencies and performs
14
+ # the additional setup, and require it from the spec files that actually need
15
+ # it.
16
+ #
17
+ # The `.rspec` file also contains a few flags that are not defaults but that
18
+ # users commonly want.
19
+ #
20
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
21
+ RSpec.configure do |config|
22
+ # rspec-expectations config goes here. You can use an alternate
23
+ # assertion/expectation library such as wrong or the stdlib/minitest
24
+ # assertions if you prefer.
25
+ config.expect_with :rspec do |expectations|
26
+ # This option will default to `true` in RSpec 4. It makes the `description`
27
+ # and `failure_message` of custom matchers include text for helper methods
28
+ # defined using `chain`, e.g.:
29
+ # be_bigger_than(2).and_smaller_than(4).description
30
+ # # => "be bigger than 2 and smaller than 4"
31
+ # ...rather than:
32
+ # # => "be bigger than 2"
33
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
34
+ expectations.syntax = [:should, :expect]
35
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
36
+ end
37
+
38
+ # rspec-mocks config goes here. You can use an alternate test double
39
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
40
+ config.mock_with :rspec do |mocks|
41
+ # Prevents you from mocking or stubbing a method that does not exist on
42
+ # a real object. This is generally recommended, and will default to
43
+ # `true` in RSpec 4.
44
+ mocks.verify_partial_doubles = true
45
+ end
46
+
47
+ # The settings below are suggested to provide a good initial experience
48
+ # with RSpec, but feel free to customize to your heart's content.
49
+ =begin
50
+ # These two settings work together to allow you to limit a spec run
51
+ # to individual examples or groups you care about by tagging them with
52
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
53
+ # get run.
54
+ config.filter_run :focus
55
+ config.run_all_when_everything_filtered = true
56
+
57
+ # Allows RSpec to persist some state between runs in order to support
58
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
59
+ # you configure your source control system to ignore this file.
60
+ config.example_status_persistence_file_path = "spec/examples.txt"
61
+
62
+ # Limits the available syntax to the non-monkey patched syntax that is
63
+ # recommended. For more details, see:
64
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
65
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
66
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
67
+ config.disable_monkey_patching!
68
+
69
+ # This setting enables warnings. It's recommended, but in some cases may
70
+ # be too noisy due to issues in dependencies.
71
+ config.warnings = true
72
+
73
+ # Many RSpec users commonly either run the entire suite or an individual
74
+ # file, and it's useful to allow more verbose output when running an
75
+ # individual spec file.
76
+ if config.files_to_run.one?
77
+ # Use the documentation formatter for detailed output,
78
+ # unless a formatter has already been configured
79
+ # (e.g. via a command-line flag).
80
+ config.default_formatter = 'doc'
81
+ end
82
+
83
+ # Print the 10 slowest examples and example groups at the
84
+ # end of the spec run, to help surface which specs are running
85
+ # particularly slow.
86
+ config.profile_examples = 10
87
+
88
+ # Run specs in random order to surface order dependencies. If you find an
89
+ # order dependency and want to debug it, you can fix the order by providing
90
+ # the seed, which is printed after each run.
91
+ # --seed 1234
92
+ config.order = :random
93
+
94
+ # Seed global randomization in this process using the `--seed` CLI option.
95
+ # Setting this allows you to use `--seed` to deterministically reproduce
96
+ # test failures related to randomization by passing the same `--seed` value
97
+ # as the one that triggered the failure.
98
+ Kernel.srand config.seed
99
+ =end
100
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+ require 'stringio'
3
+
4
+ describe 'transports' do
5
+ include Farcall
6
+
7
+ it 'should provide debug transport' do
8
+ s1, s2 = Socket.pair(:UNIX, :STREAM, 0)
9
+ t1 = Farcall::DebugSocketStream.new s1, 0.01
10
+ t2 = Farcall::SocketStream.new s2
11
+
12
+ data = 'Not too long string'
13
+ data2 = 'the end'
14
+ t = Time.now
15
+ Thread.start {
16
+ t1.write data
17
+ t1.write data2
18
+ }
19
+ x = t2.read data.length
20
+ x.should == data
21
+ x = data2.length.times.map { t2.read }.join('')
22
+ x.should == data2
23
+ end
24
+
25
+ it 'should run json transport' do
26
+ s1, s2 = Socket.pair(:UNIX, :STREAM, 0)
27
+
28
+ j1 = Farcall::JsonTransport.new socket: s1
29
+ j2 = Farcall::JsonTransport.new socket: s2
30
+
31
+ j2.receive_data { |data|
32
+ j2.send_data({ echo: data })
33
+ }
34
+
35
+ results = []
36
+ j1.receive_data { |data|
37
+ results << data
38
+ }
39
+
40
+ j1.send_data({ foo: "bar" })
41
+ j1.send_data({ one: 2 })
42
+ sleep 0.01
43
+ j1.close
44
+ j2.close
45
+
46
+ results.should == [{ 'echo' => { 'foo' => 'bar' } }, { 'echo' => { 'one' => 2 } }]
47
+ end
48
+
49
+ it 'should run json transport with long delimiter' do
50
+ s1, s2 = Socket.pair(:UNIX, :STREAM, 0)
51
+
52
+ j1 = Farcall::JsonTransport.new socket: s1, delimiter: "\n\n\n\n"
53
+ j2 = Farcall::JsonTransport.new socket: s2, delimiter: "\n\n\n\n"
54
+
55
+ j2.receive_data { |data|
56
+ j2.send_data({ echo: data })
57
+ }
58
+
59
+ results = []
60
+ j1.receive_data { |data|
61
+ results << data
62
+ }
63
+
64
+ j1.send_data({ foo: "bar" })
65
+ j1.send_data({ one: 2 })
66
+ sleep 0.01
67
+ j1.close
68
+ j2.close
69
+
70
+ results.should == [{ 'echo' => { 'foo' => 'bar' } }, { 'echo' => { 'one' => 2 } }]
71
+ end
72
+
73
+
74
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: farcall
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - sergeych
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  description: |-
42
56
  Can work with any transpot capable of conveing dictionaries (json, xml, bson, boss, yaml.
43
57
  Incides some transports.
@@ -48,13 +62,20 @@ extensions: []
48
62
  extra_rdoc_files: []
49
63
  files:
50
64
  - ".gitignore"
65
+ - ".rspec"
51
66
  - Gemfile
52
67
  - LICENSE.txt
53
68
  - README.md
54
69
  - Rakefile
55
70
  - farcall.gemspec
56
71
  - lib/farcall.rb
72
+ - lib/farcall/endpoint.rb
73
+ - lib/farcall/json_transport.rb
74
+ - lib/farcall/transport.rb
57
75
  - lib/farcall/version.rb
76
+ - spec/endpoint_spec.rb
77
+ - spec/spec_helper.rb
78
+ - spec/transports_spec.rb
58
79
  homepage: ''
59
80
  licenses:
60
81
  - MIT
@@ -79,4 +100,7 @@ rubygems_version: 2.4.5
79
100
  signing_key:
80
101
  specification_version: 4
81
102
  summary: Simple, elegant and cross-platofrm RCP and RMI protocol
82
- test_files: []
103
+ test_files:
104
+ - spec/endpoint_spec.rb
105
+ - spec/spec_helper.rb
106
+ - spec/transports_spec.rb