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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +522 -0
  3. data/Rakefile +79 -26
  4. data/ext/banned.h +32 -0
  5. data/ext/curb.c +549 -230
  6. data/ext/curb.h +19 -10
  7. data/ext/curb_easy.c +1935 -366
  8. data/ext/curb_easy.h +16 -0
  9. data/ext/curb_errors.c +115 -18
  10. data/ext/curb_errors.h +8 -5
  11. data/ext/curb_macros.h +33 -21
  12. data/ext/curb_multi.c +1858 -272
  13. data/ext/curb_multi.h +10 -3
  14. data/ext/curb_postfield.c +149 -77
  15. data/ext/curb_postfield.h +1 -0
  16. data/ext/curb_upload.c +38 -11
  17. data/ext/curb_upload.h +2 -0
  18. data/ext/extconf.rb +353 -35
  19. data/lib/curb.rb +1 -0
  20. data/lib/curl/easy.rb +314 -78
  21. data/lib/curl/multi.rb +134 -28
  22. data/lib/curl.rb +217 -11
  23. data/tests/bug_crash_on_debug.rb +14 -28
  24. data/tests/bug_crash_on_progress.rb +32 -16
  25. data/tests/bug_curb_easy_blocks_ruby_threads.rb +10 -15
  26. data/tests/bug_curb_easy_post_with_string_no_content_length_header.rb +6 -30
  27. data/tests/bug_follow_redirect_288.rb +83 -0
  28. data/tests/bug_instance_post_differs_from_class_post.rb +3 -5
  29. data/tests/bug_issue102.rb +1 -1
  30. data/tests/bug_issue_noproxy.rb +56 -0
  31. data/tests/bug_issue_post_redirect.rb +93 -0
  32. data/tests/bug_issue_spnego.rb +41 -0
  33. data/tests/bug_multi_segfault.rb +1 -0
  34. data/tests/bug_raise_on_callback.rb +30 -0
  35. data/tests/helper.rb +400 -44
  36. data/tests/leak_trace.rb +237 -0
  37. data/tests/mem_check.rb +3 -0
  38. data/tests/tc_curl.rb +31 -1
  39. data/tests/tc_curl_download.rb +12 -7
  40. data/tests/tc_curl_easy.rb +911 -63
  41. data/tests/tc_curl_easy_cookielist.rb +277 -0
  42. data/tests/tc_curl_easy_request_target.rb +41 -0
  43. data/tests/tc_curl_easy_resolve.rb +48 -0
  44. data/tests/tc_curl_maxfilesize.rb +12 -0
  45. data/tests/tc_curl_multi.rb +855 -53
  46. data/tests/tc_curl_native_coverage.rb +145 -0
  47. data/tests/tc_curl_postfield.rb +207 -30
  48. data/tests/tc_curl_protocols.rb +37 -0
  49. data/tests/tc_fiber_scheduler.rb +543 -0
  50. data/tests/tc_ftp_options.rb +39 -0
  51. data/tests/tc_gc_compact.rb +223 -0
  52. data/tests/tc_test_server_methods.rb +110 -0
  53. data/tests/timeout.rb +30 -6
  54. metadata +59 -36
  55. data/README +0 -194
data/lib/curl/multi.rb CHANGED
@@ -1,12 +1,15 @@
1
+ # frozen_string_literal: true
1
2
  module Curl
2
-
3
3
  class Multi
4
+ class DownloadError < RuntimeError
5
+ attr_accessor :errors
6
+ end
4
7
  class << self
5
8
  # call-seq:
6
- # Curl::Multi.get('url1','url2','url3','url4','url5', :follow_location => true) do|easy|
9
+ # Curl::Multi.get(['url1','url2','url3','url4','url5'], :follow_location => true) do|easy|
7
10
  # easy
8
11
  # end
9
- #
12
+ #
10
13
  # Blocking call to fetch multiple url's in parallel.
11
14
  def get(urls, easy_options={}, multi_options={}, &blk)
12
15
  url_confs = []
