tcr 0.2.1 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 228eadaf6c734a9281952492ab146d7fee17e246
4
- data.tar.gz: 64045b81372a13b4d957ea02bd266b5c7c443d3a
2
+ SHA256:
3
+ metadata.gz: ab0a471bccd0a80a5d75bec6909dc521072b2ad666af34935f1d6ab596d9f2b4
4
+ data.tar.gz: eb6d9817d928173edd5354dc206fe7dd3e35a0f38ffde356e0e6e233e0a58431
5
5
  SHA512:
6
- metadata.gz: 1210aebd2ec013acb28546caeae5b29022e8cd7ddee1cb9e7814140c86d6e8fcc351798b10b2d2637baf104b7617719d3f28001bb3cde4cfee4821fa697625a5
7
- data.tar.gz: d05fc665286ace6c2b77c6f30890aff288e872344da943a2f5c09afcf468c8c7b82acbabff10f227092fc6322aabbbf9cf82a0c10cdc758b07e004533110d84f
6
+ metadata.gz: cedcebdd9373876a73534581ef013945ab20912ba86b71375b14de26043c135420f5140a2c1cf9d920afc9eacaa9b2f567d173c5d2525aa60db19ac97f43f606
7
+ data.tar.gz: d41afdda3c2d24c85e461db3247fed88ed5c9019e0929251c03b199b37dc3dc7a261796c3266041aea90ffbdf9b0c6053a2e627a9e7b7258382430db85df66a9
@@ -0,0 +1,47 @@
1
+ name: TCR Spec Suite
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ name: Test
11
+ runs-on: ubuntu-latest
12
+
13
+ strategy:
14
+ matrix:
15
+ ruby:
16
+ - "2.0.0"
17
+ - "2.1.9"
18
+ - "2.2.10"
19
+ - "2.3.7"
20
+ - "2.4.4"
21
+ - "2.5.1"
22
+ - "2.7.3"
23
+ - "3.0.1"
24
+
25
+ steps:
26
+ - uses: actions/checkout@v2
27
+
28
+ - name: Initial setup
29
+ uses: ruby/setup-ruby@v1
30
+ with:
31
+ ruby-version: ${{ matrix.ruby }}
32
+
33
+ - name: Install dependencies
34
+ run: bundle install
35
+
36
+ - name: Run tests
37
+ run: bundle exec rspec
38
+
39
+ testall:
40
+ if: ${{ always() }}
41
+ runs-on: ubuntu-latest
42
+ name: Test (matrix)
43
+ needs: [test]
44
+ steps:
45
+ - name: Check status of all test runs
46
+ if: ${{ needs.test.result != 'success' }}
47
+ run: exit 1
@@ -5,46 +5,55 @@ require 'thread'
5
5
 
6
6
  module TCR
7
7
  class RecordableTCPSocket
8
- attr_reader :live, :socket
9
- attr_accessor :recording
8
+ attr_reader :live, :socket, :recording
10
9
 
11
10
  def initialize(address, port, cassette)
12
11
  raise TCR::NoCassetteError.new unless TCR.cassette
13
12
 
14
13
  @read_lock = Queue.new
14
+ @recording = cassette.next_session
15
+ @live = cassette.recording?
15
16
 
16
- if cassette.recording?
17
- @live = true
18
- @socket = TCPSocket.real_open(address, port)
17
+ if live
18
+ begin
19
+ @socket = TCPSocket.real_open(address, port)
20
+ rescue => e
21
+ recording << ["error", Marshal.dump(e)]
22
+ raise
23
+ end
19
24
  else
20
- @live = false
21
25
  @closed = false
26
+ check_recording_for_errors
22
27
  end
23
- @recording = cassette.next_session
24
28
  end
25
29
 
26
- def read(bytes)
27
- _read(:read, bytes)
30
+ def read(*args)
31
+ _read(:read, args)
28
32
  end
29
33
 
30
34
  def getc(*args)
31
- _read(:getc, *args)
35
+ _read(:getc, args)
32
36
  end
33
37
 
34
38
  def gets(*args)
