curb 0.8.4 → 1.3.5
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 +7 -0
- data/README.md +522 -0
- data/Rakefile +79 -26
- data/ext/banned.h +32 -0
- data/ext/curb.c +549 -230
- data/ext/curb.h +19 -10
- data/ext/curb_easy.c +1935 -366
- data/ext/curb_easy.h +16 -0
- data/ext/curb_errors.c +115 -18
- data/ext/curb_errors.h +8 -5
- data/ext/curb_macros.h +33 -21
- data/ext/curb_multi.c +1858 -272
- data/ext/curb_multi.h +10 -3
- data/ext/curb_postfield.c +149 -77
- data/ext/curb_postfield.h +1 -0
- data/ext/curb_upload.c +38 -11
- data/ext/curb_upload.h +2 -0
- data/ext/extconf.rb +353 -35
- data/lib/curb.rb +1 -0
- data/lib/curl/easy.rb +314 -78
- data/lib/curl/multi.rb +134 -28
- data/lib/curl.rb +217 -11
- data/tests/bug_crash_on_debug.rb +14 -28
- data/tests/bug_crash_on_progress.rb +32 -16
- data/tests/bug_curb_easy_blocks_ruby_threads.rb +10 -15
- data/tests/bug_curb_easy_post_with_string_no_content_length_header.rb +6 -30
- data/tests/bug_follow_redirect_288.rb +83 -0
- data/tests/bug_instance_post_differs_from_class_post.rb +3 -5
- data/tests/bug_issue102.rb +1 -1
- data/tests/bug_issue_noproxy.rb +56 -0
- data/tests/bug_issue_post_redirect.rb +93 -0
- data/tests/bug_issue_spnego.rb +41 -0
- data/tests/bug_multi_segfault.rb +1 -0
- data/tests/bug_raise_on_callback.rb +30 -0
- data/tests/helper.rb +400 -44
- data/tests/leak_trace.rb +237 -0
- data/tests/mem_check.rb +3 -0
- data/tests/tc_curl.rb +31 -1
- data/tests/tc_curl_download.rb +12 -7
- data/tests/tc_curl_easy.rb +911 -63
- data/tests/tc_curl_easy_cookielist.rb +277 -0
- data/tests/tc_curl_easy_request_target.rb +41 -0
- data/tests/tc_curl_easy_resolve.rb +48 -0
- data/tests/tc_curl_maxfilesize.rb +12 -0
- data/tests/tc_curl_multi.rb +855 -53
- data/tests/tc_curl_native_coverage.rb +145 -0
- data/tests/tc_curl_postfield.rb +207 -30
- data/tests/tc_curl_protocols.rb +37 -0
- data/tests/tc_fiber_scheduler.rb +543 -0
- data/tests/tc_ftp_options.rb +39 -0
- data/tests/tc_gc_compact.rb +223 -0
- data/tests/tc_test_server_methods.rb +110 -0
- data/tests/timeout.rb +30 -6
- metadata +59 -36
- data/README +0 -194
data/tests/tc_curl_multi.rb
CHANGED
|
@@ -1,19 +1,216 @@
|
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
|
2
|
+
require 'set'
|
|
3
|
+
require 'tmpdir'
|
|
2
4
|
|
|
3
5
|
class TestCurbCurlMulti < Test::Unit::TestCase
|
|
4
6
|
def teardown
|
|
5
7
|
# get a better read on memory loss when running in valgrind
|
|
6
8
|
ObjectSpace.garbage_collect
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# for https://github.com/taf2/curb/issues/277
|
|
13
|
+
# must connect to an external
|
|
14
|
+
def test_connection_keepalive
|
|
15
|
+
omit('Not supported on Windows runners') if WINDOWS
|
|
16
|
+
# this test fails with libcurl 7.22.0. I didn't investigate, but it may be related
|
|
17
|
+
# to CURLOPT_MAXCONNECTS bug fixed in 7.30.0:
|
|
18
|
+
# https://github.com/curl/curl/commit/e87e76e2dc108efb1cae87df496416f49c55fca0
|
|
19
|
+
omit("Skip, libcurl too old (< 7.22.0)") if Curl::CURL_VERSION.to_f < 8 && Curl::CURL_VERSION.split('.')[1].to_i <= 22
|
|
20
|
+
|
|
21
|
+
port_socket = TCPServer.new('127.0.0.1', 0)
|
|
22
|
+
port = port_socket.addr[1]
|
|
23
|
+
port_socket.close
|
|
24
|
+
|
|
25
|
+
server = WEBrick::HTTPServer.new(:Port => port, :Logger => WEBRICK_TEST_LOG, :AccessLog => [])
|
|
26
|
+
server.mount_proc(TestServlet.path) do |req, res|
|
|
27
|
+
res.body = "GET#{req.query_string}"
|
|
28
|
+
res['Content-Type'] = 'text/plain'
|
|
29
|
+
end
|
|
30
|
+
server_thread = Thread.new(server) { |srv| srv.start }
|
|
31
|
+
wait_for_server_ready(port, thread: server_thread)
|
|
32
|
+
|
|
33
|
+
test_url = "http://127.0.0.1:#{port}#{TestServlet.path}"
|
|
34
|
+
Curl::Multi.autoclose = true
|
|
35
|
+
assert Curl::Multi.autoclose
|
|
36
|
+
|
|
37
|
+
if `which ss`.strip.size == 0
|
|
38
|
+
# osx need lsof still :(
|
|
39
|
+
open_fds = lambda do
|
|
40
|
+
out = `/usr/sbin/lsof -nP -a -p #{Process.pid} -iTCP:#{port} -sTCP:ESTABLISHED`
|
|
41
|
+
[out.lines.drop(1).size, 0].max
|
|
42
|
+
end
|
|
43
|
+
else
|
|
44
|
+
ss = `which ss`.strip
|
|
45
|
+
open_fds = lambda do
|
|
46
|
+
`#{ss} -tn4 state established dport = :#{port} | wc -l`.strip.to_i
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
Curl::Multi.autoclose = false
|
|
50
|
+
before_open = open_fds.call
|
|
51
|
+
#puts "before_open: #{before_open.inspect}"
|
|
52
|
+
assert !Curl::Multi.autoclose
|
|
53
|
+
multi = Curl::Multi.new
|
|
54
|
+
multi.max_connects = 1 # limit to 1 connection within the multi handle
|
|
55
|
+
|
|
56
|
+
did_complete = false
|
|
57
|
+
5.times do |n|
|
|
58
|
+
easy = Curl::Easy.new(test_url) do |curl|
|
|
59
|
+
curl.timeout = 5 # ensure we don't hang for ever connecting to an external host
|
|
60
|
+
curl.on_complete {
|
|
61
|
+
did_complete = true
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
multi.add(easy)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
multi.perform
|
|
68
|
+
assert did_complete
|
|
69
|
+
after_open = open_fds.call
|
|
70
|
+
#puts "after_open: #{after_open} before_open: #{before_open.inspect}"
|
|
71
|
+
# Some CI/libcurl combinations tear down the loopback connection before
|
|
72
|
+
# ss/lsof observes it, so only assert that the multi cache does not retain
|
|
73
|
+
# more than one extra connection here.
|
|
74
|
+
assert (after_open - before_open) < 3, "with max connections set to 1 the multi handle should not retain more than one extra connection"
|
|
75
|
+
multi.close
|
|
76
|
+
|
|
77
|
+
after_open = open_fds.call
|
|
78
|
+
#puts "after_open: #{after_open} before_open: #{before_open.inspect}"
|
|
79
|
+
assert_equal 0, (after_open - before_open), "after closing the multi handle all connections should be closed"
|
|
80
|
+
|
|
81
|
+
Curl::Multi.autoclose = true
|
|
82
|
+
multi = Curl::Multi.new
|
|
83
|
+
did_complete = false
|
|
84
|
+
5.times do |n|
|
|
85
|
+
easy = Curl::Easy.new(test_url) do |curl|
|
|
86
|
+
curl.timeout = 5 # ensure we don't hang for ever connecting to an external host
|
|
87
|
+
curl.on_complete {
|
|
88
|
+
did_complete = true
|
|
89
|
+
}
|
|
90
|
+
end
|
|
91
|
+
multi.add(easy)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
multi.perform
|
|
95
|
+
assert did_complete
|
|
96
|
+
after_open = open_fds.call
|
|
97
|
+
#puts "after_open: #{after_open} before_open: #{before_open.inspect}"
|
|
98
|
+
assert_equal 0, (after_open - before_open), "auto close the connections"
|
|
99
|
+
ensure
|
|
100
|
+
server.shutdown if defined?(server) && server
|
|
101
|
+
server_thread.join(server_startup_timeout) if defined?(server_thread) && server_thread
|
|
102
|
+
server_thread.kill if defined?(server_thread) && server_thread&.alive?
|
|
103
|
+
Curl::Multi.autoclose = false # restore default
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def test_connection_autoclose
|
|
107
|
+
assert !Curl::Multi.autoclose
|
|
108
|
+
Curl::Multi.autoclose = true
|
|
109
|
+
assert Curl::Multi.autoclose
|
|
110
|
+
ensure
|
|
111
|
+
Curl::Multi.autoclose = false # restore default
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def test_perform_can_reuse_multi_when_autoclose_is_enabled
|
|
115
|
+
Curl::Multi.autoclose = true
|
|
116
|
+
|
|
117
|
+
multi = Curl::Multi.new
|
|
118
|
+
results = []
|
|
119
|
+
|
|
120
|
+
first = Curl::Easy.new(TestServlet.url)
|
|
121
|
+
first.on_complete { results << first.code }
|
|
122
|
+
multi.add(first)
|
|
123
|
+
multi.perform
|
|
124
|
+
|
|
125
|
+
second = Curl::Easy.new(TestServlet.url)
|
|
126
|
+
second.on_complete { results << second.code }
|
|
127
|
+
multi.add(second)
|
|
128
|
+
multi.perform
|
|
129
|
+
|
|
130
|
+
assert_equal [200, 200], results
|
|
131
|
+
ensure
|
|
132
|
+
multi.close if defined?(multi) && multi
|
|
133
|
+
Curl::Multi.autoclose = false
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def test_close_inside_completion_callback_is_rejected_without_invalidating_multi
|
|
137
|
+
multi = Curl::Multi.new
|
|
138
|
+
easy = Curl::Easy.new(TestServlet.url)
|
|
139
|
+
|
|
140
|
+
easy.on_complete { multi.close }
|
|
141
|
+
multi.add(easy)
|
|
142
|
+
|
|
143
|
+
error = assert_raise(Curl::Err::AbortedByCallbackError) { multi.perform }
|
|
144
|
+
assert_match(/Cannot close an active Curl::Multi handle/, error.message)
|
|
145
|
+
assert multi.idle?
|
|
146
|
+
assert_equal({}, multi.requests)
|
|
147
|
+
|
|
148
|
+
easy.on_complete { |_curl| }
|
|
149
|
+
multi.add(easy)
|
|
150
|
+
multi.perform
|
|
151
|
+
|
|
152
|
+
assert_equal "GET", easy.body_str
|
|
153
|
+
ensure
|
|
154
|
+
multi.close if defined?(multi) && multi
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def test_close_inside_perform_block_is_rejected_without_invalidating_multi
|
|
158
|
+
multi = Curl::Multi.new
|
|
159
|
+
easy = Curl::Easy.new(TestServlet.url)
|
|
160
|
+
multi.add(easy)
|
|
161
|
+
|
|
162
|
+
error = assert_raise(RuntimeError) do
|
|
163
|
+
multi.perform { multi.close }
|
|
164
|
+
end
|
|
165
|
+
assert_match(/Cannot close an active Curl::Multi handle/, error.message)
|
|
166
|
+
|
|
167
|
+
assert_nothing_raised { multi.close }
|
|
168
|
+
multi = nil
|
|
169
|
+
ensure
|
|
170
|
+
multi.close if defined?(multi) && multi
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def test_active_easy_cannot_be_added_to_another_multi
|
|
174
|
+
first_multi = Curl::Multi.new
|
|
175
|
+
second_multi = Curl::Multi.new
|
|
176
|
+
easy = Curl::Easy.new(TestServlet.url)
|
|
177
|
+
|
|
178
|
+
first_multi.add(easy)
|
|
179
|
+
|
|
180
|
+
error = assert_raise(RuntimeError) { second_multi.add(easy) }
|
|
181
|
+
assert_match(/active Curl::Easy/, error.message)
|
|
182
|
+
assert_same first_multi, easy.multi
|
|
183
|
+
assert_equal easy, first_multi.requests[easy.object_id]
|
|
184
|
+
|
|
185
|
+
assert_nothing_raised { first_multi.close }
|
|
186
|
+
first_multi = nil
|
|
187
|
+
ensure
|
|
188
|
+
first_multi.close if defined?(first_multi) && first_multi
|
|
189
|
+
second_multi.close if defined?(second_multi) && second_multi
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def test_close_makes_multi_unusable
|
|
193
|
+
multi = Curl::Multi.new
|
|
194
|
+
multi.close
|
|
195
|
+
|
|
196
|
+
error = assert_raise(Curl::Err::MultiBadHandle) do
|
|
197
|
+
multi.add(Curl::Easy.new($TEST_URL))
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
assert_match(/Invalid multi handle/i, error.message)
|
|
201
|
+
assert_equal 0, multi.requests.length
|
|
202
|
+
ensure
|
|
203
|
+
multi.close if multi
|
|
7
204
|
end
|
|
8
205
|
|
|
9
206
|
def test_new_multi_01
|
|
10
|
-
d1 =
|
|
207
|
+
d1 = String.new
|
|
11
208
|
c1 = Curl::Easy.new($TEST_URL) do |curl|
|
|
12
209
|
curl.headers["User-Agent"] = "myapp-0.0"
|
|
13
210
|
curl.on_body {|d| d1 << d; d.length }
|
|
14
211
|
end
|
|
15
212
|
|
|
16
|
-
d2 =
|
|
213
|
+
d2 = String.new
|
|
17
214
|
c2 = Curl::Easy.new($TEST_URL) do |curl|
|
|
18
215
|
curl.headers["User-Agent"] = "myapp-0.0"
|
|
19
216
|
curl.on_body {|d| d2 << d; d.length }
|
|
@@ -28,9 +225,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
28
225
|
|
|
29
226
|
assert_match(/^# DO NOT REMOVE THIS COMMENT/, d1)
|
|
30
227
|
assert_match(/^# DO NOT REMOVE THIS COMMENT/, d2)
|
|
31
|
-
|
|
32
|
-
m
|
|
33
|
-
|
|
228
|
+
ensure
|
|
229
|
+
m.close if m
|
|
34
230
|
end
|
|
35
231
|
|
|
36
232
|
def test_perform_block
|
|
@@ -49,9 +245,424 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
49
245
|
|
|
50
246
|
assert_match(/^# DO NOT REMOVE THIS COMMENT/, c1.body_str)
|
|
51
247
|
assert_match(/^# DO NOT REMOVE THIS COMMENT/, c2.body_str)
|
|
248
|
+
ensure
|
|
249
|
+
m.close if m
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def test_multi_perform_runs_work_added_from_final_idle_yield
|
|
253
|
+
with_queue_refill_test_server do |port, hits|
|
|
254
|
+
previous_autoclose = Curl::Multi.autoclose
|
|
255
|
+
Curl::Multi.autoclose = true
|
|
256
|
+
|
|
257
|
+
multi = Curl::Multi.new
|
|
258
|
+
slow = Curl::Easy.new("http://127.0.0.1:#{port}/slow")
|
|
259
|
+
queued = Curl::Easy.new("http://127.0.0.1:#{port}/queued")
|
|
260
|
+
completions = []
|
|
261
|
+
empty_yields = 0
|
|
262
|
+
queued_added = false
|
|
263
|
+
|
|
264
|
+
slow.on_complete { completions << :slow }
|
|
265
|
+
queued.on_complete { completions << :queued }
|
|
266
|
+
|
|
267
|
+
multi.add(slow)
|
|
268
|
+
multi.perform do |performing_multi|
|
|
269
|
+
empty_yields += 1 if performing_multi.requests.empty?
|
|
270
|
+
if !queued_added && empty_yields >= 2
|
|
271
|
+
queued_added = true
|
|
272
|
+
performing_multi.add(queued)
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
assert queued_added, "test should add queued work from the final idle yield"
|
|
277
|
+
assert_equal [:slow, :queued], completions
|
|
278
|
+
assert_equal 1, hits[:slow]
|
|
279
|
+
assert_equal 1, hits[:queued]
|
|
280
|
+
assert_equal 0, multi.requests.length
|
|
281
|
+
ensure
|
|
282
|
+
Curl::Multi.autoclose = previous_autoclose if defined?(previous_autoclose)
|
|
283
|
+
multi.close if defined?(multi) && multi
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def test_multi_easy_get
|
|
288
|
+
n = 1
|
|
289
|
+
urls = []
|
|
290
|
+
n.times { urls << $TEST_URL }
|
|
291
|
+
Curl::Multi.get(urls, {timeout: 5}) {|easy|
|
|
292
|
+
assert_match(/file:/, easy.last_effective_url)
|
|
293
|
+
}
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def test_multi_easy_get_with_error
|
|
297
|
+
omit('Path/line parsing differs on Windows') if WINDOWS
|
|
298
|
+
begin
|
|
299
|
+
did_raise = false
|
|
300
|
+
n = 3
|
|
301
|
+
urls = []
|
|
302
|
+
n.times { urls << $TEST_URL }
|
|
303
|
+
error_line_number_should_be = nil
|
|
304
|
+
Curl::Multi.get(urls, {timeout: 5}) {|easy|
|
|
305
|
+
# if we got this right the error will be reported to be on the line below our error_line_number_should_be
|
|
306
|
+
error_line_number_should_be = __LINE__
|
|
307
|
+
raise
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
rescue Curl::Err::AbortedByCallbackError => e
|
|
311
|
+
did_raise = true
|
|
312
|
+
in_file = e.backtrace.detect {|err| err.match?(File.basename(__FILE__)) }
|
|
313
|
+
#in_file_stack = e.backtrace.select {|err| err.match?(File.basename(__FILE__)) }
|
|
314
|
+
assert_match(__FILE__, in_file)
|
|
315
|
+
in_file.gsub!(__FILE__)
|
|
316
|
+
parts = in_file.split(':')
|
|
317
|
+
parts.shift
|
|
318
|
+
line_no = parts.shift.to_i
|
|
319
|
+
assert_equal error_line_number_should_be+1, line_no.to_i
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
assert did_raise, "we should have raised an exception"
|
|
323
|
+
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def test_multi_perform_reraises_on_body_exception
|
|
327
|
+
multi = Curl::Multi.new
|
|
328
|
+
easy = Curl::Easy.new(TestServlet.url)
|
|
329
|
+
easy.on_body { raise "body blew up" }
|
|
330
|
+
|
|
331
|
+
error = assert_raise(RuntimeError) do
|
|
332
|
+
multi.add(easy)
|
|
333
|
+
multi.perform
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
assert_equal "body blew up", error.message
|
|
337
|
+
assert !easy.instance_variable_defined?(:@__curb_callback_error)
|
|
338
|
+
ensure
|
|
339
|
+
multi.close if multi
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def test_multi_perform_reraises_on_body_exception_for_frozen_easy
|
|
343
|
+
multi = Curl::Multi.new
|
|
344
|
+
easy = Curl::Easy.new($TEST_URL)
|
|
345
|
+
callback_error = Class.new(RuntimeError)
|
|
346
|
+
easy.on_body { raise callback_error, "body blew up" }
|
|
347
|
+
easy.freeze
|
|
348
|
+
|
|
349
|
+
error = assert_raise(callback_error) do
|
|
350
|
+
multi.add(easy)
|
|
351
|
+
multi.perform
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
assert_equal "body blew up", error.message
|
|
355
|
+
ensure
|
|
356
|
+
begin
|
|
357
|
+
multi.close if multi
|
|
358
|
+
rescue StandardError
|
|
359
|
+
multi.instance_variable_set(:@requests, {})
|
|
360
|
+
multi._close
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def test_multi_perform_reraises_on_header_exception
|
|
365
|
+
multi = Curl::Multi.new
|
|
366
|
+
easy = Curl::Easy.new(TestServlet.url)
|
|
367
|
+
easy.on_header { raise "header blew up" }
|
|
368
|
+
|
|
369
|
+
error = assert_raise(RuntimeError) do
|
|
370
|
+
multi.add(easy)
|
|
371
|
+
multi.perform
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
assert_equal "header blew up", error.message
|
|
375
|
+
assert !easy.instance_variable_defined?(:@__curb_callback_error)
|
|
376
|
+
ensure
|
|
377
|
+
multi.close if multi
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def test_multi_perform_drains_completed_siblings_before_reraising_on_body_exception
|
|
381
|
+
multi = Curl::Multi.new
|
|
382
|
+
failed = Curl::Easy.new($TEST_URL)
|
|
383
|
+
completed = Curl::Easy.new($TEST_URL)
|
|
384
|
+
completions = []
|
|
385
|
+
|
|
386
|
+
failed.on_body { raise "body blew up" }
|
|
387
|
+
completed.on_complete { completions << :completed }
|
|
388
|
+
|
|
389
|
+
error = assert_raise(RuntimeError) do
|
|
390
|
+
multi.add(failed)
|
|
391
|
+
multi.add(completed)
|
|
392
|
+
multi.perform
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
assert_equal "body blew up", error.message
|
|
396
|
+
assert_equal [:completed], completions
|
|
397
|
+
assert multi.idle?, 'The multi handle should be idle after draining the completed batch'
|
|
398
|
+
assert_equal 0, multi.requests.length
|
|
399
|
+
assert_nil failed.multi
|
|
400
|
+
assert_nil completed.multi
|
|
401
|
+
assert !failed.instance_variable_defined?(:@__curb_callback_error)
|
|
402
|
+
ensure
|
|
403
|
+
multi.close if multi
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def test_multi_perform_waits_until_idle_before_reraising_on_complete_exception
|
|
407
|
+
port_socket = TCPServer.new('127.0.0.1', 0)
|
|
408
|
+
port = port_socket.addr[1]
|
|
409
|
+
port_socket.close
|
|
410
|
+
|
|
411
|
+
server = WEBrick::HTTPServer.new(:Port => port, :Logger => WEBRICK_TEST_LOG, :AccessLog => [])
|
|
412
|
+
server.mount_proc('/fast') do |_req, res|
|
|
413
|
+
res['Content-Type'] = 'text/plain'
|
|
414
|
+
res.body = 'fast'
|
|
415
|
+
end
|
|
416
|
+
server.mount_proc('/slow') do |_req, res|
|
|
417
|
+
res['Content-Type'] = 'text/plain'
|
|
418
|
+
sleep 0.2
|
|
419
|
+
res.body = 'slow'
|
|
420
|
+
end
|
|
421
|
+
server_thread = Thread.new(server) { |srv| srv.start }
|
|
422
|
+
wait_for_server_ready(port, thread: server_thread)
|
|
423
|
+
|
|
424
|
+
multi = Curl::Multi.new
|
|
425
|
+
fast = Curl::Easy.new("http://127.0.0.1:#{port}/fast")
|
|
426
|
+
slow = Curl::Easy.new("http://127.0.0.1:#{port}/slow")
|
|
427
|
+
completions = []
|
|
428
|
+
|
|
429
|
+
fast.on_complete do
|
|
430
|
+
completions << :fast
|
|
431
|
+
raise "complete blew up"
|
|
432
|
+
end
|
|
433
|
+
slow.on_complete { completions << :slow }
|
|
434
|
+
|
|
435
|
+
error = assert_raise(Curl::Err::AbortedByCallbackError) do
|
|
436
|
+
multi.add(fast)
|
|
437
|
+
multi.add(slow)
|
|
438
|
+
multi.perform
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
assert_equal "complete blew up", error.message
|
|
442
|
+
assert_equal [:fast, :slow], completions
|
|
443
|
+
assert multi.idle?, 'The multi handle should be idle before the deferred exception is raised'
|
|
444
|
+
assert_equal 0, multi.requests.length
|
|
445
|
+
assert_nil fast.multi
|
|
446
|
+
assert_nil slow.multi
|
|
447
|
+
ensure
|
|
448
|
+
multi.close if multi
|
|
449
|
+
server.shutdown if defined?(server) && server
|
|
450
|
+
server_thread.join(server_startup_timeout) if defined?(server_thread) && server_thread
|
|
451
|
+
server_thread.kill if defined?(server_thread) && server_thread&.alive?
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
def test_multi_perform_keeps_yielding_block_while_draining_deferred_on_complete_exception
|
|
455
|
+
with_queue_refill_test_server do |port, hits|
|
|
456
|
+
multi = Curl::Multi.new
|
|
457
|
+
failed = Curl::Easy.new("http://127.0.0.1:#{port}/fail")
|
|
458
|
+
slow = Curl::Easy.new("http://127.0.0.1:#{port}/slow")
|
|
459
|
+
failure_seen = false
|
|
460
|
+
slow_completed = false
|
|
461
|
+
yielded_during_drain = false
|
|
462
|
+
|
|
463
|
+
failed.on_complete do
|
|
464
|
+
failure_seen = true
|
|
465
|
+
raise "complete blew up"
|
|
466
|
+
end
|
|
467
|
+
slow.on_complete { slow_completed = true }
|
|
468
|
+
|
|
469
|
+
error = assert_raise(Curl::Err::AbortedByCallbackError) do
|
|
470
|
+
multi.add(failed)
|
|
471
|
+
multi.add(slow)
|
|
472
|
+
multi.perform do
|
|
473
|
+
yielded_during_drain ||= failure_seen && !slow_completed
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
assert_equal "complete blew up", error.message
|
|
478
|
+
assert yielded_during_drain,
|
|
479
|
+
"perform block should continue yielding while draining a slow sibling after a deferred callback exception"
|
|
480
|
+
assert_equal 1, hits[:fail]
|
|
481
|
+
assert_equal 1, hits[:slow]
|
|
482
|
+
ensure
|
|
483
|
+
multi.close if multi
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
def test_multi_perform_does_not_start_queued_work_after_on_complete_exception
|
|
488
|
+
with_queue_refill_test_server do |port, hits|
|
|
489
|
+
multi = Curl::Multi.new
|
|
490
|
+
failed = Curl::Easy.new("http://127.0.0.1:#{port}/fail")
|
|
491
|
+
slow = Curl::Easy.new("http://127.0.0.1:#{port}/slow")
|
|
492
|
+
queued = Curl::Easy.new("http://127.0.0.1:#{port}/queued")
|
|
493
|
+
failure_seen = false
|
|
494
|
+
|
|
495
|
+
failed.on_complete do
|
|
496
|
+
failure_seen = true
|
|
497
|
+
raise "complete blew up"
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
error = assert_raise(Curl::Err::AbortedByCallbackError) do
|
|
501
|
+
multi.add(failed)
|
|
502
|
+
multi.add(slow)
|
|
503
|
+
multi.perform do
|
|
504
|
+
next unless failure_seen
|
|
505
|
+
|
|
506
|
+
multi.add(queued)
|
|
507
|
+
failure_seen = false
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
assert_equal "complete blew up", error.message
|
|
512
|
+
assert_equal 1, hits[:fail]
|
|
513
|
+
assert_equal 1, hits[:slow]
|
|
514
|
+
assert_equal 0, hits[:queued], "queued request should not start once a deferred callback exception is pending"
|
|
515
|
+
ensure
|
|
516
|
+
multi.close if multi
|
|
517
|
+
end
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
def test_multi_perform_does_not_start_work_added_within_failing_on_complete_callback
|
|
521
|
+
with_queue_refill_test_server do |port, hits|
|
|
522
|
+
multi = Curl::Multi.new
|
|
523
|
+
failed = Curl::Easy.new("http://127.0.0.1:#{port}/fail")
|
|
524
|
+
queued = Curl::Easy.new("http://127.0.0.1:#{port}/queued")
|
|
525
|
+
|
|
526
|
+
failed.on_complete do
|
|
527
|
+
multi.add(queued)
|
|
528
|
+
raise "complete blew up"
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
error = assert_raise(Curl::Err::AbortedByCallbackError) do
|
|
532
|
+
multi.add(failed)
|
|
533
|
+
multi.perform
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
assert_equal "complete blew up", error.message
|
|
537
|
+
assert_equal 1, hits[:fail]
|
|
538
|
+
assert_equal 0, hits[:queued],
|
|
539
|
+
"request added from a failing on_complete callback should not start once the multi begins aborting"
|
|
540
|
+
ensure
|
|
541
|
+
multi.close if multi
|
|
542
|
+
end
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
def test_multi_perform_does_not_restart_sibling_removed_and_readded_from_failing_on_complete_callback
|
|
546
|
+
with_queue_refill_test_server(wait_fail_until_slow: true) do |port, hits|
|
|
547
|
+
multi = Curl::Multi.new
|
|
548
|
+
failed = Curl::Easy.new("http://127.0.0.1:#{port}/fail")
|
|
549
|
+
slow = Curl::Easy.new("http://127.0.0.1:#{port}/slow")
|
|
550
|
+
|
|
551
|
+
failed.on_complete do
|
|
552
|
+
multi.remove(slow)
|
|
553
|
+
multi.add(slow)
|
|
554
|
+
raise "complete blew up"
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
error = assert_raise(Curl::Err::AbortedByCallbackError) do
|
|
558
|
+
multi.add(failed)
|
|
559
|
+
multi.add(slow)
|
|
560
|
+
multi.perform
|
|
561
|
+
end
|
|
52
562
|
|
|
53
|
-
|
|
563
|
+
assert_equal "complete blew up", error.message
|
|
564
|
+
assert_equal 1, hits[:fail]
|
|
565
|
+
assert_equal 1, hits[:slow],
|
|
566
|
+
"sibling removed and re-added from a failing on_complete callback should be treated as replacement work and not restarted"
|
|
567
|
+
ensure
|
|
568
|
+
multi.close if multi
|
|
569
|
+
end
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
def test_multi_perform_does_not_start_work_added_within_on_complete_after_on_body_exception
|
|
573
|
+
with_queue_refill_test_server do |port, hits|
|
|
574
|
+
multi = Curl::Multi.new
|
|
575
|
+
failed = Curl::Easy.new("http://127.0.0.1:#{port}/fail")
|
|
576
|
+
queued = Curl::Easy.new("http://127.0.0.1:#{port}/queued")
|
|
577
|
+
|
|
578
|
+
failed.on_body { raise "body blew up" }
|
|
579
|
+
failed.on_complete { multi.add(queued) }
|
|
580
|
+
|
|
581
|
+
error = assert_raise(RuntimeError) do
|
|
582
|
+
multi.add(failed)
|
|
583
|
+
multi.perform
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
assert_equal "body blew up", error.message
|
|
587
|
+
assert_equal 1, hits[:fail]
|
|
588
|
+
assert_equal 0, hits[:queued],
|
|
589
|
+
"request added from on_complete after a body callback exception should not start once the multi begins aborting"
|
|
590
|
+
ensure
|
|
591
|
+
multi.close if multi
|
|
592
|
+
end
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
def test_multi_http_does_not_fetch_queued_url_after_on_body_exception
|
|
596
|
+
with_queue_refill_test_server do |port, hits|
|
|
597
|
+
urls = [
|
|
598
|
+
{ :url => "http://127.0.0.1:#{port}/queued", :method => :get },
|
|
599
|
+
{ :url => "http://127.0.0.1:#{port}/slow", :method => :get },
|
|
600
|
+
{ :url => "http://127.0.0.1:#{port}/fail", :method => :get, :on_body => proc { raise "body blew up" } }
|
|
601
|
+
]
|
|
602
|
+
|
|
603
|
+
error = assert_raise(RuntimeError) do
|
|
604
|
+
Curl::Multi.http(urls, {:max_connects => 2}) { |_easy, _code, _method| }
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
assert_equal "body blew up", error.message
|
|
608
|
+
assert_equal 1, hits[:fail]
|
|
609
|
+
assert_equal 1, hits[:slow]
|
|
610
|
+
assert_equal 0, hits[:queued], "Curl::Multi.http should not refill queued urls after a callback abort"
|
|
611
|
+
end
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
def test_multi_perform_runs_status_callbacks_after_on_body_exception
|
|
615
|
+
multi = Curl::Multi.new
|
|
616
|
+
easy = Curl::Easy.new(TestServlet.url)
|
|
617
|
+
callbacks = []
|
|
618
|
+
|
|
619
|
+
easy.on_body { raise "body blew up" }
|
|
620
|
+
easy.on_complete { callbacks << :complete }
|
|
621
|
+
easy.on_failure do |_completed_easy, error_info|
|
|
622
|
+
callbacks << :failure
|
|
623
|
+
assert_equal Curl::Err::WriteError, error_info.first
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
error = assert_raise(RuntimeError) do
|
|
627
|
+
multi.add(easy)
|
|
628
|
+
multi.perform
|
|
629
|
+
end
|
|
54
630
|
|
|
631
|
+
assert_equal "body blew up", error.message
|
|
632
|
+
assert_equal [:complete, :failure], callbacks
|
|
633
|
+
assert multi.idle?
|
|
634
|
+
assert_equal 0, multi.requests.length
|
|
635
|
+
ensure
|
|
636
|
+
multi.close if multi
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
def test_multi_perform_preserves_on_body_exception_when_status_callbacks_raise
|
|
640
|
+
multi = Curl::Multi.new
|
|
641
|
+
easy = Curl::Easy.new(TestServlet.url)
|
|
642
|
+
callbacks = []
|
|
643
|
+
|
|
644
|
+
easy.on_body { raise "body blew up" }
|
|
645
|
+
easy.on_complete do
|
|
646
|
+
callbacks << :complete
|
|
647
|
+
raise "complete blew up"
|
|
648
|
+
end
|
|
649
|
+
easy.on_failure do |_completed_easy, error_info|
|
|
650
|
+
callbacks << :failure
|
|
651
|
+
assert_equal Curl::Err::WriteError, error_info.first
|
|
652
|
+
raise "failure blew up"
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
error = assert_raise(RuntimeError) do
|
|
656
|
+
multi.add(easy)
|
|
657
|
+
multi.perform
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
assert_equal "body blew up", error.message
|
|
661
|
+
assert_equal [:complete, :failure], callbacks
|
|
662
|
+
assert multi.idle?
|
|
663
|
+
assert_equal 0, multi.requests.length
|
|
664
|
+
ensure
|
|
665
|
+
multi.close if multi
|
|
55
666
|
end
|
|
56
667
|
|
|
57
668
|
# NOTE: if this test runs slowly on Mac OSX, it is probably due to the use of a port install curl+ssl+ares install
|
|
@@ -61,7 +672,7 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
61
672
|
m = Curl::Multi.new
|
|
62
673
|
responses = []
|
|
63
674
|
n.times do|i|
|
|
64
|
-
responses[i] =
|
|
675
|
+
responses[i] = String.new
|
|
65
676
|
c = Curl::Easy.new($TEST_URL) do|curl|
|
|
66
677
|
curl.on_body{|data| responses[i] << data; data.size }
|
|
67
678
|
end
|
|
@@ -74,8 +685,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
74
685
|
n.times do|i|
|
|
75
686
|
assert_match(/^# DO NOT REMOVE THIS COMMENT/, responses[i], "response #{i}")
|
|
76
687
|
end
|
|
77
|
-
|
|
78
|
-
m
|
|
688
|
+
ensure
|
|
689
|
+
m.close if m
|
|
79
690
|
end
|
|
80
691
|
|
|
81
692
|
def test_n_requests_with_break
|
|
@@ -85,7 +696,7 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
85
696
|
5.times do|it|
|
|
86
697
|
responses = []
|
|
87
698
|
n.times do|i|
|
|
88
|
-
responses[i] =
|
|
699
|
+
responses[i] = String.new
|
|
89
700
|
c = Curl::Easy.new($TEST_URL) do|curl|
|
|
90
701
|
curl.on_body{|data| responses[i] << data; data.size }
|
|
91
702
|
end
|
|
@@ -98,9 +709,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
98
709
|
assert_match(/^# DO NOT REMOVE THIS COMMENT/, responses[i], "response #{i}")
|
|
99
710
|
end
|
|
100
711
|
end
|
|
101
|
-
|
|
102
|
-
m
|
|
103
|
-
|
|
712
|
+
ensure
|
|
713
|
+
m.close if m
|
|
104
714
|
end
|
|
105
715
|
|
|
106
716
|
def test_idle_check
|
|
@@ -109,19 +719,25 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
109
719
|
|
|
110
720
|
assert(m.idle?, 'A new Curl::Multi handle should be idle')
|
|
111
721
|
|
|
722
|
+
assert_nil e.multi
|
|
723
|
+
|
|
112
724
|
m.add(e)
|
|
113
725
|
|
|
726
|
+
assert_not_nil e.multi
|
|
727
|
+
|
|
114
728
|
assert((not m.idle?), 'A Curl::Multi handle with a request should not be idle')
|
|
115
729
|
|
|
116
730
|
m.perform
|
|
117
731
|
|
|
118
732
|
assert(m.idle?, 'A Curl::Multi handle should be idle after performing its requests')
|
|
733
|
+
ensure
|
|
734
|
+
m.close if m
|
|
119
735
|
end
|
|
120
736
|
|
|
121
737
|
def test_requests
|
|
122
738
|
m = Curl::Multi.new
|
|
123
739
|
|
|
124
|
-
assert_equal(
|
|
740
|
+
assert_equal(0, m.requests.length, 'A new Curl::Multi handle should have no requests')
|
|
125
741
|
|
|
126
742
|
10.times do
|
|
127
743
|
m.add(Curl::Easy.new($TEST_URL))
|
|
@@ -131,7 +747,46 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
131
747
|
|
|
132
748
|
m.perform
|
|
133
749
|
|
|
134
|
-
assert_equal(
|
|
750
|
+
assert_equal(0, m.requests.length, 'A new Curl::Multi handle should have no requests after a perform')
|
|
751
|
+
ensure
|
|
752
|
+
m.close if m
|
|
753
|
+
end
|
|
754
|
+
|
|
755
|
+
def test_easy_multi_is_cleared_after_perform
|
|
756
|
+
m = Curl::Multi.new
|
|
757
|
+
c = Curl::Easy.new($TEST_URL)
|
|
758
|
+
|
|
759
|
+
m.add(c)
|
|
760
|
+
assert_equal m, c.multi
|
|
761
|
+
|
|
762
|
+
m.perform
|
|
763
|
+
|
|
764
|
+
assert_nil c.multi
|
|
765
|
+
ensure
|
|
766
|
+
m.close if m
|
|
767
|
+
end
|
|
768
|
+
|
|
769
|
+
def test_easy_multi_is_available_during_completion_callbacks
|
|
770
|
+
multi = Curl::Multi.new
|
|
771
|
+
easy = Curl::Easy.new($TEST_URL)
|
|
772
|
+
seen_multi = {}
|
|
773
|
+
|
|
774
|
+
easy.on_complete do |completed_easy|
|
|
775
|
+
seen_multi[:complete] = completed_easy.multi
|
|
776
|
+
end
|
|
777
|
+
|
|
778
|
+
easy.on_success do |completed_easy|
|
|
779
|
+
seen_multi[:success] = completed_easy.multi
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
multi.add(easy)
|
|
783
|
+
multi.perform
|
|
784
|
+
|
|
785
|
+
assert_same multi, seen_multi[:complete]
|
|
786
|
+
assert_same multi, seen_multi[:success]
|
|
787
|
+
assert_nil easy.multi
|
|
788
|
+
ensure
|
|
789
|
+
multi.close if multi
|
|
135
790
|
end
|
|
136
791
|
|
|
137
792
|
def test_cancel
|
|
@@ -144,7 +799,9 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
144
799
|
|
|
145
800
|
m.cancel!
|
|
146
801
|
|
|
147
|
-
assert_equal(
|
|
802
|
+
assert_equal(0, m.requests.size, 'A new Curl::Multi handle should have no requests after being canceled')
|
|
803
|
+
ensure
|
|
804
|
+
m.close if m
|
|
148
805
|
end
|
|
149
806
|
|
|
150
807
|
def test_with_success
|
|
@@ -175,8 +832,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
175
832
|
|
|
176
833
|
assert success_called2
|
|
177
834
|
assert success_called1
|
|
178
|
-
|
|
179
|
-
m
|
|
835
|
+
ensure
|
|
836
|
+
m.close if m
|
|
180
837
|
end
|
|
181
838
|
|
|
182
839
|
def test_with_success_cb_with_404
|
|
@@ -188,7 +845,7 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
188
845
|
c1.on_success do|c|
|
|
189
846
|
success_called1 = true
|
|
190
847
|
#puts "success 1 called: #{c.body_str.inspect}"
|
|
191
|
-
|
|
848
|
+
assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body_str)
|
|
192
849
|
end
|
|
193
850
|
|
|
194
851
|
c1.on_failure do|c,rc|
|
|
@@ -218,8 +875,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
218
875
|
|
|
219
876
|
assert success_called2
|
|
220
877
|
assert !success_called1
|
|
221
|
-
|
|
222
|
-
m
|
|
878
|
+
ensure
|
|
879
|
+
m.close if m
|
|
223
880
|
end
|
|
224
881
|
|
|
225
882
|
# This tests whether, ruby's GC will trash an out of scope easy handle
|
|
@@ -227,7 +884,7 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
227
884
|
attr_reader :buf
|
|
228
885
|
|
|
229
886
|
def t_method
|
|
230
|
-
@buf =
|
|
887
|
+
@buf = String.new
|
|
231
888
|
@m = Curl::Multi.new
|
|
232
889
|
10.times do|i|
|
|
233
890
|
c = Curl::Easy.new($TEST_URL)
|
|
@@ -243,6 +900,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
243
900
|
@m.perform do
|
|
244
901
|
ObjectSpace.garbage_collect
|
|
245
902
|
end
|
|
903
|
+
ensure
|
|
904
|
+
@m.close if @m
|
|
246
905
|
end
|
|
247
906
|
|
|
248
907
|
def self.test
|
|
@@ -273,8 +932,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
273
932
|
m = Curl::Multi.new
|
|
274
933
|
# add a few easy handles
|
|
275
934
|
requests.each do |url|
|
|
276
|
-
responses[url] =
|
|
277
|
-
responses["#{url}-header"] =
|
|
935
|
+
responses[url] = String.new
|
|
936
|
+
responses["#{url}-header"] = String.new
|
|
278
937
|
c = Curl::Easy.new(url) do|curl|
|
|
279
938
|
curl.follow_location = true
|
|
280
939
|
curl.on_header{|data| responses["#{url}-header"] << data; data.size }
|
|
@@ -314,24 +973,22 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
314
973
|
urls = []
|
|
315
974
|
downloads = []
|
|
316
975
|
file_info = {}
|
|
317
|
-
FileUtils.mkdir("tmp/")
|
|
318
976
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
977
|
+
Dir.mktmpdir('curb-download-') do |download_dir|
|
|
978
|
+
# for each file store the size by file name
|
|
979
|
+
Dir[File.dirname(__FILE__) + "/../ext/*.c"].each do|path|
|
|
980
|
+
urls << (root_uri + File.basename(path))
|
|
981
|
+
downloads << File.join(download_dir, File.basename(path))
|
|
982
|
+
file_info[File.basename(path)] = {:size => File.size(path), :path => path}
|
|
983
|
+
end
|
|
325
984
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
985
|
+
# start downloads
|
|
986
|
+
Curl::Multi.download(urls,{},{},downloads) do|curl,download_path|
|
|
987
|
+
assert_equal 200, curl.response_code
|
|
988
|
+
assert File.exist?(download_path)
|
|
989
|
+
assert_equal file_info[File.basename(download_path)][:size], File.size(download_path), "incomplete download: #{download_path}"
|
|
990
|
+
end
|
|
332
991
|
end
|
|
333
|
-
ensure
|
|
334
|
-
FileUtils.rm_rf("tmp/")
|
|
335
992
|
end
|
|
336
993
|
|
|
337
994
|
def test_multi_easy_post_01
|
|
@@ -342,7 +999,7 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
342
999
|
]
|
|
343
1000
|
Curl::Multi.post(urls, {:follow_location => true, :multipart_form_post => true}, {:pipeline => true}) do|easy|
|
|
344
1001
|
str = easy.body_str
|
|
345
|
-
assert_match
|
|
1002
|
+
assert_match(/POST/, str)
|
|
346
1003
|
fields = {}
|
|
347
1004
|
str.gsub(/POST\n/,'').split('&').map{|sv| k, v = sv.split('='); fields[k] = v }
|
|
348
1005
|
expected = urls.find{|s| s[:url] == easy.last_effective_url }
|
|
@@ -357,8 +1014,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
357
1014
|
{ :url => TestServlet.url, :method => :put, :put_data => "message",
|
|
358
1015
|
:headers => {'Content-Type' => 'application/json' } }]
|
|
359
1016
|
Curl::Multi.put(urls, {}, {:pipeline => true}) do|easy|
|
|
360
|
-
assert_match
|
|
361
|
-
assert_match
|
|
1017
|
+
assert_match(/PUT/, easy.body_str)
|
|
1018
|
+
assert_match(/message/, easy.body_str)
|
|
362
1019
|
end
|
|
363
1020
|
end
|
|
364
1021
|
|
|
@@ -372,38 +1029,86 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
372
1029
|
{ :url => TestServlet.url, :method => :get }
|
|
373
1030
|
]
|
|
374
1031
|
Curl::Multi.http(urls, {:pipeline => true}) do|easy, code, method|
|
|
375
|
-
assert_equal
|
|
1032
|
+
assert_equal 200, code
|
|
376
1033
|
case method
|
|
377
1034
|
when :post
|
|
378
|
-
assert_match
|
|
1035
|
+
assert_match(/POST/, easy.body_str)
|
|
379
1036
|
when :get
|
|
380
|
-
assert_match
|
|
1037
|
+
assert_match(/GET/, easy.body_str)
|
|
381
1038
|
when :put
|
|
382
|
-
assert_match
|
|
1039
|
+
assert_match(/PUT/, easy.body_str)
|
|
383
1040
|
end
|
|
384
1041
|
#puts "#{easy.body_str.inspect}, #{method.inspect}, #{code.inspect}"
|
|
385
1042
|
end
|
|
386
1043
|
end
|
|
387
1044
|
|
|
388
1045
|
def test_multi_easy_http_with_max_connects
|
|
389
|
-
|
|
1046
|
+
urls = [
|
|
390
1047
|
{ :url => TestServlet.url + '?q=1', :method => :get },
|
|
391
1048
|
{ :url => TestServlet.url + '?q=2', :method => :get },
|
|
392
1049
|
{ :url => TestServlet.url + '?q=3', :method => :get }
|
|
393
1050
|
]
|
|
394
1051
|
Curl::Multi.http(urls, {:pipeline => true, :max_connects => 1}) do|easy, code, method|
|
|
395
|
-
assert_equal
|
|
1052
|
+
assert_equal 200, code
|
|
396
1053
|
case method
|
|
397
1054
|
when :post
|
|
398
|
-
assert_match
|
|
1055
|
+
assert_match(/POST/, easy.body)
|
|
399
1056
|
when :get
|
|
400
|
-
assert_match
|
|
1057
|
+
assert_match(/GET/, easy.body)
|
|
401
1058
|
when :put
|
|
402
|
-
assert_match
|
|
1059
|
+
assert_match(/PUT/, easy.body)
|
|
403
1060
|
end
|
|
404
1061
|
end
|
|
405
1062
|
end
|
|
406
1063
|
|
|
1064
|
+
def test_multi_easy_http_with_max_host_connections
|
|
1065
|
+
urls = [
|
|
1066
|
+
{ :url => TestServlet.url + '?q=1', :method => :get },
|
|
1067
|
+
{ :url => TestServlet.url + '?q=2', :method => :get },
|
|
1068
|
+
{ :url => TestServlet.url + '?q=3', :method => :get }
|
|
1069
|
+
]
|
|
1070
|
+
Curl::Multi.http(urls, {:pipeline => true, :max_host_connections => 1}) do|easy, code, method|
|
|
1071
|
+
assert_equal 200, code
|
|
1072
|
+
case method
|
|
1073
|
+
when :post
|
|
1074
|
+
assert_match(/POST/, easy.body)
|
|
1075
|
+
when :get
|
|
1076
|
+
assert_match(/GET/, easy.body)
|
|
1077
|
+
when :put
|
|
1078
|
+
assert_match(/PUT/, easy.body)
|
|
1079
|
+
end
|
|
1080
|
+
end
|
|
1081
|
+
end
|
|
1082
|
+
|
|
1083
|
+
# Regression test for issue #267 (2015): ensure that when reusing
|
|
1084
|
+
# easy handles with constrained concurrency, the callback receives
|
|
1085
|
+
# the correct URL for each completed request rather than repeating
|
|
1086
|
+
# the first URL.
|
|
1087
|
+
def test_multi_easy_http_urls_unique_across_max_connects
|
|
1088
|
+
urls = [
|
|
1089
|
+
{ :url => TestServlet.url + '?q=1', :method => :get },
|
|
1090
|
+
{ :url => TestServlet.url + '?q=2', :method => :get },
|
|
1091
|
+
{ :url => TestServlet.url + '?q=3', :method => :get }
|
|
1092
|
+
]
|
|
1093
|
+
|
|
1094
|
+
[1, 2, 3].each do |max|
|
|
1095
|
+
results = []
|
|
1096
|
+
Curl::Multi.http(urls.dup, {:pipeline => true, :max_connects => max}) do |easy, code, method|
|
|
1097
|
+
assert_equal 200, code
|
|
1098
|
+
assert_equal :get, method
|
|
1099
|
+
results << easy.last_effective_url
|
|
1100
|
+
end
|
|
1101
|
+
|
|
1102
|
+
# Ensure we saw one completion per input URL
|
|
1103
|
+
assert_equal urls.size, results.size, "expected #{urls.size} results with max_connects=#{max}"
|
|
1104
|
+
|
|
1105
|
+
# And that each URL completed exactly once (no accidental reuse/mis-reporting)
|
|
1106
|
+
expected_urls = urls.map { |u| u[:url] }
|
|
1107
|
+
assert_equal expected_urls.to_set, results.to_set, "unexpected URLs for max_connects=#{max}: #{results.inspect}"
|
|
1108
|
+
assert_equal expected_urls.size, results.uniq.size, "duplicate URLs seen for max_connects=#{max}: #{results.inspect}"
|
|
1109
|
+
end
|
|
1110
|
+
end
|
|
1111
|
+
|
|
407
1112
|
def test_multi_recieves_500
|
|
408
1113
|
m = Curl::Multi.new
|
|
409
1114
|
e = Curl::Easy.new("http://127.0.0.1:9129/methods")
|
|
@@ -422,6 +1127,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
422
1127
|
failure = false
|
|
423
1128
|
assert !failure
|
|
424
1129
|
assert_equal "POST\nhello=world", e2.body_str
|
|
1130
|
+
ensure
|
|
1131
|
+
m.close if m
|
|
425
1132
|
end
|
|
426
1133
|
|
|
427
1134
|
def test_remove_exception_is_descriptive
|
|
@@ -431,6 +1138,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
431
1138
|
rescue => e
|
|
432
1139
|
assert_equal 'CURLError: Invalid easy handle', e.message
|
|
433
1140
|
assert_equal 0, m.requests.size
|
|
1141
|
+
ensure
|
|
1142
|
+
m.close if m
|
|
434
1143
|
end
|
|
435
1144
|
|
|
436
1145
|
def test_retry_easy_handle
|
|
@@ -454,6 +1163,50 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
454
1163
|
m.perform
|
|
455
1164
|
assert_equal 0, tries
|
|
456
1165
|
assert_equal 0, m.requests.size
|
|
1166
|
+
ensure
|
|
1167
|
+
m.close if m
|
|
1168
|
+
end
|
|
1169
|
+
|
|
1170
|
+
def test_close_in_on_missing_callback_is_blocked
|
|
1171
|
+
m = Curl::Multi.new
|
|
1172
|
+
c = Curl::Easy.new(TestServlet.url + '/not_here')
|
|
1173
|
+
did_raise = false
|
|
1174
|
+
|
|
1175
|
+
c.on_missing do |easy, _error|
|
|
1176
|
+
begin
|
|
1177
|
+
easy.close
|
|
1178
|
+
rescue RuntimeError
|
|
1179
|
+
did_raise = true
|
|
1180
|
+
end
|
|
1181
|
+
end
|
|
1182
|
+
|
|
1183
|
+
m.add(c)
|
|
1184
|
+
m.perform
|
|
1185
|
+
|
|
1186
|
+
assert did_raise
|
|
1187
|
+
ensure
|
|
1188
|
+
m.close if m
|
|
1189
|
+
end
|
|
1190
|
+
|
|
1191
|
+
def test_close_in_on_redirect_callback_is_blocked
|
|
1192
|
+
m = Curl::Multi.new
|
|
1193
|
+
c = Curl::Easy.new(TestServlet.url + '/redirect')
|
|
1194
|
+
did_raise = false
|
|
1195
|
+
|
|
1196
|
+
c.on_redirect do |easy, _error|
|
|
1197
|
+
begin
|
|
1198
|
+
easy.close
|
|
1199
|
+
rescue RuntimeError
|
|
1200
|
+
did_raise = true
|
|
1201
|
+
end
|
|
1202
|
+
end
|
|
1203
|
+
|
|
1204
|
+
m.add(c)
|
|
1205
|
+
m.perform
|
|
1206
|
+
|
|
1207
|
+
assert did_raise
|
|
1208
|
+
ensure
|
|
1209
|
+
m.close if m
|
|
457
1210
|
end
|
|
458
1211
|
|
|
459
1212
|
def test_reusing_handle
|
|
@@ -466,7 +1219,9 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
466
1219
|
m.add(c)
|
|
467
1220
|
m.add(c)
|
|
468
1221
|
rescue => e
|
|
469
|
-
|
|
1222
|
+
assert Curl::Err::MultiBadEasyHandle == e.class || Curl::Err::MultiAddedAlready == e.class
|
|
1223
|
+
ensure
|
|
1224
|
+
m.close if m
|
|
470
1225
|
end
|
|
471
1226
|
|
|
472
1227
|
def test_multi_default_timeout
|
|
@@ -476,10 +1231,57 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
476
1231
|
assert_equal 100, (Curl::Multi.default_timeout = 100)
|
|
477
1232
|
end
|
|
478
1233
|
|
|
1234
|
+
def with_queue_refill_test_server(wait_fail_until_slow: false)
|
|
1235
|
+
port_socket = TCPServer.new('127.0.0.1', 0)
|
|
1236
|
+
port = port_socket.addr[1]
|
|
1237
|
+
port_socket.close
|
|
1238
|
+
|
|
1239
|
+
hits = Hash.new(0)
|
|
1240
|
+
slow_started = false
|
|
1241
|
+
slow_started_lock = Mutex.new
|
|
1242
|
+
slow_started_condition = ConditionVariable.new
|
|
1243
|
+
server = WEBrick::HTTPServer.new(:Port => port, :Logger => WEBRICK_TEST_LOG, :AccessLog => [])
|
|
1244
|
+
server.mount_proc('/fail') do |_req, res|
|
|
1245
|
+
if wait_fail_until_slow
|
|
1246
|
+
slow_started_lock.synchronize do
|
|
1247
|
+
slow_started_condition.wait(slow_started_lock, 1) unless slow_started
|
|
1248
|
+
end
|
|
1249
|
+
end
|
|
1250
|
+
|
|
1251
|
+
hits[:fail] += 1
|
|
1252
|
+
res['Content-Type'] = 'text/plain'
|
|
1253
|
+
res.body = 'fail'
|
|
1254
|
+
end
|
|
1255
|
+
server.mount_proc('/slow') do |_req, res|
|
|
1256
|
+
slow_started_lock.synchronize do
|
|
1257
|
+
hits[:slow] += 1
|
|
1258
|
+
slow_started = true
|
|
1259
|
+
slow_started_condition.broadcast
|
|
1260
|
+
end
|
|
1261
|
+
|
|
1262
|
+
res['Content-Type'] = 'text/plain'
|
|
1263
|
+
sleep 0.3
|
|
1264
|
+
res.body = 'slow'
|
|
1265
|
+
end
|
|
1266
|
+
server.mount_proc('/queued') do |_req, res|
|
|
1267
|
+
hits[:queued] += 1
|
|
1268
|
+
res['Content-Type'] = 'text/plain'
|
|
1269
|
+
res.body = 'queued'
|
|
1270
|
+
end
|
|
1271
|
+
|
|
1272
|
+
server_thread = Thread.new(server) { |srv| srv.start }
|
|
1273
|
+
wait_for_server_ready(port, thread: server_thread)
|
|
1274
|
+
|
|
1275
|
+
yield port, hits
|
|
1276
|
+
ensure
|
|
1277
|
+
server.shutdown if defined?(server) && server
|
|
1278
|
+
server_thread.join(server_startup_timeout) if defined?(server_thread) && server_thread
|
|
1279
|
+
server_thread.kill if defined?(server_thread) && server_thread&.alive?
|
|
1280
|
+
end
|
|
1281
|
+
|
|
479
1282
|
include TestServerMethods
|
|
480
1283
|
|
|
481
1284
|
def setup
|
|
482
1285
|
server_setup
|
|
483
1286
|
end
|
|
484
|
-
|
|
485
1287
|
end
|