@@ -22,13 +25,13 @@ module Curl
22
25
  # {:url => 'url2', :post_fields => {'field1' => 'value1', 'field2' => 'value2'}},
23
26
  # {:url => 'url3', :post_fields => {'field1' => 'value1', 'field2' => 'value2'}}],
24
27
  # { :follow_location => true, :multipart_form_post => true },
25
- # {:pipeline => true }) do|easy|
28
+ # {:pipeline => Curl::CURLPIPE_HTTP1}) do|easy|
26
29
  # easy_handle_on_request_complete
27
30
  # end
28
- #
31
+ #
29
32
  # Blocking call to POST multiple form's in parallel.
30
- #
31
- # urls_with_config: is a hash of url's pointing to the postfields to send
33
+ #
34
+ # urls_with_config: is a hash of url's pointing to the postfields to send
32
35
  # easy_options: are a set of common options to set on all easy handles
33
36
  # multi_options: options to set on the Curl::Multi handle
34
37
  #
@@ -46,13 +49,13 @@ module Curl
46
49
  # {:url => 'url2', :put_data => IO.read('filepath')},
47
50
  # {:url => 'url3', :put_data => "maybe another string or socket?"],
48
51
  # {:follow_location => true},
49
- # {:pipeline => true }) do|easy|
52
+ # {:pipeline => Curl::CURLPIPE_HTTP1}) do|easy|
50
53
  # easy_handle_on_request_complete
51
54
  # end
52
- #
55
+ #
53
56
  # Blocking call to POST multiple form's in parallel.
54
- #
55
- # urls_with_config: is a hash of url's pointing to the postfields to send
57
+ #
58
+ # urls_with_config: is a hash of url's pointing to the postfields to send
56
59
  # easy_options: are a set of common options to set on all easy handles
57
60
  # multi_options: options to set on the Curl::Multi handle
58
61
  #
@@ -74,12 +77,12 @@ module Curl
74
77
  # :follow_location => true, :max_redirects => 3 },
75
78
  # { :url => 'url3', :method => :put, :put_data => File.open('file.txt','rb') },
76
79
  # { :url => 'url4', :method => :head }
77
- # ], {:pipeline => true})
80
+ # ], {:pipeline => Curl::CURLPIPE_HTTP1})
78
81
  #
79
82
  # Blocking call to issue multiple HTTP requests with varying verb's.
80
83
  #
81
84
  # urls_with_config: is a hash of url's pointing to the easy handle options as well as the special option :method, that can by one of [:get, :post, :put, :delete, :head], when no verb is provided e.g. :method => nil -> GET is used
82
- # multi_options: options for the multi handle
85
+ # multi_options: options for the multi handle
83
86
  # blk: a callback, that yeilds when a handle is completed
84
87
  #
85
88
  def http(urls_with_config, multi_options={}, &blk)
@@ -92,7 +95,7 @@ module Curl
92
95
 
93
96
  # configure the multi handle
94
97
  multi_options.each { |k,v| m.send("#{k}=", v) }
95
- callbacks = [:on_progress,:on_debug,:on_failure,:on_success,:on_body,:on_header]
98
+ callbacks = [:on_progress,:on_debug,:on_failure,:on_success,:on_redirect,:on_missing,:on_body,:on_header]
96
99
 
97
100
  add_free_handle = proc do|conf, easy|
98
101
  c = conf.dup # avoid being destructive to input
@@ -128,23 +131,23 @@ module Curl
128
131
 
129
132
  # headers is a special key
130
133
  headers.each {|k,v| easy.headers[k] = v } if headers
131
-
134
+
132
135
  #
133
136
  # use the remaining options as specific configuration to the easy handle
134
137
  # bad options should raise an undefined method error
135
138
  #
136
139
  c.each { |k,v| easy.send("#{k}=",v) }
137
140
 
