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
data/tests/tc_curl_multi.rb
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
|
2
2
|
require 'set'
|
|
3
|
+
require 'tmpdir'
|
|
3
4
|
|
|
4
5
|
class TestCurbCurlMulti < Test::Unit::TestCase
|
|
5
6
|
def teardown
|
|
6
7
|
# get a better read on memory loss when running in valgrind
|
|
7
8
|
ObjectSpace.garbage_collect
|
|
9
|
+
super
|
|
8
10
|
end
|
|
9
11
|
|
|
10
12
|
# for https://github.com/taf2/curb/issues/277
|
|
@@ -16,41 +18,32 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
16
18
|
# https://github.com/curl/curl/commit/e87e76e2dc108efb1cae87df496416f49c55fca0
|
|
17
19
|
omit("Skip, libcurl too old (< 7.22.0)") if Curl::CURL_VERSION.to_f < 8 && Curl::CURL_VERSION.split('.')[1].to_i <= 22
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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}"
|
|
23
34
|
Curl::Multi.autoclose = true
|
|
24
35
|
assert Curl::Multi.autoclose
|
|
25
|
-
# XXX: thought maybe we can clean house here to have the full suite pass in osx... but for now running this test in isolate does pass
|
|
26
|
-
# additionally, if ss allows this to pass on linux without requesting google i think this is a good trade off... leaving some of the thoughts below
|
|
27
|
-
# in hopes that coming back to this later will find it and remember how to fix it
|
|
28
|
-
# types = Set.new
|
|
29
|
-
# close_types = Set.new([TCPServer,TCPSocket,Socket,Curl::Multi, Curl::Easy,WEBrick::Log])
|
|
30
|
-
# ObjectSpace.each_object {|o|
|
|
31
|
-
# if o.respond_to?(:close)
|
|
32
|
-
# types << o.class
|
|
33
|
-
# end
|
|
34
|
-
# if close_types.include?(o.class)
|
|
35
|
-
# o.close
|
|
36
|
-
# end
|
|
37
|
-
# }
|
|
38
|
-
#puts "unique types: #{types.to_a.join("\n")}"
|
|
39
|
-
GC.start # cleanup FDs left over from other tests
|
|
40
|
-
server_setup
|
|
41
|
-
GC.start # cleanup FDs left over from other tests
|
|
42
36
|
|
|
43
37
|
if `which ss`.strip.size == 0
|
|
44
38
|
# osx need lsof still :(
|
|
45
39
|
open_fds = lambda do
|
|
46
|
-
out = `/usr/sbin/lsof -p #{Process.pid}
|
|
47
|
-
|
|
48
|
-
out.lines.size
|
|
40
|
+
out = `/usr/sbin/lsof -nP -a -p #{Process.pid} -iTCP:#{port} -sTCP:ESTABLISHED`
|
|
41
|
+
[out.lines.drop(1).size, 0].max
|
|
49
42
|
end
|
|
50
43
|
else
|
|
51
44
|
ss = `which ss`.strip
|
|
52
45
|
open_fds = lambda do
|
|
53
|
-
`#{ss} -
|
|
46
|
+
`#{ss} -tn4 state established dport = :#{port} | wc -l`.strip.to_i
|
|
54
47
|
end
|
|
55
48
|
end
|
|
56
49
|
Curl::Multi.autoclose = false
|
|
@@ -62,7 +55,7 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
62
55
|
|
|
63
56
|
did_complete = false
|
|
64
57
|
5.times do |n|
|
|
65
|
-
easy = Curl::Easy.new(
|
|
58
|
+
easy = Curl::Easy.new(test_url) do |curl|
|
|
66
59
|
curl.timeout = 5 # ensure we don't hang for ever connecting to an external host
|
|
67
60
|
curl.on_complete {
|
|
68
61
|
did_complete = true
|
|
@@ -75,9 +68,10 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
75
68
|
assert did_complete
|
|
76
69
|
after_open = open_fds.call
|
|
77
70
|
#puts "after_open: #{after_open} before_open: #{before_open.inspect}"
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
|
|
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"
|
|
81
75
|
multi.close
|
|
82
76
|
|
|
83
77
|
after_open = open_fds.call
|
|
@@ -88,7 +82,7 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
88
82
|
multi = Curl::Multi.new
|
|
89
83
|
did_complete = false
|
|
90
84
|
5.times do |n|
|
|
91
|
-
easy = Curl::Easy.new(
|
|
85
|
+
easy = Curl::Easy.new(test_url) do |curl|
|
|
92
86
|
curl.timeout = 5 # ensure we don't hang for ever connecting to an external host
|
|
93
87
|
curl.on_complete {
|
|
94
88
|
did_complete = true
|
|
@@ -103,6 +97,9 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
103
97
|
#puts "after_open: #{after_open} before_open: #{before_open.inspect}"
|
|
104
98
|
assert_equal 0, (after_open - before_open), "auto close the connections"
|
|
105
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?
|
|
106
103
|
Curl::Multi.autoclose = false # restore default
|
|
107
104
|
end
|
|
108
105
|
|
|
@@ -114,6 +111,42 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
114
111
|
Curl::Multi.autoclose = false # restore default
|
|
115
112
|
end
|
|
116
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_makes_multi_unusable
|
|
137
|
+
multi = Curl::Multi.new
|
|
138
|
+
multi.close
|
|
139
|
+
|
|
140
|
+
error = assert_raise(Curl::Err::MultiBadHandle) do
|
|
141
|
+
multi.add(Curl::Easy.new($TEST_URL))
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
assert_match(/Invalid multi handle/i, error.message)
|
|
145
|
+
assert_equal 0, multi.requests.length
|
|
146
|
+
ensure
|
|
147
|
+
multi.close if multi
|
|
148
|
+
end
|
|
149
|
+
|
|
117
150
|
def test_new_multi_01
|
|
118
151
|
d1 = String.new
|
|
119
152
|
c1 = Curl::Easy.new($TEST_URL) do |curl|
|
|
@@ -136,9 +169,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
136
169
|
|
|
137
170
|
assert_match(/^# DO NOT REMOVE THIS COMMENT/, d1)
|
|
138
171
|
assert_match(/^# DO NOT REMOVE THIS COMMENT/, d2)
|
|
139
|
-
|
|
140
|
-
m
|
|
141
|
-
|
|
172
|
+
ensure
|
|
173
|
+
m.close if m
|
|
142
174
|
end
|
|
143
175
|
|
|
144
176
|
def test_perform_block
|
|
@@ -157,9 +189,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
157
189
|
|
|
158
190
|
assert_match(/^# DO NOT REMOVE THIS COMMENT/, c1.body_str)
|
|
159
191
|
assert_match(/^# DO NOT REMOVE THIS COMMENT/, c2.body_str)
|
|
160
|
-
|
|
161
|
-
m
|
|
162
|
-
|
|
192
|
+
ensure
|
|
193
|
+
m.close if m
|
|
163
194
|
end
|
|
164
195
|
|
|
165
196
|
def test_multi_easy_get
|
|
@@ -201,6 +232,348 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
201
232
|
|
|
202
233
|
end
|
|
203
234
|
|
|
235
|
+
def test_multi_perform_reraises_on_body_exception
|
|
236
|
+
multi = Curl::Multi.new
|
|
237
|
+
easy = Curl::Easy.new(TestServlet.url)
|
|
238
|
+
easy.on_body { raise "body blew up" }
|
|
239
|
+
|
|
240
|
+
error = assert_raise(RuntimeError) do
|
|
241
|
+
multi.add(easy)
|
|
242
|
+
multi.perform
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
assert_equal "body blew up", error.message
|
|
246
|
+
assert !easy.instance_variable_defined?(:@__curb_callback_error)
|
|
247
|
+
ensure
|
|
248
|
+
multi.close if multi
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def test_multi_perform_reraises_on_body_exception_for_frozen_easy
|
|
252
|
+
multi = Curl::Multi.new
|
|
253
|
+
easy = Curl::Easy.new($TEST_URL)
|
|
254
|
+
callback_error = Class.new(RuntimeError)
|
|
255
|
+
easy.on_body { raise callback_error, "body blew up" }
|
|
256
|
+
easy.freeze
|
|
257
|
+
|
|
258
|
+
error = assert_raise(callback_error) do
|
|
259
|
+
multi.add(easy)
|
|
260
|
+
multi.perform
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
assert_equal "body blew up", error.message
|
|
264
|
+
ensure
|
|
265
|
+
begin
|
|
266
|
+
multi.close if multi
|
|
267
|
+
rescue StandardError
|
|
268
|
+
multi.instance_variable_set(:@requests, {})
|
|
269
|
+
multi._close
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def test_multi_perform_reraises_on_header_exception
|
|
274
|
+
multi = Curl::Multi.new
|
|
275
|
+
easy = Curl::Easy.new(TestServlet.url)
|
|
276
|
+
easy.on_header { raise "header blew up" }
|
|
277
|
+
|
|
278
|
+
error = assert_raise(RuntimeError) do
|
|
279
|
+
multi.add(easy)
|
|
280
|
+
multi.perform
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
assert_equal "header blew up", error.message
|
|
284
|
+
assert !easy.instance_variable_defined?(:@__curb_callback_error)
|
|
285
|
+
ensure
|
|
286
|
+
multi.close if multi
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def test_multi_perform_drains_completed_siblings_before_reraising_on_body_exception
|
|
290
|
+
multi = Curl::Multi.new
|
|
291
|
+
failed = Curl::Easy.new($TEST_URL)
|
|
292
|
+
completed = Curl::Easy.new($TEST_URL)
|
|
293
|
+
completions = []
|
|
294
|
+
|
|
295
|
+
failed.on_body { raise "body blew up" }
|
|
296
|
+
completed.on_complete { completions << :completed }
|
|
297
|
+
|
|
298
|
+
error = assert_raise(RuntimeError) do
|
|
299
|
+
multi.add(failed)
|
|
300
|
+
multi.add(completed)
|
|
301
|
+
multi.perform
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
assert_equal "body blew up", error.message
|
|
305
|
+
assert_equal [:completed], completions
|
|
306
|
+
assert multi.idle?, 'The multi handle should be idle after draining the completed batch'
|
|
307
|
+
assert_equal 0, multi.requests.length
|
|
308
|
+
assert_nil failed.multi
|
|
309
|
+
assert_nil completed.multi
|
|
310
|
+
assert !failed.instance_variable_defined?(:@__curb_callback_error)
|
|
311
|
+
ensure
|
|
312
|
+
multi.close if multi
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def test_multi_perform_waits_until_idle_before_reraising_on_complete_exception
|
|
316
|
+
port_socket = TCPServer.new('127.0.0.1', 0)
|
|
317
|
+
port = port_socket.addr[1]
|
|
318
|
+
port_socket.close
|
|
319
|
+
|
|
320
|
+
server = WEBrick::HTTPServer.new(:Port => port, :Logger => WEBRICK_TEST_LOG, :AccessLog => [])
|
|
321
|
+
server.mount_proc('/fast') do |_req, res|
|
|
322
|
+
res['Content-Type'] = 'text/plain'
|
|
323
|
+
res.body = 'fast'
|
|
324
|
+
end
|
|
325
|
+
server.mount_proc('/slow') do |_req, res|
|
|
326
|
+
res['Content-Type'] = 'text/plain'
|
|
327
|
+
sleep 0.2
|
|
328
|
+
res.body = 'slow'
|
|
329
|
+
end
|
|
330
|
+
server_thread = Thread.new(server) { |srv| srv.start }
|
|
331
|
+
wait_for_server_ready(port, thread: server_thread)
|
|
332
|
+
|
|
333
|
+
multi = Curl::Multi.new
|
|
334
|
+
fast = Curl::Easy.new("http://127.0.0.1:#{port}/fast")
|
|
335
|
+
slow = Curl::Easy.new("http://127.0.0.1:#{port}/slow")
|
|
336
|
+
completions = []
|
|
337
|
+
|
|
338
|
+
fast.on_complete do
|
|
339
|
+
completions << :fast
|
|
340
|
+
raise "complete blew up"
|
|
341
|
+
end
|
|
342
|
+
slow.on_complete { completions << :slow }
|
|
343
|
+
|
|
344
|
+
error = assert_raise(Curl::Err::AbortedByCallbackError) do
|
|
345
|
+
multi.add(fast)
|
|
346
|
+
multi.add(slow)
|
|
347
|
+
multi.perform
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
assert_equal "complete blew up", error.message
|
|
351
|
+
assert_equal [:fast, :slow], completions
|
|
352
|
+
assert multi.idle?, 'The multi handle should be idle before the deferred exception is raised'
|
|
353
|
+
assert_equal 0, multi.requests.length
|
|
354
|
+
assert_nil fast.multi
|
|
355
|
+
assert_nil slow.multi
|
|
356
|
+
ensure
|
|
357
|
+
multi.close if multi
|
|
358
|
+
server.shutdown if defined?(server) && server
|
|
359
|
+
server_thread.join(server_startup_timeout) if defined?(server_thread) && server_thread
|
|
360
|
+
server_thread.kill if defined?(server_thread) && server_thread&.alive?
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def test_multi_perform_keeps_yielding_block_while_draining_deferred_on_complete_exception
|
|
364
|
+
with_queue_refill_test_server do |port, hits|
|
|
365
|
+
multi = Curl::Multi.new
|
|
366
|
+
failed = Curl::Easy.new("http://127.0.0.1:#{port}/fail")
|
|
367
|
+
slow = Curl::Easy.new("http://127.0.0.1:#{port}/slow")
|
|
368
|
+
failure_seen = false
|
|
369
|
+
slow_completed = false
|
|
370
|
+
yielded_during_drain = false
|
|
371
|
+
|
|
372
|
+
failed.on_complete do
|
|
373
|
+
failure_seen = true
|
|
374
|
+
raise "complete blew up"
|
|
375
|
+
end
|
|
376
|
+
slow.on_complete { slow_completed = true }
|
|
377
|
+
|
|
378
|
+
error = assert_raise(Curl::Err::AbortedByCallbackError) do
|
|
379
|
+
multi.add(failed)
|
|
380
|
+
multi.add(slow)
|
|
381
|
+
multi.perform do
|
|
382
|
+
yielded_during_drain ||= failure_seen && !slow_completed
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
assert_equal "complete blew up", error.message
|
|
387
|
+
assert yielded_during_drain,
|
|
388
|
+
"perform block should continue yielding while draining a slow sibling after a deferred callback exception"
|
|
389
|
+
assert_equal 1, hits[:fail]
|
|
390
|
+
assert_equal 1, hits[:slow]
|
|
391
|
+
ensure
|
|
392
|
+
multi.close if multi
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def test_multi_perform_does_not_start_queued_work_after_on_complete_exception
|
|
397
|
+
with_queue_refill_test_server do |port, hits|
|
|
398
|
+
multi = Curl::Multi.new
|
|
399
|
+
failed = Curl::Easy.new("http://127.0.0.1:#{port}/fail")
|
|
400
|
+
slow = Curl::Easy.new("http://127.0.0.1:#{port}/slow")
|
|
401
|
+
queued = Curl::Easy.new("http://127.0.0.1:#{port}/queued")
|
|
402
|
+
failure_seen = false
|
|
403
|
+
|
|
404
|
+
failed.on_complete do
|
|
405
|
+
failure_seen = true
|
|
406
|
+
raise "complete blew up"
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
error = assert_raise(Curl::Err::AbortedByCallbackError) do
|
|
410
|
+
multi.add(failed)
|
|
411
|
+
multi.add(slow)
|
|
412
|
+
multi.perform do
|
|
413
|
+
next unless failure_seen
|
|
414
|
+
|
|
415
|
+
multi.add(queued)
|
|
416
|
+
failure_seen = false
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
assert_equal "complete blew up", error.message
|
|
421
|
+
assert_equal 1, hits[:fail]
|
|
422
|
+
assert_equal 1, hits[:slow]
|
|
423
|
+
assert_equal 0, hits[:queued], "queued request should not start once a deferred callback exception is pending"
|
|
424
|
+
ensure
|
|
425
|
+
multi.close if multi
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def test_multi_perform_does_not_start_work_added_within_failing_on_complete_callback
|
|
430
|
+
with_queue_refill_test_server do |port, hits|
|
|
431
|
+
multi = Curl::Multi.new
|
|
432
|
+
failed = Curl::Easy.new("http://127.0.0.1:#{port}/fail")
|
|
433
|
+
queued = Curl::Easy.new("http://127.0.0.1:#{port}/queued")
|
|
434
|
+
|
|
435
|
+
failed.on_complete do
|
|
436
|
+
multi.add(queued)
|
|
437
|
+
raise "complete blew up"
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
error = assert_raise(Curl::Err::AbortedByCallbackError) do
|
|
441
|
+
multi.add(failed)
|
|
442
|
+
multi.perform
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
assert_equal "complete blew up", error.message
|
|
446
|
+
assert_equal 1, hits[:fail]
|
|
447
|
+
assert_equal 0, hits[:queued],
|
|
448
|
+
"request added from a failing on_complete callback should not start once the multi begins aborting"
|
|
449
|
+
ensure
|
|
450
|
+
multi.close if multi
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
def test_multi_perform_does_not_restart_sibling_removed_and_readded_from_failing_on_complete_callback
|
|
455
|
+
with_queue_refill_test_server(wait_fail_until_slow: true) 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
|
+
|
|
460
|
+
failed.on_complete do
|
|
461
|
+
multi.remove(slow)
|
|
462
|
+
multi.add(slow)
|
|
463
|
+
raise "complete blew up"
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
error = assert_raise(Curl::Err::AbortedByCallbackError) do
|
|
467
|
+
multi.add(failed)
|
|
468
|
+
multi.add(slow)
|
|
469
|
+
multi.perform
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
assert_equal "complete blew up", error.message
|
|
473
|
+
assert_equal 1, hits[:fail]
|
|
474
|
+
assert_equal 1, hits[:slow],
|
|
475
|
+
"sibling removed and re-added from a failing on_complete callback should be treated as replacement work and not restarted"
|
|
476
|
+
ensure
|
|
477
|
+
multi.close if multi
|
|
478
|
+
end
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
def test_multi_perform_does_not_start_work_added_within_on_complete_after_on_body_exception
|
|
482
|
+
with_queue_refill_test_server do |port, hits|
|
|
483
|
+
multi = Curl::Multi.new
|
|
484
|
+
failed = Curl::Easy.new("http://127.0.0.1:#{port}/fail")
|
|
485
|
+
queued = Curl::Easy.new("http://127.0.0.1:#{port}/queued")
|
|
486
|
+
|
|
487
|
+
failed.on_body { raise "body blew up" }
|
|
488
|
+
failed.on_complete { multi.add(queued) }
|
|
489
|
+
|
|
490
|
+
error = assert_raise(RuntimeError) do
|
|
491
|
+
multi.add(failed)
|
|
492
|
+
multi.perform
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
assert_equal "body blew up", error.message
|
|
496
|
+
assert_equal 1, hits[:fail]
|
|
497
|
+
assert_equal 0, hits[:queued],
|
|
498
|
+
"request added from on_complete after a body callback exception should not start once the multi begins aborting"
|
|
499
|
+
ensure
|
|
500
|
+
multi.close if multi
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
def test_multi_http_does_not_fetch_queued_url_after_on_body_exception
|
|
505
|
+
with_queue_refill_test_server do |port, hits|
|
|
506
|
+
urls = [
|
|
507
|
+
{ :url => "http://127.0.0.1:#{port}/queued", :method => :get },
|
|
508
|
+
{ :url => "http://127.0.0.1:#{port}/slow", :method => :get },
|
|
509
|
+
{ :url => "http://127.0.0.1:#{port}/fail", :method => :get, :on_body => proc { raise "body blew up" } }
|
|
510
|
+
]
|
|
511
|
+
|
|
512
|
+
error = assert_raise(RuntimeError) do
|
|
513
|
+
Curl::Multi.http(urls, {:max_connects => 2}) { |_easy, _code, _method| }
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
assert_equal "body blew up", error.message
|
|
517
|
+
assert_equal 1, hits[:fail]
|
|
518
|
+
assert_equal 1, hits[:slow]
|
|
519
|
+
assert_equal 0, hits[:queued], "Curl::Multi.http should not refill queued urls after a callback abort"
|
|
520
|
+
end
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
def test_multi_perform_runs_status_callbacks_after_on_body_exception
|
|
524
|
+
multi = Curl::Multi.new
|
|
525
|
+
easy = Curl::Easy.new(TestServlet.url)
|
|
526
|
+
callbacks = []
|
|
527
|
+
|
|
528
|
+
easy.on_body { raise "body blew up" }
|
|
529
|
+
easy.on_complete { callbacks << :complete }
|
|
530
|
+
easy.on_failure do |_completed_easy, error_info|
|
|
531
|
+
callbacks << :failure
|
|
532
|
+
assert_equal Curl::Err::WriteError, error_info.first
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
error = assert_raise(RuntimeError) do
|
|
536
|
+
multi.add(easy)
|
|
537
|
+
multi.perform
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
assert_equal "body blew up", error.message
|
|
541
|
+
assert_equal [:complete, :failure], callbacks
|
|
542
|
+
assert multi.idle?
|
|
543
|
+
assert_equal 0, multi.requests.length
|
|
544
|
+
ensure
|
|
545
|
+
multi.close if multi
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
def test_multi_perform_preserves_on_body_exception_when_status_callbacks_raise
|
|
549
|
+
multi = Curl::Multi.new
|
|
550
|
+
easy = Curl::Easy.new(TestServlet.url)
|
|
551
|
+
callbacks = []
|
|
552
|
+
|
|
553
|
+
easy.on_body { raise "body blew up" }
|
|
554
|
+
easy.on_complete do
|
|
555
|
+
callbacks << :complete
|
|
556
|
+
raise "complete blew up"
|
|
557
|
+
end
|
|
558
|
+
easy.on_failure do |_completed_easy, error_info|
|
|
559
|
+
callbacks << :failure
|
|
560
|
+
assert_equal Curl::Err::WriteError, error_info.first
|
|
561
|
+
raise "failure blew up"
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
error = assert_raise(RuntimeError) do
|
|
565
|
+
multi.add(easy)
|
|
566
|
+
multi.perform
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
assert_equal "body blew up", error.message
|
|
570
|
+
assert_equal [:complete, :failure], callbacks
|
|
571
|
+
assert multi.idle?
|
|
572
|
+
assert_equal 0, multi.requests.length
|
|
573
|
+
ensure
|
|
574
|
+
multi.close if multi
|
|
575
|
+
end
|
|
576
|
+
|
|
204
577
|
# NOTE: if this test runs slowly on Mac OSX, it is probably due to the use of a port install curl+ssl+ares install
|
|
205
578
|
# on my MacBook, this causes curl_easy_init to take nearly 0.01 seconds / * 100 below is 1 second too many!
|
|
206
579
|
def test_n_requests
|
|
@@ -221,8 +594,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
221
594
|
n.times do|i|
|
|
222
595
|
assert_match(/^# DO NOT REMOVE THIS COMMENT/, responses[i], "response #{i}")
|
|
223
596
|
end
|
|
224
|
-
|
|
225
|
-
m
|
|
597
|
+
ensure
|
|
598
|
+
m.close if m
|
|
226
599
|
end
|
|
227
600
|
|
|
228
601
|
def test_n_requests_with_break
|
|
@@ -245,9 +618,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
245
618
|
assert_match(/^# DO NOT REMOVE THIS COMMENT/, responses[i], "response #{i}")
|
|
246
619
|
end
|
|
247
620
|
end
|
|
248
|
-
|
|
249
|
-
m
|
|
250
|
-
|
|
621
|
+
ensure
|
|
622
|
+
m.close if m
|
|
251
623
|
end
|
|
252
624
|
|
|
253
625
|
def test_idle_check
|
|
@@ -267,6 +639,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
267
639
|
m.perform
|
|
268
640
|
|
|
269
641
|
assert(m.idle?, 'A Curl::Multi handle should be idle after performing its requests')
|
|
642
|
+
ensure
|
|
643
|
+
m.close if m
|
|
270
644
|
end
|
|
271
645
|
|
|
272
646
|
def test_requests
|
|
@@ -283,6 +657,45 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
283
657
|
m.perform
|
|
284
658
|
|
|
285
659
|
assert_equal(0, m.requests.length, 'A new Curl::Multi handle should have no requests after a perform')
|
|
660
|
+
ensure
|
|
661
|
+
m.close if m
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
def test_easy_multi_is_cleared_after_perform
|
|
665
|
+
m = Curl::Multi.new
|
|
666
|
+
c = Curl::Easy.new($TEST_URL)
|
|
667
|
+
|
|
668
|
+
m.add(c)
|
|
669
|
+
assert_equal m, c.multi
|
|
670
|
+
|
|
671
|
+
m.perform
|
|
672
|
+
|
|
673
|
+
assert_nil c.multi
|
|
674
|
+
ensure
|
|
675
|
+
m.close if m
|
|
676
|
+
end
|
|
677
|
+
|
|
678
|
+
def test_easy_multi_is_available_during_completion_callbacks
|
|
679
|
+
multi = Curl::Multi.new
|
|
680
|
+
easy = Curl::Easy.new($TEST_URL)
|
|
681
|
+
seen_multi = {}
|
|
682
|
+
|
|
683
|
+
easy.on_complete do |completed_easy|
|
|
684
|
+
seen_multi[:complete] = completed_easy.multi
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
easy.on_success do |completed_easy|
|
|
688
|
+
seen_multi[:success] = completed_easy.multi
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
multi.add(easy)
|
|
692
|
+
multi.perform
|
|
693
|
+
|
|
694
|
+
assert_same multi, seen_multi[:complete]
|
|
695
|
+
assert_same multi, seen_multi[:success]
|
|
696
|
+
assert_nil easy.multi
|
|
697
|
+
ensure
|
|
698
|
+
multi.close if multi
|
|
286
699
|
end
|
|
287
700
|
|
|
288
701
|
def test_cancel
|
|
@@ -296,6 +709,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
296
709
|
m.cancel!
|
|
297
710
|
|
|
298
711
|
assert_equal(0, m.requests.size, 'A new Curl::Multi handle should have no requests after being canceled')
|
|
712
|
+
ensure
|
|
713
|
+
m.close if m
|
|
299
714
|
end
|
|
300
715
|
|
|
301
716
|
def test_with_success
|
|
@@ -326,8 +741,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
326
741
|
|
|
327
742
|
assert success_called2
|
|
328
743
|
assert success_called1
|
|
329
|
-
|
|
330
|
-
m
|
|
744
|
+
ensure
|
|
745
|
+
m.close if m
|
|
331
746
|
end
|
|
332
747
|
|
|
333
748
|
def test_with_success_cb_with_404
|
|
@@ -369,8 +784,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
369
784
|
|
|
370
785
|
assert success_called2
|
|
371
786
|
assert !success_called1
|
|
372
|
-
|
|
373
|
-
m
|
|
787
|
+
ensure
|
|
788
|
+
m.close if m
|
|
374
789
|
end
|
|
375
790
|
|
|
376
791
|
# This tests whether, ruby's GC will trash an out of scope easy handle
|
|
@@ -394,6 +809,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
394
809
|
@m.perform do
|
|
395
810
|
ObjectSpace.garbage_collect
|
|
396
811
|
end
|
|
812
|
+
ensure
|
|
813
|
+
@m.close if @m
|
|
397
814
|
end
|
|
398
815
|
|
|
399
816
|
def self.test
|
|
@@ -465,23 +882,22 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
465
882
|
urls = []
|
|
466
883
|
downloads = []
|
|
467
884
|
file_info = {}
|
|
468
|
-
FileUtils.mkdir("tmp/")
|
|
469
885
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
886
|
+
Dir.mktmpdir('curb-download-') do |download_dir|
|
|
887
|
+
# for each file store the size by file name
|
|
888
|
+
Dir[File.dirname(__FILE__) + "/../ext/*.c"].each do|path|
|
|
889
|
+
urls << (root_uri + File.basename(path))
|
|
890
|
+
downloads << File.join(download_dir, File.basename(path))
|
|
891
|
+
file_info[File.basename(path)] = {:size => File.size(path), :path => path}
|
|
892
|
+
end
|
|
476
893
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
894
|
+
# start downloads
|
|
895
|
+
Curl::Multi.download(urls,{},{},downloads) do|curl,download_path|
|
|
896
|
+
assert_equal 200, curl.response_code
|
|
897
|
+
assert File.exist?(download_path)
|
|
898
|
+
assert_equal file_info[File.basename(download_path)][:size], File.size(download_path), "incomplete download: #{download_path}"
|
|
899
|
+
end
|
|
482
900
|
end
|
|
483
|
-
ensure
|
|
484
|
-
FileUtils.rm_rf("tmp/")
|
|
485
901
|
end
|
|
486
902
|
|
|
487
903
|
def test_multi_easy_post_01
|
|
@@ -620,6 +1036,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
620
1036
|
failure = false
|
|
621
1037
|
assert !failure
|
|
622
1038
|
assert_equal "POST\nhello=world", e2.body_str
|
|
1039
|
+
ensure
|
|
1040
|
+
m.close if m
|
|
623
1041
|
end
|
|
624
1042
|
|
|
625
1043
|
def test_remove_exception_is_descriptive
|
|
@@ -629,6 +1047,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
629
1047
|
rescue => e
|
|
630
1048
|
assert_equal 'CURLError: Invalid easy handle', e.message
|
|
631
1049
|
assert_equal 0, m.requests.size
|
|
1050
|
+
ensure
|
|
1051
|
+
m.close if m
|
|
632
1052
|
end
|
|
633
1053
|
|
|
634
1054
|
def test_retry_easy_handle
|
|
@@ -652,6 +1072,50 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
652
1072
|
m.perform
|
|
653
1073
|
assert_equal 0, tries
|
|
654
1074
|
assert_equal 0, m.requests.size
|
|
1075
|
+
ensure
|
|
1076
|
+
m.close if m
|
|
1077
|
+
end
|
|
1078
|
+
|
|
1079
|
+
def test_close_in_on_missing_callback_is_blocked
|
|
1080
|
+
m = Curl::Multi.new
|
|
1081
|
+
c = Curl::Easy.new(TestServlet.url + '/not_here')
|
|
1082
|
+
did_raise = false
|
|
1083
|
+
|
|
1084
|
+
c.on_missing do |easy, _error|
|
|
1085
|
+
begin
|
|
1086
|
+
easy.close
|
|
1087
|
+
rescue RuntimeError
|
|
1088
|
+
did_raise = true
|
|
1089
|
+
end
|
|
1090
|
+
end
|
|
1091
|
+
|
|
1092
|
+
m.add(c)
|
|
1093
|
+
m.perform
|
|
1094
|
+
|
|
1095
|
+
assert did_raise
|
|
1096
|
+
ensure
|
|
1097
|
+
m.close if m
|
|
1098
|
+
end
|
|
1099
|
+
|
|
1100
|
+
def test_close_in_on_redirect_callback_is_blocked
|
|
1101
|
+
m = Curl::Multi.new
|
|
1102
|
+
c = Curl::Easy.new(TestServlet.url + '/redirect')
|
|
1103
|
+
did_raise = false
|
|
1104
|
+
|
|
1105
|
+
c.on_redirect do |easy, _error|
|
|
1106
|
+
begin
|
|
1107
|
+
easy.close
|
|
1108
|
+
rescue RuntimeError
|
|
1109
|
+
did_raise = true
|
|
1110
|
+
end
|
|
1111
|
+
end
|
|
1112
|
+
|
|
1113
|
+
m.add(c)
|
|
1114
|
+
m.perform
|
|
1115
|
+
|
|
1116
|
+
assert did_raise
|
|
1117
|
+
ensure
|
|
1118
|
+
m.close if m
|
|
655
1119
|
end
|
|
656
1120
|
|
|
657
1121
|
def test_reusing_handle
|
|
@@ -665,6 +1129,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
665
1129
|
m.add(c)
|
|
666
1130
|
rescue => e
|
|
667
1131
|
assert Curl::Err::MultiBadEasyHandle == e.class || Curl::Err::MultiAddedAlready == e.class
|
|
1132
|
+
ensure
|
|
1133
|
+
m.close if m
|
|
668
1134
|
end
|
|
669
1135
|
|
|
670
1136
|
def test_multi_default_timeout
|
|
@@ -674,6 +1140,54 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
674
1140
|
assert_equal 100, (Curl::Multi.default_timeout = 100)
|
|
675
1141
|
end
|
|
676
1142
|
|
|
1143
|
+
def with_queue_refill_test_server(wait_fail_until_slow: false)
|
|
1144
|
+
port_socket = TCPServer.new('127.0.0.1', 0)
|
|
1145
|
+
port = port_socket.addr[1]
|
|
1146
|
+
port_socket.close
|
|
1147
|
+
|
|
1148
|
+
hits = Hash.new(0)
|
|
1149
|
+
slow_started = false
|
|
1150
|
+
slow_started_lock = Mutex.new
|
|
1151
|
+
slow_started_condition = ConditionVariable.new
|
|
1152
|
+
server = WEBrick::HTTPServer.new(:Port => port, :Logger => WEBRICK_TEST_LOG, :AccessLog => [])
|
|
1153
|
+
server.mount_proc('/fail') do |_req, res|
|
|
1154
|
+
if wait_fail_until_slow
|
|
1155
|
+
slow_started_lock.synchronize do
|
|
1156
|
+
slow_started_condition.wait(slow_started_lock, 1) unless slow_started
|
|
1157
|
+
end
|
|
1158
|
+
end
|
|
1159
|
+
|
|
1160
|
+
hits[:fail] += 1
|
|
1161
|
+
res['Content-Type'] = 'text/plain'
|
|
1162
|
+
res.body = 'fail'
|
|
1163
|
+
end
|
|
1164
|
+
server.mount_proc('/slow') do |_req, res|
|
|
1165
|
+
slow_started_lock.synchronize do
|
|
1166
|
+
hits[:slow] += 1
|
|
1167
|
+
slow_started = true
|
|
1168
|
+
slow_started_condition.broadcast
|
|
1169
|
+
end
|
|
1170
|
+
|
|
1171
|
+
res['Content-Type'] = 'text/plain'
|
|
1172
|
+
sleep 0.3
|
|
1173
|
+
res.body = 'slow'
|
|
1174
|
+
end
|
|
1175
|
+
server.mount_proc('/queued') do |_req, res|
|
|
1176
|
+
hits[:queued] += 1
|
|
1177
|
+
res['Content-Type'] = 'text/plain'
|
|
1178
|
+
res.body = 'queued'
|
|
1179
|
+
end
|
|
1180
|
+
|
|
1181
|
+
server_thread = Thread.new(server) { |srv| srv.start }
|
|
1182
|
+
wait_for_server_ready(port, thread: server_thread)
|
|
1183
|
+
|
|
1184
|
+
yield port, hits
|
|
1185
|
+
ensure
|
|
1186
|
+
server.shutdown if defined?(server) && server
|
|
1187
|
+
server_thread.join(server_startup_timeout) if defined?(server_thread) && server_thread
|
|
1188
|
+
server_thread.kill if defined?(server_thread) && server_thread&.alive?
|
|
1189
|
+
end
|
|
1190
|
+
|
|
677
1191
|
include TestServerMethods
|
|
678
1192
|
|
|
679
1193
|
def setup
|