curb 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,190 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
+
3
+ begin
4
+ require 'async'
5
+ rescue LoadError
6
+ HAS_ASYNC = false
7
+ else
8
+ HAS_ASYNC = true
9
+ end
10
+
11
+ # This test verifies that Curl::Easy#perform cooperates with Ruby's Fiber scheduler
12
+ # by running multiple requests concurrently in a single thread using the Async gem.
13
+ class TestCurbFiberScheduler < Test::Unit::TestCase
14
+ include BugTestServerSetupTeardown
15
+
16
+ ITERS = 4
17
+ MIN_S = 0.25
18
+ # Each request sleeps 0.25s; two concurrent requests should be ~0.25–0.5s.
19
+ # Allow some jitter in CI environments.
20
+ THRESHOLD = ((MIN_S*(ITERS/2.0)) + (MIN_S/2.0)) # add more jitter for slower CI environments
21
+ SERIAL_TIME_WOULD_BE_ABOUT = MIN_S * ITERS
22
+
23
+ def setup
24
+ @port = 9993
25
+
26
+ @response_proc = lambda do |res|
27
+ res['Content-Type'] = 'text/plain'
28
+ sleep MIN_S
29
+ res.body = '200'
30
+ end
31
+ super
32
+ end
33
+
34
+ def test_multi_is_scheduler_friendly
35
+ if skip_no_async
36
+ return
37
+ end
38
+
39
+ url = "http://127.0.0.1:#{@port}/test"
40
+
41
+ started = Time.now
42
+ results = []
43
+
44
+
45
+ async_run do
46
+ m = Curl::Multi.new
47
+ ITERS.times.each do
48
+ c = Curl::Easy.new(url)
49
+ c.on_complete { results << c.code }
50
+ m.add(c)
51
+ end
52
+ m.perform
53
+ end
54
+
55
+ duration = Time.now - started
56
+
57
+ assert duration < THRESHOLD, "Requests did not run concurrently under fiber scheduler (#{duration}s) which exceeds the expected threshold of: #{THRESHOLD} serial time would be about: #{SERIAL_TIME_WOULD_BE_ABOUT}"
58
+ assert_equal ITERS, results.size
59
+ assert_equal ITERS.times.map {200}, results
60
+ end
61
+
62
+ def test_easy_perform_is_scheduler_friendly
63
+ if skip_no_async
64
+ return
65
+ end
66
+
67
+ url = "http://127.0.0.1:#{@port}/test"
68
+
69
+ started = Time.now
70
+ results = []
71
+
72
+ async_run do |top|
73
+ tasks = ITERS.times.map do
74
+ top.async do
75
+ #t = Time.now.to_i
76
+ #puts "starting fiber [#{results.size}] -> #{t}"
77
+ c = Curl.get(url)
78
+ #puts "received result: #{results.size} -> #{Time.now.to_f - t.to_f}"
79
+ results << c.code
80
+
81
+ end
82
+ end
83
+ tasks.each(&:wait)
84
+ end
85
+
86
+ duration = Time.now - started
87
+
88
+ assert duration < THRESHOLD, "Requests did not run concurrently under fiber scheduler (#{duration}s) which exceeds the expected threshold of: #{THRESHOLD} serial time would be about: #{SERIAL_TIME_WOULD_BE_ABOUT}"
89
+ assert_equal ITERS, results.size
90
+ assert_equal ITERS.times.map {200}, results
91
+ end
92
+
93
+ def test_multi_perform_yields_block_under_scheduler
94
+ if skip_no_async
95
+ return
96
+ end
97
+
98
+ url = "http://127.0.0.1:#{@port}/test"
99
+ yielded = 0
100
+ results = []
101
+
102
+ async_run do
103
+ m = Curl::Multi.new
104
+ ITERS.times do
105
+ c = Curl::Easy.new(url)
106
+ c.on_complete { results << c.code }
107
+ m.add(c)
108
+ end
109
+ m.perform do
110
+ yielded += 1
111
+ end
112
+ end
113
+
114
+ assert_operator yielded, :>=, 1, 'perform did not yield block while waiting under scheduler'
115
+ assert_equal ITERS, results.size
116
+ assert_equal ITERS.times.map {200}, results
117
+ end
118
+
119
+ def test_multi_single_request_scheduler_path
120
+ if skip_no_async
121
+ return
122
+ end
123
+
124
+ url = "http://127.0.0.1:#{@port}/test"
125
+ result = nil
126
+
127
+ async_run do
128
+ m = Curl::Multi.new
129
+ c = Curl::Easy.new(url)
130
+ c.on_complete { result = c.code }
131
+ m.add(c)
132
+ m.perform
133
+ end
134
+
135
+ assert_equal 200, result
136
+ end
137
+
138
+ def test_multi_reuse_after_scheduler_perform
139
+ unless HAS_ASYNC
140
+ warn 'Skipping fiber scheduler test (Async gem not available)'
141
+ return
142
+ end
143
+
144
+ url = "http://127.0.0.1:#{@port}/test"
145
+ results = []
146
+
147
+ async_run do
148
+ m = Curl::Multi.new
149
+ # First round
150
+ c1 = Curl::Easy.new(url)
151
+ c1.on_complete { results << c1.code }
152
+ m.add(c1)
153
+ m.perform
154
+
155
+ # Second round on same multi
156
+ c2 = Curl::Easy.new(url)
157
+ c2.on_complete { results << c2.code }
158
+ m.add(c2)
159
+ m.perform
160
+ end
161
+
162
+ assert_equal [200, 200], results
163
+ end
164
+
165
+ private
166
+ def skip_no_async
167
+ if WINDOWS
168
+ warn 'Skipping fiber scheduler tests on Windows'
169
+ return true
170
+ end
171
+ unless HAS_ASYNC
172
+ warn 'Skipping fiber scheduler test (Async gem not available)'
173
+ return true
174
+ end
175
+ false
176
+ end
177
+
178
+ def async_run(&block)
179
+ # Prefer newer Async.run to avoid deprecated scheduler.async path.
180
+ if defined?(Async) && Async.respond_to?(:run)
181
+ Async.run(&block)
182
+ else
183
+ Async(&block)
184
+ end
185
+ end
186
+ end
187
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.1')
188
+ warn 'Skipping fiber scheduler tests on Ruby < 3.1'
189
+ return
190
+ end
@@ -0,0 +1,29 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
+
3
+ class TestBasic < Test::Unit::TestCase
4
+ include TestServerMethods
5
+
6
+ def setup
7
+ server_setup
8
+ end
9
+
10
+ def test_basic_request
11
+ puts "\n=== Testing basic request ==="
12
+ easy = Curl::Easy.new(TestServlet.url)
13
+ easy.perform
14
+ puts "Response code: #{easy.response_code}"
15
+ puts "Body (first 100 chars): #{easy.body_str[0..100]}"
16
+ assert_equal 200, easy.response_code
17
+ end
18
+
19
+ def test_slow_request
20
+ puts "\n=== Testing slow request ==="
21
+ url = TestServlet.url_to("/slow?seconds=0.1")
22
+ puts "URL: #{url}"
23
+ easy = Curl::Easy.new(url)
24
+ easy.perform
25
+ puts "Response code: #{easy.response_code}"
26
+ puts "Body: #{easy.body_str}"
27
+ assert_equal 200, easy.response_code
28
+ end
29
+ end
@@ -0,0 +1,69 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
+ require 'async'
3
+
4
+ class TestFiberDebug < Test::Unit::TestCase
5
+ include TestServerMethods
6
+
7
+ def setup
8
+ server_setup
9
+ end
10
+
11
+ def test_simple_fiber_request
12
+ puts "\n=== Starting simple fiber request test ==="
13
+
14
+ run_async do |task|
15
+ puts "Inside Async block"
16
+ puts "Fiber scheduler available: #{Curl::Multi.fiber_scheduler_available?}"
17
+
18
+ multi = Curl::Multi.new
19
+ easy = Curl::Easy.new(TestServlet.url)
20
+ easy.on_complete { |curl| puts "Request completed: #{curl.response_code}" }
21
+
22
+ multi.add(easy)
23
+ puts "Added easy handle to multi"
24
+
25
+ # Perform without block first
26
+ puts "Calling perform..."
27
+ multi.perform
28
+ puts "Perform completed"
29
+ end
30
+
31
+ puts "Test completed"
32
+ end
33
+
34
+ def test_fiber_with_block
35
+ puts "\n=== Starting fiber with block test ==="
36
+
37
+ run_async do |task|
38
+ puts "Inside Async block"
39
+
40
+ multi = Curl::Multi.new
41
+ easy = Curl::Easy.new(TestServlet.url_to("/slow?seconds=0.1"))
42
+ easy.on_complete { |curl| puts "Request completed: #{curl.response_code}" }
43
+
44
+ multi.add(easy)
45
+
46
+ block_calls = 0
47
+ multi.perform do
48
+ block_calls += 1
49
+ puts "Block called: #{block_calls}"
50
+ if block_calls < 5 # Limit iterations to prevent infinite loop
51
+ Async::Task.yield
52
+ end
53
+ end
54
+
55
+ puts "Perform completed, block called #{block_calls} times"
56
+ end
57
+
58
+ puts "Test completed"
59
+ end
60
+
61
+ private
62
+ def run_async(&block)
63
+ if defined?(Async) && Async.respond_to?(:run)
64
+ Async.run(&block)
65
+ else
66
+ Async(&block)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,65 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
+ require 'async'
3
+
4
+ class TestFiberSimple < Test::Unit::TestCase
5
+ include TestServerMethods
6
+
7
+ def setup
8
+ server_setup
9
+ end
10
+
11
+ def test_simple_concurrent
12
+ puts "\n=== Testing simple concurrent requests ==="
13
+
14
+ results = []
15
+
16
+ if Async.respond_to?(:run)
17
+ Async.run do |task|
18
+ puts "Fiber scheduler available: #{Curl::Multi.fiber_scheduler_available?}"
19
+
20
+ multi = Curl::Multi.new
21
+
22
+ # Add 3 requests
23
+ 3.times do |i|
24
+ easy = Curl::Easy.new(TestServlet.url_to("/slow?seconds=0.2&id=#{i}"))
25
+ easy.on_complete { |curl|
26
+ results << { id: i, code: curl.response_code }
27
+ puts "Request #{i} completed"
28
+ }
29
+ multi.add(easy)
30
+ end
31
+
32
+ puts "Starting perform..."
33
+ start_time = Time.now
34
+ multi.perform # No block
35
+ elapsed = Time.now - start_time
36
+ puts "Perform completed in #{elapsed.round(2)}s"
37
+ end
38
+ else
39
+ Async do |task|
40
+ puts "Fiber scheduler available: #{Curl::Multi.fiber_scheduler_available?}"
41
+
42
+ multi = Curl::Multi.new
43
+
44
+ # Add 3 requests
45
+ 3.times do |i|
46
+ easy = Curl::Easy.new(TestServlet.url_to("/slow?seconds=0.2&id=#{i}"))
47
+ easy.on_complete { |curl|
48
+ results << { id: i, code: curl.response_code }
49
+ puts "Request #{i} completed"
50
+ }
51
+ multi.add(easy)
52
+ end
53
+
54
+ puts "Starting perform..."
55
+ start_time = Time.now
56
+ multi.perform # No block
57
+ elapsed = Time.now - start_time
58
+ puts "Perform completed in #{elapsed.round(2)}s"
59
+ end
60
+ end
61
+
62
+ assert_equal 3, results.size
63
+ results.each { |r| assert_equal 200, r[:code] }
64
+ end
65
+ end
@@ -0,0 +1,65 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '../lib/curb'))
2
+ require 'async'
3
+
4
+ puts "Testing fiber scheduler with real URLs..."
5
+
6
+ # Test without fiber scheduler
7
+ puts "\n1. Without fiber scheduler:"
8
+ start = Time.now
9
+ multi = Curl::Multi.new
10
+
11
+ easies = []
12
+ 3.times do |i|
13
+ easy = Curl::Easy.new("https://httpbin.org/delay/1")
14
+ easy.on_complete { |curl| puts "Request #{i} completed: #{curl.response_code}" }
15
+ multi.add(easy)
16
+ easies << easy
17
+ end
18
+
19
+ multi.perform
20
+ elapsed = Time.now - start
21
+ puts "Total time: #{elapsed.round(2)}s"
22
+
23
+ # Test with fiber scheduler
24
+ puts "\n2. With fiber scheduler:"
25
+ if Async.respond_to?(:run)
26
+ Async.run do
27
+ puts "Fiber scheduler available: #{Curl::Multi.fiber_scheduler_available?}"
28
+
29
+ start = Time.now
30
+ multi = Curl::Multi.new
31
+
32
+ easies = []
33
+ 3.times do |i|
34
+ easy = Curl::Easy.new("https://httpbin.org/delay/1")
35
+ easy.on_complete { |curl| puts "Request #{i} completed: #{curl.response_code}" }
36
+ multi.add(easy)
37
+ easies << easy
38
+ end
39
+
40
+ multi.perform
41
+ elapsed = Time.now - start
42
+ puts "Total time: #{elapsed.round(2)}s"
43
+ end
44
+ else
45
+ Async do
46
+ puts "Fiber scheduler available: #{Curl::Multi.fiber_scheduler_available?}"
47
+
48
+ start = Time.now
49
+ multi = Curl::Multi.new
50
+
51
+ easies = []
52
+ 3.times do |i|
53
+ easy = Curl::Easy.new("https://httpbin.org/delay/1")
54
+ easy.on_complete { |curl| puts "Request #{i} completed: #{curl.response_code}" }
55
+ multi.add(easy)
56
+ easies << easy
57
+ end
58
+
59
+ multi.perform
60
+ elapsed = Time.now - start
61
+ puts "Total time: #{elapsed.round(2)}s"
62
+ end
63
+ end
64
+
65
+ puts "\nDone!"
@@ -0,0 +1,34 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '../lib/curb'))
2
+ require 'async'
3
+
4
+ puts "Testing simple fiber scheduler..."
5
+
6
+ if Async.respond_to?(:run)
7
+ Async.run do
8
+ puts "Fiber scheduler available: #{Curl::Multi.fiber_scheduler_available?}"
9
+
10
+ multi = Curl::Multi.new
11
+ easy = Curl::Easy.new("https://httpbin.org/delay/1")
12
+ easy.on_complete { |curl| puts "Request completed: #{curl.response_code}" }
13
+
14
+ multi.add(easy)
15
+ puts "Starting perform..."
16
+ multi.perform
17
+ puts "Perform completed"
18
+ end
19
+ else
20
+ Async do
21
+ puts "Fiber scheduler available: #{Curl::Multi.fiber_scheduler_available?}"
22
+
23
+ multi = Curl::Multi.new
24
+ easy = Curl::Easy.new("https://httpbin.org/delay/1")
25
+ easy.on_complete { |curl| puts "Request completed: #{curl.response_code}" }
26
+
27
+ multi.add(easy)
28
+ puts "Starting perform..."
29
+ multi.perform
30
+ puts "Perform completed"
31
+ end
32
+ end
33
+
34
+ puts "Done!"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: curb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ross Bamford
8
8
  - Todd A. Fisher
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-07-24 00:00:00.000000000 Z
11
+ date: 2025-08-31 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Curb (probably CUrl-RuBy or something) provides Ruby-language bindings
14
14
  for the libcurl(3), a fully-featured client-side URL transfer library. cURL and
