fastbeans 0.3.1 → 0.3.2

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
  !binary "U0hBMQ==":
3
- metadata.gz: fab0fdaf53dfe7c4f0f821c7ccf713f937034f50
4
- data.tar.gz: d14e754ccef7d5ee62ca7e78079dd155401f509f
3
+ metadata.gz: 158a2d7ac50d0efe24b525b82c28c6a9222c8df6
4
+ data.tar.gz: fec6f6afc2ad0a825df2f5f70e0d7f468acd09a8
5
5
  !binary "U0hBNTEy":
6
- metadata.gz: 6ec9d3d4080268b7604dbd51c223b370f1eeeb823fb5e7bc6bc930ca4b0b8e582dd31e2eb576fcb27375ded8b28dc66ed3633c9c65b04f0f37b56700af8e14a2
7
- data.tar.gz: 571af395f94112bc12bf2ef2d42666fe68aed6234b932e3a934fd0686865343efa51f164ffb7d7642d4bfad14637777ac0bd7408ad3db158069b12318ab10534
6
+ metadata.gz: 725a765d91e556af64d0cabc5b8f2daacd3d816e986be956356fbd3d8a0b835fa764fec3e81ad3af75c99892cc507b7ba31d090dda017bdbefff60bf3f9de8ae
7
+ data.tar.gz: 3a1c0a4cfa84149afccb88745c56ea8b950692d2f26f85e0d5feb8a7c5d6ae1c509e17b64e6f81383908dc3d62e1152b428cf04c61ed87bfccb07a1a2f3ea9f6
data/Rakefile CHANGED
@@ -1 +1,11 @@
1
1
  require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'lib/fastbeans'
7
+ t.test_files = FileList['test/*_test.rb']
8
+ t.verbose = true
9
+ end
10
+
11
+ task :default => :test
data/fastbeans.gemspec CHANGED
@@ -23,4 +23,5 @@ Gem::Specification.new do |spec|
23
23
  spec.add_runtime_dependency "connection_pool"
24
24
  spec.add_development_dependency "bundler", "~> 1.3"
25
25
  spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "mocha"
26
27
  end
@@ -1,25 +1,26 @@
1
1
  require 'msgpack'
2
2
  require 'rufus-lru'
3
- require 'fastbeans/connection'
4
- require 'fastbeans/response'
5
3
  require 'connection_pool'
4
+ require 'connection'
6
5
 
7
6
  module Fastbeans
8
7
  class Client
9
8
  CALL_CACHE_SIZE=100
10
9
 
11
10
  attr_reader :call_cache
11
+ attr_accessor :connection_class
12
12
 
13
- def initialize(host="127.0.0.1", port=12345, cache_size=nil, pool_opts={})
13
+ def initialize(host='127.0.0.1', port=12345, cache_size=nil, pool_opts={})
14
14
  @host, @port = host, port
15
15
  @cache_size ||= CALL_CACHE_SIZE
16
16
  @call_cache = Rufus::Lru::SynchronizedHash.new(@cache_size)
17
17
  @pool_opts = {:size => 5, :timeout => 5}.update(pool_opts)
18
+ @connection_class = Fastbeans::Connection
18
19
  end
19
20
 
20
21
  def pool
21
22
  @pool ||= ConnectionPool.new(@pool_opts) do
22
- Fastbeans::Connection.new(@host, @port)
23
+ @connection_class.new(@host, @port)
23
24
  end
24
25
  end
25
26
 
@@ -1,4 +1,5 @@
1
1
  require 'socket'
2
+ require 'fastbeans/request'
2
3
 
3
4
  module Fastbeans
4
5
  class Connection
@@ -9,17 +10,23 @@ module Fastbeans
9
10
  def initialize(host, port)
10
11
  Fastbeans.debug("Connecting to #{host}:#{port}")
11
12
  @host, @port = host, port
12
- connect!(@host, @port)
13
+ @socket = connect!(@host, @port)
13
14
  end
14
15
 
15
16
  def connect!(host, port)
16
17
  @socket = TCPSocket.new(host, port)
17
18
  @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
19
+ @socket
20
+ end
21
+
22
+ def get_socket
23
+ @socket || connect!(@host, @port)
18
24
  end
19
25
 
20
26
  def disconnect!
21
27
  if @socket
22
28
  @socket.close rescue nil
29
+ @socket = nil
23
30
  end
24
31
  end
25
32
 
