tcr 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: afe506ab1f3f57f65d4b9131d0b90a19f3392e00
4
+ data.tar.gz: a1b9bab2f8f7692c7be4a641c2d10c06743fccd8
5
+ SHA512:
6
+ metadata.gz: e6107c9636806831a366f43607651f6613e557026fb9a520cc630d20f327abfc935ded60fa0d410492b018b123a2d83e48843b1d004b6ab82e962b9f9f84725c
7
+ data.tar.gz: ccdc0808bed751308838b9f992ad00fecaab8917bef9cc3bc29e2eabeed9b49b759d7fa33f4c0b9bd4be31b5c299f3c304110b883825c184b4ec138e6785c4ec
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ - "2.0.0"
5
+ - "2.1.2"
6
+ script: bundle exec rspec
data/README.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # TCR (TCP + VCR)
2
2
 
3
+ [![Build Status](https://travis-ci.org/robforman/tcr.png?branch=master)](https://travis-ci.org/robforman/tcr)
4
+
5
+ [ ![Codeship Status for SalesLoft/melody](https://www.codeship.io/projects/9fcbda40-6859-0132-9920-3ad5c353d440/status?branch=master)](https://www.codeship.io/projects/53337)
6
+
7
+
8
+
9
+
3
10
  TCR is a *very* lightweight version of [VCR](https://github.com/vcr/vcr) for TCP sockets.
4
11
 
5
12
  Currently used for recording 'net/smtp' interactions so only a few of the TCPSocket methods are recorded out.
@@ -55,6 +62,14 @@ Run this test once, and TCR will record the tcp interactions to fixtures/tcr_cas
55
62
 
56
63
  Run it again, and TCR will replay the interactions from json when the tcp request is made. This test is now fast (no real TCP requests are made anymore), deterministic and accurate.
57
64
 
65
+ You can disable TCR hooking TCPSocket ports for a given block via `turned_off`:
66
+
67
+ ```ruby
68
+ TCR.turned_off do
69
+ tcp_socket = TCPSocket.open("aspmx.l.google.com", 25)
70
+ end
71
+ ```
72
+
58
73
  ## Contributing
59
74
 
60
75
  1. Fork it
data/lib/tcr.rb CHANGED
@@ -41,6 +41,7 @@ module TCR
41
41
  raise ArgumentError, "`TCR.use_cassette` requires a block." unless block
42
42
  TCR.cassette = Cassette.new(name)
43
43
  yield
44
+ TCR.cassette.save
44
45
  TCR.cassette = nil
45
46
  end
46
47
 
@@ -59,7 +60,7 @@ class TCPSocket
59
60
  class << self
60
61
  alias_method :real_open, :open
61
62
 
62
- def open(address, port)
63
+ def open(address, port, *args)
63
64
  if TCR.configuration.hook_tcp_ports.include?(port)
64
65
  TCR::RecordableTCPSocket.new(address, port, TCR.cassette)
65
66
  else
@@ -68,3 +69,15 @@ class TCPSocket
68
69
  end
69
70
  end
70
71
  end
72
+
73
+ class OpenSSL::SSL::SSLSocket
74
+ class << self
75
+ def new(io, *args)
76
+ if TCR::RecordableTCPSocket === io
77
+ TCR::RecordableSSLSocket.new(io)
78
+ else
79
+ super
80
+ end
81
+ end
82
+ end
83
+ end
@@ -20,15 +20,19 @@ module TCR
20
20
  end
21
21
 
22
22
  def next_session
23
- session = @sessions.shift
24
- raise NoMoreSessionsError unless session
25
- session
23
+ if recording?
24
+ @sessions << []
25
+ @sessions.last
26
+ else
27
+ raise NoMoreSessionsError if @sessions.empty?
28
+ @sessions.shift
29
+ end
26
30
  end
27
31
 
28
- def append(session)
29
- raise "Can't append session unless recording" unless recording?
30
- @sessions << session
31
- File.open(filename, "w") { |f| f.write(JSON.pretty_generate(@sessions)) }
32
+ def save
33
+ if recording?
34
+ File.open(filename, "w") { |f| f.write(JSON.pretty_generate(@sessions)) }
35
+ end
32
36
  end
33
37
 
34
38
  protected
@@ -37,4 +41,4 @@ module TCR
37
41
  "#{TCR.configuration.cassette_library_dir}/#{name}.json"
38
42
  end
39
43
  end
40
- end
44
+ end
@@ -1,6 +1,6 @@
1
1
  module TCR
2
2
  class Configuration
3
- attr_accessor :cassette_library_dir, :hook_tcp_ports
3
+ attr_accessor :cassette_library_dir, :hook_tcp_ports, :block_for_reads
4
4
 
5
5
  def initialize
6
6
  reset_defaults!
@@ -9,6 +9,7 @@ module TCR
9
9
  def reset_defaults!
10
10
  @cassette_library_dir = "fixtures/tcr_cassettes"
11
11
  @hook_tcp_ports = []
12
+ @block_for_reads = false
12
13
  end
13
14
  end
14
- end
15
+ end
@@ -1,45 +1,46 @@
1
+ require 'delegate'
2
+ require 'openssl'
3
+ require 'thread'
4
+
5
+
1
6
  module TCR
2
7
  class RecordableTCPSocket
3
- attr_reader :live, :cassette
8
+ attr_reader :live, :socket
4
9
  attr_accessor :recording
5
10
 
6
11
  def initialize(address, port, cassette)
7
12
  raise TCR::NoCassetteError.new unless TCR.cassette
8
13
 
14
+ @read_lock = Queue.new
15
+
9
16
  if cassette.recording?
10
17
  @live = true
11
18
  @socket = TCPSocket.real_open(address, port)
12
- @recording = []
13
19
  else
14
20
  @live = false
15
- @recording = cassette.next_session
16
21
  end
17
- @cassette = cassette
22
+ @recording = cassette.next_session
23
+ end
24
+
25
+ def read(bytes)
26
+ _read(:read, bytes)
27
+ end
28
+
29
+ def gets(*args)
30
+ _read(:gets, *args)
18
31
  end
19
32
 
20
33
  def read_nonblock(bytes)
21
- if live
22
- data = @socket.read_nonblock(bytes)
23
- recording << ["read", data]
24
- else
25
- direction, data = recording.shift
26
- raise TCR::DirectionMismatchError.new("Expected to 'read' but next in recording was 'write'") unless direction == "read"
27
- end
34
+ _read(:read_nonblock, bytes, blocking: false)
35
+ end
28
36
 
29
- data
37
+ def print(str)
38
+ _write(:print, str)
30
39
  end
31
40
 
32
41
  def write(str)
33
- if live
34
- len = @socket.write(str)
35
- recording << ["write", str]
36
- else
37
- direction, data = recording.shift
38
- raise TCR::DirectionMismatchError.new("Expected to 'write' but next in recording was 'read'") unless direction == "write"
39
- len = data.length
40
- end
41
-
42
- len
42
+ _write(:write, str)
43
+ str.length
43
44
  end
44
45
 
45
46
  def to_io
@@ -59,8 +60,94 @@ module TCR
59
60
  def close
60
61
  if live
61
62
  @socket.close
62
- cassette.append(recording)
63
63
  end
64
64
  end
65
+
66
+ private
67
+
68
+ def _intercept_socket
69
+ if @socket
70
+ @socket = yield @socket
71
+ end
72
+ end
73
+
74
+ def _block_for_read_data
75
+ while recording.first && recording.first.first != "read"
76
+ @read_lock.pop
77
+ end
78
+ end
79
+
80
+ def _check_for_blocked_reads
81
+ @read_lock << 1
82
+ end
83
+
84
+ def _write(method, data)
85
+ if live
86
+ @socket.__send__(method, data)
87
+ recording << ["write", data]
88
+ else
89
+ direction, data = recording.shift
90
+ _ensure_direction("write", direction)
91
+ _check_for_blocked_reads
92
+ end
93
+ end
94
+
95
+ def _read(method, *args, blocking: true)
96
+ if live
97
+ data = @socket.__send__(method, *args)
98
+ recording << ["read", data]
99
+ else
100
+ _block_for_read_data if blocking && TCR.configuration.block_for_reads
101
+ raise EOFError if recording.empty?
102
+ direction, data = recording.shift
103
+ _ensure_direction("read", direction)
104
+ end
105
+ data
106
+ end
107
+
108
+ def _ensure_direction(desired, actual)
109
+ raise TCR::DirectionMismatchError.new("Expected to '#{desired}' but next in recording was '#{actual}'") unless desired == actual
110
+ end
111
+ end
112
+
113
+ class RecordableSSLSocket < SimpleDelegator
114
+ def initialize(tcr_socket)
115
+ super(tcr_socket)
116
+ tcr_socket.send(:_intercept_socket) do |sock|
117
+ socket = OpenSSL::SSL::SSLSocket.new(sock, OpenSSL::SSL::SSLContext.new)
118
+ socket.sync_close = true
119
+ socket.connect
120
+ socket
121
+ end
122
+ end
123
+
124
+ def sync_close=(arg)
125
+ true
126
+ end
127
+
128
+ def session
129
+ self
130
+ end
131
+
132
+ def session=(args)
133
+ end
134
+
135
+ def io
136
+ self
137
+ end
138
+
139
+ def shutdown
140
+ if live
141
+ socket.io.shutdown
142
+ end
143
+ end
144
+
145
+ def connect
146
+ self
147
+ end
148
+
149
+ def post_connection_check(*args)
150
+ true
151
+ end
65
152
  end
66
153
  end
@@ -1,3 +1,3 @@
1
1
  module TCR
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
@@ -0,0 +1,12 @@
1
+ [
2
+ [
3
+ [
4
+ "write",
5
+ "hello\n"
6
+ ],
7
+ [
8
+ "read",
9
+ "world!\n"
10
+ ]
11
+ ]
12
+ ]
@@ -0,0 +1,48 @@
1
+ [
2
+ [
3
+ [
4
+ "write",
5
+ "GET / HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.google.com\r\n\r\n"
6
+ ],
7
+ [
8
+ "read",
9
+ "HTTP/1.1 200 OK\r\nDate: Thu, 15 May 2014 15:04:03 GMT\r\nExpires: -1\r\nCache-Control: private, max-age=0\r\nContent-Type: text/html; charset=ISO-8859-1\r\nSet-Cookie: PREF=ID=46c9c47b277ecf04:FF=0:TM=1400166243:LM=1400166243:S=godo17lIPQRSk5e6; expires=Sat, 14-May-2016 15:04:03 GMT; path=/; domain=.google.com\r\nSet-Cookie: NID=67=hkRuWOEJ-mwgTBbt5NpMcYlD7G38o1tTA_mF_l2CvgY9BPVHzf3wom6KaSnvy5Ln6EqfhpD5t0KSia4b-yBj0AUFJbjfZpf_6487tcmyQMw9PtdewdXO1ZtMDD7McNeB; expires=Fri, 14-Nov-2014 15:04:03 GMT; path=/; domain=.google.com; HttpOnly\r\nP3P: CP=\"This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info.\"\r\nServer: gws\r\nX-XSS-Protection: 1; mode=block\r\nX-Frame-Options: SAMEORIGIN\r\nAlternate-Protocol: 443:quic\r\nConnection: close\r\n\r\n<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/WebPage\" lang=\"en\"><head><meta content=\"Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for.\" name=\"description\"><meta content=\"noodp\" name=\"robots\"><meta content=\"/images/google_favicon_128.png\" itemprop=\"image\"><title>Google</title><script>(function(){\nwindow.google={kEI:\"Y9d0U_rDLo-jqAaU0oDoDA\",getEI:function(a){for(var b;a&&(!a.getAttribute||!(b=a.getAttribute(\"eid\")));)a=a.pare"
10
+ ],
11
+ [
12
+ "read",
13
+ "ntNode;return b||google.kEI},https:function(){return\"https:\"==window.location.protocol},kEXPI:\"17259,4000116,4007661,4007830,4008142,4009033,4009641,4010806,4010858,4010899,4011228,4011258,4011679,4011960,4012373,4012504,4013414,4013591,4013723,4013758,4013787,4013823,4013941,4013967,4013979,4014016,4014093,4014237,4014431,4014515,4014636,4014671,4014789,4014810,4014813,4014991,4015155,4015234,4015260,4015299,4015301,4015519,4015550,4015587,4015638,4015639,4015772,4016013,4016127,4016309,4016363,4016367,4016373,4016446,4016487,4016703,4016730,4016800,4016824,4016855,4016933,4016976,4017261,4017278,4017280,4017285,4017596,4017612,4017639,4017666,4017681,4017694,4017710,4017712,4017742,4017788,4017801,4017818,4017856,4017862,4017913,4017922,4017960,4017962,4018019,8300012,8300015,8500223,8500240,8500252,8500264,8500272,8500306,8500314,8500332,8500365,8500378,8500389,8500394,10200002,10200012,10200014,10200029,10200038,10200040,10200048,10200053,10200066,10200084,10200120,10200134,10200136,10200155,10200157,10200159,10200164,10200176,10200184,10200211,10200221,10200226,10200242,10200246,10200250,10200254,10200261,10200263,10200271,10200293,10200295,10200299\",kCSI:{e:\"17259,4000116,4007661,4007830,4008142,4009033,4009641,4010806,4010858,4010899,4011228,4011258,4011679,4011960,4012373,4012504,4013414,4013591,4013723,401375"
14
+ ],
15
+ [
16
+ "read",
17
+ "8,4013787,4013823,4013941,4013967,4013979,4014016,4014093,4014237,4014431,4014515,4014636,4014671,4014789,4014810,4014813,4014991,4015155,4015234,4015260,4015299,4015301,4015519,4015550,4015587,4015638,4015639,4015772,4016013,4016127,4016309,4016363,4016367,4016373,4016446,4016487,4016703,4016730,4016800,4016824,4016855,4016933,4016976,4017261,4017278,4017280,4017285,4017596,4017612,4017639,4017666,4017681,4017694,4017710,4017712,4017742,4017788,4017801,4017818,4017856,4017862,4017913,4017922,4017960,4017962,4018019,8300012,8300015,8500223,8500240,8500252,8500264,8500272,8500306,8500314,8500332,8500365,8500378,8500389,8500394,10200002,10200012,10200014,10200029,10200038,10200040,10200048,10200053,10200066,10200084,10200120,10200134,10200136,10200155,10200157,10200159,10200164,10200176,10200184,10200211,10200221,10200226,10200242,10200246,10200250,10200254,10200261,10200263,10200271,10200293,10200295,10200299\",ei:\"Y9d0U_rDLo-jqAaU0oDoDA\"},authuser:0,ml:function(){},kHL:\"en\",time:function(){return(new Date).getTime()},log:function(a,b,c,h,k){var d=\nnew Image,f=google.lc,e=google.li,g=\"\";d.onerror=d.onload=d.onabort=function(){delete f[e]};f[e]=d;c||-1!=b.search(\"&ei=\")||(g=\"&ei=\"+google.getEI(h));c=c||\"/\"+(k||\"gen_204\")+\"?atyp=i&ct=\"+a+\"&cad=\"+b+g+\"&zx=\"+google.time();a=/^http:/i;a.test(c)&&google.https()?(google.ml(Err"
18
+ ],
19
+ [
20
+ "read",
21
+ "or(\"GLMM\"),!1,{src:c}),delete f[e]):(d.src=c,google.li=e+1)},lc:[],li:0,y:{},x:function(a,b){google.y[a.id]=[a,b];return!1},load:function(a,b,c){google.x({id:a+l++},function(){google.load(a,b,c)})}};var l=0;})();\n(function(){google.sn=\"webhp\";google.timers={};google.startTick=function(a,b){var f=google.time();google.timers[a]={t:{start:f},bfr:!!b};};google.tick=function(a,b,f){google.timers[a]||google.startTick(a);google.timers[a].t[b]=f||google.time()};google.startTick(\"load\",!0);\ntry{}catch(d){}})();\nvar _gjwl=location;function _gjuc(){var a=_gjwl.href.indexOf(\"#\");if(0<=a&&(a=_gjwl.href.substring(a),0<a.indexOf(\"&q=\")||0<=a.indexOf(\"#q=\"))&&(a=a.substring(1),-1==a.indexOf(\"#\"))){for(var d=0;d<a.length;){var b=d;\"&\"==a.charAt(b)&&++b;var c=a.indexOf(\"&\",b);-1==c&&(c=a.length);b=a.substring(b,c);if(0==b.indexOf(\"fp=\"))a=a.substring(0,d)+a.substring(c,a.length),c=d;else if(\"cad=h\"==b)return 0;d=c}_gjwl.href=\"/search?\"+a+\"&cad=h\";return 1}return 0}\nfunction _gjh(){!_gjuc()&&window.google&&google.x&&google.x({id:\"GJH\"},function(){google.nav&&google.nav.gjh&&google.nav.gjh()})};\nwindow._gjh&&_gjh();</script><style>#gbar,#guser{font-size:13px;padding-top:1px !important;}#gbar{height:22px}#guser{padding-bottom:7px !important;text-align:right}.gbh,.gbd{border-top:1px solid #c9d7f1;font-size:1px}.gbh{height:0;position:absolu"
22
+ ],
23
+ [
24
+ "read",
25
+ "te;top:24px;width:100%}@media all{.gb1{height:22px;margin-right:.5em;vertical-align:top}#gbar{float:left}}a.gb1,a.gb4{text-decoration:underline !important}a.gb1,a.gb4{color:#00c !important}.gbi .gb4{color:#dd8e27 !important}.gbf .gb4{color:#900 !important}</style><style>body,td,a,p,.h{font-family:arial,sans-serif}body{margin:0;overflow-y:scroll}#gog{padding:3px 8px 0}td{line-height:.8em}.gac_m td{line-height:17px}form{margin-bottom:20px}.h{color:#36c}.q{color:#00c}.ts td{padding:0}.ts{border-collapse:collapse}em{font-weight:bold;font-style:normal}.lst{height:25px;width:496px}.gsfi,.lst{font:18px arial,sans-serif}.gsfs{font:17px arial,sans-serif}.ds{display:inline-box;display:inline-block;margin:3px 0 4px;margin-left:4px}input{font-family:inherit}a.gb1,a.gb2,a.gb3,a.gb4{color:#11c !important}body{background:#fff;color:black}a{color:#11c;text-decoration:none}a:hover,a:active{text-decoration:underline}.fl a{color:#36c}a:visited{color:#551a8b}a.gb1,a.gb4{text-decoration:underline}a.gb3:hover{text-decoration:none}#ghead a.gb2:hover{color:#fff !important}.sblc{padding-top:5px}.sblc a{display:block;margin:2px 0;margin-left:13px;font-size:11px}.lsbb{background:#eee;border:solid 1px;border-color:#ccc #999 #999 #ccc;height:30px}.lsbb{display:block}.ftl,#fll a{display:inline-block;margin:0 12px}.lsb{background:url(/images/srpr/n"
26
+ ],
27
+ [
28
+ "read",
29
+ "av_logo80.png) 0 -258px repeat-x;border:none;color:#000;cursor:pointer;height:30px;margin:0;outline:0;font:15px arial,sans-serif;vertical-align:top}.lsb:active{background:#ccc}.lst:focus{outline:none}#addlang a{padding:0 3px}</style><script></script></head><body bgcolor=\"#fff\"><script>(function(){var src='/images/nav_logo176.png';var iesg=false;document.body.onload = function(){window.n && window.n();if (document.images){new Image().src=src;}\nif (!iesg){document.f&&document.f.q.focus();document.gbqf&&document.gbqf.q.focus();}\n}\n})();</script><textarea id=\"csi\" style=\"display:none\"></textarea><div id=\"mngb\"> <div id=gbar><nobr><b class=gb1>Search</b> <a class=gb1 href=\"https://www.google.com/imghp?hl=en&tab=wi\">Images</a> <a class=gb1 href=\"https://maps.google.com/maps?hl=en&tab=wl\">Maps</a> <a class=gb1 href=\"https://play.google.com/?hl=en&tab=w8\">Play</a> <a class=gb1 href=\"https://www.youtube.com/?tab=w1\">YouTube</a> <a class=gb1 href=\"https://news.google.com/nwshp?hl=en&tab=wn\">News</a> <a class=gb1 href=\"https://mail.google.com/mail/?tab=wm\">Gmail</a> <a class=gb1 href=\"https://drive.google.com/?tab=wo\">Drive</a> <a class=gb1 style=\"text-decoration:none\" href=\"http://www.google.com/intl/en/options/\"><u>More</u> &raquo;</a></nobr></div><div id=guser width=100%><nobr><span id=gbn class=gbi></span><span id=gbf class="
30
+ ],
31
+ [
32
+ "read",
33
+ "gbf></span><span id=gbe></span><a href=\"http://www.google.com/history/optout?hl=en\" class=gb4>Web History</a> | <a href=\"/preferences?hl=en\" class=gb4>Settings</a> | <a target=_top id=gb_70 href=\"https://accounts.google.com/ServiceLogin?hl=en&continue=https://www.google.com/\" class=gb4>Sign in</a></nobr></div><div class=gbh style=left:0></div><div class=gbh style=right:0></div> </div><center><br clear=\"all\" id=\"lgpd\"><div id=\"lga\"><img alt=\"Google\" height=\"95\" src=\"/images/srpr/logo9w.png\" style=\"padding:28px 0 14px\" width=\"269\" id=\"hplogo\" onload=\"window.lol&&lol()\"><br><br></div><form action=\"/search\" name=\"f\"><table cellpadding=\"0\" cellspacing=\"0\"><tr valign=\"top\"><td width=\"25%\">&nbsp;</td><td align=\"center\" nowrap=\"\"><input name=\"ie\" value=\"ISO-8859-1\" type=\"hidden\"><input value=\"en\" name=\"hl\" type=\"hidden\"><input name=\"source\" type=\"hidden\" value=\"hp\"><div class=\"ds\" style=\"height:32px;margin:4px 0\"><input style=\"color:#000;margin:0;padding:5px 8px 0 6px;vertical-align:top\" autocomplete=\"off\" class=\"lst\" value=\"\" title=\"Google Search\" maxlength=\"2048\" name=\"q\" size=\"57\"></div><br style=\"line-height:0\"><span class=\"ds\"><span class=\"lsbb\"><input class=\"lsb\" value=\"Google Search\" name=\"btnG\" type=\"submit\"></span></span><span class=\"ds\"><span class=\"lsbb\"><input class=\"lsb\" value=\"I'm Feeling Lucky\" name=\"btnI\" on"
34
+ ],
35
+ [
36
+ "read",
37
+ "click=\"if(this.form.q.value)this.checked=1; else top.location='/doodles/'\" type=\"submit\"></span></span></td><td class=\"fl sblc\" align=\"left\" nowrap=\"\" width=\"25%\"><a href=\"/advanced_search?hl=en&amp;authuser=0\">Advanced search</a><a href=\"/language_tools?hl=en&amp;authuser=0\">Language tools</a></td></tr></table><input id=\"gbv\" name=\"gbv\" type=\"hidden\" value=\"1\"></form><div id=\"gac_scont\"></div><div style=\"font-size:83%;min-height:3.5em\"><br></div><span id=\"footer\"><div style=\"font-size:10pt\"><div style=\"margin:19px auto;text-align:center\" id=\"fll\"><a href=\"/intl/en/ads/\">Advertising&nbsp;Programs</a><a href=\"/services/\">Business Solutions</a><a href=\"https://plus.google.com/116899029375914044550\" rel=\"publisher\">+Google</a><a href=\"/intl/en/about.html\">About Google</a></div></div><p style=\"color:#767676;font-size:8pt\">&copy; 2013 - <a href=\"/intl/en/policies/\">Privacy & Terms</a></p></span></center><div id=xjsd></div><div id=xjsi data-jiis=\"bp\"><script>if(google.y)google.y.first=[];(function(){function b(a){window.setTimeout(function(){var c=document.createElement(\"script\");c.src=a;document.getElementById(\"xjsd\").appendChild(c)},0)}google.dljp=function(a){google.xjsu=a;b(a)};google.dlj=b;})();\nif(!google.xjs){window._=window._||{};window._._DumpException=function(e){throw e};if(google.timers&&google.timers.load.t){goo"
38
+ ],
39
+ [
40
+ "read",
41
+ "gle.timers.load.t.xjsls=new Date().getTime();}google.dljp('/xjs/_/js/k\\x3dxjs.hp.en_US.Gh3uPFri1JA.O/m\\x3dsb_he,pcc/rt\\x3dj/d\\x3d1/sv\\x3d1/rs\\x3dAItRSTNl_2-4gdX7sEzEk9xHPbjumUrcCA');google.xjs=1;}google.pmc={\"sb_he\":{\"agen\":true,\"cgen\":true,\"client\":\"heirloom-hp\",\"dh\":true,\"ds\":\"\",\"eqch\":true,\"fl\":true,\"host\":\"google.com\",\"jam\":0,\"jsonp\":true,\"msgs\":{\"cibl\":\"Clear Search\",\"dym\":\"Did you mean:\",\"lcky\":\"I\\u0026#39;m Feeling Lucky\",\"lml\":\"Learn more\",\"oskt\":\"Input tools\",\"psrc\":\"This search was removed from your \\u003Ca href=\\\"/history\\\"\\u003EWeb History\\u003C/a\\u003E\",\"psrl\":\"Remove\",\"sbit\":\"Search by image\",\"srch\":\"Google Search\"},\"ovr\":{},\"pq\":\"\",\"qcpw\":false,\"scd\":10,\"sce\":5,\"stok\":\"PoibB8u7e-sS13448HMOoIqGSu4\"},\"pcc\":{}};google.y.first.push(function(){if(google.med){google.med('init');google.initHistory();google.med('history');}});if(google.j&&google.j.en&&google.j.xi){window.setTimeout(google.j.xi,0);}</script></div><script>(function(){if(google.timers&&google.timers.load.t){var b,c,d,e,g=function(a,f){a.removeEventListener?(a.removeEventListener(\"load\",f,!1),a.removeEventListener(\"error\",f,!1)):(a.detachEvent(\"onload\",f),a.detachEvent(\"onerror\",f))},h=function(a){e=(new Date).getTime();++c;a=a||window.event;a=a.target||a.srcElement;g(a,h)},k=document.getElementsByTagName(\"img\");b=k.length;for(var l=c=0,m;l<b;++l)m"
42
+ ],
43
+ [
44
+ "read",
45
+ "=k[l],m.complete||\"string\"!=typeof m.src||!m.src?++c:m.addEventListener?(m.addEventListener(\"load\",h,!1),m.addEventListener(\"error\",\nh,!1)):(m.attachEvent(\"onload\",h),m.attachEvent(\"onerror\",h));d=b-c;var n=function(){if(google.timers.load.t){google.timers.load.t.ol=(new Date).getTime();google.timers.load.t.iml=e;google.kCSI.imc=c;google.kCSI.imn=b;google.kCSI.imp=d;void 0!==google.stt&&(google.kCSI.stt=google.stt);google.csiReport&&google.csiReport()}};window.addEventListener?window.addEventListener(\"load\",n,!1):window.attachEvent&&\nwindow.attachEvent(\"onload\",n);google.timers.load.t.prt=e=(new Date).getTime()};})();\n</script></body></html>"
46
+ ]
47
+ ]
48
+ ]
@@ -0,0 +1,92 @@
1
+ [
2
+ [
3
+ [
4
+ "read",
5
+ "* OK Gimap ready for requests from 72.16.218.22 n58mb218005052yhi\r\n"
6
+ ],
7
+ [
8
+ "write",
9
+ "RUBY0001 LOGIN"
10
+ ],
11
+ [
12
+ "write",
13
+ " "
14
+ ],
15
+ [
16
+ "write",
17
+ "ben.olive@example.net"
18
+ ],
19
+ [
20
+ "write",
21
+ " "
22
+ ],
23
+ [
24
+ "write",
25
+ "password"
26
+ ],
27
+ [
28
+ "write",
29
+ "\r\n"
30
+ ],
31
+ [
32
+ "read",
33
+ "* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA ID XLIST CHILDREN X-GM-EXT-1 UIDPLUS COMPRESS=DEFLATE ENABLE MOVE CONDSTORE ESEARCH\r\n"
34
+ ],
35
+ [
36
+ "read",
37
+ "RUBY0001 OK ben.olive@example.net Ben Olive authenticated (Success)\r\n"
38
+ ],
39
+ [
40
+ "write",
41
+ "RUBY0002 EXAMINE"
42
+ ],
43
+ [
44
+ "write",
45
+ " "
46
+ ],
47
+ [
48
+ "write",
49
+ "INBOX"
50
+ ],
51
+ [
52
+ "write",
53
+ "\r\n"
54
+ ],
55
+ [
56
+ "read",
57
+ "* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen $Phishing $NotPhishing)\r\n"
58
+ ],
59
+ [
60
+ "read",
61
+ "* OK [PERMANENTFLAGS ()] Flags permitted.\r\n"
62
+ ],
63
+ [
64
+ "read",
65
+ "* OK [UIDVALIDITY 1] UIDs valid.\r\n"
66
+ ],
67
+ [
68
+ "read",
69
+ "* 206 EXISTS\r\n"
70
+ ],
71
+ [
72
+ "read",
73
+ "* 0 RECENT\r\n"
74
+ ],
75
+ [
76
+ "read",
77
+ "* OK [UIDNEXT 5504] Predicted next UID.\r\n"
78
+ ],
79
+ [
80
+ "read",
81
+ "* OK [HIGHESTMODSEQ 609503]\r\n"
82
+ ],
83
+ [
84
+ "read",
85
+ "RUBY0002 OK [READ-ONLY] INBOX selected. (Success)\r\n"
86
+ ],
87
+ [
88
+ "read",
89
+ null
90
+ ]
91
+ ]
92
+ ]
@@ -0,0 +1,8 @@
1
+ [
2
+ [
3
+ [
4
+ "read",
5
+ "\u001b[H\u001b[J\u001b[H\r\n\r\n\r\n\r\n\r\n\r\n "
6
+ ]
7
+ ]
8
+ ]
@@ -3,6 +3,20 @@
3
3
  # Require this file using `require "spec_helper"` to ensure that it is only
4
4
  # loaded once.
5
5
  #
6
+ module SpawnTestServers
7
+ # Spawn a new server that will send the specified payload when connected to.
8
+ # The listening port number will be returned
9
+ def spawn_server(payload)
10
+ server = TCPServer.new("127.0.0.1", 0)
11
+ fork do
12
+ client = server.accept
13
+ client.write(payload)
14
+ client.close
15
+ end
16
+ server.addr[1]
17
+ end
18
+ end
19
+
6
20
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
21
  RSpec.configure do |config|
8
22
  config.treat_symbols_as_metadata_keys_with_true_values = true
@@ -14,4 +28,6 @@ RSpec.configure do |config|
14
28
  # the seed, which is printed after each run.
15
29
  # --seed 1234
16
30
  config.order = 'random'
31
+
32
+ config.include(SpawnTestServers)
17
33
  end
@@ -1,13 +1,23 @@
1
1
  require "spec_helper"
2
2
  require "tcr"
3
3
  require "net/protocol"
4
+ require "net/http"
5
+ require "net/imap"
4
6
  require "net/smtp"
7
+ require 'thread'
8
+
5
9
 
6
10
  describe TCR do
7
11
  before(:each) do
8
12
  TCR.configuration.reset_defaults!
9
13
  end
10
14
 
15
+ around(:each) do |example|
16
+ File.unlink("test.json") if File.exists?("test.json")
17
+ example.run
18
+ File.unlink("test.json") if File.exists?("test.json")
19
+ end
20
+
11
21
  describe ".configuration" do
12
22
  it "has a default cassette location configured" do
13
23
  TCR.configuration.cassette_library_dir.should == "fixtures/tcr_cassettes"
@@ -16,6 +26,10 @@ describe TCR do
16
26
  it "has an empty list of hook ports by default" do
17
27
  TCR.configuration.hook_tcp_ports.should == []
18
28
  end
29
+
30
+ it "defaults to erroring on read/write mismatch access" do
31
+ TCR.configuration.block_for_reads.should be_false
32
+ end
19
33
  end
20
34
 
21
35
  describe ".configure" do
@@ -30,6 +44,12 @@ describe TCR do
30
44
  TCR.configure { |c| c.hook_tcp_ports = [25] }
31
45
  }.to change{ TCR.configuration.hook_tcp_ports }.from([]).to([25])
32
46
  end
47
+
48
+ it "configures allowing a blocking read mode" do
49
+ expect {
50
+ TCR.configure { |c| c.block_for_reads = true }
51
+ }.to change{ TCR.configuration.block_for_reads }.from(false).to(true)
52
+ end
33
53
  end
34
54
 
35
55
  it "raises an error if you connect to a hooked port without using a cassette" do
@@ -62,16 +82,50 @@ describe TCR do
62
82
  end
63
83
  end
64
84
 
85
+ describe "block_for_reads" do
86
+ before(:each) {
87
+ TCR.configure { |c|
88
+ c.hook_tcp_ports = [9999]
89
+ c.cassette_library_dir = '.'
90
+ }
91
+ }
92
+
93
+ it "blocks read thread until data is available instead of raising mismatch error" do
94
+ TCR.configure { |c| c.block_for_reads = true }
95
+ reads = Queue.new
96
+
97
+ TCR.use_cassette("spec/fixtures/block_for_reads") do
98
+ sock = TCPSocket.open("google.com", 9999)
99
+
100
+ t = Thread.new do
101
+ reads << sock.gets
102
+ end
103
+
104
+ expect(reads.size).to eq(0)
105
+ sock.print("hello\n")
106
+ t.value
107
+ expect(reads.size).to eq(1)
108
+ end
109
+ end
110
+
111
+ context "when disabled" do
112
+ it "raises mismatch error" do
113
+ TCR.use_cassette("spec/fixtures/block_for_reads") do
114
+ sock = TCPSocket.open("google.com", 9999)
115
+ expect {
116
+ Timeout::timeout(1) { sock.gets }
117
+ }.to raise_error(TCR::DirectionMismatchError)
118
+ end
119
+ end
120
+ end
121
+ end
122
+
65
123
  describe ".use_cassette" do
66
124
  before(:each) {
67
125
  TCR.configure { |c|
68
126
  c.hook_tcp_ports = [25]
69
127
  c.cassette_library_dir = "."
70
128
  }
71
- File.unlink("test.json") if File.exists?("test.json")
72
- }
73
- after(:each) {
74
- File.unlink("test.json") if File.exists?("test.json")
75
129
  }
76
130
 
77
131
  it "requires a block to call" do
@@ -90,7 +144,6 @@ describe TCR do
90
144
  expect {
91
145
  TCR.use_cassette("test") do
92
146
  tcp_socket = TCPSocket.open("aspmx.l.google.com", 25)
93
- tcp_socket.close
94
147
  end
95
148
  }.to change{ File.exists?("./test.json") }.from(false).to(true)
96
149
  end
@@ -126,6 +179,42 @@ describe TCR do
126
179
  }.to raise_error(TCR::DirectionMismatchError)
127
180
  end
128
181
 
182
+ it "stubs out Socket#gets" do
183
+ TCR.configure { |c|
184
+ c.hook_tcp_ports = [993]
185
+ c.block_for_reads = true
186
+ }
187
+ expect {
188
+ TCR.use_cassette("spec/fixtures/google_imap") do
189
+ conn = Net::IMAP.new("imap.gmail.com", 993, true)
190
+ conn.login("ben.olive@example.net", "password")
191
+ conn.examine(Net::IMAP.encode_utf7("INBOX"))
192
+ conn.disconnect
193
+ end
194
+ }.not_to raise_error
195
+ end
196
+
197
+ it "stubs out Socket#read" do
198
+ TCR.configure { |c|
199
+ c.hook_tcp_ports = [23]
200
+ }
201
+ TCR.use_cassette("spec/fixtures/starwars_telnet") do
202
+ sock = TCPSocket.open("towel.blinkenlights.nl", 23)
203
+ expect(sock.read(50).length).to eq(50)
204
+ sock.close
205
+ end
206
+ end
207
+
208
+ it "supports ssl sockets" do
209
+ TCR.configure { |c| c.hook_tcp_ports = [443] }
210
+ http = Net::HTTP.new("www.google.com", 443)
211
+ http.use_ssl = true
212
+ expect {
213
+ TCR.use_cassette("spec/fixtures/google_https") do
214
+ http.request(Net::HTTP::Get.new("/"))
215
+ end
216
+ }.not_to raise_error
217
+ end
129
218
 
130
219
  context "multiple connections" do
131
220
  it "records multiple sessions per cassette" do
@@ -152,6 +241,35 @@ describe TCR do
152
241
  end
153
242
  end
154
243
 
244
+ context "when a cassette is recorded with connections opens concurrently" do
245
+ let(:ports) { ["Apple", "Banana"].map { |payload| spawn_server(payload) } }
246
+ before(:each) do
247
+ TCR.configure { |c|
248
+ c.hook_tcp_ports = ports
249
+ c.cassette_library_dir = "."
250
+ }
251
+ end
252
+ before(:each) do
253
+ TCR.use_cassette("test") do
254
+ apple = TCPSocket.open("127.0.0.1", ports.first)
255
+ banana = TCPSocket.open("127.0.0.1", ports.last)
256
+
257
+ banana.gets
258
+ apple.gets
259
+
260
+ banana.close
261
+ apple.close
262
+ end
263
+ end
264
+
265
+ it "replays the sessions in the order they were created" do
266
+ TCR.use_cassette("test") do
267
+ apple = TCPSocket.open("127.0.0.1", ports.first)
268
+ expect(apple.gets).to eq("Apple")
269
+ end
270
+ end
271
+ end
272
+
155
273
  it "raises an error if you try to playback more sessions than you previously recorded" do
156
274
  expect {
157
275
  TCR.use_cassette("spec/fixtures/multitest-smtp") do
@@ -163,4 +281,4 @@ describe TCR do
163
281
  end
164
282
  end
165
283
  end
166
- end
284
+ end
metadata CHANGED
@@ -1,46 +1,41 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tcr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
5
- prerelease:
4
+ version: 0.0.5
6
5
  platform: ruby
7
6
  authors:
8
7
  - Rob Forman
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-09-12 00:00:00.000000000 Z
11
+ date: 2015-01-20 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: rspec
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ! '>='
17
+ - - ">="
20
18
  - !ruby/object:Gem::Version
21
19
  version: '0'
22
20
  type: :development
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ! '>='
24
+ - - ">="
28
25
  - !ruby/object:Gem::Version
29
26
  version: '0'
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: geminabox
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - ! '>='
31
+ - - ">="
36
32
  - !ruby/object:Gem::Version
37
33
  version: '0'
38
34
  type: :development
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - ! '>='
38
+ - - ">="
44
39
  - !ruby/object:Gem::Version
45
40
  version: '0'
46
41
  description: TCR is a lightweight VCR for TCP sockets.
@@ -50,8 +45,9 @@ executables: []
50
45
  extensions: []
51
46
  extra_rdoc_files: []
52
47
  files:
53
- - .gitignore
54
- - .rvmrc
48
+ - ".gitignore"
49
+ - ".rvmrc"
50
+ - ".travis.yml"
55
51
  - Gemfile
56
52
  - LICENSE.txt
57
53
  - README.md
@@ -62,39 +58,46 @@ files:
62
58
  - lib/tcr/errors.rb
63
59
  - lib/tcr/recordable_tcp_socket.rb
64
60
  - lib/tcr/version.rb
61
+ - spec/fixtures/block_for_reads.json
62
+ - spec/fixtures/google_https.json
63
+ - spec/fixtures/google_imap.json
65
64
  - spec/fixtures/google_smtp.json
66
65
  - spec/fixtures/multitest-smtp.json
67
66
  - spec/fixtures/multitest.json
67
+ - spec/fixtures/starwars_telnet.json
68
68
  - spec/spec_helper.rb
69
69
  - spec/tcr_spec.rb
70
70
  - tcr.gemspec
71
71
  homepage: ''
72
72
  licenses: []
73
+ metadata: {}
73
74
  post_install_message:
74
75
  rdoc_options: []
75
76
  require_paths:
76
77
  - lib
77
78
  required_ruby_version: !ruby/object:Gem::Requirement
78
- none: false
79
79
  requirements:
80
- - - ! '>='
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  required_rubygems_version: !ruby/object:Gem::Requirement
84
- none: false
85
84
  requirements:
86
- - - ! '>='
85
+ - - ">="
87
86
  - !ruby/object:Gem::Version
88
87
  version: '0'
89
88
  requirements: []
90
89
  rubyforge_project:
91
- rubygems_version: 1.8.25
90
+ rubygems_version: 2.2.2
92
91
  signing_key:
93
- specification_version: 3
92
+ specification_version: 4
94
93
  summary: TCR is a lightweight VCR for TCP sockets.
95
94
  test_files:
95
+ - spec/fixtures/block_for_reads.json
96
+ - spec/fixtures/google_https.json
97
+ - spec/fixtures/google_imap.json
96
98
  - spec/fixtures/google_smtp.json
97
99
  - spec/fixtures/multitest-smtp.json
98
100
  - spec/fixtures/multitest.json
101
+ - spec/fixtures/starwars_telnet.json
99
102
  - spec/spec_helper.rb
100
103
  - spec/tcr_spec.rb