35
- _read(:gets, *args)
39
+ _read(:gets, args)
36
40
  end
37
41
 
38
42
  def read_nonblock(*args)
39
- _read(:read_nonblock, *args, blocking: false)
43
+ _read(:read_nonblock, args, blocking: false)
44
+ end
45
+
46
+ def print(str, *args)
47
+ _write(:print, str, args)
40
48
  end
41
49
 
42
- def print(str)
43
- _write(:print, str)
50
+ def write(str, *args)
51
+ _write(:write, str, args)
52
+ str.length
44
53
  end
45
54
 
46
- def write(str)
47
- _write(:write, str)
55
+ def write_nonblock(str, *args)
56
+ _write(:write_nonblock, str, args)
48
57
  str.length
49
58
  end
50
59
 
@@ -75,6 +84,10 @@ module TCR
75
84
 
76
85
  private
77
86
 
87
+ def check_recording_for_errors
88
+ raise Marshal.load(recording.first.last) if recording.first.first == "error"
89
+ end
90
+
78
91
  def _intercept_socket
79
92
  if @socket
80
93
  @socket = yield @socket
@@ -91,10 +104,10 @@ module TCR
91
104
  @read_lock << 1
92
105
  end
93
106
 
94
- def _write(method, data)
107
+ def _write(method, data, args)
95
108
  if live
96
109
  payload = data.dup if !data.is_a?(Symbol)
97
- @socket.__send__(method, payload)
110
+ _delegate_call(method, args.unshift(payload))
98
111
  recording << ["write", data.dup]
99
112
  else
100
113
  direction, data = recording.shift
@@ -103,14 +116,11 @@ module TCR
103
116
  end
104
117
  end
105
118
 
106
- def _read(method, *args)
107
- blocking = true
108
- if args.last.is_a?(::Hash)
109
- blocking = args.pop.fetch(:blocking, true)
110
- end
119
+ def _read(method, args, opts = {})
120
+ blocking = opts.fetch(:blocking, true)
111
121
 
112
122
  if live
113
- data = @socket.__send__(method, *args)
123
+ data = _delegate_call(method, args)
114
124
  payload = data.dup if !data.is_a?(Symbol)
115
125
  recording << ["read", payload]
116
126
  else
@@ -122,6 +132,15 @@ module TCR
122
132
  data
123
133
  end
124
134
 
135
+ def _delegate_call(method, args)
136
+ if RUBY_VERSION >= "2.7" && Hash === args.last
137
+ kwargs = args.pop
138
+ @socket.__send__(method, *args, **kwargs)
139
+ else
140
+ @socket.__send__(method, *args)
141
+ end
142
+ end
143
+
125
144
  def _ensure_direction(desired, actual)
126
145
  raise TCR::DirectionMismatchError.new("Expected to '#{desired}' but next in recording was '#{actual}'") unless desired == actual
127
146
  end
@@ -138,6 +157,14 @@ module TCR
138
157
  end
139
158
  end
140
159
 
160
+ def ssl_version
161
+ ""
162
+ end
163
+
164
+ def cipher
165
+ []
166
+ end
167
+
141
168
  def sync_close=(arg)
142
169
  true
143
170
  end
@@ -161,6 +188,9 @@ module TCR
161
188
  def session=(args)
162
189
  end
163
190
 
191
+ def hostname=(args)
192
+ end
193
+
164
194
  def io
165
195
  self
166
196
  end
data/lib/tcr/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module TCR
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/tcr.rb CHANGED
@@ -88,11 +88,11 @@ class Socket
88
88
  class << self
89
89
  alias_method :real_tcp, :tcp
90
90
 
91
- def tcp(host, port, socket_opts)
91
+ def tcp(host, port, *socket_opts)
92
92
  if TCR.configuration.hook_tcp_ports.include?(port)
93
93
  TCR::RecordableTCPSocket.new(host, port, TCR.cassette)
94
94
  else
95
- real_tcp(host, port, socket_opts)
95
+ real_tcp(host, port, *socket_opts)
96
96
  end
97
97
  end
98
98
  end