@@ -52,13 +52,14 @@ files:
52
52
  - tests/bug_follow_redirect_288.rb
53
53
  - tests/bug_instance_post_differs_from_class_post.rb
54
54
  - tests/bug_issue102.rb
55
- - tests/bug_issue277.rb
55
+ - tests/bug_issue_noproxy.rb
56
+ - tests/bug_issue_post_redirect.rb
57
+ - tests/bug_issue_spnego.rb
56
58
  - tests/bug_multi_segfault.rb
57
59
  - tests/bug_postfields_crash.rb
58
60
  - tests/bug_postfields_crash2.rb
59
61
  - tests/bug_raise_on_callback.rb
60
62
  - tests/bug_require_last_or_segfault.rb
61
- - tests/bug_resolve.rb
62
63
  - tests/bugtests.rb
63
64
  - tests/helper.rb
64
65
  - tests/mem_check.rb
@@ -74,6 +75,12 @@ files:
74
75
  - tests/tc_curl_multi.rb
75
76
  - tests/tc_curl_postfield.rb
76
77
  - tests/tc_curl_protocols.rb
78
+ - tests/tc_fiber_scheduler.rb
79
+ - tests/test_basic.rb
80
+ - tests/test_fiber_debug.rb
81
+ - tests/test_fiber_simple.rb
82
+ - tests/test_real_url.rb
83
+ - tests/test_simple_fiber.rb
77
84
  - tests/timeout.rb