138
- easy.on_complete {|curl,code|
141
+ easy.on_complete {|curl|
139
142
  free_handles << curl
140
- blk.call(curl,code,method) if blk
143
+ blk.call(curl,curl.response_code,method) if blk
141
144
  }
142
145
  m.add(easy)
143
146
  end
144
147
 
145
148
  max_connects.times do
146
149
  conf = urls_with_config.pop
147
- add_free_handle.call conf, nil
150
+ add_free_handle.call(conf, nil) if conf
148
151
  break if urls_with_config.empty?
149
152
  end
150
153
 
@@ -153,21 +156,26 @@ module Curl
153
156
  if urls_with_config.size > 0 && free_handles.size > 0
154
157
  easy = free_handles.pop
155
158
  conf = urls_with_config.pop
156
- add_free_handle.call conf, easy
159
+ add_free_handle.call(conf, easy) if conf
157
160
  end
158
161
  end
159
162
 
160
- if urls_with_config.empty?
161
- m.perform
162
- else
163
- until urls_with_config.empty?
164
- m.perform do
163
+ begin
164
+ if urls_with_config.empty?
165
+ m.perform
166
+ else
167
+ until urls_with_config.empty?
168
+ m.perform do
169
+ consume_free_handles.call
170
+ end
165
171
  consume_free_handles.call
166
172
  end
167
- consume_free_handles.call
173
+ free_handles = nil
168
174
  end
169
- free_handles = nil
175
+ ensure
176
+ m.close
170
177
  end
178
+
171
179
  end
172
180
 
173
181
  # call-seq:
@@ -175,7 +183,7 @@ module Curl
175
183
  # Curl::Multi.download(['http://example.com/p/a/t/h/file1.txt','http://example.com/p/a/t/h/file2.txt']){|c|}
176
184
  #
177
185
  # will create 2 new files file1.txt and file2.txt
178
- #
186
+ #
179
187
  # 2 files will be opened, and remain open until the call completes
180
188
  #
181
189
  # when using the :post or :put method, urls should be a hash, including the individual post fields per post
@@ -240,9 +248,107 @@ module Curl
240
248
  errors << e
241
249
  end
242
250
  }
243
- raise errors unless errors.empty?
251
+ if errors.any?
252
+ de = Curl::Multi::DownloadError.new
253
+ de.errors = errors
254
+ raise de
255
+ end
256
+ end
257
+ end
258
+
259
+ def cancel!
260
+ requests.each do |_,easy|
261
+ remove(easy)
262
+ end
263
+ end
264
+
265
+ def idle?
266
+ requests.empty?
267
+ end
268
+
269
+ def requests
270
+ @requests ||= {}
271
+ end
272
+
273
+ def __idle_easy_references
274
+ @__curb_idle_easy_references ||= ObjectSpace::WeakMap.new
275
+ end
276
+
277
+ def __register_idle_easy_reference(easy)
278
+ __idle_easy_references[easy] = true
279
+ self
280
+ end
281
+
282
+ def __unregister_idle_easy_reference(easy)
283
+ return self unless instance_variable_defined?(:@__curb_idle_easy_references)
284
+
285
+ if @__curb_idle_easy_references.respond_to?(:delete)
286
+ @__curb_idle_easy_references.delete(easy)
287
+ else
288
+ retained_references = ObjectSpace::WeakMap.new
289
+ @__curb_idle_easy_references.each_key do |tracked_easy|
290
+ next if tracked_easy.equal?(easy)
291
+
292
+ retained_references[tracked_easy] = true
293
+ end
294
+ @__curb_idle_easy_references = retained_references
244
295
  end
245
296
 
297
+ self
298
+ end
299
+
300
+ def __clear_idle_easy_references
301
+ return unless instance_variable_defined?(:@__curb_idle_easy_references)
302
+
303
+ @__curb_idle_easy_references.keys.each do |easy|
304
+ easy.multi = nil if easy.multi.equal?(self)
305
+ end
306
+ @__curb_idle_easy_references = ObjectSpace::WeakMap.new
307
+ end
308
+
309
+ private :__idle_easy_references, :__register_idle_easy_reference,
310
+ :__unregister_idle_easy_reference, :__clear_idle_easy_references
311
+
312
+ def add(easy)
313
+ return self if requests[easy.object_id]
314
+ # Once a deferred callback exception is pending, Multi#perform is
315
+ # draining existing transfers only and must not start replacement work.
316
+ return self if instance_variable_defined?(:@__curb_deferred_exception)
317
+ _add(easy)
318
+ __unregister_idle_easy_reference(easy)
319
+ requests[easy.object_id] = easy
320
+ self
246
321
  end
