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.
@@ -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
- @server.shutdown if @server
20
- @test_thread.kill if @test_thread
21
- @server = nil
22
- File.unlink(locked_file)
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} | egrep "TCP|UDP"`# | egrep ':#{TestServlet.port} ' | egrep ESTABLISHED`# | wc -l`.strip.to_i
47
- #puts out.lines.join("\n")
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} -n4 state established dport = :#{TestServlet.port} | wc -l`.strip.to_i
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(TestServlet.url) do |curl|
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
- # ruby process may keep a connection alive
79
- assert (after_open - before_open) < 3, "with max connections set to 1 at this point the connection to google should still be open"
80
- assert (after_open - before_open) > 0, "with max connections set to 1 at this point the connection to google should still be open"
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(TestServlet.url) do |curl|
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 = nil
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 = nil
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 = nil
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 = nil
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 = nil
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 = nil
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
- # for each file store the size by file name
471
- Dir[File.dirname(__FILE__) + "/../ext/*.c"].each do|path|
472
- urls << (root_uri + File.basename(path))
473
- downloads << "tmp/" + File.basename(path)
474
- file_info[File.basename(path)] = {:size => File.size(path), :path => path}
475
- end
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
- # start downloads
478
- Curl::Multi.download(urls,{},{},downloads) do|curl,download_path|
479
- assert_equal 200, curl.response_code
480
- assert File.exist?(download_path)
481
- assert_equal file_info[File.basename(download_path)][:size], File.size(download_path), "incomplete download: #{download_path}"
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