farcall 0.0.1 → 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.
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