tcp-client 0.7.0 → 0.8.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 +4 -4
- data/README.md +18 -16
- data/lib/tcp-client/address.rb +6 -2
- data/lib/tcp-client/configuration.rb +16 -6
- data/lib/tcp-client/errors.rb +4 -2
- data/lib/tcp-client/ssl_socket.rb +19 -2
- data/lib/tcp-client/version.rb +1 -1
- data/lib/tcp-client.rb +43 -22
- data/rakefile.rb +2 -5
- data/sample/google_ssl.rb +7 -6
- data/spec/helper.rb +12 -0
- data/spec/tcp-client/address_spec.rb +158 -0
- data/spec/tcp-client/configuration_spec.rb +308 -0
- data/spec/tcp-client/default_configuration_spec.rb +22 -0
- data/spec/tcp-client/version_spec.rb +13 -0
- data/spec/tcp_client_spec.rb +596 -0
- data/tcp-client.gemspec +2 -2
- metadata +16 -18
- data/test/helper.rb +0 -41
- data/test/tcp-client/address_test.rb +0 -65
- data/test/tcp-client/configuration_test.rb +0 -141
- data/test/tcp-client/deadline_test.rb +0 -26
- data/test/tcp-client/default_configuration_test.rb +0 -59
- data/test/tcp-client/version_test.rb +0 -9
- data/test/tcp_client_test.rb +0 -184
@@ -0,0 +1,596 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'helper'
|
4
|
+
|
5
|
+
SOCKET_ERRORS = [
|
6
|
+
Errno::EADDRNOTAVAIL,
|
7
|
+
Errno::ECONNABORTED,
|
8
|
+
Errno::ECONNREFUSED,
|
9
|
+
Errno::ECONNRESET,
|
10
|
+
Errno::EHOSTUNREACH,
|
11
|
+
Errno::EINVAL,
|
12
|
+
Errno::ENETUNREACH,
|
13
|
+
Errno::EPIPE,
|
14
|
+
IOError,
|
15
|
+
SocketError,
|
16
|
+
::OpenSSL::SSL::SSLError
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
RSpec.describe TCPClient do
|
20
|
+
subject(:client) { TCPClient.new.connect('localhost:1234', configuration) }
|
21
|
+
let(:configuration) do
|
22
|
+
TCPClient::Configuration.create(buffered: false, reverse_lookup: false)
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'a new instance' do
|
26
|
+
subject(:client) { TCPClient.new }
|
27
|
+
|
28
|
+
it 'is closed' do
|
29
|
+
expect(client.closed?).to be true
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'has no address' do
|
33
|
+
expect(client.address).to be_nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'returns no address string' do
|
37
|
+
expect(client.to_s).to eq ''
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'has no configuration' do
|
41
|
+
expect(client.configuration).to be_nil
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'failes when read is called' do
|
45
|
+
expect { client.read(42) }.to raise_error(TCPClient::NotConnectedError)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'failes when write is called' do
|
49
|
+
expect { client.write('?!') }.to raise_error(TCPClient::NotConnectedError)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'can be closed' do
|
53
|
+
expect_any_instance_of(::Socket).not_to receive(:close)
|
54
|
+
expect(client.close).to be client
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'can be flushed' do
|
58
|
+
expect_any_instance_of(::Socket).not_to receive(:flush)
|
59
|
+
expect(client.flush).to be client
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe 'a connected instance' do
|
64
|
+
before { allow_any_instance_of(::Socket).to receive(:connect) }
|
65
|
+
|
66
|
+
it 'is not closed' do
|
67
|
+
expect(client.closed?).to be false
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'has an address' do
|
71
|
+
expect(client.address).to be_a TCPClient::Address
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'returns an address string' do
|
75
|
+
expect(client.to_s).to eq 'localhost:1234'
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'uses the given configuration' do
|
79
|
+
expect(client.configuration).to be configuration
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'allows to read data' do
|
83
|
+
result = '-' * 42
|
84
|
+
allow_any_instance_of(::Socket).to receive(:read)
|
85
|
+
.with(42)
|
86
|
+
.and_return(result)
|
87
|
+
expect(client.read(42)).to be result
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'allows to write data' do
|
91
|
+
allow_any_instance_of(::Socket).to receive(:write)
|
92
|
+
.with('!' * 21)
|
93
|
+
.and_return(21)
|
94
|
+
expect(client.write('!' * 21)).to be 21
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'can be closed' do
|
98
|
+
expect_any_instance_of(::Socket).to receive(:close)
|
99
|
+
expect(client.close).to be client
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'can be flushed' do
|
103
|
+
expect_any_instance_of(::Socket).to receive(:flush)
|
104
|
+
expect(client.flush).to be client
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe 'an instance after #connect failed' do
|
109
|
+
subject(:client) do
|
110
|
+
TCPClient.new.tap do |instance|
|
111
|
+
instance.connect('', configuration)
|
112
|
+
rescue StandardError
|
113
|
+
Errno::EADDRNOTAVAIL
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'is closed' do
|
118
|
+
expect(client.closed?).to be true
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'has an address' do
|
122
|
+
expect(client.address).to be_a TCPClient::Address
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'returns an address string' do
|
126
|
+
expect(client.to_s).to eq 'localhost:0'
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'uses the given configuration' do
|
130
|
+
expect(client.configuration).to be configuration
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'failes when read is called' do
|
134
|
+
expect { client.read(42) }.to raise_error(TCPClient::NotConnectedError)
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'failes when write is called' do
|
138
|
+
expect { client.write('?!') }.to raise_error(TCPClient::NotConnectedError)
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'can be closed' do
|
142
|
+
expect_any_instance_of(::Socket).not_to receive(:close)
|
143
|
+
expect(client.close).to be client
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'can be flushed' do
|
147
|
+
expect_any_instance_of(::Socket).not_to receive(:flush)
|
148
|
+
expect(client.flush).to be client
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
xdescribe '.open' do
|
153
|
+
end
|
154
|
+
|
155
|
+
xdescribe '.with_deadline' do
|
156
|
+
end
|
157
|
+
|
158
|
+
context 'when not using SSL' do
|
159
|
+
describe '#connect' do
|
160
|
+
it 'configures the socket' do
|
161
|
+
expect_any_instance_of(::Socket).to receive(:sync=).once.with(true)
|
162
|
+
expect_any_instance_of(::Socket).to receive(:setsockopt)
|
163
|
+
.once
|
164
|
+
.with(:TCP, :NODELAY, 1)
|
165
|
+
expect_any_instance_of(::Socket).to receive(:setsockopt)
|
166
|
+
.once
|
167
|
+
.with(:SOCKET, :KEEPALIVE, 1)
|
168
|
+
expect_any_instance_of(::Socket).to receive(:do_not_reverse_lookup=)
|
169
|
+
.once
|
170
|
+
.with(false)
|
171
|
+
expect_any_instance_of(::Socket).to receive(:connect)
|
172
|
+
TCPClient.new.connect('localhost:1234', configuration)
|
173
|
+
end
|
174
|
+
|
175
|
+
context 'when a timeout is specified' do
|
176
|
+
it 'checks the time' do
|
177
|
+
expect_any_instance_of(::Socket).to receive(:connect_nonblock)
|
178
|
+
.once
|
179
|
+
.with(kind_of(String), exception: false)
|
180
|
+
TCPClient.new.connect('localhost:1234', configuration, timeout: 10)
|
181
|
+
end
|
182
|
+
|
183
|
+
context 'when the connection can not be established in time' do
|
184
|
+
before do
|
185
|
+
allow_any_instance_of(::Socket).to receive(:connect_nonblock)
|
186
|
+
.and_return(:wait_writable)
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'raises an exception' do
|
190
|
+
expect do
|
191
|
+
TCPClient.new.connect(
|
192
|
+
'localhost:1234',
|
193
|
+
configuration,
|
194
|
+
timeout: 0.25
|
195
|
+
)
|
196
|
+
end.to raise_error(TCPClient::ConnectTimeoutError)
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'allows to raise a custom exception' do
|
200
|
+
exception = Class.new(StandardError)
|
201
|
+
expect do
|
202
|
+
TCPClient.new.connect(
|
203
|
+
'localhost:1234',
|
204
|
+
configuration,
|
205
|
+
timeout: 0.25,
|
206
|
+
exception: exception
|
207
|
+
)
|
208
|
+
end.to raise_error(exception)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
context 'when a SocketError appears' do
|
214
|
+
it 'does not handle it' do
|
215
|
+
allow_any_instance_of(::Socket).to receive(:connect) {
|
216
|
+
raise SocketError
|
217
|
+
}
|
218
|
+
expect do
|
219
|
+
TCPClient.new.connect('localhost:1234', configuration)
|
220
|
+
end.to raise_error(SocketError)
|
221
|
+
end
|
222
|
+
|
223
|
+
context 'when normalize_network_errors is configured' do
|
224
|
+
let(:configuration) do
|
225
|
+
TCPClient::Configuration.create(normalize_network_errors: true)
|
226
|
+
end
|
227
|
+
|
228
|
+
SOCKET_ERRORS.each do |error_class|
|
229
|
+
it "raises a TCPClient::NetworkError when a #{error_class} appeared" do
|
230
|
+
allow_any_instance_of(::Socket).to receive(:connect) {
|
231
|
+
raise error_class
|
232
|
+
}
|
233
|
+
expect do
|
234
|
+
TCPClient.new.connect('localhost:1234', configuration)
|
235
|
+
end.to raise_error(TCPClient::NetworkError)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
describe '#read' do
|
243
|
+
let(:data) { 'some bytes' }
|
244
|
+
let(:data_size) { data.bytesize }
|
245
|
+
|
246
|
+
before { allow_any_instance_of(::Socket).to receive(:connect) }
|
247
|
+
|
248
|
+
it 'reads from socket' do
|
249
|
+
expect_any_instance_of(::Socket).to receive(:read)
|
250
|
+
.once
|
251
|
+
.with(nil)
|
252
|
+
.and_return(data)
|
253
|
+
expect(client.read).to be data
|
254
|
+
end
|
255
|
+
|
256
|
+
context 'when a number of bytes is specified' do
|
257
|
+
it 'reads the requested number of bytes' do
|
258
|
+
expect_any_instance_of(::Socket).to receive(:read)
|
259
|
+
.once
|
260
|
+
.with(data_size)
|
261
|
+
.and_return(data)
|
262
|
+
expect(client.read(data_size)).to be data
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
context 'when a timeout is specified' do
|
267
|
+
it 'checks the time' do
|
268
|
+
expect_any_instance_of(::Socket).to receive(:read_nonblock)
|
269
|
+
.and_return(data)
|
270
|
+
expect(client.read(timeout: 10)).to be data
|
271
|
+
end
|
272
|
+
|
273
|
+
context 'when data can not be fetched in a single chunk' do
|
274
|
+
it 'reads chunk by chunk' do
|
275
|
+
expect_any_instance_of(::Socket).to receive(:read_nonblock)
|
276
|
+
.once
|
277
|
+
.with(data_size * 2, exception: false)
|
278
|
+
.and_return(data)
|
279
|
+
expect_any_instance_of(::Socket).to receive(:read_nonblock)
|
280
|
+
.once
|
281
|
+
.with(data_size, exception: false)
|
282
|
+
.and_return(data)
|
283
|
+
expect(client.read(data_size * 2, timeout: 10)).to eq data * 2
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
context 'when the data can not be read in time' do
|
288
|
+
before do
|
289
|
+
allow_any_instance_of(::Socket).to receive(:read_nonblock)
|
290
|
+
.and_return(:wait_readable)
|
291
|
+
end
|
292
|
+
it 'raises an exception' do
|
293
|
+
expect { client.read(timeout: 0.25) }.to raise_error(
|
294
|
+
TCPClient::ReadTimeoutError
|
295
|
+
)
|
296
|
+
end
|
297
|
+
|
298
|
+
it 'allows to raise a custom exception' do
|
299
|
+
exception = Class.new(StandardError)
|
300
|
+
expect do
|
301
|
+
client.read(timeout: 0.25, exception: exception)
|
302
|
+
end.to raise_error(exception)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
context 'when a SocketError appears' do
|
308
|
+
it 'does not handle it' do
|
309
|
+
allow_any_instance_of(::Socket).to receive(:read) {
|
310
|
+
raise SocketError
|
311
|
+
}
|
312
|
+
expect { client.read(10) }.to raise_error(SocketError)
|
313
|
+
end
|
314
|
+
|
315
|
+
context 'when normalize_network_errors is configured' do
|
316
|
+
let(:configuration) do
|
317
|
+
TCPClient::Configuration.create(normalize_network_errors: true)
|
318
|
+
end
|
319
|
+
|
320
|
+
SOCKET_ERRORS.each do |error_class|
|
321
|
+
it "raises a TCPClient::NetworkError when a #{error_class} appeared" do
|
322
|
+
allow_any_instance_of(::Socket).to receive(:read) {
|
323
|
+
raise error_class
|
324
|
+
}
|
325
|
+
expect { client.read(12) }.to raise_error(TCPClient::NetworkError)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
describe '#write' do
|
333
|
+
let(:data) { 'some bytes' }
|
334
|
+
let(:data_size) { data.bytesize }
|
335
|
+
|
336
|
+
before { allow_any_instance_of(::Socket).to receive(:connect) }
|
337
|
+
|
338
|
+
it 'writes to the socket' do
|
339
|
+
expect_any_instance_of(::Socket).to receive(:write)
|
340
|
+
.once
|
341
|
+
.with(data)
|
342
|
+
.and_return(data_size)
|
343
|
+
expect(client.write(data)).to be data_size
|
344
|
+
end
|
345
|
+
|
346
|
+
context 'when multiple data chunks are given' do
|
347
|
+
it 'writes each chunk' do
|
348
|
+
expect_any_instance_of(::Socket).to receive(:write)
|
349
|
+
.once
|
350
|
+
.with(data, data)
|
351
|
+
.and_return(data_size * 2)
|
352
|
+
expect(client.write(data, data)).to be data_size * 2
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
context 'when a timeout is specified' do
|
357
|
+
it 'checks the time' do
|
358
|
+
expect_any_instance_of(::Socket).to receive(:write_nonblock)
|
359
|
+
.and_return(data_size)
|
360
|
+
expect(client.write(data, timeout: 10)).to be data_size
|
361
|
+
end
|
362
|
+
|
363
|
+
context 'when data can not be written in a single chunk' do
|
364
|
+
let(:chunk1) { '1234567890' }
|
365
|
+
let(:chunk2) { '12345' }
|
366
|
+
let(:data1) { chunk1 + chunk2 }
|
367
|
+
let(:chunk3) { 'abcdefghijklm' }
|
368
|
+
let(:chunk4) { 'ABCDE' }
|
369
|
+
let(:data2) { chunk3 + chunk4 }
|
370
|
+
|
371
|
+
it 'writes chunk by chunk and part by part' do
|
372
|
+
expect_any_instance_of(::Socket).to receive(:write_nonblock)
|
373
|
+
.once
|
374
|
+
.with(data1, exception: false)
|
375
|
+
.and_return(chunk1.bytesize)
|
376
|
+
expect_any_instance_of(::Socket).to receive(:write_nonblock)
|
377
|
+
.once
|
378
|
+
.with(chunk2, exception: false)
|
379
|
+
.and_return(chunk2.bytesize)
|
380
|
+
expect_any_instance_of(::Socket).to receive(:write_nonblock)
|
381
|
+
.once
|
382
|
+
.with(data2, exception: false)
|
383
|
+
.and_return(chunk3.bytesize)
|
384
|
+
expect_any_instance_of(::Socket).to receive(:write_nonblock)
|
385
|
+
.once
|
386
|
+
.with(chunk4, exception: false)
|
387
|
+
.and_return(chunk4.bytesize)
|
388
|
+
|
389
|
+
expect(client.write(data1, data2, timeout: 10)).to be(
|
390
|
+
data1.bytesize + data2.bytesize
|
391
|
+
)
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
context 'when the data can not be written in time' do
|
396
|
+
before do
|
397
|
+
allow_any_instance_of(::Socket).to receive(:write_nonblock)
|
398
|
+
.and_return(:wait_writable)
|
399
|
+
end
|
400
|
+
it 'raises an exception' do
|
401
|
+
expect { client.write(data, timeout: 0.25) }.to raise_error(
|
402
|
+
TCPClient::WriteTimeoutError
|
403
|
+
)
|
404
|
+
end
|
405
|
+
|
406
|
+
it 'allows to raise a custom exception' do
|
407
|
+
exception = Class.new(StandardError)
|
408
|
+
expect do
|
409
|
+
client.write(data, timeout: 0.25, exception: exception)
|
410
|
+
end.to raise_error(exception)
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
context 'when a SocketError appears' do
|
416
|
+
before { allow_any_instance_of(::Socket).to receive(:connect) }
|
417
|
+
|
418
|
+
it 'does not handle it' do
|
419
|
+
allow_any_instance_of(::Socket).to receive(:write) {
|
420
|
+
raise SocketError
|
421
|
+
}
|
422
|
+
expect { client.write('some data') }.to raise_error(SocketError)
|
423
|
+
end
|
424
|
+
|
425
|
+
context 'when normalize_network_errors is configured' do
|
426
|
+
let(:configuration) do
|
427
|
+
TCPClient::Configuration.create(normalize_network_errors: true)
|
428
|
+
end
|
429
|
+
|
430
|
+
SOCKET_ERRORS.each do |error_class|
|
431
|
+
it "raises a TCPClient::NetworkError when a #{error_class} appeared" do
|
432
|
+
allow_any_instance_of(::Socket).to receive(:write) {
|
433
|
+
raise error_class
|
434
|
+
}
|
435
|
+
expect { client.write('some data') }.to raise_error(
|
436
|
+
TCPClient::NetworkError
|
437
|
+
)
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
describe '#with_deadline' do
|
445
|
+
subject(:client) { TCPClient.new }
|
446
|
+
let(:configuration) { TCPClient::Configuration.create(timeout: 60) }
|
447
|
+
|
448
|
+
before do
|
449
|
+
allow_any_instance_of(::Socket).to receive(:connect_nonblock)
|
450
|
+
allow_any_instance_of(::Socket).to receive(:read_nonblock) do |_, size|
|
451
|
+
'r' * size
|
452
|
+
end
|
453
|
+
allow_any_instance_of(::Socket).to receive(
|
454
|
+
:write_nonblock
|
455
|
+
) do |_, data|
|
456
|
+
data.bytesize
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
460
|
+
it 'allows to use a timeout value for all actions in the given block' do
|
461
|
+
expect_any_instance_of(::Socket).to receive(:connect_nonblock)
|
462
|
+
.once
|
463
|
+
.with(kind_of(String), exception: false)
|
464
|
+
expect_any_instance_of(::Socket).to receive(:read_nonblock)
|
465
|
+
.once
|
466
|
+
.with(12, exception: false)
|
467
|
+
.and_return('123456789012')
|
468
|
+
expect_any_instance_of(::Socket).to receive(:write_nonblock)
|
469
|
+
.once
|
470
|
+
.with('123456', exception: false)
|
471
|
+
.and_return(6)
|
472
|
+
expect_any_instance_of(::Socket).to receive(:read_nonblock)
|
473
|
+
.once
|
474
|
+
.with(7, exception: false)
|
475
|
+
.and_return('abcdefg')
|
476
|
+
expect_any_instance_of(::Socket).to receive(:read_nonblock)
|
477
|
+
.once
|
478
|
+
.with(7, exception: false)
|
479
|
+
.and_return('ABCDEFG')
|
480
|
+
expect_any_instance_of(::Socket).to receive(:write_nonblock)
|
481
|
+
.once
|
482
|
+
.with('abc', exception: false)
|
483
|
+
.and_return(3)
|
484
|
+
expect_any_instance_of(::Socket).to receive(:write_nonblock)
|
485
|
+
.once
|
486
|
+
.with('ABC', exception: false)
|
487
|
+
.and_return(3)
|
488
|
+
expect_any_instance_of(::Socket).to receive(:write_nonblock)
|
489
|
+
.once
|
490
|
+
.with('ABCDEF', exception: false)
|
491
|
+
.and_return(6)
|
492
|
+
|
493
|
+
client.with_deadline(10) do
|
494
|
+
expect(client.connect('localhost:1234', configuration)).to be client
|
495
|
+
expect(client.read(12)).to eq '123456789012'
|
496
|
+
expect(client.write('123456')).to be 6
|
497
|
+
expect(client.read(7)).to eq 'abcdefg'
|
498
|
+
expect(client.read(7)).to eq 'ABCDEFG'
|
499
|
+
expect(client.write('abc')).to be 3
|
500
|
+
expect(client.write('ABC', 'ABCDEF')).to be 9
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
context 'when called without a block' do
|
505
|
+
it 'raises an exception' do
|
506
|
+
expect { client.with_deadline(0.25) }.to raise_error(ArgumentError)
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
context 'when #connect fails' do
|
511
|
+
it 'raises an exception' do
|
512
|
+
expect_any_instance_of(::Socket).to receive(:connect_nonblock)
|
513
|
+
.and_return(:wait_writable)
|
514
|
+
expect do
|
515
|
+
client.with_deadline(0.25) do
|
516
|
+
client.connect('localhost:1234', configuration)
|
517
|
+
client.read(12)
|
518
|
+
client.write('Hello World!')
|
519
|
+
end
|
520
|
+
end.to raise_error(TCPClient::ConnectTimeoutError)
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
context 'when #read fails' do
|
525
|
+
it 'raises an exception' do
|
526
|
+
expect_any_instance_of(::Socket).to receive(:read_nonblock)
|
527
|
+
.and_return(:wait_readable)
|
528
|
+
expect do
|
529
|
+
client.with_deadline(0.25) do
|
530
|
+
client.connect('localhost:1234', configuration)
|
531
|
+
client.write('Hello World!')
|
532
|
+
client.read(12)
|
533
|
+
end
|
534
|
+
end.to raise_error(TCPClient::ReadTimeoutError)
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
context 'when #write fails' do
|
539
|
+
it 'raises an exception' do
|
540
|
+
expect_any_instance_of(::Socket).to receive(:write_nonblock)
|
541
|
+
.and_return(:wait_writable)
|
542
|
+
expect do
|
543
|
+
client.with_deadline(0.25) do
|
544
|
+
client.connect('localhost:1234', configuration)
|
545
|
+
client.read(12)
|
546
|
+
client.write('Hello World!')
|
547
|
+
end
|
548
|
+
end.to raise_error(TCPClient::WriteTimeoutError)
|
549
|
+
end
|
550
|
+
end
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
context 'when using SSL' do
|
555
|
+
let(:configuration) do
|
556
|
+
TCPClient::Configuration.create(
|
557
|
+
buffered: false,
|
558
|
+
reverse_lookup: false,
|
559
|
+
ssl: {
|
560
|
+
min_version: :TLS1_2,
|
561
|
+
max_version: :TLS1_3
|
562
|
+
}
|
563
|
+
)
|
564
|
+
end
|
565
|
+
|
566
|
+
before do
|
567
|
+
allow_any_instance_of(::Socket).to receive(:connect)
|
568
|
+
allow_any_instance_of(::OpenSSL::SSL::SSLSocket).to receive(:connect)
|
569
|
+
end
|
570
|
+
|
571
|
+
describe '#connect' do
|
572
|
+
it 'configures the SSL socket' do
|
573
|
+
# this produces a mock warning :(
|
574
|
+
# expect_any_instance_of(::OpenSSL::SSL::SSLContext).to receive(
|
575
|
+
# :set_params
|
576
|
+
# )
|
577
|
+
# .once
|
578
|
+
# .with(ssl_version: :TLSv1_2)
|
579
|
+
# .and_call_original
|
580
|
+
expect_any_instance_of(::OpenSSL::SSL::SSLSocket).to receive(
|
581
|
+
:sync_close=
|
582
|
+
)
|
583
|
+
.once
|
584
|
+
.with(true)
|
585
|
+
.and_call_original
|
586
|
+
expect_any_instance_of(::OpenSSL::SSL::SSLSocket).to receive(
|
587
|
+
:post_connection_check
|
588
|
+
)
|
589
|
+
.once
|
590
|
+
.with('localhost')
|
591
|
+
|
592
|
+
TCPClient.new.connect('localhost:1234', configuration)
|
593
|
+
end
|
594
|
+
end
|
595
|
+
end
|
596
|
+
end
|
data/tcp-client.gemspec
CHANGED
@@ -27,11 +27,11 @@ Gem::Specification.new do |spec|
|
|
27
27
|
'https://github.com/mblumtritt/tcp-client/issues'
|
28
28
|
|
29
29
|
spec.add_development_dependency 'bundler'
|
30
|
-
spec.add_development_dependency 'minitest'
|
31
30
|
spec.add_development_dependency 'rake'
|
31
|
+
spec.add_development_dependency 'rspec'
|
32
32
|
|
33
33
|
all_files = Dir.chdir(__dir__) { `git ls-files -z`.split(0.chr) }
|
34
|
-
spec.test_files = all_files.grep(%r{^
|
34
|
+
spec.test_files = all_files.grep(%r{^spec/})
|
35
35
|
spec.files = all_files - spec.test_files
|
36
36
|
|
37
37
|
spec.extra_rdoc_files = %w[README.md LICENSE]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tcp-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Blumtritt
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-11-
|
11
|
+
date: 2021-11-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -25,7 +25,7 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
@@ -39,7 +39,7 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
@@ -85,14 +85,13 @@ files:
|
|
85
85
|
- rakefile.rb
|
86
86
|
- sample/google.rb
|
87
87
|
- sample/google_ssl.rb
|
88
|
+
- spec/helper.rb
|
89
|
+
- spec/tcp-client/address_spec.rb
|
90
|
+
- spec/tcp-client/configuration_spec.rb
|
91
|
+
- spec/tcp-client/default_configuration_spec.rb
|
92
|
+
- spec/tcp-client/version_spec.rb
|
93
|
+
- spec/tcp_client_spec.rb
|
88
94
|
- tcp-client.gemspec
|
89
|
-
- test/helper.rb
|
90
|
-
- test/tcp-client/address_test.rb
|
91
|
-
- test/tcp-client/configuration_test.rb
|
92
|
-
- test/tcp-client/deadline_test.rb
|
93
|
-
- test/tcp-client/default_configuration_test.rb
|
94
|
-
- test/tcp-client/version_test.rb
|
95
|
-
- test/tcp_client_test.rb
|
96
95
|
homepage: https://github.com/mblumtritt/tcp-client
|
97
96
|
licenses:
|
98
97
|
- BSD-3-Clause
|
@@ -119,10 +118,9 @@ signing_key:
|
|
119
118
|
specification_version: 4
|
120
119
|
summary: A TCP client implementation with working timeout support.
|
121
120
|
test_files:
|
122
|
-
-
|
123
|
-
-
|
124
|
-
-
|
125
|
-
-
|
126
|
-
-
|
127
|
-
-
|
128
|
-
- test/tcp_client_test.rb
|
121
|
+
- spec/helper.rb
|
122
|
+
- spec/tcp-client/address_spec.rb
|
123
|
+
- spec/tcp-client/configuration_spec.rb
|
124
|
+
- spec/tcp-client/default_configuration_spec.rb
|
125
|
+
- spec/tcp-client/version_spec.rb
|
126
|
+
- spec/tcp_client_spec.rb
|