322
+
323
+ def remove(easy)
324
+ return self if !requests[easy.object_id]
325
+ requests.delete(easy.object_id)
326
+ _remove(easy)
327
+ self
328
+ end
329
+
330
+ def close
331
+ __close(true)
332
+ end
333
+
334
+ def _autoclose
335
+ __close(false)
336
+ end
337
+
338
+ private :_autoclose
339
+
340
+ private
341
+
342
+ def __close(permanent)
343
+ requests.values.each {|easy|
344
+ _remove(easy)
345
+ }
346
+ __clear_idle_easy_references if permanent
347
+ @requests = {}
348
+ _close
349
+ _mark_closed if permanent
350
+ self
351
+ end
352
+
247
353
  end
248
354
  end
data/lib/curl.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'curb_core'
2
3
  require 'curl/easy'
3
4
  require 'curl/multi'
@@ -5,13 +6,215 @@ require 'uri'
5
6
 
6
7
  # expose shortcut methods
7
8
  module Curl
8
-
9
+ def self.scheduler_active?
10
+ Fiber.respond_to?(:scheduler) && !Fiber.scheduler.nil?
11
+ end
12
+
13
+ def self.deferred_exception_source_id(state)
14
+ return unless state[:multi].instance_variable_defined?(:@__curb_deferred_exception_source_id)
15
+
16
+ state[:multi].instance_variable_get(:@__curb_deferred_exception_source_id)
17
+ end
18
+
19
+ def self.scheduler_waiter_blocking_supported?
20
+ scheduler = Fiber.scheduler
21
+ scheduler && scheduler.respond_to?(:block) && scheduler.respond_to?(:unblock)
22
+ end
23
+
24
+ def self.wake_scheduler_waiter(waiter)
25
+ fiber = waiter[:fiber]
26
+ scheduler = waiter[:scheduler]
27
+ return unless fiber&.alive? && scheduler&.respond_to?(:unblock)
28
+
29
+ scheduler.unblock(waiter, fiber)
30
+ end
31
+
32
+ def self.complete_scheduler_waiter(waiter)
33
+ return if waiter[:done]
34
+
35
+ waiter[:done] = true
36
+ wake_scheduler_waiter(waiter)
37
+ end
38
+
39
+ def self.fail_scheduler_waiter(waiter, error)
40
+ return if waiter[:error]
41
+
42
+ waiter[:error] = error
43
+ wake_scheduler_waiter(waiter)
44
+ end
45
+
46
+ def self.release_scheduler_error(state, error)
47
+ source_waiter = state[:waiters][deferred_exception_source_id(state)]
48
+
49
+ if source_waiter
50
+ fail_scheduler_waiter(source_waiter, error)
51
+ else
52
+ state[:error] = error
53
+ state[:waiters].each_value { |waiter| wake_scheduler_waiter(waiter) }
54
+ end
55
+ end
56
+
57
+ def self.block_scheduler_waiter(waiter)
58
+ unless scheduler_waiter_blocking_supported?
59
+ sleep 0
60
+ return
61
+ end
62
+
63
+ waiter[:fiber] = Fiber.current
64
+ waiter[:scheduler] ||= Fiber.scheduler
65
+ return if waiter[:done] || waiter[:error]
66
+
67
+ waiter[:scheduler].block(waiter, nil)
68
+ ensure
69
+ waiter[:fiber] = nil if waiter[:fiber].equal?(Fiber.current)
70
+ end
71
+
72
+ def self.scheduler_yield
73
+ scheduler = Fiber.scheduler
74
+
75
+ if scheduler&.respond_to?(:kernel_sleep)
76
+ scheduler.kernel_sleep(0)
77
+ else
78
+ sleep 0
79
+ end
80
+ end
81
+
82
+ def self.release_scheduler_waiters(state)
83
+ source_id = deferred_exception_source_id(state)
84
+
85
+ state[:waiters].each do |easy_id, waiter|
86
+ next if source_id == easy_id
87
+
88
+ complete_scheduler_waiter(waiter) if waiter[:completed]
89
+ end
90
+ end
91
+
92
+ def self.perform_with_scheduler(easy)
93
+ state = scheduler_state
94
+ waiter = {completed: false, done: false, error: nil, fiber: nil, scheduler: Fiber.scheduler}
95
+ state[:waiters][easy.object_id] = waiter
96
+ previous_complete = easy.on_complete do |completed_easy|
97
+ previous_complete.call(completed_easy) if previous_complete
98
+ waiter[:completed] = true
99
+ end
100
+
101
+ state[:pending] << easy
102
+ ensure_scheduler_driver(state)
103
+
104
+ until waiter[:done]
105
+ raise waiter[:error] if waiter[:error]
106
+ raise state[:error] if state[:error]
107
+ block_scheduler_waiter(waiter)
108
+ end
109
+
110
+ while state[:driver_running] && state[:pending].empty? &&
111
+ state[:waiters].length == 1 && state[:waiters].key?(easy.object_id)
112
+ scheduler_yield
113
+ end
114
+
115
+ true
116
+ ensure
117
+ state[:waiters].delete(easy.object_id) if defined?(state) && state[:waiters]
118
+ if defined?(previous_complete)
119
+ if previous_complete
120
+ easy.on_complete(&previous_complete)
121
+ else
122
+ easy.on_complete
123
+ end
124
+ end
125
+ end
126
+
127
+ def self.scheduler_state
128
+ Thread.current.thread_variable_get(:curb_scheduler_state) || begin
129
+ state = {
130
+ multi: Curl::Multi.new,
131
+ pending: [],
132
+ driver_running: false,
133
+ error: nil,
134
+ waiters: {},
135
+ }
136
+ Thread.current.thread_variable_set(:curb_scheduler_state, state)
137
+ state
138
+ end
139
+ end
140
+
141
+ def self.ensure_scheduler_driver(state)
142
+ return if state[:driver_running]
143
+
144
+ state[:driver_running] = true
145
+ state[:error] = nil
146
+
147
+ runner = proc do
148
+ begin
149
+ # Give sibling fibers a chance to enqueue work so the shared multi can
150
+ # batch scheduler-driven Easy#perform calls together.
151
+ pending_count = -1
152
+ until pending_count == state[:pending].size
153
+ pending_count = state[:pending].size
154
+ scheduler_yield
155
+ end
156
+
157
+ loop do
158
+ drain_scheduler_pending(state)
159
+ break if state[:multi].idle?
160
+
161
+ begin
162
+ state[:multi].perform do
163
+ drain_scheduler_pending(state)
164
+ release_scheduler_waiters(state)
165
+ scheduler_yield
166
+ end
167
+ ensure
168
+ # Release any siblings that completed just before a deferred
169
+ # callback exception is re-raised.
170
+ release_scheduler_waiters(state)
171
+ end
172
+ end
173
+ rescue => e
174
+ release_scheduler_waiters(state)
175
+ release_scheduler_error(state, e)
176
+ ensure
177
+ state[:driver_running] = false
178
+ ensure_scheduler_driver(state) if state[:error].nil? && !state[:pending].empty?
179
+ end
180
+ end
181
+
182
+ if Fiber.respond_to?(:schedule)
183
+ Fiber.schedule(&runner)
184
+ else
185
+ Fiber.new(blocking: false, &runner).resume
186
+ end
187
+ end
188
+
189
+ def self.drain_scheduler_pending(state)
190
+ pending = state[:pending]
191
+ until pending.empty?
192
+ easy = pending.first
193
+
194
+ break if state[:multi].instance_variable_defined?(:@__curb_deferred_exception)
195
+
196
+ state[:multi].add(easy)
197
+ break unless state[:multi].requests.key?(easy.object_id)
198
+
199
+ pending.shift
200
+ end
201
+ end
202
+
9
203
  def self.http(verb, url, post_body=nil, put_data=nil, &block)