78
85
  - tests/timeout_server.rb
79
86
  - tests/unittests.rb
@@ -111,13 +118,14 @@ test_files:
111
118
  - tests/bug_follow_redirect_288.rb
112
119
  - tests/bug_instance_post_differs_from_class_post.rb
113
120
  - tests/bug_issue102.rb
114
- - tests/bug_issue277.rb
121
+ - tests/bug_issue_noproxy.rb
122
+ - tests/bug_issue_post_redirect.rb
123
+ - tests/bug_issue_spnego.rb
115
124
  - tests/bug_multi_segfault.rb
116
125
  - tests/bug_postfields_crash.rb
117
126
  - tests/bug_postfields_crash2.rb
118
127
  - tests/bug_raise_on_callback.rb
119
128
  - tests/bug_require_last_or_segfault.rb
120
- - tests/bug_resolve.rb
121
129
  - tests/bugtests.rb
122
130
  - tests/helper.rb
123
131
  - tests/mem_check.rb
@@ -133,6 +141,12 @@ test_files:
133
141
  - tests/tc_curl_multi.rb
134
142
  - tests/tc_curl_postfield.rb
135
143
  - tests/tc_curl_protocols.rb
144
+ - tests/tc_fiber_scheduler.rb
145
+ - tests/test_basic.rb
146
+ - tests/test_fiber_debug.rb
147
+ - tests/test_fiber_simple.rb
148
+ - tests/test_real_url.rb
149
+ - tests/test_simple_fiber.rb
136
150
  - tests/timeout.rb