data/spec/spec_helper.rb CHANGED
@@ -19,7 +19,6 @@ end
19
19
 
20
20
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
21
21
  RSpec.configure do |config|
22
- config.treat_symbols_as_metadata_keys_with_true_values = true
23
22
  config.run_all_when_everything_filtered = true
24
23
  config.filter_run :focus
25
24
 
data/spec/tcr_spec.rb CHANGED
@@ -25,54 +25,91 @@ RSpec.describe TCR do
25
25
  end
26
26
 
27
27
  describe ".configuration" do
28
- it "has a default cassette location configured" do
29
- TCR.configuration.cassette_library_dir.should == "fixtures/tcr_cassettes"
30
- end
28
+ it "has a default cassette location configured" do
29
+ TCR.configuration.cassette_library_dir.should == "fixtures/tcr_cassettes"
30
+ end
31
31
 
32
- it "has an empty list of hook ports by default" do
33
- TCR.configuration.hook_tcp_ports.should == []
34
- end
32
+ it "has an empty list of hook ports by default" do
33
+ TCR.configuration.hook_tcp_ports.should == []
34
+ end
35
35
 
36
- it "defaults to erroring on read/write mismatch access" do
37
- TCR.configuration.block_for_reads.should be_falsey
38
- end
36
+ it "defaults to erroring on read/write mismatch access" do
37
+ TCR.configuration.block_for_reads.should be_falsey
38
+ end
39
39
 
40
- it "defaults to hit all to false" do
41
- TCR.configuration.hit_all.should be_falsey
42
- end
43
- end
40
+ it "defaults to hit all to false" do
41
+ TCR.configuration.hit_all.should be_falsey
42
+ end
43
+ end
44
44
 
45
45
  describe ".configure" do
46
- it "configures cassette location" do
47
- expect {
48
- TCR.configure { |c| c.cassette_library_dir = "some/dir" }
49
- }.to change{ TCR.configuration.cassette_library_dir }.from("fixtures/tcr_cassettes").to("some/dir")
46
+ context "with cassette_library_dir option" do
47
+ it "configures cassette location" do
48
+ expect {
49
+ TCR.configure { |c| c.cassette_library_dir = "some/dir" }
50
+ }.to change{ TCR.configuration.cassette_library_dir }.from("fixtures/tcr_cassettes").to("some/dir")
51
+ end
50
52
  end
51
53
 
52
- it "configures tcp ports to hook" do
53
- expect {
54
- TCR.configure { |c| c.hook_tcp_ports = [2525] }
55
- }.to change{ TCR.configuration.hook_tcp_ports }.from([]).to([2525])
54
+ context "with hook_tcp_ports option" do
55
+ it "configures tcp ports to hook" do
56
+ expect {
57
+ TCR.configure { |c| c.hook_tcp_ports = [2525] }
58
+ }.to change{ TCR.configuration.hook_tcp_ports }.from([]).to([2525])
59
+ end
56
60
  end
57
61
 
58
- it "configures allowing a blocking read mode" do
59
- expect {
62
+ context "with block_for_reads option" do
63
+ before(:each) {
64
+ TCR.configure { |c|
65
+ c.hook_tcp_ports = [9999]
66
+ c.cassette_library_dir = '.'
67
+ }
68
+ }
69
+
70
+ it "configures allowing a blocking read mode" do
71
+ expect {
72
+ TCR.configure { |c| c.block_for_reads = true }
73
+ }.to change{ TCR.configuration.block_for_reads }.from(false).to(true)
74
+ end
75
+
76
+ it "blocks read thread until data is available instead of raising mismatch error" do
60
77
  TCR.configure { |c| c.block_for_reads = true }
61
- }.to change{ TCR.configuration.block_for_reads }.from(false).to(true)
62
- end
78
+ reads = Queue.new
63
79
 