10
- handle = Thread.current[:curb_curl] ||= Curl::Easy.new
204
+ if Thread.current[:curb_curl_yielding]
205
+ handle = Curl::Easy.new # we can't reuse this
206
+ else
207
+ handle = Thread.current[:curb_curl] ||= Curl::Easy.new
208
+ handle.reset
209
+ end
11
210
  handle.url = url
12
211
  handle.post_body = post_body if post_body
13
212
  handle.put_data = put_data if put_data
14
- yield handle if block_given?
213
+ if block_given?
214
+ Thread.current[:curb_curl_yielding] = true
215
+ yield handle
216
+ Thread.current[:curb_curl_yielding] = false
217
+ end
15
218
  handle.http(verb)
16
219
  handle
17
220
  end
@@ -45,18 +248,21 @@ module Curl
45
248
  end
46
249
 
47
250
  def self.urlalize(url, params={})
48
- query_str = params.map {|k,v| "#{URI.escape(k.to_s)}=#{URI.escape(v.to_s)}" }.join('&')
49
- if url.match(/\?/)
50
- "#{url}&#{query_str}"
51
- elsif query_str.size > 0
52
- "#{url}?#{query_str}"
53
- else
54
- url
55
- end
251
+ uri = URI(url)
252
+ # early return if we didn't specify any extra params
253
+ return uri.to_s if (params || {}).empty?
254
+
255
+ params_query = URI.encode_www_form(params || {})
256
+ uri.query = [uri.query.to_s, params_query].reject(&:empty?).join('&')
257
+ uri.to_s
56
258
  end
