patron 0.14.0 → 1.0.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/CHANGELOG.md +77 -0
- data/README.md +83 -49
- data/ext/patron/extconf.rb +54 -23
- data/ext/patron/session_ext.c +1044 -923
- data/lib/patron/compat.rb +57 -0
- data/lib/patron/error.rb +21 -13
- data/lib/patron/header_parser.rb +27 -25
- data/lib/patron/request.rb +98 -144
- data/lib/patron/response.rb +28 -24
- data/lib/patron/response_decoding.rb +60 -67
- data/lib/patron/session.rb +114 -108
- data/lib/patron/util.rb +14 -12
- data/lib/patron/version.rb +3 -1
- data/lib/patron.rb +9 -15
- metadata +12 -58
- data/.autotest +0 -15
- data/.github/dependabot.yml +0 -6
- data/.github/workflows/ci.yml +0 -79
- data/.gitignore +0 -10
- data/.rspec +0 -0
- data/.travis.yml +0 -13
- data/.yardopts +0 -1
- data/Gemfile +0 -18
- data/Rakefile +0 -31
- data/ext/patron/.gitignore +0 -4
- data/ext/patron/membuffer.c +0 -85
- data/ext/patron/membuffer.h +0 -78
- data/ext/patron/sglib.h +0 -1952
- data/lib/patron/proxy_type.rb +0 -11
- data/patron.gemspec +0 -39
- data/pic.png +0 -0
- data/script/console +0 -11
- data/script/generate_client_certs +0 -65
- data/script/test_server +0 -7
- data/spec/header_parser_spec.rb +0 -101
- data/spec/patron_spec.rb +0 -28
- data/spec/request_spec.rb +0 -89
- data/spec/response_spec.rb +0 -145
- data/spec/sample_response_headers/headers_wetransfer.txt +0 -18
- data/spec/sample_response_headers/headers_wetransfer_with_proxy_status.txt +0 -20
- data/spec/sample_response_headers/headers_wetransfer_with_redirect.txt +0 -24
- data/spec/sample_response_headers/headers_with_set_cookie.txt +0 -9
- data/spec/sample_response_headers/sample_http2_header.txt +0 -16
- data/spec/sample_response_headers/webmock_headers_without_trailing_crlf.txt +0 -15
- data/spec/session_spec.rb +0 -865
- data/spec/session_ssl_spec.rb +0 -431
- data/spec/spec_helper.rb +0 -40
- data/spec/support/certs/cacert.pem +0 -36
- data/spec/support/certs/client.p12 +0 -0
- data/spec/support/certs/client_ca.pem +0 -20
- data/spec/support/certs/client_cert.pem +0 -20
- data/spec/support/certs/client_key.pem +0 -30
- data/spec/support/certs/privkey.pem +0 -51
- data/spec/support/certs/rogue_client.p12 +0 -0
- data/spec/support/config.ru +0 -153
- data/spec/support/fork.rb +0 -70
- data/spec/support/raw_request_capture.rb +0 -93
- data/spec/support/test_server.rb +0 -29
- data/spec/support/valgrind.supp +0 -2
- data/spec/util_spec.rb +0 -71
data/spec/session_spec.rb
DELETED
|
@@ -1,865 +0,0 @@
|
|
|
1
|
-
require File.expand_path("./spec") + '/spec_helper.rb'
|
|
2
|
-
require 'webrick'
|
|
3
|
-
require 'yaml'
|
|
4
|
-
require 'base64'
|
|
5
|
-
require 'fileutils'
|
|
6
|
-
require 'securerandom'
|
|
7
|
-
require 'timeout'
|
|
8
|
-
|
|
9
|
-
describe Patron::Session do
|
|
10
|
-
|
|
11
|
-
def yaml_load(str)
|
|
12
|
-
if RUBY_VERSION >= '3.1.0'
|
|
13
|
-
YAML::safe_load(str, permitted_classes: [OpenStruct])
|
|
14
|
-
else
|
|
15
|
-
YAML::load(str)
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
before(:each) do
|
|
20
|
-
@session = Patron::Session.new
|
|
21
|
-
@session.base_url = "http://localhost:9001"
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
context 'when trying a non-HTTP(s) URL' do
|
|
25
|
-
forbidden_protos = %w( smb tftp imap smtp telnet dict ftp sftp scp file gopher )
|
|
26
|
-
forbidden_protos.each do |forbidden_proto|
|
|
27
|
-
it "should deny a #{forbidden_proto.upcase} request" do
|
|
28
|
-
expect {
|
|
29
|
-
@session.base_url = '%s://localhost' % forbidden_proto
|
|
30
|
-
@session.get('')
|
|
31
|
-
}.to raise_error(Patron::UnsupportedProtocol)
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
it 'does not follow a redirect to a non-HTTP/HTTPS URL' do
|
|
37
|
-
# The "/evil-redirect" servlet tries to do a redirect to SMTP,
|
|
38
|
-
# which can lead to exploits. By default, libCURL will just follow
|
|
39
|
-
# that redirect.
|
|
40
|
-
expect {
|
|
41
|
-
@session.get('/evil-redirect')
|
|
42
|
-
}.to raise_error(Patron::UnsupportedProtocol)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
it "should work when forcing ipv4" do
|
|
46
|
-
@session.force_ipv4 = true
|
|
47
|
-
expect { @session.get("/test") }.to_not raise_error
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
describe '.escape and #escape' do
|
|
51
|
-
it 'makes escape() and unescape() available on the class' do
|
|
52
|
-
string = "foo~bar baz/"
|
|
53
|
-
escaped = described_class.escape(string)
|
|
54
|
-
unescaped = described_class.unescape(escaped)
|
|
55
|
-
expect(unescaped).to be == string
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
it "should escape and unescape strings symetrically" do
|
|
59
|
-
string = "foo~bar baz/"
|
|
60
|
-
escaped = @session.escape(string)
|
|
61
|
-
unescaped = @session.unescape(escaped)
|
|
62
|
-
expect(unescaped).to be == string
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
it "should make e and unescape strings symetrically" do
|
|
66
|
-
string = "foo~bar baz/"
|
|
67
|
-
escaped = @session.escape(string)
|
|
68
|
-
unescaped = @session.unescape(escaped)
|
|
69
|
-
expect(unescaped).to be == string
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
it "unescapes a %00 to a NUL byte instead of cutting the string there" do
|
|
73
|
-
expect(@session.unescape("a%00b").b).to be == "a\0b".b
|
|
74
|
-
expect(@session.escape("a\0b")).to be == "a%00b"
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
it "rejects a file path with an embedded NUL byte" do
|
|
78
|
-
expect { @session.enable_debug("/tmp/x\0y") }.to raise_error(ArgumentError)
|
|
79
|
-
expect { @session.handle_cookies("/tmp/x\0y") }.to raise_error(ArgumentError)
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
it "should raise an error when passed an invalid action" do
|
|
84
|
-
expect { @session.request(:bogus, "/test", {}) }.to raise_error(ArgumentError)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
it "should raise an error when no URL is provided" do
|
|
88
|
-
@session.base_url = nil
|
|
89
|
-
expect {@session.get(nil)}.to raise_error(ArgumentError)
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
it "should retrieve a url with :get" do
|
|
93
|
-
response = @session.get("/test")
|
|
94
|
-
body = yaml_load(response.body)
|
|
95
|
-
expect(body.request_method).to be == "GET"
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
it "should expose curl's cumulative transfer timings" do
|
|
99
|
-
timings = @session.get("/test").timings
|
|
100
|
-
|
|
101
|
-
expect(timings.keys).to contain_exactly(
|
|
102
|
-
:namelookup, :connect, :appconnect, :pretransfer, :starttransfer, :redirect, :total
|
|
103
|
-
)
|
|
104
|
-
expect(timings.values).to all(be_a(Float).and(be >= 0))
|
|
105
|
-
expect(timings[:namelookup]).to be <= timings[:connect]
|
|
106
|
-
expect(timings[:connect]).to be <= timings[:starttransfer]
|
|
107
|
-
expect(timings[:starttransfer]).to be <= timings[:total]
|
|
108
|
-
expect(timings[:total]).to be > 0
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
it "should use full base url" do
|
|
112
|
-
@session.base_url = "http://localhost:9001/api/v1"
|
|
113
|
-
response = @session.get("/test")
|
|
114
|
-
expect(response.url).to be == "http://localhost:9001/api/v1/test"
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
it 'should ignore #base_url when a full URL is provided' do
|
|
118
|
-
@session.base_url = "http://example.com:123"
|
|
119
|
-
expect { @session.get("http://localhost:9001/test") }.to_not raise_error(URI::InvalidURIError)
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
it "should download content with :get and a file path" do
|
|
123
|
-
tf = Tempfile.new
|
|
124
|
-
tf.close
|
|
125
|
-
tmpfile = tf.path
|
|
126
|
-
|
|
127
|
-
response = @session.get_file "/test", tmpfile
|
|
128
|
-
expect(response.body).to be_nil
|
|
129
|
-
body = yaml_load(File.open(tmpfile).read)
|
|
130
|
-
expect(body.request_method).to be == "GET"
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
it "should download correctly(md5 ok) with get_file" do
|
|
134
|
-
tf = Tempfile.new
|
|
135
|
-
tf.close
|
|
136
|
-
tmpfile = tf.path
|
|
137
|
-
|
|
138
|
-
response = @session.get_file "/picture", tmpfile
|
|
139
|
-
expect(response.body).to be_nil
|
|
140
|
-
expect(File.size(File.join(File.dirname(__FILE__),"../pic.png"))).to be == File.size(tmpfile)
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
it "should follow a 307 redirect with Range headers" do
|
|
144
|
-
tf = Tempfile.new
|
|
145
|
-
tf.close
|
|
146
|
-
tmpfile = tf.path
|
|
147
|
-
|
|
148
|
-
response = @session.get_file "/redirect-to-picture", tmpfile, {'Range' => 'bytes=0-3'}
|
|
149
|
-
expect(response.body).to be_nil
|
|
150
|
-
expect(File.size(tmpfile)).to eq(4)
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
it "downloads a very large file" do
|
|
154
|
-
tf = Tempfile.new
|
|
155
|
-
tf.close
|
|
156
|
-
tmpfile = tf.path
|
|
157
|
-
|
|
158
|
-
@session.get_file "/very-large", tmpfile
|
|
159
|
-
expect(File.size(tmpfile)).to eq(15 * 1024 * 1024)
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
it "raises an exception if the download body limit is exceeded when using direct-to-file download" do
|
|
163
|
-
tf = Tempfile.new
|
|
164
|
-
tf.close
|
|
165
|
-
tmpfile = tf.path
|
|
166
|
-
|
|
167
|
-
@session.download_byte_limit = 1024
|
|
168
|
-
@session.buffer_size = 1024
|
|
169
|
-
expect {
|
|
170
|
-
@session.get_file "/very-large", tmpfile
|
|
171
|
-
}.to raise_error(Patron::Error)
|
|
172
|
-
# On Ruby 2.x Patron::Aborted takes precedence, but
|
|
173
|
-
# on 1.9 it will be Patron::PartialFileError, so we cannot match on exception.
|
|
174
|
-
#
|
|
175
|
-
# Not clear why, but Patron (or libCURL, or the two in combination)
|
|
176
|
-
# may fetch more bytes than we permit it to - for example,
|
|
177
|
-
# on Darwin the limit is respected exactly, while on Linux in GH actions
|
|
178
|
-
# it does retrieve substantially more.
|
|
179
|
-
# The URL we are hitting is serving a file that is 15 megabytes. On Linux
|
|
180
|
-
# the tempfile fills to 103344 in our tests, so this is probably either
|
|
181
|
-
# OS-dependent or related to timings with the RubyVM context switching.
|
|
182
|
-
# To ensure our limiting works, it is enough to check that the downloaded file
|
|
183
|
-
# is perceptibly smaller than the complete size of 15MB. Let's say < 1MB.
|
|
184
|
-
expect(File.size(tmpfile)).to be < (1024 * 1024)
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
it "raises an exception if the download body limit is exceeded when using download-to-memory" do
|
|
188
|
-
@session.download_byte_limit = 1024
|
|
189
|
-
@session.buffer_size = 1024
|
|
190
|
-
expect {
|
|
191
|
-
@session.get "/very-large"
|
|
192
|
-
}.to raise_error(Patron::Error)
|
|
193
|
-
# On Ruby 2.x Patron::Aborted takes precedence, but
|
|
194
|
-
# on 1.9 it will be Patron::PartialFileError
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
it "accepts a download body limit above 2 GiB and timeouts given as Floats" do
|
|
198
|
-
@session.download_byte_limit = 5 * 1024 * 1024 * 1024
|
|
199
|
-
@session.dns_cache_timeout = 2.5
|
|
200
|
-
@session.low_speed_time = 2.5
|
|
201
|
-
@session.low_speed_limit = 1.5
|
|
202
|
-
expect(@session.get("/test").status).to be == 200
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
it "should not send the user-agent if it has been deleted from headers" do
|
|
206
|
-
@session.headers.delete 'User-Agent'
|
|
207
|
-
response = @session.get("/test")
|
|
208
|
-
body = yaml_load(response.body)
|
|
209
|
-
expect(body.header["user-agent"]).to be_nil
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
it "should set the default User-agent" do
|
|
213
|
-
response = @session.get("/test")
|
|
214
|
-
body = yaml_load(response.body)
|
|
215
|
-
expect(body.header["user-agent"]).to be == [Patron.user_agent_string]
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
it "should include custom headers in a request" do
|
|
219
|
-
response = @session.get("/test", {"User-Agent" => "PatronTest"})
|
|
220
|
-
body = yaml_load(response.body)
|
|
221
|
-
expect(body.header["user-agent"]).to be == ["PatronTest"]
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
it "should include default headers in a request, if they were defined" do
|
|
225
|
-
@session.headers = {"User-Agent" => "PatronTest"}
|
|
226
|
-
response = @session.get("/test")
|
|
227
|
-
body = yaml_load(response.body)
|
|
228
|
-
expect(body.header["user-agent"]).to be == ["PatronTest"]
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
it "should merge custom headers with session headers" do
|
|
232
|
-
@session.headers["X-Test"] = "Testing"
|
|
233
|
-
response = @session.get("/test", {"User-Agent" => "PatronTest"})
|
|
234
|
-
body = yaml_load(response.body)
|
|
235
|
-
expect(body.header["user-agent"]).to be == ["PatronTest"]
|
|
236
|
-
expect(body.header["x-test"]).to be == ["Testing"]
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
it "accepts the DNS cache timeout option" do
|
|
240
|
-
@session.dns_cache_timeout = 60
|
|
241
|
-
@session.get("/")
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
it "should raise an exception on timeout" do
|
|
245
|
-
@session.timeout = 1
|
|
246
|
-
expect {@session.get("/timeout?millis=1100")}.to raise_error(Patron::TimeoutError)
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
it "should raise an exception on a sub-second timeout" do
|
|
250
|
-
@session.timeout = 0.3
|
|
251
|
-
expect {@session.get("/timeout?millis=400")}.to raise_error(Patron::TimeoutError)
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
it "should raise an exception on timeout when reading from a slow resource and low speed limit is set", :timing do
|
|
255
|
-
@session.timeout = 10 # Greater than the low_speed_time
|
|
256
|
-
@session.low_speed_time = 2
|
|
257
|
-
@session.low_speed_limit = 10
|
|
258
|
-
|
|
259
|
-
# Our test server starts sending the body only after 5 seconds. We should be able to abort
|
|
260
|
-
# by exceeding the low_speed_time.
|
|
261
|
-
started = Time.now.to_f
|
|
262
|
-
expect {@session.get("/slow")}.to raise_error(Patron::TimeoutError)
|
|
263
|
-
delta_s = Time.now.to_f - started
|
|
264
|
-
# For some reason libCURL times out after 3 seconds instead of 2 sometimes.
|
|
265
|
-
expect(delta_s).to be_within(0.1).of(2).or be_within(0.1).of(3)
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
it "is able to terminate the thread that is running a slow request using Thread#kill (uses the custom unblock)", :timing do
|
|
269
|
-
t = Thread.new do
|
|
270
|
-
session = Patron::Session.new
|
|
271
|
-
session.timeout = 10 # Greater than the sleep duration
|
|
272
|
-
session.base_url = "http://localhost:9001"
|
|
273
|
-
session.get("/slow")
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
# Our test server starts sending the body only after 5 seconds. We should be able to abort
|
|
277
|
-
# using a signal during that time.
|
|
278
|
-
started = Time.now.to_f
|
|
279
|
-
sleep 2 # Less than what it takes for the server to respond
|
|
280
|
-
t.kill # Kill the thread forcibly
|
|
281
|
-
t.join # wrap up the thread. If Patron is still busy there, this join call will still take another 1s.
|
|
282
|
-
|
|
283
|
-
delta_s = Time.now.to_f - started
|
|
284
|
-
# For some reason libCURL times out after 3 seconds instead of 2 sometimes.
|
|
285
|
-
expect(delta_s).to be_within(0.1).of(2).or be_within(0.1).of(3)
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
it "is able to terminate the process that is running a slow request with SIGINT", :timing do
|
|
289
|
-
pid = Process.fork do
|
|
290
|
-
trap('SIGINT') do
|
|
291
|
-
exit # exit the process
|
|
292
|
-
end
|
|
293
|
-
session = Patron::Session.new
|
|
294
|
-
session.timeout = 10 # Greater than the sleep duration
|
|
295
|
-
session.base_url = "http://localhost:9001"
|
|
296
|
-
session.get("/slow")
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
# Our test server starts sending the body only after 5 seconds. We should be able to abort
|
|
300
|
-
# using a signal during that time.
|
|
301
|
-
started = Time.now.to_f
|
|
302
|
-
sleep 2 # Less than what it takes for the server to respond
|
|
303
|
-
Process.kill("INT", pid) # Signal the process...
|
|
304
|
-
Process.wait(pid) # wrap up the process. If Patron is still busy there, this call will still take another 1s.
|
|
305
|
-
delta_s = Time.now.to_f - started
|
|
306
|
-
# For some reason libCURL times out after 3 seconds instead of 2 sometimes.
|
|
307
|
-
expect(delta_s).to be_within(0.1).of(2).or be_within(0.1).of(3)
|
|
308
|
-
end
|
|
309
|
-
|
|
310
|
-
it "receives progress callbacks" do
|
|
311
|
-
session = Patron::Session.new
|
|
312
|
-
session.timeout = 10 # # Greater than the /slow respond time (5 seconds)
|
|
313
|
-
session.base_url = "http://localhost:9001"
|
|
314
|
-
callback_args = []
|
|
315
|
-
session.progress_callback = Proc.new {|dltotal, dlnow, ultotal, ulnow|
|
|
316
|
-
callback_args << [dltotal, dlnow, ultotal, ulnow]
|
|
317
|
-
}
|
|
318
|
-
session.get("/slow")
|
|
319
|
-
expect(callback_args).not_to be_empty
|
|
320
|
-
|
|
321
|
-
# Make sure that the callback setting was not reset.
|
|
322
|
-
callback_args = []
|
|
323
|
-
session.get("/slow")
|
|
324
|
-
expect(callback_args).not_to be_empty
|
|
325
|
-
end
|
|
326
|
-
|
|
327
|
-
it "propagates an exception raised in the progress callback and keeps the session usable" do
|
|
328
|
-
session = Patron::Session.new
|
|
329
|
-
session.timeout = 10
|
|
330
|
-
session.base_url = "http://localhost:9001"
|
|
331
|
-
boom = Class.new(StandardError)
|
|
332
|
-
session.progress_callback = Proc.new { raise boom, "from the callback" }
|
|
333
|
-
|
|
334
|
-
expect { session.get("/slow") }.to raise_error(boom, "from the callback")
|
|
335
|
-
|
|
336
|
-
session.progress_callback = nil
|
|
337
|
-
expect(session.get("/test").status).to be == 200
|
|
338
|
-
end
|
|
339
|
-
|
|
340
|
-
it "lets a throw from the progress callback unwind to the catch around the request" do
|
|
341
|
-
# Timeout.timeout on Rubies before 3.1 unwinds with throw rather than raise
|
|
342
|
-
session = Patron::Session.new
|
|
343
|
-
session.timeout = 10
|
|
344
|
-
session.base_url = "http://localhost:9001"
|
|
345
|
-
session.progress_callback = Proc.new { throw :stop_right_there, :thrown }
|
|
346
|
-
|
|
347
|
-
expect(catch(:stop_right_there) { session.get("/slow") }).to be == :thrown
|
|
348
|
-
|
|
349
|
-
session.progress_callback = nil
|
|
350
|
-
expect(session.get("/test").status).to be == 200
|
|
351
|
-
end
|
|
352
|
-
|
|
353
|
-
it "can be interrupted with Timeout.timeout while a progress callback is set", :timing do
|
|
354
|
-
session = Patron::Session.new
|
|
355
|
-
session.timeout = 10
|
|
356
|
-
session.base_url = "http://localhost:9001"
|
|
357
|
-
session.progress_callback = Proc.new { |*| }
|
|
358
|
-
|
|
359
|
-
started = Time.now.to_f
|
|
360
|
-
expect { Timeout.timeout(1) { session.get("/slow") } }.to raise_error(Timeout::Error)
|
|
361
|
-
expect(Time.now.to_f - started).to be < 3
|
|
362
|
-
|
|
363
|
-
session.progress_callback = nil
|
|
364
|
-
expect(session.get("/test").status).to be == 200
|
|
365
|
-
end
|
|
366
|
-
|
|
367
|
-
it "raises the right exception class after the heap has been compacted" do
|
|
368
|
-
# The exception classes are looked up from C once at load time; if they
|
|
369
|
-
# were not registered with the GC, compaction would move them away
|
|
370
|
-
skip "no compaction on this Ruby" unless GC.respond_to?(:verify_compaction_references)
|
|
371
|
-
begin
|
|
372
|
-
begin
|
|
373
|
-
GC.verify_compaction_references(expand_heap: true, toward: :empty)
|
|
374
|
-
rescue ArgumentError # expand_heap: is double_heap: on Rubies before 3.2
|
|
375
|
-
GC.verify_compaction_references(double_heap: true, toward: :empty)
|
|
376
|
-
end
|
|
377
|
-
rescue NotImplementedError
|
|
378
|
-
skip "compaction is not supported on this platform"
|
|
379
|
-
end
|
|
380
|
-
|
|
381
|
-
session = Patron::Session.new
|
|
382
|
-
expect { session.get("http://localhost:1/") }.to raise_error(Patron::ConnectionFailed)
|
|
383
|
-
end
|
|
384
|
-
|
|
385
|
-
it "should follow redirects by default" do
|
|
386
|
-
@session.max_redirects = 1
|
|
387
|
-
response = @session.get("/redirect")
|
|
388
|
-
body = yaml_load(response.body)
|
|
389
|
-
expect(response.status).to be == 200
|
|
390
|
-
expect(body.path).to be == "/test"
|
|
391
|
-
end
|
|
392
|
-
|
|
393
|
-
it "should not keep the Location header from the redirecting response" do
|
|
394
|
-
@session.max_redirects = 1
|
|
395
|
-
response = @session.get("/redirect")
|
|
396
|
-
expect(response.headers['Location']).to be_nil
|
|
397
|
-
end
|
|
398
|
-
|
|
399
|
-
it "should include redirect count in response" do
|
|
400
|
-
@session.max_redirects = 1
|
|
401
|
-
response = @session.get("/redirect")
|
|
402
|
-
expect(response.redirect_count).to be == 1
|
|
403
|
-
end
|
|
404
|
-
|
|
405
|
-
it "should not follow redirects when configured to do so" do
|
|
406
|
-
@session.max_redirects = 0
|
|
407
|
-
response = @session.get("/redirect")
|
|
408
|
-
expect(response.status).to be == 301
|
|
409
|
-
expect(response.body).to be_empty
|
|
410
|
-
end
|
|
411
|
-
|
|
412
|
-
it "should retrieve URL metadata with :head" do
|
|
413
|
-
response = @session.head("/test")
|
|
414
|
-
expect(response.status).to be == 200
|
|
415
|
-
expect(response.body).to be_empty
|
|
416
|
-
expect(response.headers).to_not be_empty
|
|
417
|
-
end
|
|
418
|
-
|
|
419
|
-
it "should send a delete request with :delete" do
|
|
420
|
-
response = @session.delete("/test")
|
|
421
|
-
body = yaml_load(response.body)
|
|
422
|
-
expect(body.request_method).to be == "DELETE"
|
|
423
|
-
end
|
|
424
|
-
|
|
425
|
-
it "should send a COPY request with :copy" do
|
|
426
|
-
response = @session.copy("/test", "/test2")
|
|
427
|
-
body = yaml_load(response.body)
|
|
428
|
-
expect(body.request_method).to be == "COPY"
|
|
429
|
-
end
|
|
430
|
-
|
|
431
|
-
it "should include a Destination header in COPY requests" do
|
|
432
|
-
response = @session.copy("/test", "/test2")
|
|
433
|
-
body = yaml_load(response.body)
|
|
434
|
-
expect(body.header['destination'].first).to be == "/test2"
|
|
435
|
-
end
|
|
436
|
-
|
|
437
|
-
it "should upload data with :get" do
|
|
438
|
-
data = "upload data"
|
|
439
|
-
response = @session.request(:get, "/test", {}, :data => data)
|
|
440
|
-
body = yaml_load(response.body)
|
|
441
|
-
expect(body.request_method).to be == "GET"
|
|
442
|
-
expect(body.header['content-length']).to be == [data.size.to_s]
|
|
443
|
-
end
|
|
444
|
-
|
|
445
|
-
it "should call to_s on the data being uploaded via GET if it is not already a String" do
|
|
446
|
-
data = 12345
|
|
447
|
-
response = @session.request(:get, "/test", {}, :data => data)
|
|
448
|
-
body = yaml_load(response.body)
|
|
449
|
-
expect(body.request_method).to be == "GET"
|
|
450
|
-
end
|
|
451
|
-
|
|
452
|
-
it "should upload data with :get" do
|
|
453
|
-
# Sending a request body with a GET request is a technique seldom used,
|
|
454
|
-
# but it does get used nevertheless - for instance, it is a usual
|
|
455
|
-
# practice when interacting with an ElasticSearch cluster where
|
|
456
|
-
# you can have very deeply going queries, which are still technically GETs
|
|
457
|
-
data = Random.new.bytes(1024 * 24)
|
|
458
|
-
response = @session.request(:get, "/test", {}, :data => data)
|
|
459
|
-
body = yaml_load(response.body)
|
|
460
|
-
expect(body.request_method).to be == "GET"
|
|
461
|
-
expect(body.header['content-length']).to be == [data.size.to_s]
|
|
462
|
-
end
|
|
463
|
-
|
|
464
|
-
it "should upload data with :put" do
|
|
465
|
-
data = Random.new.bytes(1024 * 24)
|
|
466
|
-
response = @session.put("/test", data)
|
|
467
|
-
body = yaml_load(response.body)
|
|
468
|
-
expect(body.request_method).to be == "PUT"
|
|
469
|
-
expect(body.header['content-length']).to be == [data.size.to_s]
|
|
470
|
-
end
|
|
471
|
-
|
|
472
|
-
it "should upload a Tempfile with :put" do
|
|
473
|
-
data = Tempfile.new 'data-buffer'
|
|
474
|
-
data << Random.new.bytes(1024 * 64)
|
|
475
|
-
data.flush; data.rewind
|
|
476
|
-
|
|
477
|
-
response = @session.put("/test", data, {'Expect' => ''})
|
|
478
|
-
body = yaml_load(response.body)
|
|
479
|
-
expect(body.request_method).to be == "PUT"
|
|
480
|
-
expect(body.header['content-length']).to be == [data.size.to_s]
|
|
481
|
-
end
|
|
482
|
-
|
|
483
|
-
it "should upload data with :patch" do
|
|
484
|
-
data = "upload data"
|
|
485
|
-
response = @session.patch("/testpatch", data)
|
|
486
|
-
body = yaml_load(response.body)
|
|
487
|
-
expect(body.body).to eq("upload data")
|
|
488
|
-
end
|
|
489
|
-
|
|
490
|
-
it "should upload data with :delete" do
|
|
491
|
-
data = "upload data"
|
|
492
|
-
response = @session.request(:delete, "/test", {}, :data => data)
|
|
493
|
-
body = yaml_load(response.body)
|
|
494
|
-
expect(body.request_method).to be == "DELETE"
|
|
495
|
-
expect(body.header['content-length']).to be == [data.size.to_s]
|
|
496
|
-
end
|
|
497
|
-
|
|
498
|
-
it "keeps a request body built by #to_s alive for the duration of the transfer" do
|
|
499
|
-
# The body only exists as the return value of #to_s, and libcurl reads it
|
|
500
|
-
# while the GVL is released - GC.stress makes that window reliable
|
|
501
|
-
payload = Random.new.bytes(64 * 1024)
|
|
502
|
-
body_object = Object.new
|
|
503
|
-
body_object.define_singleton_method(:to_s) { payload + '' }
|
|
504
|
-
|
|
505
|
-
begin
|
|
506
|
-
GC.stress = true
|
|
507
|
-
[:post, :put, :delete].each do |verb|
|
|
508
|
-
response = @session.request(verb, "/test", {}, :data => body_object)
|
|
509
|
-
body = yaml_load(response.body)
|
|
510
|
-
expect(body.request_method).to be == verb.to_s.upcase
|
|
511
|
-
expect(body.body.b).to be == payload
|
|
512
|
-
end
|
|
513
|
-
ensure
|
|
514
|
-
GC.stress = false
|
|
515
|
-
end
|
|
516
|
-
end
|
|
517
|
-
|
|
518
|
-
it "should raise when no data is provided to :put" do
|
|
519
|
-
expect { @session.put("/test", nil) }.to raise_error(ArgumentError)
|
|
520
|
-
end
|
|
521
|
-
|
|
522
|
-
it "should upload a file with :put" do
|
|
523
|
-
response = @session.put_file("/test", "LICENSE")
|
|
524
|
-
body = yaml_load(response.body)
|
|
525
|
-
expect(body.request_method).to be == "PUT"
|
|
526
|
-
end
|
|
527
|
-
|
|
528
|
-
it "should raise when no file is provided to :put" do
|
|
529
|
-
expect { @session.put_file("/test", nil) }.to raise_error(ArgumentError)
|
|
530
|
-
end
|
|
531
|
-
|
|
532
|
-
it "should upload a file with :put using chunked transfer-encoding" do
|
|
533
|
-
# Transfer-Encoding is a hop-by-hop header (RFC 7230 3.3.1): a conformant server
|
|
534
|
-
# decodes the chunked framing and never exposes the header to the application, so
|
|
535
|
-
# we capture the raw request to assert how Patron actually frames the upload.
|
|
536
|
-
captured = capture_request do |url|
|
|
537
|
-
session = Patron::Session.new
|
|
538
|
-
session.base_url = url
|
|
539
|
-
session.timeout = 5
|
|
540
|
-
session.put_file("/test", "LICENSE")
|
|
541
|
-
end
|
|
542
|
-
expect(captured.request_line).to start_with("PUT ")
|
|
543
|
-
expect(captured.headers["transfer-encoding"]).to be == "chunked"
|
|
544
|
-
expect(captured.headers).to_not have_key("content-length")
|
|
545
|
-
expect(captured.body.bytesize).to be == File.size("LICENSE")
|
|
546
|
-
end
|
|
547
|
-
|
|
548
|
-
it "should call to_s on the data being uploaded via POST if it is not already a String" do
|
|
549
|
-
data = 12345
|
|
550
|
-
response = @session.post("/testpost", data)
|
|
551
|
-
body = yaml_load(response.body)
|
|
552
|
-
expect(body['body']).to eq("12345")
|
|
553
|
-
end
|
|
554
|
-
|
|
555
|
-
it "should upload data with :post" do
|
|
556
|
-
data = "upload data"
|
|
557
|
-
response = @session.post("/test", data)
|
|
558
|
-
body = yaml_load(response.body)
|
|
559
|
-
expect(body.request_method).to be == "POST"
|
|
560
|
-
expect(body.header['content-length']).to be == [data.size.to_s]
|
|
561
|
-
end
|
|
562
|
-
|
|
563
|
-
it "should POST a hash of arguments as a urlencoded form" do
|
|
564
|
-
data = {:foo => 123, 'baz' => '++hello world++'}
|
|
565
|
-
response = @session.post("/testpost", data)
|
|
566
|
-
body = yaml_load(response.body)
|
|
567
|
-
expect(body['content_type']).to be == "application/x-www-form-urlencoded"
|
|
568
|
-
expect(body['body']).to match(/baz=%2B%2Bhello%20world%2B%2B/)
|
|
569
|
-
expect(body['body']).to match(/foo=123/)
|
|
570
|
-
end
|
|
571
|
-
|
|
572
|
-
it "should raise when no data is provided to :post" do
|
|
573
|
-
expect { @session.post("/test", nil) }.to raise_error(ArgumentError)
|
|
574
|
-
end
|
|
575
|
-
|
|
576
|
-
it "should upload a file with :post" do
|
|
577
|
-
response = @session.post_file("/test", "LICENSE")
|
|
578
|
-
body = yaml_load(response.body)
|
|
579
|
-
expect(body.request_method).to be == "POST"
|
|
580
|
-
end
|
|
581
|
-
|
|
582
|
-
it "should upload a multipart with :post" do
|
|
583
|
-
response = @session.post_multipart("/test", { :test_data => "123" }, { :test_file => "LICENSE" } )
|
|
584
|
-
body = yaml_load(response.body)
|
|
585
|
-
expect(body.request_method).to be == "POST"
|
|
586
|
-
end
|
|
587
|
-
|
|
588
|
-
it "sends multipart field names and binary values intact" do
|
|
589
|
-
# A Symbol key gets stringified into a temporary libcurl must copy,
|
|
590
|
-
# and a value with NUL bytes must survive past the first one
|
|
591
|
-
value = "before\0middle\0after".b
|
|
592
|
-
response = @session.post_multipart("/test", { :sym_key => value }, { :test_file => "LICENSE" })
|
|
593
|
-
body = yaml_load(response.body)
|
|
594
|
-
raw = body.body.b
|
|
595
|
-
expect(raw).to include('name="sym_key"')
|
|
596
|
-
expect(raw).to include(value)
|
|
597
|
-
expect(raw).to include('name="test_file"; filename="LICENSE"')
|
|
598
|
-
expect(raw).to include(File.read("LICENSE").b)
|
|
599
|
-
end
|
|
600
|
-
|
|
601
|
-
it "should raise when no file is provided to :post" do
|
|
602
|
-
expect { @session.post_file("/test", nil) }.to raise_error(ArgumentError)
|
|
603
|
-
end
|
|
604
|
-
|
|
605
|
-
it "should upload a file with :post using chunked transfer-encoding" do
|
|
606
|
-
# See the :put counterpart above: Transfer-Encoding is hop-by-hop and invisible
|
|
607
|
-
# to the application, so we capture the raw request to assert the framing.
|
|
608
|
-
captured = capture_request do |url|
|
|
609
|
-
session = Patron::Session.new
|
|
610
|
-
session.base_url = url
|
|
611
|
-
session.timeout = 5
|
|
612
|
-
session.post_file("/test", "LICENSE")
|
|
613
|
-
end
|
|
614
|
-
expect(captured.request_line).to start_with("POST ")
|
|
615
|
-
expect(captured.headers["transfer-encoding"]).to be == "chunked"
|
|
616
|
-
expect(captured.headers).to_not have_key("content-length")
|
|
617
|
-
expect(captured.body.bytesize).to be == File.size("LICENSE")
|
|
618
|
-
end
|
|
619
|
-
|
|
620
|
-
it "should pass credentials as http basic auth" do
|
|
621
|
-
@session.username = "foo"
|
|
622
|
-
@session.password = "bar"
|
|
623
|
-
response = @session.get("/test")
|
|
624
|
-
body = yaml_load(response.body)
|
|
625
|
-
expect(body.header['authorization']).to be == [encode_authz("foo", "bar")]
|
|
626
|
-
end
|
|
627
|
-
|
|
628
|
-
it "should store cookies across multiple requests" do
|
|
629
|
-
tf = Tempfile.new('cookiejar')
|
|
630
|
-
tf.close
|
|
631
|
-
cookie_jar_path = tf.path
|
|
632
|
-
|
|
633
|
-
@session.handle_cookies(cookie_jar_path)
|
|
634
|
-
response = @session.get("/setcookie").body
|
|
635
|
-
|
|
636
|
-
cookie_jar_contents = tf.open.read
|
|
637
|
-
tf.unlink
|
|
638
|
-
expect(cookie_jar_contents).not_to be_empty
|
|
639
|
-
expect(cookie_jar_contents).to include('Netscape HTTP Cookie File')
|
|
640
|
-
end
|
|
641
|
-
|
|
642
|
-
it "should handle cookies if set" do
|
|
643
|
-
@session.handle_cookies
|
|
644
|
-
response = @session.get("/setcookie").body
|
|
645
|
-
expect(yaml_load(response).header['cookie'].first).to be == "session_id=foo123"
|
|
646
|
-
end
|
|
647
|
-
|
|
648
|
-
it "should not handle cookies by default" do
|
|
649
|
-
response = @session.get("/setcookie").body
|
|
650
|
-
expect(yaml_load(response).header).to_not include('cookie')
|
|
651
|
-
end
|
|
652
|
-
|
|
653
|
-
it "should ignore a wrong Content-Length when asked to" do
|
|
654
|
-
expect {
|
|
655
|
-
@session.ignore_content_length = true
|
|
656
|
-
@session.get("/wrongcontentlength", { 'Connection' => 'close' })
|
|
657
|
-
}.to_not raise_error
|
|
658
|
-
end
|
|
659
|
-
|
|
660
|
-
it "should fail by default with a Content-Length too high" do
|
|
661
|
-
expect {
|
|
662
|
-
@session.ignore_content_length = nil
|
|
663
|
-
@session.get("/wrongcontentlength", { 'Connection' => 'close' })
|
|
664
|
-
}.to raise_error(Patron::PartialFileError)
|
|
665
|
-
end
|
|
666
|
-
|
|
667
|
-
it "should raise exception if cookie store is not writable or readable" do
|
|
668
|
-
expect { @session.handle_cookies("/trash/clash/foo") }.to raise_error(ArgumentError)
|
|
669
|
-
end
|
|
670
|
-
|
|
671
|
-
it "refuses to run two requests on one Session from two threads at the same time" do
|
|
672
|
-
session = Patron::Session.new
|
|
673
|
-
session.timeout = 10
|
|
674
|
-
session.base_url = "http://localhost:9001"
|
|
675
|
-
|
|
676
|
-
outcomes = Array.new(2) do
|
|
677
|
-
Thread.new do
|
|
678
|
-
begin
|
|
679
|
-
session.get("/slow")
|
|
680
|
-
:completed
|
|
681
|
-
rescue Patron::Error => e
|
|
682
|
-
e
|
|
683
|
-
end
|
|
684
|
-
end
|
|
685
|
-
end.map(&:value)
|
|
686
|
-
|
|
687
|
-
refused = outcomes.grep(Patron::Error)
|
|
688
|
-
expect(refused.size).to be == 1
|
|
689
|
-
expect(refused.first.message).to include("another thread")
|
|
690
|
-
expect(outcomes - refused).to be == [:completed]
|
|
691
|
-
|
|
692
|
-
# ...and is free again once the in-flight request is done
|
|
693
|
-
expect(session.get("/test").status).to be == 200
|
|
694
|
-
end
|
|
695
|
-
|
|
696
|
-
it "should work with multiple threads" do
|
|
697
|
-
threads = []
|
|
698
|
-
3.times do
|
|
699
|
-
threads << Thread.new do
|
|
700
|
-
session = Patron::Session.new
|
|
701
|
-
session.base_url = "http://localhost:9001"
|
|
702
|
-
session.post_file("/test", "LICENSE")
|
|
703
|
-
end
|
|
704
|
-
end
|
|
705
|
-
threads.each {|t| t.join }
|
|
706
|
-
end
|
|
707
|
-
|
|
708
|
-
it "should limit the buffer_size" do
|
|
709
|
-
# Buffer size is tricky to test, as it only affects the buffer size for each
|
|
710
|
-
# read and it's not really visible at this, higher level. It's also only a
|
|
711
|
-
# suggestion rather than a command so it may not even take affect. Currently
|
|
712
|
-
# we just test that the response completes without any issues, it would be nice
|
|
713
|
-
# to have a more robust test here.
|
|
714
|
-
@session.buffer_size = 1
|
|
715
|
-
|
|
716
|
-
body = nil
|
|
717
|
-
|
|
718
|
-
expect {
|
|
719
|
-
response = @session.get("/test")
|
|
720
|
-
body = yaml_load(response.body)
|
|
721
|
-
}.to_not raise_error
|
|
722
|
-
|
|
723
|
-
expect(body.request_method).to be == "GET"
|
|
724
|
-
end
|
|
725
|
-
|
|
726
|
-
it "should automatically decompress using Content-Encoding if requested" do
|
|
727
|
-
@session.automatic_content_encoding = true
|
|
728
|
-
response = @session.get('/gzip-compressed')
|
|
729
|
-
|
|
730
|
-
expect(response.headers['Content-Length']).to eq('125')
|
|
731
|
-
|
|
732
|
-
body = response.body
|
|
733
|
-
expect(body).to match(/Some highly compressible data/)
|
|
734
|
-
expect(body.bytesize).to eq(29696)
|
|
735
|
-
end
|
|
736
|
-
|
|
737
|
-
it "should serialize query params and append them to the url" do
|
|
738
|
-
response = @session.request(:get, "/test", {}, :query => {:foo => "bar"})
|
|
739
|
-
request = yaml_load(response.body)
|
|
740
|
-
expect(request.path + '?' + request.query_string).to be == "/test?foo=bar"
|
|
741
|
-
end
|
|
742
|
-
|
|
743
|
-
it "should merge parameters in the :query option with pre-existing query parameters" do
|
|
744
|
-
response = @session.request(:get, "/test?foo=bar", {}, :query => {:baz => "quux"})
|
|
745
|
-
request = yaml_load(response.body)
|
|
746
|
-
expect(request.path + '?' + request.query_string).to be == "/test?foo=bar&baz=quux"
|
|
747
|
-
end
|
|
748
|
-
|
|
749
|
-
def encode_authz(user, passwd)
|
|
750
|
-
"Basic " + Base64.encode64("#{user}:#{passwd}").strip
|
|
751
|
-
end
|
|
752
|
-
|
|
753
|
-
describe 'when using a subclass with a custom Response' do
|
|
754
|
-
|
|
755
|
-
class CustomResponse
|
|
756
|
-
attr_reader :constructor_args
|
|
757
|
-
def initialize(*constructor_args)
|
|
758
|
-
@constructor_args = constructor_args
|
|
759
|
-
end
|
|
760
|
-
end
|
|
761
|
-
|
|
762
|
-
class CustomizedSession < Patron::Session
|
|
763
|
-
def response_class
|
|
764
|
-
CustomResponse
|
|
765
|
-
end
|
|
766
|
-
end
|
|
767
|
-
|
|
768
|
-
it 'instantiates the customized response object' do
|
|
769
|
-
@session = CustomizedSession.new
|
|
770
|
-
@session.base_url = "http://localhost:9001"
|
|
771
|
-
response = @session.request(:get, "/test", {}, :query => {:foo => "bar"})
|
|
772
|
-
|
|
773
|
-
expect(response).to be_kind_of(CustomResponse)
|
|
774
|
-
expect(response.constructor_args.length).to eq(6)
|
|
775
|
-
end
|
|
776
|
-
end
|
|
777
|
-
|
|
778
|
-
describe 'when instantiating with hash arguments' do
|
|
779
|
-
|
|
780
|
-
let(:args) { {
|
|
781
|
-
:timeout => 10,
|
|
782
|
-
:dns_cache_timeout => 10,
|
|
783
|
-
:base_url => 'http://localhost:9001',
|
|
784
|
-
:headers => {'User-Agent' => 'myapp/1.0'}
|
|
785
|
-
} }
|
|
786
|
-
|
|
787
|
-
let(:session) { Patron::Session.new(args) }
|
|
788
|
-
|
|
789
|
-
it 'sets the base_url' do
|
|
790
|
-
expect(session.base_url).to be == args[:base_url]
|
|
791
|
-
end
|
|
792
|
-
|
|
793
|
-
it 'sets timeout' do
|
|
794
|
-
expect(session.timeout).to be == args[:timeout]
|
|
795
|
-
end
|
|
796
|
-
|
|
797
|
-
it 'sets DNS cache timeout' do
|
|
798
|
-
expect(session.dns_cache_timeout).to be == args[:dns_cache_timeout]
|
|
799
|
-
end
|
|
800
|
-
|
|
801
|
-
it 'sets headers' do
|
|
802
|
-
expect(session.headers).to be == args[:headers]
|
|
803
|
-
end
|
|
804
|
-
|
|
805
|
-
context 'when given an incorrect accessor name' do
|
|
806
|
-
let(:args) { { :not_a_real_accessor => 'http://localhost:9001' }}
|
|
807
|
-
it 'raises no method error' do
|
|
808
|
-
expect { session }.to raise_error NoMethodError
|
|
809
|
-
end
|
|
810
|
-
end
|
|
811
|
-
|
|
812
|
-
end
|
|
813
|
-
|
|
814
|
-
describe 'when instantiating with a block' do
|
|
815
|
-
args = {
|
|
816
|
-
:timeout => 10,
|
|
817
|
-
:base_url => 'http://localhost:9001',
|
|
818
|
-
:headers => {'User-Agent' => 'myapp/1.0'}
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
session = Patron::Session.new do |patron|
|
|
822
|
-
patron.timeout = args[:timeout]
|
|
823
|
-
patron.base_url = args[:base_url]
|
|
824
|
-
patron.headers = args[:headers]
|
|
825
|
-
end
|
|
826
|
-
|
|
827
|
-
it 'sets the base_url' do
|
|
828
|
-
expect(session.base_url).to be == args[:base_url]
|
|
829
|
-
end
|
|
830
|
-
|
|
831
|
-
it 'sets timeout' do
|
|
832
|
-
expect(session.timeout).to be == args[:timeout]
|
|
833
|
-
end
|
|
834
|
-
|
|
835
|
-
it 'sets headers' do
|
|
836
|
-
expect(session.headers).to be == args[:headers]
|
|
837
|
-
end
|
|
838
|
-
|
|
839
|
-
context 'when given an incorrect accessor name' do
|
|
840
|
-
it 'raises no method error' do
|
|
841
|
-
expect {
|
|
842
|
-
Patron::Session.new do |patron|
|
|
843
|
-
patron.timeoutttt = args[:timeout]
|
|
844
|
-
end
|
|
845
|
-
}.to raise_error NoMethodError
|
|
846
|
-
end
|
|
847
|
-
end
|
|
848
|
-
end
|
|
849
|
-
|
|
850
|
-
# ------------------------------------------------------------------------
|
|
851
|
-
describe 'when debug is enabled' do
|
|
852
|
-
it 'it should not clobber stderr' do
|
|
853
|
-
rdev = STDERR.stat.rdev
|
|
854
|
-
|
|
855
|
-
retval = @session.enable_debug
|
|
856
|
-
expect(retval).to eq(@session)
|
|
857
|
-
expect(STDERR.stat.rdev).to be == rdev
|
|
858
|
-
|
|
859
|
-
retval = @session.enable_debug
|
|
860
|
-
expect(retval).to eq(@session)
|
|
861
|
-
expect(STDERR.stat.rdev).to be == rdev
|
|
862
|
-
end
|
|
863
|
-
end
|
|
864
|
-
|
|
865
|
-
end
|