@@ -31,7 +38,7 @@ module Fastbeans
31
38
  def call(*data)
32
39
  retries = 0
33
40
  begin
34
- call_without_retries(*data)
41
+ call_without_retries(data)
35
42
  rescue Fastbeans::RemoteConnectionFailed => e
36
43
  Fastbeans.debug(e)
37
44
  if retries < MAX_RETRIES
@@ -49,18 +56,26 @@ module Fastbeans
49
56
  end
50
57
  end
51
58
 
52
- def call_without_retries(*data)
53
- payload = MessagePack.pack(data).force_encoding("BINARY")
54
- @socket.write([payload.bytesize].pack("N"))
55
- @socket.write(payload)
56
- raw_resp = MessagePack.load(@socket)
57
- resp = Response.new(data, raw_resp)
58
- resp.payload
59
+ def call_without_retries(data)
60
+ perform(data)
61
+
59
62
  rescue IOError, Errno::EPIPE, MessagePack::MalformedFormatError => e
63
+ disconnect!
60
64
  ne = RemoteConnectionFailed.new(e.message)
61
65
  ne.orig_exc = e
62
66
  raise ne
67
+
68
+ rescue Exception
69
+ disconnect!
70
+ raise
63
71
  end
64
72
 
73
+ def with_socket
74
+ yield(get_socket)
75
+ end
76
+
77
+ def perform(data)
78
+ Request.new(self).perform(data)
79
+ end
65
80
  end
66
81
  end
@@ -1,13 +1,14 @@
1
1
  module Fastbeans
2
2
 
3
- class Exception < StandardError
3
+ class RPCException < StandardError
4
4
  attr_accessor :orig_exc
5
5
  end
6
6
 
7
- class RemoteCallFailed < Fastbeans::Exception; end
8
- class RemoteException < Fastbeans::Exception; end
9
- class RemoteConnectionFailed < Fastbeans::Exception; end
10
- class RemoteConnectionDead < Fastbeans::Exception; end
11
- class AutogeneratedException < Fastbeans::Exception; end
7
+ class RemoteCallFailed < Fastbeans::RPCException; end
8
+ class RemoteException < Fastbeans::RPCException; end
9
+ class RemoteConnectionFailed < Fastbeans::RPCException; end
10
+ class RemoteConnectionDead < Fastbeans::RPCException; end
11
+ class AutogeneratedException < Fastbeans::RPCException; end
12
+ class ResponseSignatureMismatch < RemoteConnectionFailed; end
12
13
 
13
14
  end
@@ -0,0 +1,49 @@
1
+ require 'digest/md5'
2
+ require 'response'
3
+
4
+ module Fastbeans
5
+ class Request
6
+ attr_reader :connection
7
+
8
+ def initialize(connection)
9
+ @connection = connection
10
+ end
11
+
12
+ def sign(call_data)
13
+ Digest::MD5.hexdigest(call_data.inspect)
14
+ end
15
+
16
+ def build_payload(call_data)
17
+ signature = sign(call_data)
18
+ signed_data = [signature, call_data]
19
+ payload = MessagePack.pack(signed_data)
20
+ if payload.respond_to?(:force_encoding)
21
+ payload.force_encoding('BINARY')
22
+ end
23
+ [signature, payload]
24
+ end
25
+
26
+ def write_payload(sock, payload)
27
+ sock.write([payload.bytesize].pack('N'))
28
+ sock.write(payload)
29
+ end
30
+
31
+ def read_response(sock, call_data)
32
+ raw_resp = MessagePack.load(sock)
33
+ Fastbeans::Response.new(call_data, raw_resp)
34
+ end
35
+
36
+ def perform(call_data)
37
+ connection.with_socket do |sock|
38
+ signature, payload = build_payload(call_data)
39
+ write_payload(sock, payload)
40
+ resp = read_response(sock, call_data)
41
+ if resp.signed_with?(signature)
42
+ resp.payload
43
+ else
44
+ raise ResponseSignatureMismatch, "Received #{resp.signature} signature instead of expected #{signature} for #{call_data} call"
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,6 +1,4 @@
1
1
  module Fastbeans
2
-
3
-
4
2
  class Response
5
3
 
6
4
  def initialize(call_data, raw_response)
@@ -12,9 +10,21 @@ module Fastbeans
12
10
  @raw_response.is_a?(Hash) and @raw_response.has_key?("fastbeans-error")
