tcp-client 0.9.3 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -26,7 +26,7 @@ RSpec.describe TCPClient do
26
26
  subject(:client) { TCPClient.new }
27
27
 
28
28
  it 'is closed' do
29
- expect(client.closed?).to be true
29
+ expect(client).to be_closed
30
30
  end
31
31
 
32
32
  it 'has no address' do
@@ -64,7 +64,7 @@ RSpec.describe TCPClient do
64
64
  before { allow_any_instance_of(::Socket).to receive(:connect) }
65
65
 
66
66
  it 'is not closed' do
67
- expect(client.closed?).to be false
67
+ expect(client).not_to be_closed
68
68
  end
69
69
 
70
70
  it 'has an address' do
@@ -115,7 +115,7 @@ RSpec.describe TCPClient do
115
115
  end
116
116
 
117
117
  it 'is closed' do
118
- expect(client.closed?).to be true
118
+ expect(client).to be_closed
119
119
  end
120
120
 
121
121
  it 'has an address' do
@@ -157,6 +157,8 @@ RSpec.describe TCPClient do
157
157
 
158
158
  context 'when not using SSL' do
159
159
  describe '#connect' do
160
+ subject(:client) { TCPClient.new }
161
+
160
162
  it 'configures the socket' do
161
163
  expect_any_instance_of(::Socket).to receive(:sync=).once.with(true)
162
164
  expect_any_instance_of(::Socket).to receive(:setsockopt)
@@ -169,7 +171,7 @@ RSpec.describe TCPClient do
169
171
  .once
170
172
  .with(false)
171
173
  expect_any_instance_of(::Socket).to receive(:connect)
172
- TCPClient.new.connect('localhost:1234', configuration)
174
+ client.connect('localhost:1234', configuration)
173
175
  end
174
176
 
175
177
  context 'when a timeout is specified' do
@@ -177,7 +179,26 @@ RSpec.describe TCPClient do
177
179
  expect_any_instance_of(::Socket).to receive(:connect_nonblock)
178
180
  .once
179
181
  .with(kind_of(String), exception: false)
180
- TCPClient.new.connect('localhost:1234', configuration, timeout: 10)
182
+ client.connect('localhost:1234', configuration, timeout: 10)
183
+ end
184
+
185
+ it 'is returns itself' do
186
+ allow_any_instance_of(::Socket).to receive(:connect_nonblock).with(
187
+ kind_of(String),
188
+ exception: false
189
+ )
190
+ result = client.connect('localhost:1234', configuration, timeout: 10)
191
+
192
+ expect(result).to be client
193
+ end
194
+
195
+ it 'is not closed' do
196
+ allow_any_instance_of(::Socket).to receive(:connect_nonblock).with(
197
+ kind_of(String),
198
+ exception: false
199
+ )
200
+ client.connect('localhost:1234', configuration, timeout: 10)
201
+ expect(client).not_to be_closed
181
202
  end
182
203
 
183
204
  context 'when the connection can not be established in time' do
@@ -188,25 +209,29 @@ RSpec.describe TCPClient do
188
209
 
189
210
  it 'raises an exception' do
190
211
  expect do
191
- TCPClient.new.connect(
192
- 'localhost:1234',
193
- configuration,
194
- timeout: 0.25
195
- )
212
+ client.connect('localhost:1234', configuration, timeout: 0.1)
196
213
  end.to raise_error(TCPClient::ConnectTimeoutError)
197
214
  end
198
215
 
199
216
  it 'allows to raise a custom exception' do
200
217
  exception = Class.new(StandardError)
201
218
  expect do
