curb 0.8.4 → 1.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +522 -0
- data/Rakefile +79 -26
- data/ext/banned.h +32 -0
- data/ext/curb.c +549 -230
- data/ext/curb.h +19 -10
- data/ext/curb_easy.c +1935 -366
- data/ext/curb_easy.h +16 -0
- data/ext/curb_errors.c +115 -18
- data/ext/curb_errors.h +8 -5
- data/ext/curb_macros.h +33 -21
- data/ext/curb_multi.c +1858 -272
- data/ext/curb_multi.h +10 -3
- data/ext/curb_postfield.c +149 -77
- data/ext/curb_postfield.h +1 -0
- data/ext/curb_upload.c +38 -11
- data/ext/curb_upload.h +2 -0
- data/ext/extconf.rb +353 -35
- data/lib/curb.rb +1 -0
- data/lib/curl/easy.rb +314 -78
- data/lib/curl/multi.rb +134 -28
- data/lib/curl.rb +217 -11
- data/tests/bug_crash_on_debug.rb +14 -28
- data/tests/bug_crash_on_progress.rb +32 -16
- data/tests/bug_curb_easy_blocks_ruby_threads.rb +10 -15
- data/tests/bug_curb_easy_post_with_string_no_content_length_header.rb +6 -30
- data/tests/bug_follow_redirect_288.rb +83 -0
- data/tests/bug_instance_post_differs_from_class_post.rb +3 -5
- data/tests/bug_issue102.rb +1 -1
- data/tests/bug_issue_noproxy.rb +56 -0
- data/tests/bug_issue_post_redirect.rb +93 -0
- data/tests/bug_issue_spnego.rb +41 -0
- data/tests/bug_multi_segfault.rb +1 -0
- data/tests/bug_raise_on_callback.rb +30 -0
- data/tests/helper.rb +400 -44
- data/tests/leak_trace.rb +237 -0
- data/tests/mem_check.rb +3 -0
- data/tests/tc_curl.rb +31 -1
- data/tests/tc_curl_download.rb +12 -7
- data/tests/tc_curl_easy.rb +911 -63
- data/tests/tc_curl_easy_cookielist.rb +277 -0
- data/tests/tc_curl_easy_request_target.rb +41 -0
- data/tests/tc_curl_easy_resolve.rb +48 -0
- data/tests/tc_curl_maxfilesize.rb +12 -0
- data/tests/tc_curl_multi.rb +855 -53
- data/tests/tc_curl_native_coverage.rb +145 -0
- data/tests/tc_curl_postfield.rb +207 -30
- data/tests/tc_curl_protocols.rb +37 -0
- data/tests/tc_fiber_scheduler.rb +543 -0
- data/tests/tc_ftp_options.rb +39 -0
- data/tests/tc_gc_compact.rb +223 -0
- data/tests/tc_test_server_methods.rb +110 -0
- data/tests/timeout.rb +30 -6
- metadata +59 -36
- data/README +0 -194
|
@@ -0,0 +1,543 @@
|
|
|
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
|
+
include TestServerMethods
|
|
16
|
+
|
|
17
|
+
class RecordingScheduler
|
|
18
|
+
attr_reader :io_wait_events, :io_select_calls
|
|
19
|
+
|
|
20
|
+
def initialize
|
|
21
|
+
@io_wait_events = []
|
|
22
|
+
@io_select_calls = 0
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def fiber(&block)
|
|
26
|
+
Fiber.new(blocking: false, &block)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def io_wait(io, events, timeout = nil)
|
|
30
|
+
@io_wait_events << events
|
|
31
|
+
raise TypeError, "expected Integer events, got #{events.class}" unless events.is_a?(Integer)
|
|
32
|
+
|
|
33
|
+
readers = (events & IO::READABLE) != 0 ? [io] : nil
|
|
34
|
+
writers = (events & IO::WRITABLE) != 0 ? [io] : nil
|
|
35
|
+
readable, writable = blocking_io { IO.select(readers, writers, nil, timeout) }
|
|
36
|
+
|
|
37
|
+
ready = 0
|
|
38
|
+
ready |= IO::READABLE if readable && !readable.empty?
|
|
39
|
+
ready |= IO::WRITABLE if writable && !writable.empty?
|
|
40
|
+
ready.zero? ? false : ready
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def io_select(readers, writers, excepts, timeout = nil)
|
|
44
|
+
@io_select_calls += 1
|
|
45
|
+
blocking_io { IO.select(readers, writers, excepts, timeout) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def kernel_sleep(duration = nil)
|
|
49
|
+
sleep(duration || 0)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def block(_blocker, timeout = nil)
|
|
53
|
+
sleep(timeout || 0)
|
|
54
|
+
false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def unblock(*)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def close
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def fiber_interrupt(*)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def blocking_io(&block)
|
|
69
|
+
if Fiber.respond_to?(:blocking)
|
|
70
|
+
Fiber.blocking(&block)
|
|
71
|
+
else
|
|
72
|
+
block.call
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
ITERS = 4
|
|
78
|
+
MIN_S = 0.25
|
|
79
|
+
# Each request sleeps 0.25s; two concurrent requests should be ~0.25–0.5s.
|
|
80
|
+
# Allow some jitter in CI environments.
|
|
81
|
+
THRESHOLD = ((MIN_S*(ITERS/2.0)) + (MIN_S/2.0)) # add more jitter for slower CI environments
|
|
82
|
+
SERIAL_TIME_WOULD_BE_ABOUT = MIN_S * ITERS
|
|
83
|
+
|
|
84
|
+
def setup
|
|
85
|
+
@port = unused_local_port
|
|
86
|
+
|
|
87
|
+
@response_proc = lambda do |res|
|
|
88
|
+
res['Content-Type'] = 'text/plain'
|
|
89
|
+
sleep MIN_S
|
|
90
|
+
res.body = '200'
|
|
91
|
+
end
|
|
92
|
+
super
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def test_multi_is_scheduler_friendly
|
|
96
|
+
if skip_no_async
|
|
97
|
+
return
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
url = "http://127.0.0.1:#{@port}/test"
|
|
101
|
+
|
|
102
|
+
started = Time.now
|
|
103
|
+
results = []
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
async_run do
|
|
107
|
+
m = Curl::Multi.new
|
|
108
|
+
ITERS.times.each do
|
|
109
|
+
c = Curl::Easy.new(url)
|
|
110
|
+
c.on_complete { results << c.code }
|
|
111
|
+
m.add(c)
|
|
112
|
+
end
|
|
113
|
+
m.perform
|
|
114
|
+
ensure
|
|
115
|
+
m.close if m
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
duration = Time.now - started
|
|
119
|
+
|
|
120
|
+
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}"
|
|
121
|
+
assert_equal ITERS, results.size
|
|
122
|
+
assert_equal ITERS.times.map {200}, results
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def test_easy_perform_is_scheduler_friendly
|
|
126
|
+
if skip_no_async
|
|
127
|
+
return
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
url = "http://127.0.0.1:#{@port}/test"
|
|
131
|
+
|
|
132
|
+
started = Time.now
|
|
133
|
+
results = []
|
|
134
|
+
|
|
135
|
+
async_run do |top|
|
|
136
|
+
tasks = ITERS.times.map do
|
|
137
|
+
top.async do
|
|
138
|
+
#t = Time.now.to_i
|
|
139
|
+
#puts "starting fiber [#{results.size}] -> #{t}"
|
|
140
|
+
c = Curl.get(url)
|
|
141
|
+
#puts "received result: #{results.size} -> #{Time.now.to_f - t.to_f}"
|
|
142
|
+
results << c.code
|
|
143
|
+
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
tasks.each(&:wait)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
duration = Time.now - started
|
|
150
|
+
|
|
151
|
+
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}"
|
|
152
|
+
assert_equal ITERS, results.size
|
|
153
|
+
assert_equal ITERS.times.map {200}, results
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def test_multi_perform_yields_block_under_scheduler
|
|
157
|
+
if skip_no_async
|
|
158
|
+
return
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
url = "http://127.0.0.1:#{@port}/test"
|
|
162
|
+
yielded = 0
|
|
163
|
+
results = []
|
|
164
|
+
|
|
165
|
+
async_run do
|
|
166
|
+
m = Curl::Multi.new
|
|
167
|
+
ITERS.times do
|
|
168
|
+
c = Curl::Easy.new(url)
|
|
169
|
+
c.on_complete { results << c.code }
|
|
170
|
+
m.add(c)
|
|
171
|
+
end
|
|
172
|
+
m.perform do
|
|
173
|
+
yielded += 1
|
|
174
|
+
end
|
|
175
|
+
ensure
|
|
176
|
+
m.close if m
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
assert_operator yielded, :>=, 1, 'perform did not yield block while waiting under scheduler'
|
|
180
|
+
assert_equal ITERS, results.size
|
|
181
|
+
assert_equal ITERS.times.map {200}, results
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def test_multi_single_request_scheduler_path
|
|
185
|
+
if skip_no_async
|
|
186
|
+
return
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
url = "http://127.0.0.1:#{@port}/test"
|
|
190
|
+
result = nil
|
|
191
|
+
|
|
192
|
+
async_run do
|
|
193
|
+
m = Curl::Multi.new
|
|
194
|
+
c = Curl::Easy.new(url)
|
|
195
|
+
c.on_complete { result = c.code }
|
|
196
|
+
m.add(c)
|
|
197
|
+
m.perform
|
|
198
|
+
ensure
|
|
199
|
+
m.close if m
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
assert_equal 200, result
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def test_multi_single_request_socket_perform_passes_integer_events_to_io_wait
|
|
206
|
+
omit('Skipping custom scheduler test on Windows') if WINDOWS
|
|
207
|
+
omit('Fiber scheduler API unavailable on this Ruby') unless fiber_scheduler_supported?
|
|
208
|
+
|
|
209
|
+
url = "http://127.0.0.1:#{@port}/test"
|
|
210
|
+
scheduler = RecordingScheduler.new
|
|
211
|
+
result = nil
|
|
212
|
+
|
|
213
|
+
with_scheduler(scheduler) do
|
|
214
|
+
m = Curl::Multi.new
|
|
215
|
+
c = Curl::Easy.new(url)
|
|
216
|
+
c.on_complete { result = c.code }
|
|
217
|
+
m.add(c)
|
|
218
|
+
unless m.respond_to?(:_socket_perform, true)
|
|
219
|
+
omit('socket-action perform path is not available in this build')
|
|
220
|
+
end
|
|
221
|
+
m.send(:_socket_perform)
|
|
222
|
+
ensure
|
|
223
|
+
m.close if m
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
assert_equal 200, result
|
|
227
|
+
assert_operator scheduler.io_wait_events.length + scheduler.io_select_calls, :>=, 1
|
|
228
|
+
unless scheduler.io_wait_events.empty?
|
|
229
|
+
assert scheduler.io_wait_events.all? { |events| events.is_a?(Integer) }
|
|
230
|
+
assert scheduler.io_wait_events.any? { |events| (events & (IO::READABLE | IO::WRITABLE)) != 0 }
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def test_multi_reuse_after_scheduler_perform
|
|
235
|
+
unless HAS_ASYNC
|
|
236
|
+
warn 'Skipping fiber scheduler test (Async gem not available)'
|
|
237
|
+
return
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
url = "http://127.0.0.1:#{@port}/test"
|
|
241
|
+
results = []
|
|
242
|
+
|
|
243
|
+
async_run do
|
|
244
|
+
m = Curl::Multi.new
|
|
245
|
+
# First round
|
|
246
|
+
c1 = Curl::Easy.new(url)
|
|
247
|
+
c1.on_complete { results << c1.code }
|
|
248
|
+
m.add(c1)
|
|
249
|
+
m.perform
|
|
250
|
+
|
|
251
|
+
# Second round on same multi
|
|
252
|
+
c2 = Curl::Easy.new(url)
|
|
253
|
+
c2.on_complete { results << c2.code }
|
|
254
|
+
m.add(c2)
|
|
255
|
+
m.perform
|
|
256
|
+
ensure
|
|
257
|
+
m.close if m
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
assert_equal [200, 200], results
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def test_easy_perform_reuses_scheduler_multi_when_autoclose_is_enabled
|
|
264
|
+
if skip_no_async
|
|
265
|
+
return
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
url = "http://127.0.0.1:#{@port}/test"
|
|
269
|
+
results = []
|
|
270
|
+
previous_autoclose = Curl::Multi.autoclose
|
|
271
|
+
Curl::Multi.autoclose = true
|
|
272
|
+
|
|
273
|
+
async_run do
|
|
274
|
+
2.times do
|
|
275
|
+
easy = Curl::Easy.new(url)
|
|
276
|
+
easy.perform
|
|
277
|
+
results << easy.code
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
assert_equal [200, 200], results
|
|
282
|
+
ensure
|
|
283
|
+
Curl::Multi.autoclose = previous_autoclose if defined?(previous_autoclose)
|
|
284
|
+
cleanup_scheduler_state
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def test_easy_perform_reraises_on_body_exception_under_scheduler
|
|
288
|
+
if skip_no_async
|
|
289
|
+
return
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
url = "http://127.0.0.1:#{@port}/test"
|
|
293
|
+
|
|
294
|
+
async_run do |top|
|
|
295
|
+
task = top.async do
|
|
296
|
+
c = Curl::Easy.new(url)
|
|
297
|
+
c.on_body { raise "body blew up" }
|
|
298
|
+
c.on_complete { sleep 0 }
|
|
299
|
+
c.perform
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
error = assert_raise(RuntimeError) do
|
|
303
|
+
task.wait
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
assert_equal "body blew up", error.message
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def test_easy_perform_reraises_on_header_exception_under_scheduler
|
|
311
|
+
if skip_no_async
|
|
312
|
+
return
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
url = "http://127.0.0.1:#{@port}/test"
|
|
316
|
+
|
|
317
|
+
async_run do |top|
|
|
318
|
+
task = top.async do
|
|
319
|
+
c = Curl::Easy.new(url)
|
|
320
|
+
c.on_header { raise "header blew up" }
|
|
321
|
+
c.on_complete { sleep 0 }
|
|
322
|
+
c.perform
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
error = assert_raise(RuntimeError) do
|
|
326
|
+
task.wait
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
assert_equal "header blew up", error.message
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def test_easy_perform_does_not_reraise_sibling_on_complete_exception_under_scheduler
|
|
334
|
+
if skip_no_async
|
|
335
|
+
return
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
with_ephemeral_http_server do |port, hits|
|
|
339
|
+
results = {}
|
|
340
|
+
|
|
341
|
+
async_run do |top|
|
|
342
|
+
successful = top.async do
|
|
343
|
+
easy = Curl::Easy.new("http://127.0.0.1:#{port}/fast")
|
|
344
|
+
easy.perform
|
|
345
|
+
results[:successful] = easy.response_code
|
|
346
|
+
rescue => e
|
|
347
|
+
results[:successful] = e
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
failing = top.async do
|
|
351
|
+
easy = Curl::Easy.new("http://127.0.0.1:#{port}/slow")
|
|
352
|
+
easy.on_complete { raise "boom" }
|
|
353
|
+
easy.perform
|
|
354
|
+
results[:failing] = :returned
|
|
355
|
+
rescue => e
|
|
356
|
+
results[:failing] = e
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
successful.wait
|
|
360
|
+
failing.wait
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
assert_equal 200, results[:successful], "successful scheduler peer should return normally"
|
|
364
|
+
assert_kind_of Curl::Err::AbortedByCallbackError, results[:failing]
|
|
365
|
+
assert_equal "boom", results[:failing].message
|
|
366
|
+
assert_equal 1, hits[:fast]
|
|
367
|
+
assert_equal 1, hits[:slow]
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def test_easy_perform_does_not_reraise_fast_on_complete_exception_into_slow_successful_peer_under_scheduler
|
|
372
|
+
if skip_no_async
|
|
373
|
+
return
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
with_ephemeral_http_server do |port, hits|
|
|
377
|
+
results = {}
|
|
378
|
+
|
|
379
|
+
async_run do |top|
|
|
380
|
+
failing = top.async do
|
|
381
|
+
easy = Curl::Easy.new("http://127.0.0.1:#{port}/fast")
|
|
382
|
+
easy.on_complete { raise "boom" }
|
|
383
|
+
easy.perform
|
|
384
|
+
results[:failing] = :returned
|
|
385
|
+
rescue => e
|
|
386
|
+
results[:failing] = e
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
successful = top.async do
|
|
390
|
+
easy = Curl::Easy.new("http://127.0.0.1:#{port}/slow")
|
|
391
|
+
easy.perform
|
|
392
|
+
results[:successful] = easy.response_code
|
|
393
|
+
rescue => e
|
|
394
|
+
results[:successful] = e
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
failing.wait
|
|
398
|
+
successful.wait
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
assert_equal 200, results[:successful], "slow successful scheduler peer should not inherit a fast sibling callback error"
|
|
402
|
+
assert_kind_of Curl::Err::AbortedByCallbackError, results[:failing]
|
|
403
|
+
assert_equal "boom", results[:failing].message
|
|
404
|
+
assert_equal 1, hits[:fast]
|
|
405
|
+
assert_equal 1, hits[:slow]
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def test_easy_perform_keeps_scheduler_timers_running_while_draining_deferred_on_complete_exception
|
|
410
|
+
if skip_no_async
|
|
411
|
+
return
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
with_ephemeral_http_server do |port, hits|
|
|
415
|
+
marks = {}
|
|
416
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
417
|
+
|
|
418
|
+
async_run do |top|
|
|
419
|
+
timer = top.async do
|
|
420
|
+
sleep 0.05
|
|
421
|
+
marks[:timer] = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
failing = top.async do
|
|
425
|
+
easy = Curl::Easy.new("http://127.0.0.1:#{port}/fast")
|
|
426
|
+
easy.on_complete { raise "boom" }
|
|
427
|
+
easy.perform
|
|
428
|
+
marks[:failing] = :returned
|
|
429
|
+
rescue => e
|
|
430
|
+
marks[:failing] = e
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
successful = top.async do
|
|
434
|
+
easy = Curl::Easy.new("http://127.0.0.1:#{port}/slow")
|
|
435
|
+
easy.perform
|
|
436
|
+
marks[:slow_done] = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
timer.wait
|
|
440
|
+
failing.wait
|
|
441
|
+
successful.wait
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
assert_kind_of Curl::Err::AbortedByCallbackError, marks[:failing]
|
|
445
|
+
assert_operator marks[:timer], :<, marks[:slow_done] - 0.02,
|
|
446
|
+
"scheduler timer should fire before the slow sibling finishes draining"
|
|
447
|
+
assert_equal 1, hits[:fast]
|
|
448
|
+
assert_equal 1, hits[:slow]
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def test_drain_scheduler_pending_does_not_drop_work_rejected_during_deferred_abort
|
|
453
|
+
state = Curl.scheduler_state
|
|
454
|
+
easy = Curl::Easy.new("http://127.0.0.1:#{@port}/test")
|
|
455
|
+
waiter = {completed: false, done: false, error: nil}
|
|
456
|
+
|
|
457
|
+
state[:waiters][easy.object_id] = waiter
|
|
458
|
+
state[:pending] << easy
|
|
459
|
+
state[:multi].instance_variable_set(:@__curb_deferred_exception, RuntimeError.new("boom"))
|
|
460
|
+
|
|
461
|
+
Curl.drain_scheduler_pending(state)
|
|
462
|
+
|
|
463
|
+
assert(state[:pending].include?(easy) || waiter[:error],
|
|
464
|
+
"scheduler enqueue rejected during deferred abort should remain pending or fail its waiter instead of disappearing")
|
|
465
|
+
assert_equal 0, state[:multi].requests.length
|
|
466
|
+
ensure
|
|
467
|
+
cleanup_scheduler_state
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
private
|
|
471
|
+
def skip_no_async
|
|
472
|
+
if WINDOWS
|
|
473
|
+
warn 'Skipping fiber scheduler tests on Windows'
|
|
474
|
+
return true
|
|
475
|
+
end
|
|
476
|
+
unless fiber_scheduler_supported?
|
|
477
|
+
warn 'Skipping fiber scheduler tests (Fiber scheduler API unavailable)'
|
|
478
|
+
return true
|
|
479
|
+
end
|
|
480
|
+
unless HAS_ASYNC
|
|
481
|
+
warn 'Skipping fiber scheduler test (Async gem not available)'
|
|
482
|
+
return true
|
|
483
|
+
end
|
|
484
|
+
false
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
def async_run(&block)
|
|
488
|
+
# Prefer newer Async.run to avoid deprecated scheduler.async path.
|
|
489
|
+
if defined?(Async) && Async.respond_to?(:run)
|
|
490
|
+
Async.run(&block)
|
|
491
|
+
else
|
|
492
|
+
Async(&block)
|
|
493
|
+
end
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
def fiber_scheduler_supported?
|
|
497
|
+
Fiber.respond_to?(:set_scheduler) && Fiber.respond_to?(:schedule)
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def with_scheduler(scheduler)
|
|
501
|
+
previous_scheduler = Fiber.scheduler if Fiber.respond_to?(:scheduler)
|
|
502
|
+
Fiber.set_scheduler(scheduler)
|
|
503
|
+
fiber = Fiber.schedule { yield }
|
|
504
|
+
fiber.resume while fiber.alive?
|
|
505
|
+
ensure
|
|
506
|
+
Fiber.set_scheduler(previous_scheduler) if Fiber.respond_to?(:set_scheduler)
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
def cleanup_scheduler_state
|
|
510
|
+
state = Thread.current.thread_variable_get(:curb_scheduler_state)
|
|
511
|
+
state[:multi].close if state && state[:multi]
|
|
512
|
+
Thread.current.thread_variable_set(:curb_scheduler_state, nil)
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
def with_ephemeral_http_server
|
|
516
|
+
port_socket = TCPServer.new('127.0.0.1', 0)
|
|
517
|
+
port = port_socket.addr[1]
|
|
518
|
+
port_socket.close
|
|
519
|
+
|
|
520
|
+
hits = Hash.new(0)
|
|
521
|
+
server = WEBrick::HTTPServer.new(:Port => port, :Logger => WEBRICK_TEST_LOG, :AccessLog => [])
|
|
522
|
+
server.mount_proc('/fast') do |_req, res|
|
|
523
|
+
hits[:fast] += 1
|
|
524
|
+
res['Content-Type'] = 'text/plain'
|
|
525
|
+
res.body = 'fast'
|
|
526
|
+
end
|
|
527
|
+
server.mount_proc('/slow') do |_req, res|
|
|
528
|
+
hits[:slow] += 1
|
|
529
|
+
res['Content-Type'] = 'text/plain'
|
|
530
|
+
sleep 0.2
|
|
531
|
+
res.body = 'slow'
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
server_thread = Thread.new(server) { |srv| srv.start }
|
|
535
|
+
wait_for_server_ready(port, thread: server_thread)
|
|
536
|
+
|
|
537
|
+
yield port, hits
|
|
538
|
+
ensure
|
|
539
|
+
server.shutdown if defined?(server) && server
|
|
540
|
+
server_thread.join(server_startup_timeout) if defined?(server_thread) && server_thread
|
|
541
|
+
server_thread.kill if defined?(server_thread) && server_thread&.alive?
|
|
542
|
+
end
|
|
543
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
|
2
|
+
|
|
3
|
+
class TestCurbFtpOptions < Test::Unit::TestCase
|
|
4
|
+
# Ensure FTP-related set(:option, ...) mappings are accepted and do not raise
|
|
5
|
+
# a TypeError (they used to be unsupported in setopt dispatch).
|
|
6
|
+
def test_can_set_ftp_listing_related_flags
|
|
7
|
+
c = Curl::Easy.new('ftp://example.com/')
|
|
8
|
+
|
|
9
|
+
assert_nothing_raised do
|
|
10
|
+
c.set(:dirlistonly, true) if Curl.const_defined?(:CURLOPT_DIRLISTONLY)
|
|
11
|
+
c.set(:ftp_use_epsv, 0) if Curl.const_defined?(:CURLOPT_FTP_USE_EPSV)
|
|
12
|
+
# These may not be present on all libcurl builds; guard by constant
|
|
13
|
+
c.set(:ftp_use_eprt, 0) if Curl.const_defined?(:CURLOPT_FTP_USE_EPRT)
|
|
14
|
+
c.set(:ftp_skip_pasv_ip, 1) if Curl.const_defined?(:CURLOPT_FTP_SKIP_PASV_IP)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Setting ftp_commands remains supported for control-connection commands.
|
|
19
|
+
def test_can_assign_ftp_commands
|
|
20
|
+
c = Curl::Easy.new('ftp://example.com/')
|
|
21
|
+
c.ftp_commands = ["PWD", "CWD /"]
|
|
22
|
+
assert_kind_of(Array, c.ftp_commands)
|
|
23
|
+
assert_equal ["PWD", "CWD /"], c.ftp_commands
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_ftp_command_entries_can_be_objects_that_convert_to_string
|
|
27
|
+
command = Object.new
|
|
28
|
+
command.define_singleton_method(:to_s) { "PWD" }
|
|
29
|
+
|
|
30
|
+
c = Curl::Easy.new($TEST_URL)
|
|
31
|
+
c.ftp_commands = [command]
|
|
32
|
+
m = Curl::Multi.new
|
|
33
|
+
|
|
34
|
+
assert_nothing_raised { m.add(c) }
|
|
35
|
+
ensure
|
|
36
|
+
m.remove(c) if defined?(m) && m && m.requests[c.object_id]
|
|
37
|
+
m.close if defined?(m) && m
|
|
38
|
+
end
|
|
39
|
+
end
|