57
259
 
58
260
  def self.postalize(params={})
59
261
  params.respond_to?(:map) ? URI.encode_www_form(params) : (params.respond_to?(:to_s) ? params.to_s : params)
60
262
  end
61
263
 
264
+ def self.reset
265
+ Thread.current[:curb_curl] = Curl::Easy.new
266
+ end
267
+
62
268
  end
@@ -1,37 +1,23 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
2
 
3
- require 'webrick'
4
- class ::WEBrick::HTTPServer ; def access_log(config, req, res) ; end ; end
5
- class ::WEBrick::BasicLog ; def log(level, data) ; end ; end
6
-
7
- require 'curl'
8
-
9
3
  class BugCrashOnDebug < Test::Unit::TestCase
4
+ include BugTestServerSetupTeardown
10
5
 
11
6
  def test_on_debug
12
- server = WEBrick::HTTPServer.new( :Port => 9999 )
13
- server.mount_proc("/test") do|req,res|
14
- res.body = "hi"
15
- res['Content-Type'] = "text/html"
16
- end
17
- puts 'a'
18
- thread = Thread.new(server) do|srv|
19
- srv.start
20
- end
21
- puts 'b'
22
- c = Curl::Easy.new('http://127.0.0.1:9999/test')
23
- c.on_debug do|x|
24
- puts x.inspect
25
- raise "error" # this will get swallowed
7
+ c = Curl::Easy.new("http://127.0.0.1:#{@port}/test")
8
+ did_raise = false
9
+ did_call = false
10
+ begin
11
+ c.on_success do|x|
12
+ did_call = true
13
+ raise "error" # this will get swallowed
14
+ end
15
+ c.perform
16
+ rescue => e
17
+ did_raise = true
26
18
  end
27
- c.perform
28
- puts 'c'
29
- ensure
30
- puts 'd'
31
- server.shutdown
32
- puts 'e'
33
- puts thread.exit
34
- puts 'f'
19
+ assert did_raise
20
+ assert did_call
35
21
  end
36
22
 
37
23
  end
@@ -1,33 +1,49 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
- require 'webrick'
3
- class ::WEBrick::HTTPServer ; def access_log(config, req, res) ; end ; end
4
- class ::WEBrick::BasicLog ; def log(level, data) ; end ; end
5
2
 
6
3
  class BugCrashOnDebug < Test::Unit::TestCase