13
11
  end
14
12
 
13
+ def signature
14
+ unless error?
15
+ @raw_response[0]
16
+ else
17
+ nil
18
+ end
19
+ end
20
+
21
+ def signed_with?(orig_signature)
22
+ signature == orig_signature
23
+ end
24
+
15
25
  def payload
16
26
  unless error?
17
- @raw_response
27
+ @raw_response[1]
18
28
  else
19
29
  raise to_exception
20
30
  end
@@ -44,9 +54,9 @@ module Fastbeans
44
54
  word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
45
55
  word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
46
56
  word.tr!("-", "_")
57
+ word.gsub!(/\s+/, "_")
47
58
  word.downcase!
48
59
  word
49
60
  end
50
61
  end
51
-
52
62
  end
@@ -1,3 +1,3 @@
1
1
  module Fastbeans
2
- VERSION = "0.3.1"
2
+ VERSION = "0.3.2"
3
3
  end
data/lib/fastbeans.rb CHANGED
@@ -27,9 +27,9 @@ module Fastbeans
27
27
 
28
28
  def exception(classname)
29
29
  begin
30
- Fastbeans.const_get(classname)
30
+ Fastbeans.const_get(classname.to_s)
31
31
  rescue NameError
32
- Fastbeans.const_set(classname, Class.new(Fastbeans::AutogeneratedException))
32
+ Fastbeans.const_set(classname.to_s, Class.new(Fastbeans::AutogeneratedException))
33
33
  end
34
34
  end
35
35
  end