64
- it "configures to check if all sesstions was hit" do
65
- expect {
66
- TCR.configure { |c| c.hit_all = true }
67
- }.to change{ TCR.configuration.hit_all }.from(false).to(true)
80
+ TCR.use_cassette("spec/fixtures/block_for_reads") do
81
+ sock = TCPSocket.open("google.com", 9999)
82
+
83
+ t = Thread.new do
84
+ reads << sock.gets
85
+ end
86
+
87
+ expect(reads.size).to eq(0)
88
+ sock.print("hello\n")
89
+ t.value
90
+ expect(reads.size).to eq(1)
91
+ end
92
+ end
93
+
94
+ context "when disabled" do
95
+ it "raises mismatch error" do
96
+ TCR.use_cassette("spec/fixtures/block_for_reads") do
97
+ sock = TCPSocket.open("google.com", 9999)
98
+ expect {
99
+ Timeout::timeout(1) { sock.gets }
100
+ }.to raise_error(TCR::DirectionMismatchError)
101
+ end
102
+ end
103
+ end
68
104
  end
69
- end
70
105
 
71
- it "raises an error if you connect to a hooked port without using a cassette" do
72
- TCR.configure { |c| c.hook_tcp_ports = [2525] }
73
- expect {
74
- tcp_socket = TCPSocket.open("smtp.mandrillapp.com", 2525)
75
- }.to raise_error(TCR::NoCassetteError)
106
+ context "with hit_all option" do
107
+ it "configures to check if all sessions were hit" do
108
+ expect {
109
+ TCR.configure { |c| c.hit_all = true }
110
+ }.to change{ TCR.configuration.hit_all }.from(false).to(true)
111
+ end
112
+ end
76
113
  end
77
114
 
78
115
  describe ".turned_off" do
@@ -98,44 +135,6 @@ RSpec.describe TCR do
98
135
  end
99
136
  end
100
137
 
101
- describe "block_for_reads" do
102
- before(:each) {
103
- TCR.configure { |c|
104
- c.hook_tcp_ports = [9999]
105
- c.cassette_library_dir = '.'
106
- }
107
- }
108
-
109
- it "blocks read thread until data is available instead of raising mismatch error" do
110
- TCR.configure { |c| c.block_for_reads = true }
111
- reads = Queue.new
112
-
113
- TCR.use_cassette("spec/fixtures/block_for_reads") do
114
- sock = TCPSocket.open("google.com", 9999)
115
-
116
- t = Thread.new do
117
- reads << sock.gets
118
- end
119
-
120
- expect(reads.size).to eq(0)
121
- sock.print("hello\n")
122
- t.value
123
- expect(reads.size).to eq(1)
124
- end
125
- end
126
-
127
- context "when disabled" do
128
- it "raises mismatch error" do
129
- TCR.use_cassette("spec/fixtures/block_for_reads") do
130
- sock = TCPSocket.open("google.com", 9999)
131
- expect {
132
- Timeout::timeout(1) { sock.gets }
133
- }.to raise_error(TCR::DirectionMismatchError)
134
- end
135
- end
136
- end
137
- end
138
-
139
138
  describe ".use_cassette" do
140
139
  before(:each) {
141
140
  TCR.configure { |c|
@@ -144,6 +143,13 @@ RSpec.describe TCR do
144
143
  }
145
144
  }
146
145
 
146
+ it "MUST be used when connecting to hooked ports (or else raises an error)" do
147
+ TCR.configure { |c| c.hook_tcp_ports = [2525] }
148
+ expect {
149
+ tcp_socket = TCPSocket.open("smtp.mandrillapp.com", 2525)
150
+ }.to raise_error(TCR::NoCassetteError)
151
+ end
152
+
147
153
  it "requires a block to call" do