202
- TCPClient.new.connect(
219
+ client.connect(
203
220
  'localhost:1234',
204
221
  configuration,
205
- timeout: 0.25,
222
+ timeout: 0.1,
206
223
  exception: exception
207
224
  )
208
225
  end.to raise_error(exception)
209
226
  end
227
+
228
+ it 'is still closed' do
229
+ begin
230
+ client.connect('localhost:1234', configuration, timeout: 0.1)
231
+ rescue TCPClient::ConnectTimeoutError
232
+ end
233
+ expect(client).to be_closed
234
+ end
210
235
  end
211
236
  end
212
237
 
@@ -226,7 +251,7 @@ RSpec.describe TCPClient do
226
251
  end
227
252
 
228
253
  SOCKET_ERRORS.each do |error_class|
229
- it "raises a TCPClient::NetworkError when a #{error_class} appeared" do
254
+ it "raises TCPClient::NetworkError when a #{error_class} appeared" do
230
255
  allow_any_instance_of(::Socket).to receive(:connect) {
231
256
  raise error_class
232
257
  }
@@ -270,18 +295,61 @@ RSpec.describe TCPClient do
270
295
  expect(client.read(timeout: 10)).to be data
271
296
  end
272
297
 
298
+ context 'when socket closed before any data can be read' do
299
+ it 'returns empty buffer' do
300
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
301
+ .and_return(nil)
302
+ expect(client.read(timeout: 10)).to be_empty
303
+ end
304
+
305
+ it 'is closed' do
306
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
307
+ .and_return(nil)
308
+
309
+ client.read(timeout: 10)
310
+ expect(client).to be_closed
311
+ end
312
+ end
313
+
273
314
  context 'when data can not be fetched in a single chunk' do
274
315
  it 'reads chunk by chunk' do
275
316
  expect_any_instance_of(::Socket).to receive(:read_nonblock)
276
317
  .once
277
- .with(data_size * 2, exception: false)
318
+ .with(instance_of(Integer), exception: false)
278
319
  .and_return(data)
279
320
  expect_any_instance_of(::Socket).to receive(:read_nonblock)
280
321
  .once
281
- .with(data_size, exception: false)
322
+ .with(instance_of(Integer), exception: false)
282
323
  .and_return(data)
283
324
  expect(client.read(data_size * 2, timeout: 10)).to eq data * 2
284
325
  end
326
+
327
+ context 'when socket closed before enough data is avail' do
328
+ it 'returns available data only' do
329
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
330
+ .once
331
+ .with(instance_of(Integer), exception: false)
332
+ .and_return(data)
333
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
334
+ .once
335
+ .with(instance_of(Integer), exception: false)
336
+ .and_return(nil)
337
+ expect(client.read(data_size * 2, timeout: 10)).to eq data
338
+ end
339
+
340
+ it 'is closed' do
341
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
342
+ .once
343
+ .with(instance_of(Integer), exception: false)
344
+ .and_return(data)
345
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
346
+ .once
347
+ .with(instance_of(Integer), exception: false)
348
+ .and_return(nil)
349
+ client.read(data_size * 2, timeout: 10)
350
+ expect(client).to be_closed
351
+ end
352
+ end
285
353
  end
286
354
 
287
355
  context 'when the data can not be read in time' do
@@ -329,6 +397,146 @@ RSpec.describe TCPClient do
329
397
  end
330
398
  end
331
399
 
400
+ describe '#readline' do
401
+ before { allow_any_instance_of(::Socket).to receive(:connect) }
402
+
403
+ it 'reads from socket' do
404
+ expect_any_instance_of(::Socket).to receive(:readline)
405
+ .once
406
+ .with($/, chomp: false)
407
+ .and_return("Hello World\n")
408
+ expect(client.readline).to eq "Hello World\n"
409
+ end
410
+
411
+ context 'when a separator is specified' do
412
+ it 'forwards the separator' do
413
+ expect_any_instance_of(::Socket).to receive(:readline)
414
+ .once
415
+ .with('/', chomp: false)
416
+ .and_return('Hello/')
417
+ expect(client.readline('/')).to eq 'Hello/'
418
+ end
419
+ end
420
+
421
+ context 'when chomp is true' do
422
+ it 'forwards the flag' do
423
+ expect_any_instance_of(::Socket).to receive(:readline)
424
+ .once
425
+ .with($/, chomp: true)
426
+ .and_return('Hello World')
427
+ expect(client.readline(chomp: true)).to eq 'Hello World'
428
+ end
429
+ end
430
+
431
+ context 'when a timeout is specified' do
432
+ it 'checks the time' do
433
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
434
+ .and_return("Hello World\nHello World\n")
435
+ expect(client.readline(timeout: 10)).to eq "Hello World\n"
436
+ end
437
+
438
+ it 'optional chomps the line' do
439
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
440
+ .and_return("Hello World\nHello World\n")
441
+ expect(client.readline(chomp: true, timeout: 10)).to eq 'Hello World'
442
+ end
443
+
444
+ it 'uses the given separator' do
445
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
446
+ .and_return("Hello/World\n")
447
+ expect(client.readline('/', timeout: 10)).to eq 'Hello/'
448
+ end
449
+
450
+ context 'when data can not be fetched in a single chunk' do
451
+ it 'reads chunk by chunk' do
452
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
453
+ .once
454
+ .with(instance_of(Integer), exception: false)
455
+ .and_return('Hello ')
456
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
457
+ .once
458
+ .with(instance_of(Integer), exception: false)
459
+ .and_return('World')
460
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
461
+ .once
462
+ .with(instance_of(Integer), exception: false)
463
+ .and_return("\nAnd so...")
464
+ expect(client.readline(timeout: 10)).to eq "Hello World\n"
465
+ end
466
+
467
+ context 'when socket closed before enough data is avail' do
468
+ it 'returns available data only' do
469
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
470
+ .once
471
+ .with(instance_of(Integer), exception: false)
472
+ .and_return('Hello ')
473
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
474
+ .once
475
+ .with(instance_of(Integer), exception: false)
476
+ .and_return(nil)
477
+ expect(client.readline(timeout: 10)).to eq "Hello "
478
+ end
479
+
480
+ it 'is closed' do
481
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
482
+ .once
483
+ .with(instance_of(Integer), exception: false)
484
+ .and_return('Hello ')
485
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
486
+ .once
487
+ .with(instance_of(Integer), exception: false)
488
+ .and_return(nil)
489
+ client.readline(timeout: 10)
490
+ expect(client).to be_closed
491
+ end
492
+ end
493
+ end
494
+
495
+ context 'when the data can not be read in time' do
496
+ before do
497
+ allow_any_instance_of(::Socket).to receive(:read_nonblock)
498
+ .and_return(:wait_readable)
499
+ end
500
+ it 'raises an exception' do
501
+ expect { client.readline(timeout: 0.25) }.to raise_error(
502
+ TCPClient::ReadTimeoutError
503
+ )
504
+ end
505
+
506
+ it 'allows to raise a custom exception' do
507
+ exception = Class.new(StandardError)
508
+ expect do
509
+ client.read(timeout: 0.25, exception: exception)
510
+ end.to raise_error(exception)
511
+ end
512
+ end
513
+ end
514
+
515
+ context 'when a SocketError appears' do
516
+ it 'does not handle it' do
517
+ allow_any_instance_of(::Socket).to receive(:read) {
518
+ raise SocketError
519
+ }
520
+ expect { client.read(10) }.to raise_error(SocketError)
521
+ end
522
+
523
+ context 'when normalize_network_errors is configured' do
524
+ let(:configuration) do
525
+ TCPClient::Configuration.create(normalize_network_errors: true)
526
+ end
527
+
528
+ SOCKET_ERRORS.each do |error_class|
529
+ it "raises a TCPClient::NetworkError when a #{error_class} appeared" do
530
+ allow_any_instance_of(::Socket).to receive(:read) {
531
+ raise error_class
532
+ }
533
+ expect { client.read(12) }.to raise_error(TCPClient::NetworkError)
534
+ end
535
+ end
536
+ end
537
+ end
538
+ end
539
+
332
540
  describe '#write' do
333
541
  let(:data) { 'some bytes' }
334
542
  let(:data_size) { data.bytesize }
@@ -463,20 +671,16 @@ RSpec.describe TCPClient do
463
671
  .with(kind_of(String), exception: false)
464
672
  expect_any_instance_of(::Socket).to receive(:read_nonblock)
465
673
  .once
466
- .with(12, exception: false)
467
- .and_return('123456789012')
674
+ .with(instance_of(Integer), exception: false)
675
+ .and_return('123456789012abcdefgAB')
468
676
  expect_any_instance_of(::Socket).to receive(:write_nonblock)
469
677
  .once
470
678
  .with('123456', exception: false)
471
679
  .and_return(6)
472
680
  expect_any_instance_of(::Socket).to receive(:read_nonblock)
473
681
  .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')
682
+ .with(instance_of(Integer), exception: false)
683
+ .and_return('CDEFG')
480
684
  expect_any_instance_of(::Socket).to receive(:write_nonblock)
481
685
  .once
482
686
  .with('abc', exception: false)
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.9.3
4
+ version: 0.10.1
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-12-07 00:00:00.000000000 Z
11
+ date: 2022-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -129,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
129
  - !ruby/object:Gem::Version
130
130
  version: '0'
131
131
  requirements: []
132
- rubygems_version: 3.2.32
132
+ rubygems_version: 3.3.7
133
133
  signing_key:
134
134
  specification_version: 4
135
135
  summary: A TCP client implementation with working timeout support.