thrifter 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 +7 -0
- data/.dockerignore +3 -0
- data/.gitignore +14 -0
- data/Dockerfile +15 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/Makefile +57 -0
- data/README.md +283 -0
- data/Rakefile +8 -0
- data/Vagrantfile +126 -0
- data/circle.yml +10 -0
- data/lib/thrifter.rb +166 -0
- data/lib/thrifter/error_wrapping_middleware.rb +49 -0
- data/lib/thrifter/ping.rb +10 -0
- data/lib/thrifter/queueing.rb +38 -0
- data/lib/thrifter/retry.rb +57 -0
- data/lib/thrifter/statsd_middleware.rb +26 -0
- data/lib/thrifter/version.rb +3 -0
- data/script/monkey-client +61 -0
- data/script/server +32 -0
- data/test.thrift +8 -0
- data/test/acceptance_test.rb +221 -0
- data/test/error_wrapping_middleware_test.rb +73 -0
- data/test/ping_test.rb +31 -0
- data/test/queuing_test.rb +73 -0
- data/test/retry_test.rb +116 -0
- data/test/statsd_middleware_test.rb +99 -0
- data/test/test_helper.rb +45 -0
- data/thrifter.gemspec +35 -0
- data/vendor/gen-rb/test_constants.rb +9 -0
- data/vendor/gen-rb/test_service.rb +122 -0
- data/vendor/gen-rb/test_types.rb +25 -0
- metadata +264 -0
data/test.thrift
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class AcceptanceTest < MiniTest::Unit::TestCase
|
4
|
+
SimulatedError = Class.new StandardError
|
5
|
+
|
6
|
+
class TestClient < TestService::Client
|
7
|
+
def echo(message)
|
8
|
+
message
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class BrokenTestClient < TestService::Client
|
13
|
+
def echo(message)
|
14
|
+
fail SimulatedError
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :uri
|
19
|
+
|
20
|
+
def setup
|
21
|
+
super
|
22
|
+
|
23
|
+
@uri = URI('tcp://localhost:9090')
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_defaults_to_framed_transport
|
27
|
+
client = Thrifter.build TestClient
|
28
|
+
assert_equal Thrift::FramedTransport, client.config.transport
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_defaults_to_binary_protocol
|
32
|
+
client = Thrifter.build TestClient
|
33
|
+
assert_equal Thrift::BinaryProtocol, client.config.protocol
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_defaults_to_12_connections
|
37
|
+
client = Thrifter.build TestClient
|
38
|
+
assert_equal 12, client.config.pool_size
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_has_reasonable_default_pool_timeout
|
42
|
+
client = Thrifter.build TestClient
|
43
|
+
assert_equal 0.1, client.config.pool_timeout
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_has_reasonable_default_rpc_timeout
|
47
|
+
client = Thrifter.build TestClient
|
48
|
+
assert_equal 0.3, client.config.rpc_timeout
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_defaults_to_null_statd
|
52
|
+
client = Thrifter.build TestClient
|
53
|
+
assert_instance_of Thrifter::NullStatsd, client.config.statsd
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_configuration_has_a_block_form
|
57
|
+
client = Thrifter.build TestClient
|
58
|
+
|
59
|
+
client.configure do |config|
|
60
|
+
config.transport = :via_block
|
61
|
+
end
|
62
|
+
|
63
|
+
assert :via_block == client.config.transport, 'Block form incorrect'
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_fails_if_uri_not_configured
|
67
|
+
client = Thrifter.build TestClient do
|
68
|
+
config.uri = nil
|
69
|
+
end
|
70
|
+
|
71
|
+
error = assert_raises ArgumentError do
|
72
|
+
client.new
|
73
|
+
end
|
74
|
+
|
75
|
+
assert_match /uri/, error.message
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_fails_if_uri_does_not_contain_port
|
79
|
+
client = Thrifter.build TestClient do
|
80
|
+
config.uri = 'tcp://localhost'
|
81
|
+
end
|
82
|
+
|
83
|
+
error = assert_raises ArgumentError do
|
84
|
+
client.new
|
85
|
+
end
|
86
|
+
|
87
|
+
assert_match /port/, error.message
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_pool_options_are_forwarded
|
91
|
+
client = Thrifter.build TestClient do
|
92
|
+
config.uri = 'http://localhost:9090'
|
93
|
+
config.pool_size = 50
|
94
|
+
config.pool_timeout = 75
|
95
|
+
end
|
96
|
+
|
97
|
+
ConnectionPool.expects(:new).with(size: 50, timeout: 75)
|
98
|
+
|
99
|
+
client.new
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_pool_options_work_as_string
|
103
|
+
client = Thrifter.build TestClient do
|
104
|
+
config.uri = 'http://localhost:9090'
|
105
|
+
config.pool_size = '50'
|
106
|
+
config.pool_timeout = '75.5'
|
107
|
+
end
|
108
|
+
|
109
|
+
ConnectionPool.expects(:new).with(size: 50, timeout: 75.5)
|
110
|
+
|
111
|
+
client.new
|
112
|
+
end
|
113
|
+
|
114
|
+
# NOTE: This test is quite unfortunate, but there does not seem to be a good
|
115
|
+
# way to simulate the 4 objects required to get a Thrift::Client instance
|
116
|
+
# off the ground. The monkey tests will ensure the final product can communicate
|
117
|
+
# to a server correctly. For now, just test the things are built in a sane way.
|
118
|
+
def test_builds_thrift_object_chain_correctly
|
119
|
+
test_message = TestMessage.new message: 'testing 123'
|
120
|
+
|
121
|
+
client = Thrifter.build TestClient
|
122
|
+
client.config.uri = uri
|
123
|
+
|
124
|
+
socket, transport, protocol = stub, stub(open: nil, close: nil), stub
|
125
|
+
|
126
|
+
Thrift::Socket.expects(:new).
|
127
|
+
with(uri.host, uri.port, client.config.rpc_timeout).
|
128
|
+
returns(socket)
|
129
|
+
|
130
|
+
client.config.transport.expects(:new).
|
131
|
+
with(socket).
|
132
|
+
returns(transport)
|
133
|
+
|
134
|
+
client.config.protocol.expects(:new).
|
135
|
+
with(transport).
|
136
|
+
returns(protocol)
|
137
|
+
|
138
|
+
thrift_client = stub
|
139
|
+
thrift_client.expects(:echo).
|
140
|
+
with(test_message).
|
141
|
+
returns(test_message)
|
142
|
+
|
143
|
+
TestClient.expects(:new).with(protocol).returns(thrift_client)
|
144
|
+
|
145
|
+
thrifter = client.new
|
146
|
+
|
147
|
+
# connection pool is built lazily, so an RPC must be made to
|
148
|
+
# trigger the build
|
149
|
+
thrifter.echo test_message
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_rpc_timeout_may_be_provided_as_a_string
|
153
|
+
test_message = TestMessage.new message: 'testing 123'
|
154
|
+
|
155
|
+
client = Thrifter.build TestClient
|
156
|
+
client.config.transport = FakeTransport
|
157
|
+
client.config.rpc_timeout = '15.2'
|
158
|
+
client.config.uri = uri
|
159
|
+
|
160
|
+
Thrift::Socket.expects(:new).
|
161
|
+
with(uri.host, uri.port, 15.2)
|
162
|
+
|
163
|
+
thrifter = client.new
|
164
|
+
|
165
|
+
# connection pool is built lazily, so an RPC must be made to
|
166
|
+
# trigger the build
|
167
|
+
thrifter.echo test_message
|
168
|
+
end
|
169
|
+
|
170
|
+
def test_middleware_can_be_configured
|
171
|
+
test_message = TestMessage.new message: '123'
|
172
|
+
|
173
|
+
client = Thrifter.build TestClient
|
174
|
+
client.config.uri = uri
|
175
|
+
client.config.transport = FakeTransport
|
176
|
+
|
177
|
+
test_middleware = Class.new do
|
178
|
+
include Concord.new(:app, :salt)
|
179
|
+
|
180
|
+
def call(rpc)
|
181
|
+
rpc.args.first.message = salt + rpc.args.first.message
|
182
|
+
app.call rpc
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
client.use test_middleware, 'testing'
|
187
|
+
|
188
|
+
thrifter = client.new
|
189
|
+
result = thrifter.echo test_message
|
190
|
+
assert_match /testing/, result.message, 'Middleware not called'
|
191
|
+
end
|
192
|
+
|
193
|
+
def test_close_the_transport_on_successful_rpc
|
194
|
+
transport = mock
|
195
|
+
client = Thrifter.build TestClient
|
196
|
+
client.config.uri = uri
|
197
|
+
client.config.transport.stubs(:new).returns(transport)
|
198
|
+
|
199
|
+
transport.expects(:open)
|
200
|
+
transport.expects(:close)
|
201
|
+
|
202
|
+
thrifter = client.new
|
203
|
+
thrifter.echo message
|
204
|
+
end
|
205
|
+
|
206
|
+
def test_close_the_transport_if_client_fails
|
207
|
+
transport = mock
|
208
|
+
client = Thrifter.build BrokenTestClient
|
209
|
+
client.config.uri = uri
|
210
|
+
client.config.transport.stubs(:new).returns(transport)
|
211
|
+
|
212
|
+
transport.expects(:open)
|
213
|
+
transport.expects(:close)
|
214
|
+
|
215
|
+
thrifter = client.new
|
216
|
+
|
217
|
+
assert_raises SimulatedError do
|
218
|
+
thrifter.echo message
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require_relative './test_helper'
|
2
|
+
|
3
|
+
class ErrorWrappingMiddlewareTest < MiniTest::Unit::TestCase
|
4
|
+
TestError = Class.new StandardError
|
5
|
+
|
6
|
+
attr_reader :rpc
|
7
|
+
|
8
|
+
def known_errors
|
9
|
+
Thrifter::ErrorWrappingMiddleware.wrapped
|
10
|
+
end
|
11
|
+
|
12
|
+
def setup
|
13
|
+
super
|
14
|
+
|
15
|
+
@rpc = Thrifter::RPC.new(:foo, :args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_wraps_known_exceptions
|
19
|
+
app = stub
|
20
|
+
app.stubs(:call).with(rpc).raises(known_errors.first)
|
21
|
+
|
22
|
+
middleware = Thrifter::ErrorWrappingMiddleware.new app
|
23
|
+
|
24
|
+
assert_raises Thrifter::ClientError do
|
25
|
+
middleware.call rpc
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_can_provid_extras_errors_to_wrap
|
30
|
+
app = stub
|
31
|
+
app.stubs(:call).with(rpc).raises(TestError)
|
32
|
+
|
33
|
+
middleware = Thrifter::ErrorWrappingMiddleware.new app, [ TestError ]
|
34
|
+
|
35
|
+
assert_raises Thrifter::ClientError do
|
36
|
+
middleware.call rpc
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_includes_the_cause_and_message_in_wrapped_message
|
41
|
+
app = stub
|
42
|
+
app.stubs(:call).with(rpc).raises(TestError.new('testing 123'))
|
43
|
+
|
44
|
+
middleware = Thrifter::ErrorWrappingMiddleware.new app, [ TestError ]
|
45
|
+
|
46
|
+
error = assert_raises Thrifter::ClientError do
|
47
|
+
middleware.call rpc
|
48
|
+
end
|
49
|
+
|
50
|
+
assert_match /TestError/, error.message, 'Error class missing'
|
51
|
+
assert_match /testing 123/, error.message, 'Message missing'
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_wraps_protocol_exception
|
55
|
+
assert_includes known_errors, Thrift::ProtocolException
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_wraps_transport_exception
|
59
|
+
assert_includes known_errors, Thrift::TransportException
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_wraps_application_exception
|
63
|
+
assert_includes known_errors, Thrift::ApplicationException
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_wraps_timeout_error
|
67
|
+
assert_includes known_errors, TimeoutError
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_wraps_system_call_error
|
71
|
+
assert_includes known_errors, SystemCallError
|
72
|
+
end
|
73
|
+
end
|
data/test/ping_test.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative './test_helper'
|
2
|
+
|
3
|
+
class PingTest < MiniTest::Unit::TestCase
|
4
|
+
def test_returns_false_if_anything_goes_wrong
|
5
|
+
down_class = Class.new do
|
6
|
+
include Thrifter::Ping
|
7
|
+
|
8
|
+
def ping
|
9
|
+
raise StandardError
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
client = down_class.new
|
14
|
+
|
15
|
+
refute client.up?
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_returns_true_if_nothing_goes_wrong
|
19
|
+
down_class = Class.new do
|
20
|
+
include Thrifter::Ping
|
21
|
+
|
22
|
+
def ping
|
23
|
+
true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
client = down_class.new
|
28
|
+
|
29
|
+
assert client.up?
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class QueuingTest < MiniTest::Unit::TestCase
|
4
|
+
class TestClient < TestService::Client
|
5
|
+
def echo(message)
|
6
|
+
message
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
# Must have a constant name to work with sidekiq
|
11
|
+
class QueuedClient < Thrifter.build(TestClient)
|
12
|
+
include Thrifter::Queueing
|
13
|
+
|
14
|
+
self.config.uri = 'tcp://localhost:9090'
|
15
|
+
end
|
16
|
+
|
17
|
+
def queue
|
18
|
+
Thrifter::Queueing::Job
|
19
|
+
end
|
20
|
+
|
21
|
+
def jobs
|
22
|
+
queue.jobs
|
23
|
+
end
|
24
|
+
|
25
|
+
def setup
|
26
|
+
queue.clear
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_sends_rpcs_with_sidekiq
|
30
|
+
client = QueuedClient.new
|
31
|
+
|
32
|
+
message = TestMessage.new message: 'echo'
|
33
|
+
|
34
|
+
client.queued.echo(message)
|
35
|
+
|
36
|
+
refute jobs.empty?, 'Nothing enqueued'
|
37
|
+
|
38
|
+
# Now mock out an instance of QueuedClient that should be
|
39
|
+
# instantiated and used to make the RPC in the job
|
40
|
+
mock_client = mock
|
41
|
+
mock_client.expects(:echo).with do |rpc|
|
42
|
+
# NOTE: Thrift structs do not implement equality on attributes, only
|
43
|
+
# on object identity. This is why the expectation tests the
|
44
|
+
# message is sent all the way through.
|
45
|
+
rpc.message == message.message
|
46
|
+
end
|
47
|
+
|
48
|
+
QueuedClient.stubs(:new).returns(mock_client)
|
49
|
+
|
50
|
+
queue.drain
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_works_with_block_form
|
54
|
+
client = QueuedClient.new
|
55
|
+
|
56
|
+
message = TestMessage.new message: 'echo'
|
57
|
+
|
58
|
+
client.queued do |queue|
|
59
|
+
queue.echo message
|
60
|
+
end
|
61
|
+
|
62
|
+
refute jobs.empty?, 'Nothing enqueued'
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_fails_if_given_rpc_name
|
66
|
+
client = QueuedClient.new
|
67
|
+
message = TestMessage.new message: 'echo'
|
68
|
+
|
69
|
+
assert_raises NoMethodError do
|
70
|
+
client.queued.foo
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/test/retry_test.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class RetryTest < MiniTest::Unit::TestCase
|
4
|
+
JunkError = Class.new StandardError
|
5
|
+
|
6
|
+
class RetryClient < Thrifter.build(TestService::Client)
|
7
|
+
include Thrifter::Retry
|
8
|
+
|
9
|
+
config.uri = 'tcp://localhost:9090'
|
10
|
+
|
11
|
+
# This is enough to get around thrift's networking objects and focus
|
12
|
+
# on stubbing the actual thrift client.
|
13
|
+
config.transport = FakeTransport
|
14
|
+
end
|
15
|
+
|
16
|
+
def known_errors
|
17
|
+
Thrifter::Retry::RETRIABLE_ERRORS
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_does_not_retry_on_unexpected_errors
|
21
|
+
thrift_client = mock
|
22
|
+
thrift_client.expects(:echo).with(:request).raises(JunkError)
|
23
|
+
TestService::Client.stubs(:new).returns(thrift_client)
|
24
|
+
|
25
|
+
client = RetryClient.new
|
26
|
+
|
27
|
+
assert_raises JunkError do
|
28
|
+
client.with_retry({ tries: 2, interval: 0.01 }).echo(:request)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_retries_on_known_exceptions
|
33
|
+
thrift_client = mock
|
34
|
+
retries = sequence(:retries)
|
35
|
+
thrift_client.expects(:echo).with(:request).in_sequence(retries).raises(known_errors.sample)
|
36
|
+
thrift_client.expects(:echo).with(:request).in_sequence(retries).returns(:response)
|
37
|
+
TestService::Client.stubs(:new).returns(thrift_client)
|
38
|
+
|
39
|
+
client = RetryClient.new
|
40
|
+
|
41
|
+
result = client.with_retry({ tries: 2, interval: 0.01 }).echo(:request)
|
42
|
+
|
43
|
+
assert :response == result, 'return value incorrect'
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_fails_if_does_not_respond_successfully
|
47
|
+
thrift_client = mock
|
48
|
+
thrift_client.expects(:echo).with(:request).raises(known_errors.sample).times(5)
|
49
|
+
TestService::Client.stubs(:new).returns(thrift_client)
|
50
|
+
|
51
|
+
client = RetryClient.new
|
52
|
+
|
53
|
+
error = assert_raises Thrifter::RetryError do
|
54
|
+
client.with_retry({ tries: 5, interval: 0.01 }).echo(:request)
|
55
|
+
end
|
56
|
+
|
57
|
+
assert_match /5/, error.message, 'Error not descriptive'
|
58
|
+
assert_match /echo/, error.message, 'Error not descriptive'
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_retries_on_application_exception
|
62
|
+
assert_includes known_errors, Thrift::ApplicationException
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_retries_on_protocol_exception
|
66
|
+
assert_includes known_errors, Thrift::ProtocolException
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_retries_on_transport_exception
|
70
|
+
assert_includes known_errors, Thrift::TransportException
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_retries_on_timeout_error
|
74
|
+
assert_includes known_errors, Timeout::Error
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_retries_on_econrefused
|
78
|
+
assert_includes known_errors, Errno::ECONNREFUSED
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_retries_on_eaddrnotavail
|
82
|
+
assert_includes known_errors, Errno::EADDRNOTAVAIL
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_retries_on_ehostdown
|
86
|
+
assert_includes known_errors, Errno::EHOSTDOWN
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_retries_on_ehostunreach
|
90
|
+
assert_includes known_errors, Errno::EHOSTUNREACH
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_retries_on_etimedout
|
94
|
+
assert_includes known_errors, Errno::ETIMEDOUT
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_works_with_block_form
|
98
|
+
thrift_client = mock
|
99
|
+
thrift_client.expects(:echo).with(:request).returns(:response)
|
100
|
+
TestService::Client.stubs(:new).returns(thrift_client)
|
101
|
+
|
102
|
+
client = RetryClient.new
|
103
|
+
|
104
|
+
client.with_retry do |with_retry|
|
105
|
+
with_retry.echo :request
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_fails_if_given_rpc_name
|
110
|
+
client = RetryClient.new
|
111
|
+
|
112
|
+
assert_raises NoMethodError do
|
113
|
+
client.with_retry.foo
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|