thrifter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ struct TestMessage {
2
+ 1: required string message
3
+ }
4
+
5
+ service TestService {
6
+ TestMessage echo(1: TestMessage message)
7
+ oneway void onewayEcho(1: TestMessage message)
8
+ }
@@ -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
@@ -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
@@ -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