curb 1.2.2 → 1.3.2
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/Rakefile +22 -0
- data/ext/curb.c +282 -231
- data/ext/curb.h +3 -3
- data/ext/curb_easy.c +766 -299
- data/ext/curb_easy.h +5 -0
- data/ext/curb_errors.c +5 -5
- data/ext/curb_errors.h +1 -1
- data/ext/curb_macros.h +14 -14
- data/ext/curb_multi.c +612 -142
- data/ext/curb_multi.h +3 -1
- data/ext/curb_postfield.c +48 -21
- data/ext/curb_postfield.h +1 -0
- data/ext/curb_upload.c +32 -9
- data/ext/curb_upload.h +2 -0
- data/ext/extconf.rb +42 -1
- data/lib/curl/easy.rb +154 -13
- data/lib/curl/multi.rb +69 -9
- data/lib/curl.rb +193 -0
- data/tests/helper.rb +222 -36
- data/tests/leak_trace.rb +237 -0
- data/tests/tc_curl_download.rb +6 -2
- data/tests/tc_curl_easy.rb +509 -1
- data/tests/tc_curl_multi.rb +573 -59
- data/tests/tc_curl_native_coverage.rb +145 -0
- data/tests/tc_curl_postfield.rb +176 -0
- data/tests/tc_fiber_scheduler.rb +342 -7
- data/tests/tc_gc_compact.rb +178 -16
- data/tests/tc_test_server_methods.rb +110 -0
- metadata +10 -14
- data/tests/test_basic.rb +0 -29
- data/tests/test_fiber_debug.rb +0 -69
- data/tests/test_fiber_simple.rb +0 -65
- data/tests/test_real_url.rb +0 -65
- data/tests/test_simple_fiber.rb +0 -34
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
|
2
|
+
|
|
3
|
+
class TestCurbCurlErrorMappings < Test::Unit::TestCase
|
|
4
|
+
def assert_easy_error_mapping(code, expected_class)
|
|
5
|
+
actual_class, message = Curl::Easy.error(code)
|
|
6
|
+
|
|
7
|
+
assert_equal expected_class, actual_class
|
|
8
|
+
assert_kind_of String, message
|
|
9
|
+
assert_not_empty message
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def test_easy_error_known_mappings
|
|
13
|
+
assert_easy_error_mapping(0, Curl::Err::CurlOK)
|
|
14
|
+
assert_easy_error_mapping(1, Curl::Err::UnsupportedProtocolError)
|
|
15
|
+
assert_easy_error_mapping(2, Curl::Err::FailedInitError)
|
|
16
|
+
assert_easy_error_mapping(3, Curl::Err::MalformedURLError)
|
|
17
|
+
assert_easy_error_mapping(5, Curl::Err::ProxyResolutionError)
|
|
18
|
+
assert_easy_error_mapping(6, Curl::Err::HostResolutionError)
|
|
19
|
+
assert_easy_error_mapping(7, Curl::Err::ConnectionFailedError)
|
|
20
|
+
assert_easy_error_mapping(23, Curl::Err::WriteError)
|
|
21
|
+
assert_easy_error_mapping(26, Curl::Err::ReadError)
|
|
22
|
+
assert_easy_error_mapping(28, Curl::Err::TimeoutError)
|
|
23
|
+
assert_easy_error_mapping(42, Curl::Err::AbortedByCallbackError)
|
|
24
|
+
assert_easy_error_mapping(47, Curl::Err::TooManyRedirectsError)
|
|
25
|
+
assert_easy_error_mapping(52, Curl::Err::GotNothingError)
|
|
26
|
+
assert_easy_error_mapping(55, Curl::Err::SendError)
|
|
27
|
+
assert_easy_error_mapping(56, Curl::Err::RecvError)
|
|
28
|
+
assert_easy_error_mapping(57, Curl::Err::ShareInUseError)
|
|
29
|
+
assert_easy_error_mapping(58, Curl::Err::SSLCertificateError)
|
|
30
|
+
assert_easy_error_mapping(59, Curl::Err::SSLCypherError)
|
|
31
|
+
assert_easy_error_mapping(61, Curl::Err::BadContentEncodingError)
|
|
32
|
+
assert_easy_error_mapping(63, Curl::Err::FileSizeExceededError)
|
|
33
|
+
assert_easy_error_mapping(64, Curl::Err::FTPSSLFailed)
|
|
34
|
+
|
|
35
|
+
ssl_peer_or_ca_error =
|
|
36
|
+
if Gem::Version.new(Curl::CURL_VERSION) >= Gem::Version.new('7.62.0')
|
|
37
|
+
Curl::Err::SSLPeerCertificateError
|
|
38
|
+
else
|
|
39
|
+
Curl::Err::SSLCACertificateError
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
assert_easy_error_mapping(60, ssl_peer_or_ca_error)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_easy_error_returns_error_info_for_known_numeric_range
|
|
46
|
+
0.upto(92) do |code|
|
|
47
|
+
error_class, message = Curl::Easy.error(code)
|
|
48
|
+
|
|
49
|
+
assert_kind_of Class, error_class
|
|
50
|
+
assert error_class <= Curl::Err::CurlError
|
|
51
|
+
assert_kind_of String, message
|
|
52
|
+
assert_not_empty message
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_easy_error_uses_generic_mapping_for_unknown_codes
|
|
57
|
+
error_class, message = Curl::Easy.error(9_999)
|
|
58
|
+
|
|
59
|
+
assert_equal Curl::Err::CurlError, error_class
|
|
60
|
+
assert_equal 'Unknown error result from libcurl', message
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class TestCurbCurlNativeCoverage < Test::Unit::TestCase
|
|
65
|
+
include TestServerMethods
|
|
66
|
+
|
|
67
|
+
def setup
|
|
68
|
+
server_setup
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def test_clone_preserves_native_lists_after_original_handle_closes
|
|
72
|
+
easy = Curl::Easy.new("http://curb.invalid:#{TestServlet.port}#{TestServlet.path}")
|
|
73
|
+
easy.headers['X-Test'] = '1'
|
|
74
|
+
easy.proxy_headers['X-Proxy'] = '2'
|
|
75
|
+
easy.ftp_commands = ['PWD']
|
|
76
|
+
easy.resolve = ["curb.invalid:#{TestServlet.port}:127.0.0.1"]
|
|
77
|
+
|
|
78
|
+
clone = easy.clone
|
|
79
|
+
easy.close
|
|
80
|
+
|
|
81
|
+
clone.http_get
|
|
82
|
+
|
|
83
|
+
assert_equal 'GET', clone.body_str
|
|
84
|
+
assert_equal '1', clone.headers['X-Test']
|
|
85
|
+
assert_equal '2', clone.proxy_headers['X-Proxy']
|
|
86
|
+
assert_equal ['PWD'], clone.ftp_commands
|
|
87
|
+
assert_equal ["curb.invalid:#{TestServlet.port}:127.0.0.1"], clone.resolve
|
|
88
|
+
ensure
|
|
89
|
+
clone.close if defined?(clone) && clone
|
|
90
|
+
easy.close if defined?(easy) && easy
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def test_clone_rebinds_upload_callbacks_to_clone_state
|
|
94
|
+
easy = Curl::Easy.new(TestServlet.url)
|
|
95
|
+
easy.put_data = 'clone-data'
|
|
96
|
+
|
|
97
|
+
clone = easy.clone
|
|
98
|
+
easy.put_data = 'other-data'
|
|
99
|
+
|
|
100
|
+
clone.perform
|
|
101
|
+
|
|
102
|
+
assert_equal "PUT\nclone-data", clone.body_str
|
|
103
|
+
ensure
|
|
104
|
+
clone.close if defined?(clone) && clone
|
|
105
|
+
easy.close if defined?(easy) && easy
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def test_native_accessors_round_trip
|
|
109
|
+
easy = Curl::Easy.new(TestServlet.url)
|
|
110
|
+
|
|
111
|
+
assert_equal '127.0.0.1', easy.interface = '127.0.0.1'
|
|
112
|
+
assert_equal 'user:pass', easy.userpwd = 'user:pass'
|
|
113
|
+
assert_equal 'proxy:pass', easy.proxypwd = 'proxy:pass'
|
|
114
|
+
assert_equal 'tests/cert.pem', easy.cert_key = 'tests/cert.pem'
|
|
115
|
+
assert_equal 'gzip', easy.encoding = 'gzip'
|
|
116
|
+
|
|
117
|
+
if easy.respond_to?(:max_send_speed_large=)
|
|
118
|
+
assert_equal 123, easy.max_send_speed_large = 123
|
|
119
|
+
assert_equal 123, easy.max_send_speed_large
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
if easy.respond_to?(:max_recv_speed_large=)
|
|
123
|
+
assert_equal 456, easy.max_recv_speed_large = 456
|
|
124
|
+
assert_equal 456, easy.max_recv_speed_large
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
assert_equal '127.0.0.1', easy.interface
|
|
128
|
+
assert_equal 'user:pass', easy.userpwd
|
|
129
|
+
assert_equal 'proxy:pass', easy.proxypwd
|
|
130
|
+
assert_equal 'tests/cert.pem', easy.cert_key
|
|
131
|
+
assert_equal 'gzip', easy.encoding
|
|
132
|
+
ensure
|
|
133
|
+
easy.close if defined?(easy) && easy
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def test_upload_round_trips_stream_and_offset
|
|
137
|
+
upload = Curl::Upload.new
|
|
138
|
+
stream = StringIO.new('payload')
|
|
139
|
+
|
|
140
|
+
assert_same stream, upload.stream = stream
|
|
141
|
+
assert_same stream, upload.stream
|
|
142
|
+
assert_equal 7, upload.offset = 7
|
|
143
|
+
assert_equal 7, upload.offset
|
|
144
|
+
end
|
|
145
|
+
end
|
data/tests/tc_curl_postfield.rb
CHANGED
|
@@ -136,9 +136,185 @@ class TestCurbCurlPostfield < Test::Unit::TestCase
|
|
|
136
136
|
assert_equal "foo=FOOBAR", pf.to_s
|
|
137
137
|
end
|
|
138
138
|
|
|
139
|
+
def test_content_proc_survives_gc
|
|
140
|
+
pf = postfield_with_only_native_proc_reference
|
|
141
|
+
|
|
142
|
+
10.times do
|
|
143
|
+
GC.start(full_mark: true, immediate_sweep: true)
|
|
144
|
+
GC.compact if GC.respond_to?(:compact)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
assert_equal "foo=FOOBAR", pf.to_s
|
|
148
|
+
end
|
|
149
|
+
|
|
139
150
|
def test_to_s_04
|
|
140
151
|
pf = Curl::PostField.file('foo.file', 'bar.file')
|
|
141
152
|
assert_nothing_raised { pf.to_s }
|
|
142
153
|
#assert_raise(Curl::Err::InvalidPostFieldError) { pf.to_s }
|
|
143
154
|
end
|
|
155
|
+
|
|
156
|
+
def postfield_with_only_native_proc_reference
|
|
157
|
+
Curl::PostField.content('foo') { |field| field.name.upcase + "BAR" }
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
class TestCurbCurlPostfieldNativeCoverage < Test::Unit::TestCase
|
|
162
|
+
def test_attribute_writers_round_trip
|
|
163
|
+
pf = Curl::PostField.content('foo', 'bar')
|
|
164
|
+
|
|
165
|
+
assert_equal 'renamed', pf.name = 'renamed'
|
|
166
|
+
assert_equal 'payload', pf.content = 'payload'
|
|
167
|
+
assert_equal 'text/plain', pf.content_type = 'text/plain'
|
|
168
|
+
assert_equal 'local.txt', pf.local_file = 'local.txt'
|
|
169
|
+
assert_equal 'remote.txt', pf.remote_file = 'remote.txt'
|
|
170
|
+
|
|
171
|
+
assert_equal 'renamed', pf.name
|
|
172
|
+
assert_equal 'payload', pf.content
|
|
173
|
+
assert_equal 'text/plain', pf.content_type
|
|
174
|
+
assert_equal 'local.txt', pf.local_file
|
|
175
|
+
assert_equal 'remote.txt', pf.remote_file
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def test_to_s_accepts_non_string_name_via_to_s
|
|
179
|
+
name_like = Object.new
|
|
180
|
+
def name_like.to_s
|
|
181
|
+
'fancy name'
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
pf = Curl::PostField.content(name_like, 'value')
|
|
185
|
+
assert_equal 'fancy%20name=value', pf.to_s
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def test_to_s_uses_remote_file_when_local_file_is_missing
|
|
189
|
+
pf = Curl::PostField.file('upload', 'local.txt', 'remote.txt')
|
|
190
|
+
pf.local_file = nil
|
|
191
|
+
|
|
192
|
+
assert_equal 'upload=remote.txt', pf.to_s
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def test_to_s_rejects_name_without_to_s
|
|
196
|
+
name_like = Class.new do
|
|
197
|
+
undef to_s
|
|
198
|
+
end.new
|
|
199
|
+
|
|
200
|
+
pf = Curl::PostField.content(name_like, 'value')
|
|
201
|
+
|
|
202
|
+
error = assert_raise(Curl::Err::InvalidPostFieldError) { pf.to_s }
|
|
203
|
+
assert_match(/Cannot convert unnamed field to string/, error.message)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def test_to_s_rejects_content_without_to_s
|
|
207
|
+
content_like = Class.new do
|
|
208
|
+
undef to_s
|
|
209
|
+
end.new
|
|
210
|
+
|
|
211
|
+
pf = Curl::PostField.content('name', content_like)
|
|
212
|
+
|
|
213
|
+
error = assert_raise(RuntimeError) { pf.to_s }
|
|
214
|
+
assert_match(/does not respond_to to_s/, error.message)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def test_multipart_rejects_unnamed_field
|
|
218
|
+
curl = Curl::Easy.new(TestServlet.url)
|
|
219
|
+
curl.multipart_form_post = true
|
|
220
|
+
pf = Curl::PostField.content('name', 'value')
|
|
221
|
+
pf.name = nil
|
|
222
|
+
|
|
223
|
+
error = assert_raise(Curl::Err::InvalidPostFieldError) { curl.http_post(pf) }
|
|
224
|
+
assert_match(/Cannot post unnamed field/, error.message)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def test_multipart_rejects_content_field_without_data
|
|
228
|
+
curl = Curl::Easy.new(TestServlet.url)
|
|
229
|
+
curl.multipart_form_post = true
|
|
230
|
+
pf = Curl::PostField.content('name', 'value')
|
|
231
|
+
pf.content = nil
|
|
232
|
+
|
|
233
|
+
error = assert_raise(Curl::Err::InvalidPostFieldError) { curl.http_post(pf) }
|
|
234
|
+
assert_match(/Cannot post content field with no data/, error.message)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def test_multipart_rejects_file_field_without_filename
|
|
238
|
+
curl = Curl::Easy.new(TestServlet.url)
|
|
239
|
+
curl.multipart_form_post = true
|
|
240
|
+
pf = Curl::PostField.file('upload', 'remote.txt') { 'payload' }
|
|
241
|
+
pf.local_file = 'dummy.txt'
|
|
242
|
+
pf.remote_file = nil
|
|
243
|
+
|
|
244
|
+
error = assert_raise(Curl::Err::InvalidPostFieldError) { curl.http_post(pf) }
|
|
245
|
+
assert_match(/Cannot post file upload field with no filename/, error.message)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
class TestCurbCurlPostfieldMultipartCoverage < Test::Unit::TestCase
|
|
250
|
+
include TestServerMethods
|
|
251
|
+
|
|
252
|
+
def setup
|
|
253
|
+
server_setup
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def test_multipart_content_variants_include_dynamic_and_typed_fields
|
|
257
|
+
curl = Curl::Easy.new(TestServlet.url)
|
|
258
|
+
curl.multipart_form_post = true
|
|
259
|
+
|
|
260
|
+
proc_without_type = Curl::PostField.content('proc_without_type') { 'alpha' }
|
|
261
|
+
proc_with_type = Curl::PostField.content('proc_with_type', 'text/plain') { 'beta' }
|
|
262
|
+
direct_with_type = Curl::PostField.content('direct_with_type', 'gamma', 'text/plain')
|
|
263
|
+
|
|
264
|
+
curl.http_post([proc_without_type, proc_with_type, direct_with_type])
|
|
265
|
+
body = curl.body_str
|
|
266
|
+
|
|
267
|
+
assert_match(/name="proc_without_type"/, body)
|
|
268
|
+
assert_match(/alpha/, body)
|
|
269
|
+
assert_match(/name="proc_with_type"/, body)
|
|
270
|
+
assert_match(/beta/, body)
|
|
271
|
+
assert_match(/name="direct_with_type"/, body)
|
|
272
|
+
assert_match(/gamma/, body)
|
|
273
|
+
assert_match(/Content-Type: text\/plain/, body)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def test_multipart_file_variants_include_buffered_and_local_uploads
|
|
277
|
+
readme = File.expand_path(File.join(File.dirname(__FILE__), '..', 'README.md'))
|
|
278
|
+
|
|
279
|
+
proc_without_type = Curl::PostField.file('proc_without_type', 'proc_without_type.txt') { 'alpha' }
|
|
280
|
+
proc_with_type = Curl::PostField.file('proc_with_type', 'proc_with_type.txt') { 'beta' }
|
|
281
|
+
proc_with_type.content_type = 'text/plain'
|
|
282
|
+
|
|
283
|
+
direct_without_type = Curl::PostField.file('direct_without_type', 'ignored.txt', 'direct_without_type.txt')
|
|
284
|
+
direct_without_type.content = 'gamma'
|
|
285
|
+
direct_without_type.local_file = nil
|
|
286
|
+
|
|
287
|
+
direct_with_type = Curl::PostField.file('direct_with_type', 'ignored.txt', 'direct_with_type.txt')
|
|
288
|
+
direct_with_type.content = 'delta'
|
|
289
|
+
direct_with_type.local_file = nil
|
|
290
|
+
direct_with_type.content_type = 'text/plain'
|
|
291
|
+
|
|
292
|
+
local_with_type = Curl::PostField.file('local_with_type', readme)
|
|
293
|
+
local_with_type.content_type = 'text/plain'
|
|
294
|
+
|
|
295
|
+
curl = Curl::Easy.new(TestServlet.url)
|
|
296
|
+
curl.multipart_form_post = true
|
|
297
|
+
curl.http_post([proc_without_type, proc_with_type, direct_without_type, direct_with_type, local_with_type])
|
|
298
|
+
body = curl.body_str
|
|
299
|
+
|
|
300
|
+
assert_match(/name="proc_without_type"/, body)
|
|
301
|
+
assert_match(/filename="proc_without_type.txt"/, body)
|
|
302
|
+
assert_match(/alpha/, body)
|
|
303
|
+
|
|
304
|
+
assert_match(/name="proc_with_type"/, body)
|
|
305
|
+
assert_match(/filename="proc_with_type.txt"/, body)
|
|
306
|
+
assert_match(/beta/, body)
|
|
307
|
+
|
|
308
|
+
assert_match(/name="direct_without_type"/, body)
|
|
309
|
+
assert_match(/filename="direct_without_type.txt"/, body)
|
|
310
|
+
assert_match(/gamma/, body)
|
|
311
|
+
|
|
312
|
+
assert_match(/name="direct_with_type"/, body)
|
|
313
|
+
assert_match(/filename="direct_with_type.txt"/, body)
|
|
314
|
+
assert_match(/delta/, body)
|
|
315
|
+
|
|
316
|
+
assert_match(/name="local_with_type"/, body)
|
|
317
|
+
assert_match(/Curb - Libcurl bindings for Ruby/, body)
|
|
318
|
+
assert_match(/Content-Type: text\/plain/, body)
|
|
319
|
+
end
|
|
144
320
|
end
|