148
154
  expect {
149
155
  TCR.use_cassette("test")
@@ -416,9 +422,9 @@ RSpec.describe TCR do
416
422
  it "raises an error if you try to playback more sessions than you previously recorded" do
417
423
  expect {
418
424
  TCR.use_cassette("spec/fixtures/multitest-smtp") do
419
- smtp = Net::SMTP.start("smtp.mandrillapp.com", 2525)
420
- smtp = Net::SMTP.start("mail.smtp2go.com", 2525)
421
- smtp = Net::SMTP.start("mail.smtp2go.com", 2525)
425
+ smtp = Net::SMTP.start("smtp.mandrillapp.com", 2525, starttls: false)
426
+ smtp = Net::SMTP.start("mail.smtp2go.com", 2525, starttls: false)
427
+ smtp = Net::SMTP.start("mail.smtp2go.com", 2525, starttls: false)
422
428
  end
423
429
  }.to raise_error(TCR::NoMoreSessionsError)
424
430
  end
@@ -426,21 +432,47 @@ RSpec.describe TCR do
426
432
  it "raises an error if you try to playback less sessions than you previously recorded" do
427
433
  expect {
428
434
  TCR.use_cassette("spec/fixtures/multitest-extra-smtp", hit_all: true) do
429
- smtp = Net::SMTP.start("smtp.mandrillapp.com", 2525)
435
+ smtp = Net::SMTP.start("smtp.mandrillapp.com", 2525, starttls: false)
430
436
  end
431
437
  }.to raise_error(TCR::ExtraSessionsError)
432
438
  end
433
439
  end
434
440
  end
435
441
 
442
+ context "when TCPSocket.open raises an error during recording" do
443
+ before do
444
+ TCR.configure { |c|
445
+ c.format = "yaml" # JSON borks on binary strings
446
+ c.hook_tcp_ports = [143]
447
+ c.cassette_library_dir = "."
448
+ }
449
+
450
+ # record cassette
451
+ TCR.use_cassette("test") do
452
+ expect { Net::IMAP.new(nil) }.to raise_error(SystemCallError)
453
+ end
454
+ end
455
+
456
+ it "records error to cassette" do
457
+ expect(File.exist?('test.yaml')).to be(true)
458
+ expect(File.read('test.yaml')).not_to be_empty
459
+ end
460
+
461
+ it "re-raises the error during replay" do
462
+ TCR.use_cassette("test") do
463
+ expect { Net::IMAP.new(nil) }.to raise_error(SystemCallError)
464
+ end
465
+ end
466
+ end
467
+
436
468
  it "replaces sockets created with Socket.tcp" do
437
469
  TCR.configure { |c|
438
- c.hook_tcp_ports = [23]
470
+ c.hook_tcp_ports = [80]
439
471
  c.cassette_library_dir = "."
440
472
  }
441
473
 
442
474
  TCR.use_cassette("test") do
443
- sock = Socket.tcp("towel.blinkenlights.nl", 23, {})
475
+ sock = Socket.tcp("google.com", 80)
444
476
  expect(sock).to be_a(TCR::RecordableTCPSocket)
445
477
  end
446
478
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tcr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Forman
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-09-20 00:00:00.000000000 Z
11
+ date: 2023-02-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -87,9 +87,9 @@ executables: []
87
87
  extensions: []
88
88
  extra_rdoc_files: []
89
89
  files:
90
+ - ".github/workflows/tcr.yml"
90
91
  - ".gitignore"
91
92
  - ".rvmrc"
92
- - ".travis.yml"
93
93
  - Gemfile
94
94
  - LICENSE.txt
95
95
  - README.md
@@ -118,7 +118,7 @@ files:
118
118
  homepage: ''
119
119
  licenses: []
120
120
  metadata: {}
121
- post_install_message:
121
+ post_install_message:
122
122
  rdoc_options: []
123
123
  require_paths:
124
124
  - lib
@@ -133,9 +133,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
133
133
  - !ruby/object:Gem::Version
134
134
  version: '0'
135
135
  requirements: []
136
- rubyforge_project:
137
- rubygems_version: 2.4.5
138
- signing_key:
136
+ rubygems_version: 3.4.7
137
+ signing_key:
139
138
  specification_version: 4
140
139
  summary: TCR is a lightweight VCR for TCP sockets.
141
140
  test_files:
data/.travis.yml DELETED
@@ -1,11 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - "2.0.0"
4
- - "2.1.10"
5
- - "2.2.10"
6
- - "2.3.7"
7
- - "2.4.4"
8
- - "2.5.1"
9
- before_install:
10
- - gem install bundler
11
- script: bundle exec rspec