137
151
  - tests/timeout_server.rb
138
152
  - tests/unittests.rb
@@ -1,32 +0,0 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
-
3
-
4
- require 'curb'
5
-
6
- class BugIssue102 < Test::Unit::TestCase
7
-
8
- def test_gc_closewait
9
- 100.times do
10
- responses = {}
11
- requests = ["http://www.google.co.uk/", "http://www.ruby-lang.org/"]
12
- m = Curl::Multi.new
13
- # add a few easy handles
14
- requests.each do |url|
15
- responses[url] = ""
16
- c = Curl::Easy.new(url) do|curl|
17
- curl.follow_location = true
18
- curl.on_body{|data| responses[url] << data; data.size }
19
- curl.on_success {|easy| #puts "success, add more easy handles"
20
- }
21
- end
22
- m.add(c)
23
- end
24
-
25
- m.perform do
26
- #puts "idling... can do some work here"
27
- end
28
- GC.start
29
- end
30
- end
31
-
32
- end
data/tests/bug_resolve.rb DELETED
@@ -1,15 +0,0 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
-
3
- class BugResolve < Test::Unit::TestCase
4
- include BugTestServerSetupTeardown
5
-
6
- def test_abuse_resolve
7
- c = Curl::Easy.new("http://localhost:#{@port}/test")
8
- while true
9
- c.get
10
- assert_equal 200, c.response_code
11
- assert_match(/hi/, c.body)
12
- end
13
- end
14
-
15
- end