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.
@@ -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