7
-
8
- def test_on_debug
9
- server = WEBrick::HTTPServer.new( :Port => 9999 )
10
- server.mount_proc("/test") do|req,res|
11
- res.body = "hi"
12
- res['Content-Type'] = "text/html"
13
- end
4
+ include BugTestServerSetupTeardown
14
5
 
15
- thread = Thread.new(server) do|srv|
16
- srv.start
6
+ def test_on_progress_raise
7
+ c = Curl::Easy.new("http://127.0.0.1:#{@port}/test")
8
+ c.on_progress do|x|
9
+ raise "error"
17
10
  end
11
+ c.perform
18
12
 
19
- c = Curl::Easy.new('http://127.0.0.1:9999/test')
13
+ assert false, "should not reach this point"
14
+
15
+ rescue => e
16
+ assert_equal 'Curl::Err::AbortedByCallbackError', e.class.to_s
17
+ c.close
18
+ end
19
+
20
+ def test_on_progress_abort
21
+ # see: https://github.com/taf2/curb/issues/192,
22
+ # to pass:
23
+ #
24
+ # c = Curl::Easy.new('http://127.0.0.1:9999/test')
25
+ # c.on_progress do|x|
26
+ # puts "we're in the progress callback"
27
+ # false
28
+ # end
29
+ # c.perform
30
+ #
31
+ # notice no return keyword
32
+ #
33
+ c = Curl::Easy.new("http://127.0.0.1:#{@port}/test")
34
+ did_progress = false
20
35
  c.on_progress do|x|
21
- raise "error"
36
+ did_progress = true
37
+ return false
22
38
  end
23
39
  c.perform
40
+ assert did_progress
24
41
 
25
42
  assert false, "should not reach this point"
26
43
 
27
44
  rescue => e
28
45
  assert_equal 'Curl::Err::AbortedByCallbackError', e.class.to_s
29
46
  c.close
30
- ensure
31
- server.shutdown
32
47
  end
48
+
33
49
  end
@@ -1,27 +1,25 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
- require 'webrick'
3
- class ::WEBrick::HTTPServer ; def access_log(config, req, res) ; end ; end
4
- class ::WEBrick::BasicLog ; def log(level, data) ; end ; end
5
2
 
6
3
  class BugTestInstancePostDiffersFromClassPost < Test::Unit::TestCase
7
- def test_bug
8
- server = WEBrick::HTTPServer.new( :Port => 9999 )
9
- server.mount_proc("/test") do|req,res|
4
+ include BugTestServerSetupTeardown
5
+
6
+ def setup
7
+ @port = unused_local_port
8
+ @response_proc = lambda do|res|
10
9
  sleep 0.5
11
10
  res.body = "hi"
12
11
  res['Content-Type'] = "text/html"
13
12
  end
13
+ super
14
+ end
14
15
 
15
- thread = Thread.new(server) do|srv|
16
- srv.start
17
- end
18
-
16
+ def test_bug
19
17
  threads = []
20
18
  timer = Time.now
21
19
 
22
20
  5.times do |i|
23
21
  t = Thread.new do
24
- c = Curl::Easy.perform('http://127.0.0.1:9999/test')
22
+ c = Curl::Easy.perform("http://127.0.0.1:#{@port}/test")
25
23
  c.header_str
26
24
  end
27
25
  threads << t
@@ -37,7 +35,7 @@ class BugTestInstancePostDiffersFromClassPost < Test::Unit::TestCase
37
35
  timer = Time.now
38
36
  single_responses = []
39
37
  5.times do |i|
40
- c = Curl::Easy.perform('http://127.0.0.1:9999/test')
38
+ c = Curl::Easy.perform("http://127.0.0.1:#{@port}/test")
41
39
  single_responses << c.header_str
42
40
  end
43
41
 
@@ -45,8 +43,5 @@ class BugTestInstancePostDiffersFromClassPost < Test::Unit::TestCase
45
43
  puts "requested in #{single_time}"
46
44
 
47
45
  assert single_time > multi_time
48
-
49
- server.shutdown
50
- thread.join
51
46
  end
52
47
  end