@@ -0,0 +1,26 @@
1
+ require_relative 'test_helper'
2
+
3
+ class ClientTest < MiniTest::Unit::TestCase
4
+
5
+ def setup
6
+ @client = Fastbeans::Client.new
7
+ @client.connection_class = MockConnection
8
+ end
9
+
10
+ def test_how_call_works
11
+ real = @client.call("+", 1, 2, 3)
12
+ assert_equal ["+", 1, 2, 3], real
13
+ end
14
+
15
+ def test_how_cached_call_works
16
+ real1 = @client.cached_call("+", 1, 2, 3)
17
+ real2 = @client.cached_call("+", 1, 2, 3)
18
+ assert_equal real1.object_id, real2.object_id
19
+
20
+ @client.clear_call_cache!
21
+
22
+ real3 = @client.cached_call("+", 1, 2, 3)
23
+ assert real1.object_id != real3.object_id
24
+ end
25
+ end
26
+
@@ -0,0 +1,85 @@
1
+ require_relative 'test_helper'
2
+
3
+ class ConnectionTest < MiniTest::Unit::TestCase
4
+
5
+ def setup
6
+ Fastbeans::Connection.any_instance.stubs(:connect!).returns(StringIO.new)
7
+ @conn = Fastbeans::Connection.new('localhost', 12345)
8
+ @test_msg = ['+', 1, 2, 3]
9
+ end
10
+
11
+ def test_socket_presence
12
+ assert_is_a_socket @conn.socket
13
+ end
14
+
15
+ def test_disconnect
16
+ @conn.disconnect!
17
+ assert_nil @conn.socket
18
+ end
19
+
20
+ def test_get_socket
21
+ assert_is_a_socket @conn.get_socket
22
+
23
+ @conn.disconnect!
24
+
25
+ assert_is_a_socket @conn.get_socket
26
+ end
27
+
28
+ def test_reconnect
29
+ @conn.expects(:disconnect!)
30
+ @conn.expects(:connect!).with('localhost', 12345)
31
+ @conn.reconnect!
32
+ end
33
+
34
+ def test_with_socket
35
+ @conn.with_socket do |sock|
36
+ assert_is_a_socket sock
37
+ end
38
+ end
39
+
40
+ def test_call_without_retries
41
+ Fastbeans::Request.any_instance.expects(:perform).with(@test_msg).returns(:reply)
42
+ assert_equal :reply, @conn.call_without_retries(@test_msg)
43
+ end
44
+
45
+ def test_exception_during_call
46
+ @conn.expects(:perform).with(@test_msg).raises(RuntimeError)
47
+ @conn.expects(:disconnect!)
48
+ assert_raises RuntimeError do
49
+ @conn.call_without_retries(@test_msg)
50
+ end
51
+ end
52
+
53
+ def test_io_exceptions_during_call
54
+ ioexcs = [IOError, Errno::EPIPE, MessagePack::MalformedFormatError]
55
+ ioexcs.each do |exc|
56
+ @conn.expects(:perform).with(@test_msg).raises(exc)
57
+ @conn.expects(:disconnect!)
58
+ assert_raises Fastbeans::RemoteConnectionFailed do
59
+ @conn.call_without_retries(@test_msg)
60
+ end
61
+ end
62
+ end
63
+
64
+ def test_perform
65
+ req = mock
66
+ req.expects(:perform).with(@test_msg).returns(:result)
67
+ Fastbeans::Request.expects(:new).with(@conn).returns(req)
68
+ assert_equal :result, @conn.perform(@test_msg)
69
+ end
70
+
71
+ def test_call_with_retries
72
+ @conn.expects(:call_without_retries).
73
+ with(@test_msg).times(4).
74
+ raises(Fastbeans::RemoteConnectionFailed)
75
+ @conn.expects(:reconnect!).times(3)
76
+ assert_raises Fastbeans::RemoteConnectionDead do
77
+ assert_nil @conn.call(*@test_msg)
78
+ end
79
+ end
80
+
81
+ def assert_is_a_socket(sock)
82
+ assert_instance_of StringIO, sock
83
+ end
84
+
85
+ end
@@ -0,0 +1,9 @@
1
+ require_relative 'test_helper'
2
+
3
+ class FastbeansTest < MiniTest::Unit::TestCase
4
+
5
+ def test_should_autocreate_exceptions
6
+ assert_equal Fastbeans::RemoteException, Fastbeans.exception('RemoteException')
7
+ assert_equal 'Fastbeans::SomeException', Fastbeans.exception('SomeException').to_s
8
+ end
9
+ end
@@ -0,0 +1,78 @@
1
+ require_relative 'test_helper'
2
+
3
+ class RequestTest < MiniTest::Unit::TestCase
4
+
5
+ def setup
6
+ @conn = MockConnection.new
7
+ @req = Fastbeans::Request.new(@conn)
8
+ @test_msg = ['+', 1, 2, 3]
9
+ @test_sign = '2446e1d1a2947cfac9bbb0290b40d413'
10
+ @test_msgpack = "\x92\xDA\0 2446e1d1a2947cfac9bbb0290b40d413\x94\xA1+\x01\x02\x03"
11
+ @test_msgpack.force_encoding("BINARY")
12
+ end
13
+
14
+ def test_should_sign_call_data
15
+ samples = {
16
+ ['a', 1, 2, 3] => '60fb262a7b770985a64bb29e84d92af4',
17
+ ['a', 1, 2, {:a => 3}] => '3d8e75d66df7134d7260954c6e9c3b64',
18
+ ['clojure.something/blah', [1, 2, 3]] => 'f8c15a77f1c43ccd1ca29b0b6d969e9f',
19
+ ['hello'] => '1f0bf5038e61951a95c4a2a9db7c400a',
20
+ [:hello] => '776222fe61dcc5551da706f3ec5c12c1',
21
+ @test_msg => @test_sign
22
+ }
23
+ samples.each do |inp, outp|
24
+ assert_equal outp, @req.sign(inp), "Wrong signature for #{inp}"
25
+ end
26
+ end
27
+
28
+ def test_build_payload
29
+ signature, payload = @req.build_payload(@test_msg)
30
+ assert_equal @test_sign, signature
31
+ assert_equal @test_msgpack, payload
32
+ if payload.respond_to?(:force_encoding)
33
+ # Ruby 2.0 case
34
+ assert_equal Encoding::BINARY, payload.encoding
35
+ end
36
+ end
37
+
38
+ def test_write_payload
39
+ io = StringIO.new
40
+ @req.write_payload(io, "simple-test")
41
+ io.rewind
42
+ assert_equal "\0\0\0\vsimple-test", io.read
43
+ end
44
+
45
+ def test_read_response
46
+ io = StringIO.new(@test_msgpack)
47
+ resp = @req.read_response(io, @test_msg)
48
+ assert_instance_of Fastbeans::Response, resp
49
+ assert_equal '2446e1d1a2947cfac9bbb0290b40d413', resp.signature
50
+ assert_equal @test_msg, resp.payload
51
+ end
52
+
53
+ def test_perform
54
+ @conn.socket = StringIO.new
55
+ @req.expects(:build_payload).with(@test_msg).returns([@test_sign, @test_msgpack])
56
+ @req.expects(:write_payload).with(@conn.socket, @test_msgpack)
57
+ resp = mock
58
+ resp.expects(:signed_with?).with(@test_sign).returns(true)
59
+ resp.expects(:payload).returns(@test_msg)
60
+ @req.expects(:read_response).with(@conn.socket, @test_msg).returns(resp)
61
+
62
+ assert_equal @test_msg, @req.perform(@test_msg)
63
+ end
64
+
65
+ def test_perform_signature_failure
66
+ @conn.socket = StringIO.new
67
+ @req.expects(:build_payload).with(@test_msg).returns([@test_sign, @test_msgpack])
68
+ @req.expects(:write_payload).with(@conn.socket, @test_msgpack)
69
+ resp = mock
70
+ resp.expects(:signed_with?).with(@test_sign).returns(false)
71
+ resp.expects(:signature).returns("incorrectsignature")
72
+ @req.expects(:read_response).with(@conn.socket, @test_msg).returns(resp)
73
+
74
+ assert_raises Fastbeans::ResponseSignatureMismatch do
75
+ @req.perform(@test_msg)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,47 @@
1
+ require_relative 'test_helper'
2
+
3
+ class ResponseTest < MiniTest::Unit::TestCase
4
+
5
+ def setup
6
+ @test_msg = ['+', 1, 2, 3]
7
+ @test_sign = '2446e1d1a2947cfac9bbb0290b40d413'
8
+ @test_resp = [@test_sign, ['+', 1, 2, 3]]
9
+ @resp = Fastbeans::Response.new(@test_msg, @test_resp)
10
+ @err_resp = { 'fastbeans-error' => 'Total screw-up',
11
+ 'error-information' => 'No ways to recover.'}
12
+ @eresp = Fastbeans::Response.new(@test_msg, @err_resp)
13
+ end
14
+
15
+ def test_error?
16
+ assert @eresp.error?
17
+ assert !@resp.error?
18
+ end
19
+
20
+ def test_signature
21
+ assert_nil @eresp.signature
22
+ assert_equal @test_sign, @resp.signature
23
+ end
24
+
25
+ def test_signed_with
26
+ assert @resp.signed_with?(@test_sign)
27
+ end
28
+
29
+ def test_payload_for_errors
30
+ begin
31
+ @eresp.payload
32
+ rescue Fastbeans::AutogeneratedException => e
33
+ assert_equal "Fastbeans::TotalScrewUp", e.class.to_s
34
+ end
35
+ end
36
+
37
+ def test_payload
38
+ assert_equal @test_msg, @resp.payload
39
+ end
40
+
41
+ def test_support_methods
42
+ assert_equal 'hello_word', @resp.underscore('hello-word')
43
+ assert_equal 'hello_word', @resp.underscore('hello word')
44
+ assert_equal 'HelloWord', @resp.camelize('hello_word')
45
+ assert_equal 'ABC', @resp.camelize('a_b_c')
46
+ end
47
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastbeans
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dima Sabanin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-05-30 00:00:00.000000000 Z
11
+ date: 2013-06-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ! '>='
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: mocha
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  description: Tiny and fast RPC client for Ruby to call Clojure code
84
98
  email:
85
99
  - sdmitry@gmail.com
@@ -97,8 +111,14 @@ files:
97
111
  - lib/fastbeans/client.rb
98
112
  - lib/fastbeans/connection.rb
99
113
  - lib/fastbeans/errors.rb
114
+ - lib/fastbeans/request.rb
100
115
  - lib/fastbeans/response.rb
101
116
  - lib/fastbeans/version.rb
117
+ - test/client_test.rb
118
+ - test/connection_test.rb
119
+ - test/fastbeans_test.rb
120
+ - test/request_test.rb
121
+ - test/response_test.rb
102
122
  homepage: https://github.com/dsabanin/fastbeans-rb
103
123
  licenses:
104
124
  - MIT
@@ -123,5 +143,10 @@ rubygems_version: 2.0.3
123
143
  signing_key:
124
144
  specification_version: 4
125
145
  summary: Ruby piece of Ruby/Clojure RPC system extracted from beanstalkapp.com
126
- test_files: []
146
+ test_files:
147
+ - test/client_test.rb
148
+ - test/connection_test.rb
149
+ - test/fastbeans_test.rb
150
+ - test/request_test.rb
151
+ - test/response_test.rb
127
152
  has_rdoc: